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

Issue:
Add tokens support in TxBuilder and ErgoBoxCandidateBuilder
TxBuilder:
- Token minting;
- Token support in `BoxSelector`;
- Burning tokens;

ErgoBoxCandidateBuilder:
- read/write;

WASM ErgoBox/ErgoBoxCandidate:
- read;



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 13b2f050b..608edcaa0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/target
+target/
 **/*.rs.bk
 Cargo.lock
 bin/
diff --git a/bindings/ergo-lib-wasm/src/box_coll.rs b/bindings/ergo-lib-wasm/src/box_coll.rs
index dee2a529d..658d4a049 100644
--- a/bindings/ergo-lib-wasm/src/box_coll.rs
+++ b/bindings/ergo-lib-wasm/src/box_coll.rs
@@ -7,7 +7,7 @@ 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>);
+pub struct ErgoBoxes(Vec<ErgoBox>);
 
 #[wasm_bindgen]
 impl ErgoBoxes {
@@ -34,43 +34,56 @@ impl ErgoBoxes {
                 acc.push(b);
                 Ok(acc)
             })
-            .map(ErgoBoxes)
+            .map(ErgoBoxes::from)
     }
 
     /// Create new collection with one element
     #[wasm_bindgen(constructor)]
     pub fn new(b: &ErgoBox) -> ErgoBoxes {
-        ErgoBoxes(vec![b.clone().into()])
+        ErgoBoxes(vec![b.clone()])
     }
 
     /// Add an element to the collection
     pub fn add(&mut self, b: &ErgoBox) {
-        self.0.push(b.clone().into());
+        self.0.push(b.clone());
+    }
+
+    /// Returns the element of the collection with a given index
+    pub fn get(&self, index: usize) -> ErgoBox {
+        self.0[index].clone()
+    }
+}
+
+impl From<Vec<chain::ergo_box::ErgoBox>> for ErgoBoxes {
+    fn from(bs: Vec<chain::ergo_box::ErgoBox>) -> Self {
+        ErgoBoxes(bs.into_iter().map(ErgoBox::from).collect())
     }
 }
 
-impl Into<Vec<chain::ergo_box::ErgoBox>> for ErgoBoxes {
-    fn into(self) -> Vec<chain::ergo_box::ErgoBox> {
-        self.0
+impl From<ErgoBoxes> for Vec<chain::ergo_box::ErgoBox> {
+    fn from(bs: ErgoBoxes) -> Self {
+        bs.0.into_iter()
+            .map(chain::ergo_box::ErgoBox::from)
+            .collect()
     }
 }
 
 /// Collection of ErgoBoxCandidates
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoBoxCandidates(Vec<chain::ergo_box::ErgoBoxCandidate>);
+pub struct ErgoBoxCandidates(Vec<ErgoBoxCandidate>);
 
 #[wasm_bindgen]
 impl ErgoBoxCandidates {
     /// Create new outputs
     #[wasm_bindgen(constructor)]
     pub fn new(box_candidate: &ErgoBoxCandidate) -> ErgoBoxCandidates {
-        ErgoBoxCandidates(vec![box_candidate.clone().into()])
+        ErgoBoxCandidates(vec![box_candidate.clone()])
     }
 }
 
-impl Into<Vec<chain::ergo_box::ErgoBoxCandidate>> for ErgoBoxCandidates {
-    fn into(self) -> Vec<chain::ergo_box::ErgoBoxCandidate> {
-        self.0
+impl From<ErgoBoxCandidates> for Vec<chain::ergo_box::ErgoBoxCandidate> {
+    fn from(v: ErgoBoxCandidates) -> Self {
+        v.0.iter().map(|i| i.clone().into()).collect()
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/box_selector.rs b/bindings/ergo-lib-wasm/src/box_selector.rs
index 139368d22..a68598786 100644
--- a/bindings/ergo-lib-wasm/src/box_selector.rs
+++ b/bindings/ergo-lib-wasm/src/box_selector.rs
@@ -1,23 +1,63 @@
-//! Box selection algorithms
-use ergo_lib::chain::ergo_box::ErgoBoxAssets;
+//! Simple box selection algorithms
+use ergo_lib::chain;
+use ergo_lib::wallet;
+use ergo_lib::wallet::box_selector::BoxSelector;
 use wasm_bindgen::prelude::*;
 
-/// Box selector implementations
+use crate::box_coll::ErgoBoxes;
+use crate::ergo_box::BoxValue;
+use crate::token::Tokens;
+
+/// Selected boxes with change boxes (by [`BoxSelector`])
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct BoxSelection(wallet::box_selector::BoxSelection<ergo_lib::chain::ergo_box::ErgoBox>);
+
 #[wasm_bindgen]
-pub enum BoxSelector {
-    /// Naive box selector, collects inputs until target balance is reached
-    Simple = 0,
+impl BoxSelection {
+    /// Selected boxes to spend as transaction inputs
+    pub fn boxes(&self) -> ErgoBoxes {
+        self.0.boxes.clone().into()
+    }
+}
+
+impl From<BoxSelection> for wallet::box_selector::BoxSelection<chain::ergo_box::ErgoBox> {
+    fn from(v: BoxSelection) -> Self {
+        v.0
+    }
 }
 
-impl BoxSelector {
-    /// Get underlying ergo-lib BoxSelector implementation
-    pub fn inner<T: ErgoBoxAssets>(
+/// Naive box selector, collects inputs until target balance is reached
+#[wasm_bindgen]
+pub struct SimpleBoxSelector(wallet::box_selector::SimpleBoxSelector);
+
+#[wasm_bindgen]
+impl SimpleBoxSelector {
+    /// Create empty DataInputs
+    #[wasm_bindgen(constructor)]
+    pub fn new() -> Self {
+        SimpleBoxSelector(wallet::box_selector::SimpleBoxSelector::new())
+    }
+
+    /// 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.
+    pub fn select(
         &self,
-    ) -> Box<dyn ergo_lib::wallet::box_selector::BoxSelector<T>> {
-        match self {
-            BoxSelector::Simple => {
-                Box::new(ergo_lib::wallet::box_selector::simple::SimpleBoxSelector {})
-            }
-        }
+        inputs: &ErgoBoxes,
+        target_balance: &BoxValue,
+        target_tokens: &Tokens,
+    ) -> Result<BoxSelection, JsValue> {
+        let target_tokens: Vec<chain::token::Token> = target_tokens.clone().into();
+        self.0
+            .select(
+                inputs.clone().into(),
+                target_balance.clone().into(),
+                target_tokens.as_slice(),
+            )
+            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map(BoxSelection)
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/ergo_box.rs b/bindings/ergo-lib-wasm/src/ergo_box.rs
index 088f34d60..e0525cd15 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box.rs
@@ -23,10 +23,36 @@ use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
 use crate::ast::Constant;
+use crate::utils::I64;
 use crate::{contract::Contract, transaction::TxId};
 
 pub mod box_builder;
 
+/// Box id (32-byte digest)
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct BoxId(chain::ergo_box::box_id::BoxId);
+
+#[wasm_bindgen]
+impl BoxId {
+    /// Base16 encoded string
+    pub fn to_str(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
+impl From<chain::ergo_box::box_id::BoxId> for BoxId {
+    fn from(b: chain::ergo_box::box_id::BoxId) -> Self {
+        BoxId(b)
+    }
+}
+
+impl From<BoxId> for chain::ergo_box::box_id::BoxId {
+    fn from(b: BoxId) -> Self {
+        b.0
+    }
+}
+
 /// ErgoBox candidate not yet included in any transaction on the chain
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -86,6 +112,16 @@ impl ErgoBox {
         ErgoBox(b)
     }
 
+    /// Get box id
+    pub fn box_id(&self) -> BoxId {
+        self.0.box_id().into()
+    }
+
+    /// Get box value in nanoERGs
+    pub fn value(&self) -> BoxValue {
+        self.0.value.into()
+    }
+
     /// Returns value (ErgoTree constant) stored in the register or None if the register is empty
     pub fn register_value(&self, register_id: NonMandatoryRegisterId) -> Option<Constant> {
         self.0
@@ -101,9 +137,15 @@ impl ErgoBox {
     // }
 }
 
-impl Into<chain::ergo_box::ErgoBox> for ErgoBox {
-    fn into(self) -> chain::ergo_box::ErgoBox {
-        self.0
+impl From<ErgoBox> for chain::ergo_box::ErgoBox {
+    fn from(b: ErgoBox) -> Self {
+        b.0
+    }
+}
+
+impl From<chain::ergo_box::ErgoBox> for ErgoBox {
+    fn from(b: chain::ergo_box::ErgoBox) -> Self {
+        ErgoBox(b)
     }
 }
 
@@ -128,6 +170,11 @@ impl BoxValue {
                 .map_err(|e| JsValue::from_str(&format!("{}", e)))?,
         ))
     }
+
+    /// Get value as signed 64-bit long (I64)
+    pub fn as_i64(&self) -> I64 {
+        self.0.as_i64().into()
+    }
 }
 
 impl From<BoxValue> for chain::ergo_box::box_value::BoxValue {
diff --git a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
index 1473f2fbb..a5c028a34 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
@@ -4,6 +4,9 @@ use wasm_bindgen::prelude::*;
 
 use crate::ast::Constant;
 use crate::contract::Contract;
+use crate::token::Token;
+use crate::token::TokenAmount;
+use crate::token::TokenId;
 
 use super::BoxValue;
 use super::ErgoBoxCandidate;
@@ -84,6 +87,30 @@ impl ErgoBoxCandidateBuilder {
         self.0.delete_register_value(&register_id.into());
     }
 
+    /// Mint token, as defined in https://github.com/ergoplatform/eips/blob/master/eip-0004.md
+    /// `token` - token id(box id of the first input box in transaction) and token amount,
+    /// `token_name` - token name (will be encoded in R4),
+    /// `token_desc` - token description (will be encoded in R5),
+    /// `num_decimals` - number of decimals (will be encoded in R6)
+    pub fn mint_token(
+        &mut self,
+        token: &Token,
+        token_name: String,
+        token_desc: String,
+        num_decimals: usize,
+    ) {
+        self.0
+            .mint_token(token.clone().into(), token_name, token_desc, num_decimals);
+    }
+
+    /// Add given token id and token amount
+    pub fn add_token(&mut self, token_id: &TokenId, amount: &TokenAmount) {
+        self.0.add_token(chain::token::Token {
+            token_id: token_id.clone().into(),
+            amount: amount.clone().into(),
+        });
+    }
+
     /// Build the box candidate
     pub fn build(self) -> Result<ErgoBoxCandidate, JsValue> {
         self.0
diff --git a/bindings/ergo-lib-wasm/src/lib.rs b/bindings/ergo-lib-wasm/src/lib.rs
index 0d30bd3f0..f881838a5 100644
--- a/bindings/ergo-lib-wasm/src/lib.rs
+++ b/bindings/ergo-lib-wasm/src/lib.rs
@@ -23,6 +23,7 @@ pub mod data_input;
 pub mod ergo_box;
 pub mod ergo_state_ctx;
 pub mod secret_key;
+pub mod token;
 pub mod transaction;
 pub mod tx_builder;
 pub mod utils;
diff --git a/bindings/ergo-lib-wasm/src/token.rs b/bindings/ergo-lib-wasm/src/token.rs
new file mode 100644
index 000000000..3d600b298
--- /dev/null
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -0,0 +1,128 @@
+//! Token types
+
+use std::convert::TryFrom;
+
+use ergo_lib::chain;
+use ergo_lib::chain::Base16DecodedBytes;
+use ergo_lib::chain::Digest32;
+use wasm_bindgen::prelude::*;
+
+use crate::ergo_box::BoxId;
+use crate::utils::I64;
+
+/// Token id (32 byte digest)
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct TokenId(chain::token::TokenId);
+
+#[wasm_bindgen]
+impl TokenId {
+    /// Create token id from erbo box id (32 byte digest)
+    pub fn from_box_id(box_id: &BoxId) -> TokenId {
+        let box_id: chain::ergo_box::box_id::BoxId = box_id.clone().into();
+        TokenId(chain::token::TokenId::from(box_id))
+    }
+
+    /// Parse token id (32 byte digets) from base16-encoded string
+    #[allow(clippy::should_implement_trait)]
+    pub fn from_str(str: &str) -> Result<TokenId, JsValue> {
+        Base16DecodedBytes::try_from(str.to_string())
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+            .and_then(|bytes| {
+                Digest32::try_from(bytes).map_err(|e| JsValue::from_str(&format!("{}", e)))
+            })
+            .map(chain::token::TokenId)
+            .map(TokenId)
+    }
+}
+
+impl From<TokenId> for chain::token::TokenId {
+    fn from(t_id: TokenId) -> Self {
+        t_id.0
+    }
+}
+
+/// Token amount with bound checks
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct TokenAmount(chain::token::TokenAmount);
+
+#[wasm_bindgen]
+impl TokenAmount {
+    /// Create from u32 with bounds check
+    pub fn from_u32(v: u32) -> Result<TokenAmount, JsValue> {
+        Ok(Self(
+            chain::token::TokenAmount::try_from(v as u64)
+                .map_err(|e| JsValue::from_str(&format!("{}", e)))?,
+        ))
+    }
+
+    /// Get value as signed 64-bit long (I64)
+    pub fn as_i64(&self) -> I64 {
+        i64::from(self.0).into()
+    }
+}
+
+impl From<TokenAmount> for chain::token::TokenAmount {
+    fn from(ta: TokenAmount) -> Self {
+        ta.0
+    }
+}
+
+/// Token represented with token id paired with it's amount
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Token(chain::token::Token);
+
+#[wasm_bindgen]
+impl Token {
+    /// Create a token with given token id and amount
+    #[wasm_bindgen(constructor)]
+    pub fn new(token_id: &TokenId, amount: &TokenAmount) -> Self {
+        Token(chain::token::Token {
+            token_id: token_id.clone().into(),
+            amount: amount.clone().into(),
+        })
+    }
+}
+
+impl From<Token> for chain::token::Token {
+    fn from(t: Token) -> Self {
+        t.0
+    }
+}
+
+/// Array of tokens
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Tokens(Vec<Token>);
+
+#[wasm_bindgen]
+impl Tokens {
+    /// Create empty Tokens
+    #[wasm_bindgen(constructor)]
+    pub fn new() -> Self {
+        Tokens(vec![])
+    }
+
+    /// Returns the number of elements in the collection
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    /// Returns the element of the collection with a given index
+    pub fn get(&self, index: usize) -> Token {
+        self.0[index].clone()
+    }
+
+    /// Adds an elements to the collection
+    pub fn add(&mut self, elem: &Token) {
+        self.0.push(elem.clone());
+    }
+}
+
+impl From<Tokens> for Vec<chain::token::Token> {
+    fn from(v: Tokens) -> Self {
+        v.0.iter().map(|i| i.0.clone()).collect()
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/tx_builder.rs b/bindings/ergo-lib-wasm/src/tx_builder.rs
index b6ada9af6..158640b4e 100644
--- a/bindings/ergo-lib-wasm/src/tx_builder.rs
+++ b/bindings/ergo-lib-wasm/src/tx_builder.rs
@@ -2,12 +2,10 @@
 use ergo_lib::{chain, wallet};
 use wasm_bindgen::prelude::*;
 
+use crate::box_selector::BoxSelection;
 use crate::data_input::DataInputs;
 use crate::{
-    address::Address,
-    box_coll::{ErgoBoxCandidates, ErgoBoxes},
-    box_selector::BoxSelector,
-    ergo_box::BoxValue,
+    address::Address, box_coll::ErgoBoxCandidates, ergo_box::BoxValue,
     transaction::UnsignedTransaction,
 };
 
@@ -18,8 +16,7 @@ pub struct TxBuilder(wallet::tx_builder::TxBuilder<chain::ergo_box::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,
+    /// `box_selection` - selected input boxes (via [`BoxSelector`])
     /// `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,
@@ -27,8 +24,7 @@ impl TxBuilder {
     /// `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,
+        box_selection: &BoxSelection,
         output_candidates: &ErgoBoxCandidates,
         current_height: u32,
         fee_amount: &BoxValue,
@@ -36,8 +32,7 @@ impl TxBuilder {
         min_change_value: &BoxValue,
     ) -> TxBuilder {
         TxBuilder(ergo_lib::wallet::tx_builder::TxBuilder::new(
-            box_selector.inner::<chain::ergo_box::ErgoBox>(),
-            inputs.clone().into(),
+            box_selection.clone().into(),
             output_candidates.clone().into(),
             current_height,
             fee_amount.clone().into(),
diff --git a/ergo-lib/src/ast/constant.rs b/ergo-lib/src/ast/constant.rs
index 8a88a4ad4..552c43205 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergo-lib/src/ast/constant.rs
@@ -103,9 +103,9 @@ pub struct Constant {
     pub v: ConstantVal,
 }
 
-impl Into<Base16EncodedBytes> for Constant {
-    fn into(self) -> Base16EncodedBytes {
-        Base16EncodedBytes::new(&self.sigma_serialise_bytes())
+impl From<Constant> for Base16EncodedBytes {
+    fn from(v: Constant) -> Base16EncodedBytes {
+        Base16EncodedBytes::new(&v.sigma_serialize_bytes())
     }
 }
 
@@ -130,11 +130,11 @@ impl Into<ConstantVal> for bool {
     }
 }
 
-impl Into<Constant> for bool {
-    fn into(self) -> Constant {
+impl From<bool> for Constant {
+    fn from(v: bool) -> Constant {
         Constant {
             tpe: bool::stype(),
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -145,11 +145,11 @@ impl Into<ConstantVal> for i8 {
     }
 }
 
-impl Into<Constant> for i8 {
-    fn into(self) -> Constant {
+impl From<i8> for Constant {
+    fn from(v: i8) -> Constant {
         Constant {
             tpe: i8::stype(),
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -160,11 +160,11 @@ impl Into<ConstantVal> for i16 {
     }
 }
 
-impl Into<Constant> for i16 {
-    fn into(self) -> Constant {
+impl From<i16> for Constant {
+    fn from(v: i16) -> Constant {
         Constant {
             tpe: i16::stype(),
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -175,11 +175,11 @@ impl Into<ConstantVal> for i32 {
     }
 }
 
-impl Into<Constant> for i32 {
-    fn into(self) -> Constant {
+impl From<i32> for Constant {
+    fn from(v: i32) -> Constant {
         Constant {
             tpe: i32::stype(),
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -190,11 +190,11 @@ impl Into<ConstantVal> for i64 {
     }
 }
 
-impl Into<Constant> for i64 {
-    fn into(self) -> Constant {
+impl From<i64> for Constant {
+    fn from(v: i64) -> Constant {
         Constant {
             tpe: i64::stype(),
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -205,11 +205,11 @@ impl<T: Into<SigmaProp>> From<T> for ConstantVal {
     }
 }
 
-impl Into<Constant> for SigmaProp {
-    fn into(self) -> Constant {
+impl From<SigmaProp> for Constant {
+    fn from(v: SigmaProp) -> Constant {
         Constant {
             tpe: SType::SSigmaProp,
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -220,11 +220,11 @@ impl Into<ConstantVal> for EcPoint {
     }
 }
 
-impl Into<Constant> for EcPoint {
-    fn into(self) -> Constant {
+impl From<EcPoint> for Constant {
+    fn from(v: EcPoint) -> Constant {
         Constant {
             tpe: SType::SGroupElement,
-            v: self.into(),
+            v: v.into(),
         }
     }
 }
@@ -246,31 +246,31 @@ impl<T: LiftIntoSType + StoredNonPrimitive + Into<ConstantVal>> Into<ConstantVal
     }
 }
 
-impl<T: LiftIntoSType + StoredNonPrimitive + Into<ConstantVal>> Into<Constant> for Vec<T> {
-    fn into(self) -> Constant {
+impl<T: LiftIntoSType + StoredNonPrimitive + Into<ConstantVal>> From<Vec<T>> for Constant {
+    fn from(v: Vec<T>) -> Self {
         Constant {
-            tpe: Self::stype(),
-            v: self.into(),
+            tpe: Vec::<T>::stype(),
+            v: v.into(),
         }
     }
 }
 
-impl Into<Constant> for Vec<u8> {
-    fn into(self) -> Constant {
+impl From<Vec<u8>> for Constant {
+    fn from(v: Vec<u8>) -> Self {
         Constant {
             tpe: SType::SColl(Box::new(SType::SByte)),
             v: ConstantVal::Coll(ConstantColl::Primitive(CollPrim::CollByte(
-                self.into_iter().map(|b| b as i8).collect(),
+                v.into_iter().map(|b| b as i8).collect(),
             ))),
         }
     }
 }
 
-impl Into<Constant> for Vec<i8> {
-    fn into(self) -> Constant {
+impl From<Vec<i8>> for Constant {
+    fn from(v: Vec<i8>) -> Constant {
         Constant {
             tpe: SType::SColl(Box::new(SType::SByte)),
-            v: ConstantVal::Coll(ConstantColl::Primitive(CollPrim::CollByte(self))),
+            v: ConstantVal::Coll(ConstantColl::Primitive(CollPrim::CollByte(v))),
         }
     }
 }
diff --git a/ergo-lib/src/chain/address.rs b/ergo-lib/src/chain/address.rs
index bb725c65e..1565e0003 100644
--- a/ergo-lib/src/chain/address.rs
+++ b/ergo-lib/src/chain/address.rs
@@ -81,7 +81,7 @@ impl Address {
     /// byte array
     pub fn content_bytes(&self) -> Vec<u8> {
         match self {
-            Address::P2PK(prove_dlog) => prove_dlog.h.sigma_serialise_bytes(),
+            Address::P2PK(prove_dlog) => prove_dlog.h.sigma_serialize_bytes(),
             Address::P2S(bytes) => bytes.clone(),
         }
     }
@@ -270,7 +270,7 @@ mod tests {
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             prop_oneof![
                 any::<ProveDlog>().prop_map(Address::P2PK),
-                any::<ErgoTree>().prop_map(|t| Address::P2S(t.sigma_serialise_bytes())),
+                any::<ErgoTree>().prop_map(|t| Address::P2S(t.sigma_serialize_bytes())),
             ]
             .boxed()
         }
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index 076a26e03..c8b36aa47 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -9,7 +9,7 @@ pub mod register;
 use super::json;
 use super::{
     digest32::blake2b256_hash,
-    token::{TokenAmount, TokenId},
+    token::{Token, TokenId},
     transaction::TxId,
 };
 
@@ -28,6 +28,7 @@ use indexmap::IndexSet;
 use register::NonMandatoryRegisters;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
 #[cfg(feature = "json")]
 use std::convert::TryFrom;
 use std::io;
@@ -65,7 +66,7 @@ pub struct ErgoBox {
     pub ergo_tree: ErgoTree,
     /// secondary tokens the box contains
     #[cfg_attr(feature = "json", serde(rename = "assets"))]
-    pub tokens: Vec<TokenAmount>,
+    pub tokens: Vec<Token>,
     ///  additional registers the box can carry over
     #[cfg_attr(feature = "json", serde(rename = "additionalRegisters"))]
     pub additional_registers: NonMandatoryRegisters,
@@ -87,7 +88,7 @@ impl ErgoBox {
     pub fn new(
         value: BoxValue,
         ergo_tree: ErgoTree,
-        tokens: Vec<TokenAmount>,
+        tokens: Vec<Token>,
         additional_registers: NonMandatoryRegisters,
         creation_height: u32,
         transaction_id: TxId,
@@ -140,7 +141,7 @@ impl ErgoBox {
     }
 
     fn calc_box_id(&self) -> BoxId {
-        let bytes = self.sigma_serialise_bytes();
+        let bytes = self.sigma_serialize_bytes();
         BoxId(blake2b256_hash(&bytes))
     }
 }
@@ -150,7 +151,38 @@ pub trait ErgoBoxAssets {
     /// Box value
     fn value(&self) -> BoxValue;
     /// Tokens (ids and amounts)
-    fn tokens(&self) -> Vec<TokenAmount>;
+    fn tokens(&self) -> Vec<Token>;
+}
+
+/// Returns the total value of the given boxes
+pub fn sum_value<T: ErgoBoxAssets>(bs: &[T]) -> u64 {
+    bs.iter().map(|b| *b.value().as_u64()).sum()
+}
+
+/// Returns the total token amounts (all tokens combined)
+pub fn sum_tokens(ts: &[Token]) -> HashMap<TokenId, u64> {
+    let mut res = HashMap::new();
+    ts.iter().for_each(|t| {
+        let token_amt = u64::from(t.amount);
+        res.entry(t.token_id.clone())
+            .and_modify(|amt| *amt += token_amt)
+            .or_insert(token_amt);
+    });
+    res
+}
+
+/// Returns the total token amounts (all tokens combined) of the given boxes
+pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(bs: &[T]) -> HashMap<TokenId, u64> {
+    let mut res: HashMap<TokenId, u64> = HashMap::new();
+    bs.iter().for_each(|b| {
+        b.tokens().iter().for_each(|t| {
+            let token_amt = u64::from(t.amount);
+            res.entry(t.token_id.clone())
+                .and_modify(|a| *a += token_amt)
+                .or_insert(token_amt);
+        });
+    });
+    res
 }
 
 /// Simple struct to hold ErgoBoxAssets values
@@ -159,7 +191,7 @@ pub struct ErgoBoxAssetsData {
     /// Box value
     pub value: BoxValue,
     /// Tokens
-    pub tokens: Vec<TokenAmount>,
+    pub tokens: Vec<Token>,
 }
 
 impl ErgoBoxAssets for ErgoBoxAssetsData {
@@ -167,7 +199,7 @@ impl ErgoBoxAssets for ErgoBoxAssetsData {
         self.value
     }
 
-    fn tokens(&self) -> Vec<TokenAmount> {
+    fn tokens(&self) -> Vec<Token> {
         self.tokens.clone()
     }
 }
@@ -177,7 +209,7 @@ impl ErgoBoxAssets for ErgoBoxCandidate {
         self.value
     }
 
-    fn tokens(&self) -> Vec<TokenAmount> {
+    fn tokens(&self) -> Vec<Token> {
         self.tokens.clone()
     }
 }
@@ -187,7 +219,7 @@ impl ErgoBoxAssets for ErgoBox {
         self.value
     }
 
-    fn tokens(&self) -> Vec<TokenAmount> {
+    fn tokens(&self) -> Vec<Token> {
         self.tokens.clone()
     }
 }
@@ -242,7 +274,7 @@ impl TryFrom<json::ergo_box::ErgoBoxFromJson> for ErgoBox {
 
 impl SigmaSerializable for ErgoBox {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
-        let ergo_tree_bytes = self.ergo_tree.sigma_serialise_bytes();
+        let ergo_tree_bytes = self.ergo_tree.sigma_serialize_bytes();
         serialize_box_with_indexed_digests(
             &self.value,
             ergo_tree_bytes,
@@ -274,7 +306,7 @@ pub struct ErgoBoxCandidate {
     /// guarding script, which should be evaluated to true in order to open this box
     pub ergo_tree: ErgoTree,
     /// secondary tokens the box contains
-    pub tokens: Vec<TokenAmount>,
+    pub tokens: Vec<Token>,
     ///  additional registers the box can carry over
     pub additional_registers: NonMandatoryRegisters,
     /// height when a transaction containing the box was created.
@@ -293,7 +325,7 @@ impl ErgoBoxCandidate {
     ) -> Result<(), io::Error> {
         serialize_box_with_indexed_digests(
             &self.value,
-            self.ergo_tree.sigma_serialise_bytes(),
+            self.ergo_tree.sigma_serialize_bytes(),
             &self.tokens,
             &self.additional_registers,
             self.creation_height,
@@ -334,18 +366,21 @@ impl From<ErgoBox> for ErgoBoxCandidate {
 
 #[cfg(test)]
 mod tests {
+
+    use super::box_value::tests::ArbBoxValueRange;
     use super::*;
     use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
     impl Arbitrary for ErgoBoxCandidate {
-        type Parameters = super::box_value::tests::ArbBoxValueRange;
+        type Parameters = ArbBoxValueRange;
 
         fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
             (
                 any_with::<BoxValue>(args),
                 any::<ErgoTree>(),
-                vec(any::<TokenAmount>(), 0..10),
+                vec(any::<Token>(), 0..3),
                 any::<u32>(),
                 any::<NonMandatoryRegisters>(),
             )
@@ -364,7 +399,7 @@ mod tests {
     }
 
     impl Arbitrary for ErgoBox {
-        type Parameters = super::box_value::tests::ArbBoxValueRange;
+        type Parameters = ArbBoxValueRange;
 
         fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
             (
@@ -380,8 +415,40 @@ mod tests {
         type Strategy = BoxedStrategy<Self>;
     }
 
+    impl Arbitrary for ErgoBoxAssetsData {
+        type Parameters = ArbBoxValueRange;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            (any_with::<BoxValue>(args), vec(any::<Token>(), 0..3))
+                .prop_map(|(value, tokens)| Self { value, tokens })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    #[test]
+    fn test_sum_tokens_repeating_token_id() {
+        let token = force_any_val::<Token>();
+        let b = ErgoBoxAssetsData {
+            value: BoxValue::SAFE_USER_MIN,
+            tokens: vec![token.clone(), token.clone()],
+        };
+        assert_eq!(
+            *sum_tokens_from_boxes(vec![b.clone(), b].as_slice())
+                .get(&token.token_id)
+                .unwrap(),
+            u64::from(token.amount) * 4
+        );
+    }
+
     proptest! {
 
+        #[test]
+        fn sum_tokens_eq(b in any::<ErgoBoxAssetsData>()) {
+            prop_assert_eq!(sum_tokens(b.tokens().as_slice()), sum_tokens_from_boxes(vec![b].as_slice()))
+        }
+
         #[test]
         fn ergo_box_candidate_ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
             prop_assert_eq![sigma_serialize_roundtrip(&v), v];
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index 589ece6b1..528d15e68 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -5,7 +5,7 @@ use std::convert::TryFrom;
 use std::convert::TryInto;
 
 use crate::ast::Constant;
-use crate::chain::token::TokenAmount;
+use crate::chain::token::Token;
 use crate::serialization::SigmaSerializable;
 use crate::ErgoTree;
 
@@ -39,7 +39,7 @@ pub struct ErgoBoxCandidateBuilder {
     min_value_per_byte: u32,
     value: BoxValue,
     ergo_tree: ErgoTree,
-    tokens: Vec<TokenAmount>,
+    tokens: Vec<Token>,
     additional_registers: HashMap<NonMandatoryRegisterId, Constant>,
     creation_height: u32,
 }
@@ -96,7 +96,7 @@ impl ErgoBoxCandidateBuilder {
             additional_registers: regs,
             creation_height: self.creation_height,
         };
-        Ok(b.sigma_serialise_bytes().len())
+        Ok(b.sigma_serialize_bytes().len())
     }
 
     /// Calculate minimal box value for the current box serialized size(in bytes)
@@ -123,8 +123,41 @@ impl ErgoBoxCandidateBuilder {
         self.additional_registers.remove(register_id);
     }
 
+    /// Mint token, as defined in https://github.com/ergoplatform/eips/blob/master/eip-0004.md
+    /// `token` - token id(box id of the first input box in transaction) and token amount,
+    /// `token_name` - token name (will be encoded in R4),
+    /// `token_desc` - token description (will be encoded in R5),
+    /// `num_decimals` - number of decimals (will be encoded in R6)
+    pub fn mint_token(
+        &mut self,
+        token: Token,
+        token_name: String,
+        token_desc: String,
+        num_decimals: usize,
+    ) {
+        self.tokens.push(token);
+        self.set_register_value(
+            NonMandatoryRegisterId::R4,
+            token_name.as_bytes().to_vec().into(),
+        );
+        self.set_register_value(
+            NonMandatoryRegisterId::R5,
+            token_desc.as_bytes().to_vec().into(),
+        );
+        self.set_register_value(
+            NonMandatoryRegisterId::R6,
+            num_decimals.to_string().as_bytes().to_vec().into(),
+        );
+    }
+
+    /// Add given token id and token amount
+    pub fn add_token(&mut self, token: Token) {
+        self.tokens.push(token);
+    }
+
     /// Build the box candidate
     pub fn build(self) -> Result<ErgoBoxCandidate, ErgoBoxCandidateBuilderError> {
+        // TODO: according to EIP4 if token is minted in this box there should be no other tokens
         let regs = self.build_registers()?;
         let b = ErgoBoxCandidate {
             value: self.value,
@@ -133,7 +166,7 @@ impl ErgoBoxCandidateBuilder {
             additional_registers: regs,
             creation_height: self.creation_height,
         };
-        let box_size_bytes = b.sigma_serialise_bytes().len();
+        let box_size_bytes = b.sigma_serialize_bytes().len();
         let min_box_value: BoxValue = (box_size_bytes as i64 * self.min_value_per_byte as i64)
             .try_into()
             .unwrap();
@@ -153,6 +186,7 @@ mod tests {
 
     use NonMandatoryRegisterId::*;
 
+    use crate::chain::token::TokenId;
     use crate::test_util::force_any_val;
 
     use super::*;
@@ -196,7 +230,7 @@ mod tests {
             ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
         let estimated_box_size = builder.calc_box_size_bytes().unwrap();
         let b = builder.build().unwrap();
-        assert_eq!(b.sigma_serialise_bytes().len(), estimated_box_size);
+        assert_eq!(b.sigma_serialize_bytes().len(), estimated_box_size);
     }
 
     #[test]
@@ -235,4 +269,63 @@ mod tests {
         let b = builder.build().unwrap();
         assert!(b.additional_registers.get(R4).is_none());
     }
+
+    #[test]
+    fn test_mint_token() {
+        let token_pair = Token {
+            token_id: force_any_val::<TokenId>(),
+            amount: 1.try_into().unwrap(),
+        };
+        let out_box_value = BoxValue::SAFE_USER_MIN;
+        let token_name = "USD".to_string();
+        let token_desc = "Nothing backed USD token".to_string();
+        let token_num_dec = 2;
+        let mut box_builder =
+            ErgoBoxCandidateBuilder::new(out_box_value, force_any_val::<ErgoTree>(), 0);
+        box_builder.mint_token(token_pair.clone(), token_name, token_desc, token_num_dec);
+        let out_box = box_builder.build().unwrap();
+        assert_eq!(out_box.tokens.get(0).unwrap(), &token_pair);
+        // test registers are encoded according to https://github.com/ergoplatform/eips/blob/master/eip-0004.md
+        assert_eq!(
+            out_box
+                .additional_registers
+                .get(NonMandatoryRegisterId::R4)
+                .unwrap()
+                .base16_str(),
+            "0e03555344",
+            "invalid encoding of token name in R4"
+        );
+        assert_eq!(
+            out_box
+                .additional_registers
+                .get(NonMandatoryRegisterId::R5)
+                .unwrap()
+                .base16_str(),
+            "0e184e6f7468696e67206261636b65642055534420746f6b656e",
+            "invalid encoding of token description in R5"
+        );
+        assert_eq!(
+            out_box
+                .additional_registers
+                .get(NonMandatoryRegisterId::R6)
+                .unwrap()
+                .base16_str(),
+            "0e0132",
+            "invalid encoding of token number of decimals in R6"
+        );
+    }
+
+    #[test]
+    fn test_add_token() {
+        let token = Token {
+            token_id: force_any_val::<TokenId>(),
+            amount: 1.try_into().unwrap(),
+        };
+        let out_box_value = BoxValue::SAFE_USER_MIN;
+        let mut box_builder =
+            ErgoBoxCandidateBuilder::new(out_box_value, force_any_val::<ErgoTree>(), 0);
+        box_builder.add_token(token.clone());
+        let out_box = box_builder.build().unwrap();
+        assert_eq!(out_box.tokens.first().unwrap(), &token);
+    }
 }
diff --git a/ergo-lib/src/chain/ergo_box/box_value.rs b/ergo-lib/src/chain/ergo_box/box_value.rs
index e4f5ae262..af6c17ad5 100644
--- a/ergo-lib/src/chain/ergo_box/box_value.rs
+++ b/ergo-lib/src/chain/ergo_box/box_value.rs
@@ -1,5 +1,6 @@
 //! Box value newtype
 
+use crate::chain::token::TokenAmountError;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
@@ -9,10 +10,10 @@ use sigma_ser::vlq_encode;
 use std::{convert::TryFrom, io};
 use thiserror::Error;
 
-/// Box value with with bound checks
+/// Box value with bound checks
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct BoxValue(i64);
+pub struct BoxValue(u64);
 
 impl BoxValue {
     /// Minimal box value per byte of the serialized box that was set on on launch
@@ -21,10 +22,10 @@ impl BoxValue {
     const MIN_BOX_SIZE_BYTES: usize = 30;
 
     /// Absolute minimal value, calculated from smallest possible box size and original value per byte requirement
-    pub const MIN_RAW: i64 =
-        BoxValue::MIN_VALUE_PER_BOX_BYTE as i64 * BoxValue::MIN_BOX_SIZE_BYTES as i64;
+    pub const MIN_RAW: u64 =
+        BoxValue::MIN_VALUE_PER_BOX_BYTE as u64 * BoxValue::MIN_BOX_SIZE_BYTES as u64;
     /// Absolue maximal allowed box value
-    pub const MAX_RAW: i64 = i64::MAX;
+    pub const MAX_RAW: u64 = i64::MAX as u64;
 
     /// Absolute minimal value, calculated from smallest possible box size and original value per byte requirement
     pub const MIN: BoxValue = BoxValue(BoxValue::MIN_RAW);
@@ -44,53 +45,62 @@ impl BoxValue {
     }
 
     /// Get the value as u64
-    pub fn as_u64(&self) -> u64 {
-        self.0 as u64
+    pub fn as_u64(&self) -> &u64 {
+        &self.0
     }
 
     /// Get the value as i64
-    pub fn as_i64(&self) -> &i64 {
-        &self.0
+    pub fn as_i64(&self) -> i64 {
+        self.0 as i64
     }
 
     /// 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)
+        let raw = self.0.checked_add(rhs.0).ok_or(BoxValueError::Overflow)?;
+        if raw > BoxValue::MAX_RAW {
+            Err(BoxValueError::OutOfBounds(raw))
+        } else {
+            Ok(BoxValue(raw))
+        }
     }
 
     /// 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)
+        let raw = self.0.checked_sub(rhs.0).ok_or(BoxValueError::Overflow)?;
+        if raw < BoxValue::MIN_RAW {
+            Err(BoxValueError::OutOfBounds(raw))
         } else {
-            Ok(BoxValue(raw_i64))
+            Ok(BoxValue(raw))
         }
     }
 
     /// 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)
+        let raw = self.0.checked_mul(rhs.0).ok_or(BoxValueError::Overflow)?;
+        if raw > BoxValue::MAX_RAW {
+            Err(BoxValueError::OutOfBounds(raw))
+        } else {
+            Ok(BoxValue(raw))
+        }
     }
 
     /// 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)
+        let raw = self
+            .0
+            .checked_mul(rhs as u64)
+            .ok_or(BoxValueError::Overflow)?;
+        if raw > BoxValue::MAX_RAW {
+            Err(BoxValueError::OutOfBounds(raw))
+        } else {
+            Ok(BoxValue(raw))
+        }
     }
 }
 
 impl PartialOrd for BoxValue {
     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        self.0.partial_cmp(other.as_i64())
+        self.0.partial_cmp(other.as_u64())
     }
 }
 
@@ -98,9 +108,9 @@ 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 as i64))
+            Ok(BoxValue(v))
         } else {
-            Err(BoxValueError::OutOfBounds)
+            Err(BoxValueError::OutOfBounds(v))
         }
     }
 }
@@ -108,24 +118,24 @@ impl TryFrom<u64> for BoxValue {
 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))
+        if v >= BoxValue::MIN_RAW as i64 {
+            Ok(BoxValue(v as u64))
         } else {
-            Err(BoxValueError::OutOfBounds)
+            Err(BoxValueError::OutOfBounds(v as u64))
         }
     }
 }
 
-impl Into<u64> for BoxValue {
-    fn into(self) -> u64 {
-        self.0 as u64
+impl From<BoxValue> for u64 {
+    fn from(v: BoxValue) -> Self {
+        v.0
     }
 }
 
-impl Into<i64> for BoxValue {
-    fn into(self) -> i64 {
+impl From<BoxValue> for i64 {
+    fn from(v: BoxValue) -> Self {
         // it's safe since upper bound is i64::MAX
-        self.0 as i64
+        v.0 as i64
     }
 }
 
@@ -143,8 +153,8 @@ impl SigmaSerializable for BoxValue {
 #[derive(Error, Eq, PartialEq, Debug, Clone)]
 pub enum BoxValueError {
     /// Value is out of bounds
-    #[error("Value is out of bounds")]
-    OutOfBounds,
+    #[error("Value is out of bounds: {0}")]
+    OutOfBounds(u64),
     /// Overflow
     #[error("Overflow")]
     Overflow,
@@ -156,6 +166,12 @@ impl From<BoxValueError> for SerializationError {
     }
 }
 
+impl From<TokenAmountError> for SerializationError {
+    fn from(e: TokenAmountError) -> 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> {
@@ -163,7 +179,7 @@ pub fn checked_sum<I: Iterator<Item = BoxValue>>(mut iter: I) -> Result<BoxValue
         .map_or_else(Err, |v| {
             if v.0 == 0 {
                 // input list was empty (sum is zero)
-                Err(BoxValueError::OutOfBounds)
+                Err(BoxValueError::OutOfBounds(0))
             } else {
                 Ok(v)
             }
@@ -178,7 +194,7 @@ pub mod tests {
     use super::*;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
-    pub struct ArbBoxValueRange(Range<i64>);
+    pub struct ArbBoxValueRange(Range<u64>);
 
     impl Default for ArbBoxValueRange {
         fn default() -> Self {
@@ -186,7 +202,7 @@ pub mod tests {
         }
     }
 
-    impl Into<ArbBoxValueRange> for Range<i64> {
+    impl Into<ArbBoxValueRange> for Range<u64> {
         fn into(self) -> ArbBoxValueRange {
             ArbBoxValueRange(self)
         }
@@ -233,6 +249,35 @@ pub mod tests {
             .is_err())
     }
 
+    #[test]
+    fn test_checked_mul() {
+        let a = BoxValue::MIN.checked_mul(&BoxValue::MIN).unwrap();
+        assert_eq!(
+            a,
+            (BoxValue::MIN_RAW * BoxValue::MIN_RAW).try_into().unwrap()
+        );
+    }
+
+    #[test]
+    fn test_checked_mul_overflow() {
+        assert!(
+            BoxValue::try_from(BoxValue::MAX_RAW / BoxValue::MIN_RAW + 1)
+                .unwrap()
+                .checked_mul(&BoxValue::MIN)
+                .is_err()
+        )
+    }
+
+    #[test]
+    fn test_checked_mul_u32_overflow() {
+        assert!(
+            BoxValue::try_from(BoxValue::MAX_RAW / BoxValue::MIN_RAW + 1)
+                .unwrap()
+                .checked_mul_u32(BoxValue::MIN_RAW as u32)
+                .is_err()
+        )
+    }
+
     #[test]
     fn test_checked_sum_empty_input() {
         let empty_input: Vec<BoxValue> = vec![];
@@ -249,9 +294,9 @@ pub mod tests {
 
         #[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 expected_sum: u64 = inputs.clone().into_iter().map(|v| *v.as_u64()).sum();
             let checked_sum = checked_sum(inputs.into_iter()).unwrap();
-            assert_eq!(*checked_sum.as_i64(), expected_sum);
+            assert_eq!(*checked_sum.as_u64(), expected_sum);
         }
     }
 }
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index d5bdab9b6..ef001de60 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -20,7 +20,7 @@ pub mod ergo_tree {
     where
         S: Serializer,
     {
-        let bytes = ergo_tree.sigma_serialise_bytes();
+        let bytes = ergo_tree.sigma_serialize_bytes();
         serialize_bytes(&bytes[..], serializer)
     }
 
@@ -41,7 +41,7 @@ pub mod ergo_box {
     use crate::{
         chain::{
             ergo_box::{box_id::BoxId, box_value::BoxValue, register::NonMandatoryRegisters},
-            token::TokenAmount,
+            token::Token,
             transaction::TxId,
         },
         ErgoTree,
@@ -60,7 +60,7 @@ pub mod ergo_box {
         pub ergo_tree: ErgoTree,
         /// secondary tokens the box contains
         #[serde(rename = "assets")]
-        pub tokens: Vec<TokenAmount>,
+        pub tokens: Vec<Token>,
         ///  additional registers the box can carry over
         #[serde(rename = "additionalRegisters")]
         pub additional_registers: NonMandatoryRegisters,
diff --git a/ergo-lib/src/chain/token.rs b/ergo-lib/src/chain/token.rs
index ae3c3b041..0b927bc56 100644
--- a/ergo-lib/src/chain/token.rs
+++ b/ergo-lib/src/chain/token.rs
@@ -4,17 +4,19 @@ use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
     SigmaSerializable,
 };
+use std::convert::TryFrom;
 use std::io;
 
 use super::digest32::Digest32;
+use super::ergo_box::box_id::BoxId;
 #[cfg(test)]
 use proptest_derive::Arbitrary;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 /// newtype for token id
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
-#[cfg_attr(test, derive(Arbitrary))]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 pub struct TokenId(pub Digest32);
 
@@ -23,6 +25,18 @@ impl TokenId {
     pub const SIZE: usize = Digest32::SIZE;
 }
 
+impl From<Digest32> for TokenId {
+    fn from(d: Digest32) -> Self {
+        TokenId(d)
+    }
+}
+
+impl From<BoxId> for TokenId {
+    fn from(i: BoxId) -> Self {
+        TokenId(i.0)
+    }
+}
+
 impl SigmaSerializable for TokenId {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.0.sigma_serialize(w)?;
@@ -33,25 +47,126 @@ impl SigmaSerializable for TokenId {
     }
 }
 
-/// Token amount represented with token id paired with it's amount
+/// Token amount with bound checks
+#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
+#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+pub struct TokenAmount(u64);
+
+impl TokenAmount {
+    /// minimal allowed value
+    pub const MIN: u64 = 1;
+    /// maximal allowed value
+    pub const MAX: u64 = i64::MAX as u64;
+}
+
+/// BoxValue errors
+#[derive(Error, Eq, PartialEq, Debug, Clone)]
+pub enum TokenAmountError {
+    /// Value is out of bounds
+    #[error("Value is out of bounds: {0}")]
+    OutOfBounds(u64),
+    /// Overflow
+    #[error("Overflow")]
+    Overflow,
+}
+
+impl TryFrom<u64> for TokenAmount {
+    type Error = TokenAmountError;
+
+    fn try_from(v: u64) -> Result<Self, Self::Error> {
+        if v >= TokenAmount::MIN && v <= TokenAmount::MAX {
+            Ok(TokenAmount(v))
+        } else {
+            Err(TokenAmountError::OutOfBounds(v))
+        }
+    }
+}
+
+impl From<TokenAmount> for u64 {
+    fn from(ta: TokenAmount) -> Self {
+        ta.0
+    }
+}
+
+impl From<TokenAmount> for i64 {
+    fn from(ta: TokenAmount) -> Self {
+        ta.0 as i64
+    }
+}
+
+/// Token represented with token id paired with it's amount
 #[derive(PartialEq, Eq, Debug, Clone)]
 #[cfg_attr(test, derive(Arbitrary))]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
-pub struct TokenAmount {
+pub struct Token {
     /// token id
     #[cfg_attr(feature = "json", serde(rename = "tokenId"))]
     pub token_id: TokenId,
     /// token amount
     #[cfg_attr(feature = "json", serde(rename = "amount"))]
-    pub amount: u64,
+    pub amount: TokenAmount,
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::chain::Base16DecodedBytes;
     use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
+    impl Arbitrary for TokenId {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                Just(TokenId::from(
+                    Digest32::try_from(
+                        Base16DecodedBytes::try_from(
+                            "3130a82e45842aebb888742868e055e2f554ab7d92f233f2c828ed4a43793710"
+                                .to_string()
+                        )
+                        .unwrap()
+                    )
+                    .unwrap()
+                )),
+                Just(TokenId::from(
+                    Digest32::try_from(
+                        Base16DecodedBytes::try_from(
+                            "e7321ffb4ec5d71deb3110eb1ac09612b9cf57445acab1e0e3b1222d5b5a6c60"
+                                .to_string()
+                        )
+                        .unwrap()
+                    )
+                    .unwrap()
+                )),
+                Just(TokenId::from(
+                    Digest32::try_from(
+                        Base16DecodedBytes::try_from(
+                            "ad62f6dd92e7dc850bc406770dfac9a943dd221a7fb440b7b2bcc7d3149c1792"
+                                .to_string()
+                        )
+                        .unwrap()
+                    )
+                    .unwrap()
+                ))
+            ]
+            .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+    }
+
+    impl Arbitrary for TokenAmount {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (TokenAmount::MIN..=TokenAmount::MAX / 100000)
+                .prop_map(Self)
+                .boxed()
+        }
+        type Strategy = BoxedStrategy<Self>;
+    }
+
     proptest! {
 
         #[test]
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 3ea390411..533924ffc 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -132,7 +132,7 @@ impl Transaction {
             inputs: empty_proof_inputs,
             ..(*self).clone()
         };
-        tx_to_sign.sigma_serialise_bytes()
+        tx_to_sign.sigma_serialize_bytes()
     }
 
     /// Get transaction id
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index f6bdf5942..08f62af5d 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -89,7 +89,7 @@ impl UnsignedTransaction {
             self.data_inputs.clone(),
             self.output_candidates.clone(),
         );
-        tx.sigma_serialise_bytes()
+        tx.sigma_serialize_bytes()
     }
 }
 
@@ -160,6 +160,8 @@ pub mod tests {
 
     proptest! {
 
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
         #[test]
         fn test_unsigned_tx_bytes_to_sign(v in any::<UnsignedTransaction>()) {
             prop_assert!(!v.bytes_to_sign().is_empty());
diff --git a/ergo-lib/src/ergo_tree.rs b/ergo-lib/src/ergo_tree.rs
index 7aec13f7a..9fb4ad20e 100644
--- a/ergo-lib/src/ergo_tree.rs
+++ b/ergo-lib/src/ergo_tree.rs
@@ -308,7 +308,7 @@ mod tests {
             .unwrap();
 
         let contract = chain::contract::Contract::pay_to_address(&address).unwrap();
-        let bytes = &contract.ergo_tree().sigma_serialise_bytes();
+        let bytes = &contract.ergo_tree().sigma_serialize_bytes();
         assert_eq!(&bytes[..2], vec![0u8, 8u8].as_slice());
     }
 
@@ -319,7 +319,7 @@ mod tests {
             v: ConstantVal::Boolean(true),
         });
         let ergo_tree = ErgoTree::with_segregation(Rc::new(expr.clone()));
-        let bytes = ergo_tree.sigma_serialise_bytes();
+        let bytes = ergo_tree.sigma_serialize_bytes();
         let parsed_expr = ErgoTree::sigma_parse_bytes(bytes)
             .unwrap()
             .proposition()
diff --git a/ergo-lib/src/serialization/ergo_box.rs b/ergo-lib/src/serialization/ergo_box.rs
index d54d416ba..a91311bfb 100644
--- a/ergo-lib/src/serialization/ergo_box.rs
+++ b/ergo-lib/src/serialization/ergo_box.rs
@@ -6,7 +6,7 @@ use crate::{
     ast::Constant,
     chain::{
         ergo_box::{box_value::BoxValue, register::NonMandatoryRegisters, ErgoBoxCandidate},
-        token::{TokenAmount, TokenId},
+        token::{Token, TokenId},
     },
     ErgoTree,
 };
@@ -14,6 +14,7 @@ use indexmap::IndexSet;
 
 use super::sigma_byte_writer::SigmaByteWrite;
 use std::convert::TryFrom;
+use std::convert::TryInto;
 use std::io;
 
 /// Box serialization with token ids optionally saved in transaction
@@ -21,7 +22,7 @@ use std::io;
 pub fn serialize_box_with_indexed_digests<W: SigmaByteWrite>(
     box_value: &BoxValue,
     ergo_tree_bytes: Vec<u8>,
-    tokens: &[TokenAmount],
+    tokens: &[Token],
     additional_registers: &NonMandatoryRegisters,
     creation_height: u32,
     token_ids_in_tx: Option<&IndexSet<TokenId>>,
@@ -48,7 +49,7 @@ pub fn serialize_box_with_indexed_digests<W: SigmaByteWrite>(
             ),
             None => t.token_id.sigma_serialize(w),
         }
-        .and_then(|()| w.put_u64(t.amount))
+        .and_then(|()| w.put_u64(t.amount.into()))
     })?;
 
     let regs_num = additional_registers.len();
@@ -88,7 +89,10 @@ pub fn parse_box_with_indexed_digests<R: SigmaByteRead>(
             }
         };
         let amount = r.get_u64()?;
-        tokens.push(TokenAmount { token_id, amount })
+        tokens.push(Token {
+            token_id,
+            amount: amount.try_into()?,
+        })
     }
 
     let regs_num = r.get_u8()?;
diff --git a/ergo-lib/src/serialization/serializable.rs b/ergo-lib/src/serialization/serializable.rs
index c87c44004..0b83893a5 100644
--- a/ergo-lib/src/serialization/serializable.rs
+++ b/ergo-lib/src/serialization/serializable.rs
@@ -70,7 +70,7 @@ pub trait SigmaSerializable: Sized {
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError>;
 
     /// Serialize any SigmaSerializable value into bytes
-    fn sigma_serialise_bytes(&self) -> Vec<u8> {
+    fn sigma_serialize_bytes(&self) -> Vec<u8> {
         let mut data = Vec::new();
         let mut w = SigmaByteWriter::new(&mut data, None);
         self.sigma_serialize(&mut w)
diff --git a/ergo-lib/src/sigma_protocol/dlog_protocol.rs b/ergo-lib/src/sigma_protocol/dlog_protocol.rs
index f8fe538b4..80a55a76e 100644
--- a/ergo-lib/src/sigma_protocol/dlog_protocol.rs
+++ b/ergo-lib/src/sigma_protocol/dlog_protocol.rs
@@ -16,7 +16,7 @@ impl From<EcPoint> for FirstDlogProverMessage {
 
 impl ProverMessage for FirstDlogProverMessage {
     fn bytes(&self) -> Vec<u8> {
-        self.0.sigma_serialise_bytes()
+        self.0.sigma_serialize_bytes()
     }
 }
 
@@ -106,6 +106,8 @@ mod tests {
 
     proptest! {
 
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
         #[test]
         fn test_compute_commitment(secret in any::<DlogProverInput>(), challenge in any::<Challenge>()) {
             let pk = secret.public_image();
diff --git a/ergo-lib/src/sigma_protocol/fiat_shamir.rs b/ergo-lib/src/sigma_protocol/fiat_shamir.rs
index 008f586d2..4d389b8b2 100644
--- a/ergo-lib/src/sigma_protocol/fiat_shamir.rs
+++ b/ergo-lib/src/sigma_protocol/fiat_shamir.rs
@@ -79,7 +79,7 @@ pub fn fiat_shamir_tree_to_bytes(tree: &ProofTree) -> Vec<u8> {
     let prop_tree = ErgoTree::with_segregation(Rc::new(Expr::Const(
         SigmaProp::new(leaf.proposition()).into(),
     )));
-    let mut prop_bytes = prop_tree.sigma_serialise_bytes();
+    let mut prop_bytes = prop_tree.sigma_serialize_bytes();
     // TODO: is unwrap safe here? Create new type with non-optional commitment? Decide when other scenarios
     // are implemented (leafs and trees)
     let mut commitment_bytes = leaf.commitment_opt().unwrap().bytes();
diff --git a/ergo-lib/src/sigma_protocol/verifier.rs b/ergo-lib/src/sigma_protocol/verifier.rs
index f10713d34..c06b85119 100644
--- a/ergo-lib/src/sigma_protocol/verifier.rs
+++ b/ergo-lib/src/sigma_protocol/verifier.rs
@@ -131,6 +131,9 @@ mod tests {
     use std::rc::Rc;
 
     proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
         #[test]
         fn test_prover_verifier_p2pk(secret in any::<DlogProverInput>(), message in any::<Vec<u8>>()) {
             prop_assume!(!message.is_empty());
diff --git a/ergo-lib/src/wallet/box_selector.rs b/ergo-lib/src/wallet/box_selector.rs
index 6979bcef5..c0c7368ca 100644
--- a/ergo-lib/src/wallet/box_selector.rs
+++ b/ergo-lib/src/wallet/box_selector.rs
@@ -1,18 +1,20 @@
 //! Box selection for transaction inputs
 
-pub mod simple;
+mod simple;
+pub use 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,
+    token::Token,
 };
 use thiserror::Error;
 
 /// Selected boxes (by [`BoxSelector`])
+#[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BoxSelection<T: ErgoBoxAssets> {
-    /// selected boxes to spend
+    /// Selected boxes to spend as transaction inputs
     pub boxes: Vec<T>,
     /// box assets with returning change amounts (to be put in tx outputs)
     pub change_boxes: Vec<ErgoBoxAssetsData>,
@@ -21,11 +23,14 @@ pub struct BoxSelection<T: ErgoBoxAssets> {
 /// Box selector
 pub trait BoxSelector<T: ErgoBoxAssets> {
     /// Selects boxes out of the provided inputs to satisfy target balance and tokens
+    /// `inputs` - spendable boxes
+    /// `target_balance` - value (in nanoERGs) to find in input boxes (inputs)
+    /// `target_tokens` - token amounts to find in input boxes(inputs)
     fn select(
         &self,
         inputs: Vec<T>,
         target_balance: BoxValue,
-        target_tokens: &[TokenAmount],
+        target_tokens: &[Token],
     ) -> Result<BoxSelection<T>, BoxSelectorError>;
 }
 
@@ -36,6 +41,10 @@ pub enum BoxSelectorError {
     #[error("Not enough coins({0} nanoERGs are missing)")]
     NotEnoughCoins(u64),
 
+    /// Not enough tokens
+    #[error("Not enough tokens, missing {0:?}")]
+    NotEnoughTokens(Vec<Token>),
+
     /// BoxValue out of bounds
     #[error("BoxValue out of bounds")]
     BoxValueError(BoxValueError),
diff --git a/ergo-lib/src/wallet/box_selector/simple.rs b/ergo-lib/src/wallet/box_selector/simple.rs
index 8ba8a2c20..6cec41e8a 100644
--- a/ergo-lib/src/wallet/box_selector/simple.rs
+++ b/ergo-lib/src/wallet/box_selector/simple.rs
@@ -1,11 +1,18 @@
-//! Box selector which selects all provided inputs
+//! Naive box selector, collects inputs until target balance is reached
 
+use std::cmp::min;
+use std::collections::HashMap;
+use std::convert::TryFrom;
 use std::convert::TryInto;
 
 use crate::chain::ergo_box::box_value::BoxValue;
+use crate::chain::ergo_box::sum_tokens;
+use crate::chain::ergo_box::sum_tokens_from_boxes;
 use crate::chain::ergo_box::ErgoBoxAssets;
 use crate::chain::ergo_box::ErgoBoxAssetsData;
+use crate::chain::token::Token;
 use crate::chain::token::TokenAmount;
+use crate::chain::token::TokenId;
 
 use super::BoxSelectorError;
 use super::{BoxSelection, BoxSelector};
@@ -16,8 +23,8 @@ pub struct SimpleBoxSelector {}
 
 impl SimpleBoxSelector {
     /// Create new boxed instance
-    pub fn new() -> Box<Self> {
-        Box::new(SimpleBoxSelector {})
+    pub fn new() -> Self {
+        SimpleBoxSelector {}
     }
 }
 
@@ -31,30 +38,106 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
         &self,
         inputs: Vec<T>,
         target_balance: BoxValue,
-        target_tokens: &[TokenAmount],
+        target_tokens: &[Token],
     ) -> 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();
+        let mut selected_boxes_value: u64 = 0;
+        let target_balance: u64 = target_balance.into();
+        let mut target_tokens_left: HashMap<TokenId, u64> = HashMap::new();
+        // sum all target tokens into hash map (think repeating token ids)
+        target_tokens.iter().for_each(|t| {
+            let token_amt = u64::from(t.amount);
+            target_tokens_left
+                .entry(t.token_id.clone())
+                .and_modify(|amt| *amt += token_amt)
+                .or_insert(token_amt);
+        });
+        let mut has_value_change = false;
+        let mut has_token_change = false;
         inputs.into_iter().for_each(|b| {
-            if unmet_target_balance > 0 {
-                let b_value: i64 = b.value().into();
-                unmet_target_balance -= b_value;
+            let value_change_amt: u64 = if target_balance > selected_boxes_value {
+                0
+            } else {
+                selected_boxes_value - target_balance
+            };
+            if target_balance > selected_boxes_value
+                || (has_value_change || has_token_change)
+                    && (value_change_amt < *BoxValue::SAFE_USER_MIN.as_u64())
+                || !target_tokens_left.is_empty()
+            {
+                selected_boxes_value += u64::from(b.value());
+                if selected_boxes_value > target_balance {
+                    has_value_change = true;
+                }
+                let mut selected_tokens_from_this_box: HashMap<TokenId, u64> = HashMap::new();
+                b.tokens().iter().for_each(|t| {
+                    let token_amount_left_to_select =
+                        *target_tokens_left.get(&t.token_id).unwrap_or(&0);
+                    let token_amount_in_box = u64::from(t.amount);
+                    if token_amount_left_to_select <= token_amount_in_box {
+                        target_tokens_left.remove(&t.token_id);
+                    } else {
+                        target_tokens_left.insert(
+                            t.token_id.clone(),
+                            token_amount_left_to_select - token_amount_in_box,
+                        );
+                    }
+                    if token_amount_left_to_select > 0 {
+                        let selected_token_amt =
+                            min(token_amount_in_box, token_amount_left_to_select);
+                        selected_tokens_from_this_box
+                            .entry(t.token_id.clone())
+                            .and_modify(|amt| {
+                                *amt += selected_token_amt;
+                            })
+                            .or_insert(selected_token_amt);
+                    }
+                });
+                if sum_tokens(b.tokens().as_slice()) != selected_tokens_from_this_box {
+                    has_token_change = true;
+                };
                 selected_inputs.push(b);
             };
         });
-        if unmet_target_balance > 0 {
+        if selected_boxes_value < target_balance {
             return Err(BoxSelectorError::NotEnoughCoins(
-                unmet_target_balance.abs() as u64
+                target_balance - selected_boxes_value,
+            ));
+        }
+        if !target_tokens.is_empty() && !target_tokens_left.is_empty() {
+            return Err(BoxSelectorError::NotEnoughTokens(
+                target_tokens_left
+                    .iter()
+                    .map(|(token_id, token_amount)| Token {
+                        token_id: token_id.clone(),
+                        amount: TokenAmount::try_from(*token_amount).unwrap(),
+                    })
+                    .collect(),
             ));
         }
-        let change_boxes: Vec<ErgoBoxAssetsData> = if unmet_target_balance == 0 {
+        let change_boxes: Vec<ErgoBoxAssetsData> = if !has_value_change && !has_token_change {
             vec![]
         } else {
-            let change_value: BoxValue = unmet_target_balance.abs().try_into()?;
+            let change_value: BoxValue = (selected_boxes_value - target_balance).try_into()?;
+            let mut change_tokens = sum_tokens_from_boxes(selected_inputs.as_slice());
+            target_tokens.iter().for_each(|t| {
+                let selected_boxes_t_amt = change_tokens.get(&t.token_id).unwrap();
+                let t_change_amt = *selected_boxes_t_amt - u64::from(t.amount);
+                if t_change_amt == 0 {
+                    change_tokens.remove(&t.token_id);
+                } else {
+                    change_tokens.insert(t.token_id.clone(), t_change_amt);
+                };
+            });
             vec![ErgoBoxAssetsData {
                 value: change_value,
-                tokens: vec![],
+                tokens: change_tokens
+                    .iter()
+                    .map(|t| Token {
+                        token_id: t.0.clone(),
+                        amount: TokenAmount::try_from(*t.1).unwrap(),
+                    })
+                    .collect(),
             }]
         };
         Ok(BoxSelection {
@@ -73,7 +156,9 @@ impl Default for SimpleBoxSelector {
 #[cfg(test)]
 mod tests {
     use crate::chain::ergo_box::box_value;
+    use crate::chain::ergo_box::sum_value;
     use crate::chain::ergo_box::ErgoBox;
+    use crate::chain::ergo_box::ErgoBoxAssetsData;
     use proptest::{collection::vec, prelude::*};
 
     use super::*;
@@ -87,23 +172,206 @@ mod tests {
     }
 
     proptest! {
+
         #[test]
-        fn test_select(inputs in vec(any_with::<ErgoBox>((BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10)) {
+        fn test_select_not_enough_value(inputs in
+                                        vec(any_with::<ErgoBoxAssetsData>(
+                                            (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).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::SAFE_USER_MIN).unwrap();
-            prop_assert!(s.select(inputs.clone(), balance_too_much, vec![].as_slice()).is_err());
+            prop_assert!(s.select(inputs, balance_too_much, vec![].as_slice()).is_err());
+        }
+
+        #[test]
+        fn test_select_only_value_exact_no_change(inputs in
+                                                  vec(any_with::<ErgoBoxAssetsData>(
+                                                      (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 2..10)) {
+            let first_input_box = inputs.get(0).unwrap().clone();
+            let s = SimpleBoxSelector::new();
+            let balance = first_input_box.value();
+            let selection = s.select(inputs, balance, first_input_box.tokens.as_slice()).unwrap();
+            prop_assert_eq!(selection.boxes.clone(), vec![first_input_box]);
+            prop_assert_eq!(selection.change_boxes, vec![]);
+        }
+
+        #[test]
+        fn test_select_change_value_is_too_small(inputs in
+                                                 vec(any_with::<ErgoBoxAssetsData>(
+                                                     (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 2..10)) {
+            let first_input_box = inputs.get(0).unwrap().clone();
+            let s = SimpleBoxSelector::new();
+            let target_balance = BoxValue::try_from(first_input_box.value().as_u64() - 1).unwrap();
+            let selection = s.select(inputs, target_balance, vec![].as_slice()).unwrap();
+            prop_assert!(selection.boxes.len() > 1);
+            prop_assert!(!selection.change_boxes.is_empty());
+            let out_box = ErgoBoxAssetsData {value: target_balance, tokens: vec![]};
+            let mut change_boxes_plus_out = vec![out_box];
+            change_boxes_plus_out.append(&mut selection.change_boxes.clone());
+            prop_assert_eq!(sum_value(selection.boxes.as_slice()),
+                            sum_value(change_boxes_plus_out.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+                            "all tokens from selected boxes should equal all tokens from the change boxes + target tokens")
+        }
 
-            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);
+        #[test]
+        fn test_select_no_value_change_but_tokens(inputs in
+                                                  vec(any_with::<ErgoBoxAssetsData>(
+                                                      (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 2..10)) {
+            let first_input_box = inputs.get(0).unwrap().clone();
+            prop_assume!(!first_input_box.tokens.is_empty());
+            let first_input_box_token = first_input_box.tokens.get(0).unwrap();
+            let first_input_box_token_amount = u64::from(first_input_box_token.amount);
+            prop_assume!(first_input_box_token_amount > 1);
+            let s = SimpleBoxSelector::new();
+            let target_token_amount = first_input_box_token_amount / 2;
+            let target_token = Token {token_id: first_input_box_token.token_id.clone(),
+                                      amount: target_token_amount.try_into().unwrap()};
+            let target_balance = first_input_box.value();
+            let selection = s.select(inputs, target_balance, vec![target_token.clone()].as_slice()).unwrap();
+            prop_assert!(selection.boxes.len() > 1);
+            prop_assert!(!selection.change_boxes.is_empty());
+            let out_box = ErgoBoxAssetsData {value: target_balance, tokens: vec![target_token]};
+            let mut change_boxes_plus_out = vec![out_box];
+            change_boxes_plus_out.append(&mut selection.change_boxes.clone());
+            prop_assert_eq!(sum_value(selection.boxes.as_slice()),
+                            sum_value(change_boxes_plus_out.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+                            "all tokens from selected boxes should equal all tokens from the change boxes + target tokens")
+        }
+
+        #[test]
+        fn test_select_only_value(inputs in
+                                  vec(any_with::<ErgoBoxAssetsData>(
+                                      (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10)) {
+            let s = SimpleBoxSelector::new();
+            let all_inputs_val = box_value::checked_sum(inputs.iter().map(|b| b.value)).unwrap();
             let balance_less = all_inputs_val.checked_sub(&BoxValue::SAFE_USER_MIN).unwrap();
             let selection_less = s.select(inputs.clone(), balance_less, vec![].as_slice()).unwrap();
-            let expected_change_box = ErgoBoxAssetsData {value: BoxValue::SAFE_USER_MIN, tokens: vec![]};
-            prop_assert!(selection_less.change_boxes == vec![expected_change_box]);
             prop_assert!(selection_less.boxes == inputs);
+            prop_assert_eq!(sum_value(selection_less.boxes.as_slice()),
+                            balance_less.as_u64() + sum_value(selection_less.change_boxes.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection_less.boxes.as_slice()),
+                            sum_tokens_from_boxes(selection_less.change_boxes.as_slice()),
+                            "all tokens from change boxes should equal all tokens from the input boxes");
+        }
+
+        #[test]
+        fn test_select_single_token(inputs in
+                                    vec(any_with::<ErgoBoxAssetsData>(
+                                        (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
+                                    target_balance in
+                                    any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 1000).into()),
+                                    target_token_amount in 1..100u64) {
+            let s = SimpleBoxSelector::new();
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            prop_assume!(!all_input_tokens.is_empty());
+            let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get((all_input_tokens.len() - 1) / 2)
+                                                                                    .cloned().unwrap();
+            let input_token_amount = *all_input_tokens.get(target_token_id).unwrap();
+            prop_assume!(input_token_amount >= target_token_amount);
+            let target_token = Token {token_id: target_token_id.clone(), amount: target_token_amount.try_into().unwrap()};
+            let selection = s.select(inputs, target_balance, vec![target_token.clone()].as_slice()).unwrap();
+            let out_box = ErgoBoxAssetsData {value: target_balance, tokens: vec![target_token]};
+            let mut change_boxes_plus_out = vec![out_box];
+            change_boxes_plus_out.append(&mut selection.change_boxes.clone());
+            prop_assert_eq!(sum_value(selection.boxes.as_slice()),
+                            sum_value(change_boxes_plus_out.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+                            "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
+        }
+
+        #[test]
+        fn test_select_single_token_all_amount(inputs in
+                                               vec(any_with::<ErgoBoxAssetsData>(
+                                                   (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
+                                               target_balance in
+                                               any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 1000).into())) {
+            let s = SimpleBoxSelector::new();
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            prop_assume!(!all_input_tokens.is_empty());
+            let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get((all_input_tokens.len() - 1) / 2)
+                                                                                    .cloned().unwrap();
+            let input_token_amount = *all_input_tokens.get(target_token_id).unwrap();
+            prop_assume!(input_token_amount > 0);
+            let target_token = Token {token_id: target_token_id.clone(), amount: input_token_amount.try_into().unwrap()};
+            let selection = s.select(inputs, target_balance, vec![target_token.clone()].as_slice()).unwrap();
+            let out_box = ErgoBoxAssetsData {value: target_balance, tokens: vec![target_token]};
+            let mut change_boxes_plus_out = vec![out_box];
+            change_boxes_plus_out.append(&mut selection.change_boxes.clone());
+            prop_assert_eq!(sum_value(selection.boxes.as_slice()),
+                            sum_value(change_boxes_plus_out.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+                            "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
         }
+
+        #[test]
+        fn test_select_multiple_tokens(inputs in
+                                       vec(any_with::<ErgoBoxAssetsData>(
+                                           (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
+                                       target_balance in
+                                       any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 1000).into()),
+                                       target_token1_amount in 1..100u64,
+                                       target_token2_amount in 2..100u64) {
+            let s = SimpleBoxSelector::new();
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            prop_assume!(all_input_tokens.len() >= 2);
+            let all_input_tokens_keys = all_input_tokens.keys().collect::<Vec<&TokenId>>();
+            let target_token1_id = all_input_tokens_keys.first().cloned().unwrap();
+            let target_token2_id = all_input_tokens_keys.last().cloned().unwrap();
+            let input_token1_amount = *all_input_tokens.get(target_token1_id).unwrap();
+            let input_token2_amount = *all_input_tokens.get(target_token2_id).unwrap();
+            prop_assume!(input_token1_amount >= target_token1_amount);
+            prop_assume!(input_token2_amount >= target_token2_amount);
+            let target_token1 = Token {token_id: target_token1_id.clone(), amount: target_token1_amount.try_into().unwrap()};
+            // simulate repeating token ids (e.g the same token id mentioned twice)
+            let target_token2_amount_part1 = target_token2_amount / 2;
+            let target_token2_amount_part2 = target_token2_amount - target_token2_amount_part1;
+            let target_token2_part1 = Token {token_id: target_token2_id.clone(),
+                                             amount: target_token2_amount_part1.try_into().unwrap()};
+            let target_token2_part2 = Token {token_id: target_token2_id.clone(),
+                                             amount: target_token2_amount_part2.try_into().unwrap()};
+            let target_tokens = vec![target_token1.clone(), target_token2_part1.clone(), target_token2_part2.clone()];
+            let selection = s.select(inputs, target_balance, target_tokens.as_slice()).unwrap();
+            let out_box = ErgoBoxAssetsData {value: target_balance,
+                                             tokens: vec![target_token1, target_token2_part1, target_token2_part2]};
+            let mut change_boxes_plus_out = vec![out_box];
+            change_boxes_plus_out.append(&mut selection.change_boxes.clone());
+            prop_assert_eq!(sum_value(selection.boxes.as_slice()),
+                            sum_value(change_boxes_plus_out.as_slice()),
+                            "total value of the selected boxes should equal target balance + total value in change boxes");
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+                            "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
+        }
+
+        #[test]
+        fn test_select_not_enough_tokens(inputs in
+                                         vec(any_with::<ErgoBoxAssetsData>(
+                                             (BoxValue::MIN_RAW * 1000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
+                                         target_balance in
+                                         any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 1000).into())) {
+            let s = SimpleBoxSelector::new();
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            prop_assume!(!all_input_tokens.is_empty());
+            let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get(0).cloned().unwrap();
+            let input_token_amount = all_input_tokens.get(target_token_id).unwrap() / 2;
+            let target_token_amount = TokenAmount::MAX;
+            prop_assume!(input_token_amount < target_token_amount);
+            let target_token = Token {token_id: target_token_id.clone(), amount: target_token_amount.try_into().unwrap()};
+            let selection = s.select(inputs, target_balance, vec![target_token].as_slice());
+            prop_assert!(selection.is_err());
+        }
+
     }
 }
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 9e1ff6514..993ad182e 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -109,6 +109,8 @@ mod tests {
 
     proptest! {
 
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
         #[test]
         fn test_tx_signing(secrets in vec(any::<DlogProverInput>(), 1..10)) {
             let boxes_to_spend: Vec<ErgoBox> = secrets.iter().map(|secret|{
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index ec2904c24..0dca50e19 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -21,12 +21,11 @@ use crate::chain::{
 use crate::constants::MINERS_FEE_MAINNET_ADDRESS;
 use crate::serialization::SerializationError;
 
-use super::box_selector::{BoxSelection, BoxSelector, BoxSelectorError};
+use super::box_selector::{BoxSelection, BoxSelectorError};
 
 /// Unsigned transaction builder
 pub struct TxBuilder<S: ErgoBoxAssets> {
-    box_selector: Box<dyn BoxSelector<S>>,
-    boxes_to_spend: Vec<S>,
+    box_selection: BoxSelection<S>,
     data_inputs: Vec<DataInput>,
     output_candidates: Vec<ErgoBoxCandidate>,
     current_height: u32,
@@ -37,8 +36,7 @@ pub struct TxBuilder<S: ErgoBoxAssets> {
 
 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,
+    /// `box_selection` - selected input boxes  (via [`BoxSelector`])
     /// `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,
@@ -46,8 +44,7 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
     /// `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>,
+        box_selection: BoxSelection<S>,
         output_candidates: Vec<ErgoBoxCandidate>,
         current_height: u32,
         fee_amount: BoxValue,
@@ -55,8 +52,7 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
         min_change_value: BoxValue,
     ) -> TxBuilder<S> {
         TxBuilder {
-            box_selector,
-            boxes_to_spend,
+            box_selection,
             data_inputs: vec![],
             output_candidates,
             current_height,
@@ -73,28 +69,19 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
 
     /// Build the unsigned transaction
     pub fn build(self) -> Result<UnsignedTransaction, TxBuilderError> {
-        if self.boxes_to_spend.is_empty() {
-            return Err(TxBuilderError::InvalidArgs(
-                "boxes_to_spend is empty".to_string(),
-            ));
+        if self.box_selection.boxes.is_empty() {
+            return Err(TxBuilderError::InvalidArgs("inputs is empty".to_string()));
         }
         if self.output_candidates.is_empty() {
             return Err(TxBuilderError::InvalidArgs(
                 "output_candidates is empty".to_string(),
             ));
         }
-        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)?.ergo_tree();
-        let change_boxes: Result<Vec<ErgoBoxCandidate>, ErgoBoxCandidateBuilderError> = selection
+        let change_boxes: Result<Vec<ErgoBoxCandidate>, ErgoBoxCandidateBuilderError> = self
+            .box_selection
             .change_boxes
             .iter()
             .filter(|b| b.value >= self.min_change_value)
@@ -112,7 +99,7 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
         let miner_fee_box = new_miner_fee_box(self.fee_amount, self.current_height)?;
         output_candidates.push(miner_fee_box);
         Ok(UnsignedTransaction::new(
-            selection
+            self.box_selection
                 .boxes
                 .into_iter()
                 .map(UnsignedInput::from)
@@ -141,62 +128,48 @@ pub fn new_miner_fee_box(
 pub enum TxBuilderError {
     /// Box selection error
     #[error("Box selector error: {0}")]
-    BoxSelectorError(BoxSelectorError),
+    BoxSelectorError(#[from] BoxSelectorError),
     /// Box value error
     #[error("Box value error")]
-    BoxValueError(BoxValueError),
+    BoxValueError(#[from] BoxValueError),
     /// Serialization error
     #[error("Serialization error")]
-    SerializationError(SerializationError),
+    SerializationError(#[from] SerializationError),
     /// Invalid arguments
     #[error("Invalid arguments: {0}")]
     InvalidArgs(String),
     /// ErgoBoxCandidate error
     #[error("ErgoBoxCandidateBuilder error: {0}")]
-    ErgoBoxCandidateBuilderError(ErgoBoxCandidateBuilderError),
-}
-
-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<ErgoBoxCandidateBuilderError> for TxBuilderError {
-    fn from(e: ErgoBoxCandidateBuilderError) -> Self {
-        TxBuilderError::ErgoBoxCandidateBuilderError(e)
-    }
+    ErgoBoxCandidateBuilderError(#[from] ErgoBoxCandidateBuilderError),
 }
 
 #[cfg(test)]
 mod tests {
 
+    use std::convert::TryInto;
+
     use proptest::{collection::vec, prelude::*};
 
+    use crate::chain::ergo_box::register::NonMandatoryRegisters;
     use crate::chain::ergo_box::ErgoBox;
+    use crate::chain::token::Token;
+    use crate::chain::token::TokenId;
+    use crate::chain::transaction::TxId;
     use crate::test_util::force_any_val;
-    use crate::wallet::box_selector::simple::SimpleBoxSelector;
+    use crate::wallet::box_selector::BoxSelector;
+    use crate::wallet::box_selector::SimpleBoxSelector;
+    use crate::ErgoTree;
 
     use super::*;
 
     #[test]
     fn test_empty_inputs() {
-        let inputs: Vec<ErgoBox> = vec![];
+        let box_selection: BoxSelection<ErgoBox> = BoxSelection {
+            boxes: vec![],
+            change_boxes: vec![],
+        };
         let r = TxBuilder::new(
-            SimpleBoxSelector::new(),
-            inputs,
+            box_selection,
             vec![force_any_val::<ErgoBoxCandidate>()],
             1,
             force_any_val::<BoxValue>(),
@@ -208,10 +181,12 @@ mod tests {
 
     #[test]
     fn test_empty_outputs() {
+        let inputs = vec![force_any_val::<ErgoBox>()];
         let outputs: Vec<ErgoBoxCandidate> = vec![];
         let r = TxBuilder::new(
-            SimpleBoxSelector::new(),
-            vec![force_any_val::<ErgoBox>()],
+            SimpleBoxSelector::new()
+                .select(inputs, BoxValue::MIN, &[])
+                .unwrap(),
             outputs,
             1,
             force_any_val::<BoxValue>(),
@@ -221,8 +196,52 @@ mod tests {
         assert!(r.build().is_err());
     }
 
+    #[test]
+    fn test_burn_token() {
+        let token_pair = Token {
+            token_id: force_any_val::<TokenId>(),
+            amount: 100.try_into().unwrap(),
+        };
+        let input_box = ErgoBox::new(
+            10000000i64.try_into().unwrap(),
+            force_any_val::<ErgoTree>(),
+            vec![token_pair.clone()],
+            NonMandatoryRegisters::empty(),
+            1,
+            force_any_val::<TxId>(),
+            0,
+        );
+        let inputs: Vec<ErgoBox> = vec![input_box];
+        let tx_fee = BoxValue::SAFE_USER_MIN;
+        let out_box_value = BoxValue::SAFE_USER_MIN;
+        let target_balance = out_box_value.checked_add(&tx_fee).unwrap();
+        let target_tokens = vec![Token {
+            amount: 10.try_into().unwrap(),
+            ..token_pair
+        }];
+        let box_selection = SimpleBoxSelector::new()
+            .select(inputs, target_balance, target_tokens.as_slice())
+            .unwrap();
+        let box_builder =
+            ErgoBoxCandidateBuilder::new(out_box_value, force_any_val::<ErgoTree>(), 0);
+        let out_box = box_builder.build().unwrap();
+        let outputs = vec![out_box];
+        let tx_builder = TxBuilder::new(
+            box_selection,
+            outputs,
+            0,
+            tx_fee,
+            force_any_val::<Address>(),
+            BoxValue::SAFE_USER_MIN,
+        );
+        let tx = tx_builder.build().unwrap();
+        assert!(tx.output_candidates.get(0).unwrap().tokens().is_empty());
+    }
+
     proptest! {
 
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
         #[test]
         fn test_build_tx(inputs in vec(any_with::<ErgoBox>((BoxValue::MIN_RAW * 5000 .. BoxValue::MIN_RAW * 10000).into()), 1..10),
                          outputs in vec(any_with::<ErgoBoxCandidate>((BoxValue::MIN_RAW * 1000 ..BoxValue::MIN_RAW * 2000).into()), 1..2),
@@ -238,9 +257,11 @@ mod tests {
 
             prop_assume!(all_outputs < all_inputs);
 
+            let total_output_value: BoxValue = box_value::checked_sum(outputs.iter().map(|b| b.value)).unwrap()
+                .checked_add(&miners_fee).unwrap();
+
             let mut tx_builder = TxBuilder::new(
-                SimpleBoxSelector::new(),
-                inputs.clone(),
+                SimpleBoxSelector::new().select(inputs.clone(), total_output_value, &[]).unwrap(),
                 outputs.clone(),
                 1,
                 miners_fee,


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_box_selector.js b/bindings/ergo-lib-wasm/tests/test_box_selector.js
new file mode 100644
index 000000000..ea199fec2
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/test_box_selector.js
@@ -0,0 +1,25 @@
+import { expect, assert } from 'chai';
+
+import {
+  ErgoBoxes, SimpleBoxSelector, Tokens, BoxValue
+} from '../pkg/ergo_lib_wasm';
+
+it('SimpleBoxSelector test', async () => {
+  const unspent_boxes = ErgoBoxes.from_boxes_json([
+    {
+      "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+      "value": 67500000000,
+      "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+      "assets": [],
+      "creationHeight": 284761,
+      "additionalRegisters": {},
+      "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+      "index": 1
+    }
+  ]);
+  const box_selector = new SimpleBoxSelector();
+  const selection = box_selector.select(unspent_boxes, BoxValue.from_u32(1000000), new Tokens());
+  assert(selection != null);
+  assert(selection.boxes().get(0).box_id().to_str() == unspent_boxes.get(0).box_id().to_str());
+});
+
diff --git a/bindings/ergo-lib-wasm/tests/test_transaction.js b/bindings/ergo-lib-wasm/tests/test_transaction.js
index a835f80e1..afb342af3 100644
--- a/bindings/ergo-lib-wasm/tests/test_transaction.js
+++ b/bindings/ergo-lib-wasm/tests/test_transaction.js
@@ -3,7 +3,7 @@ import { expect, assert } from 'chai';
 import {
   Address, Wallet, ErgoBox, ErgoBoxCandidateBuilder, Contract,
   ErgoBoxes, ErgoBoxCandidates,
-  ErgoStateContext, TxBuilder, BoxValue, BoxSelector, SecretKey, TxId, DataInputs
+  ErgoStateContext, TxBuilder, BoxValue, BoxSelector, SecretKey, TxId, DataInputs, SimpleBoxSelector, Tokens, Token, TokenAmount, TokenId
 } from '../pkg/ergo_lib_wasm';
 
 it('TxBuilder test', async () => {
@@ -27,7 +27,9 @@ it('TxBuilder test', async () => {
   const change_address = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
   const min_change_value = BoxValue.SAFE_USER_MIN();
   const data_inputs = new DataInputs();
-  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value);
+  const box_selector = new SimpleBoxSelector();
+  const box_selection = box_selector.select(unspent_boxes, BoxValue.from_u32(11000000), new Tokens());
+  const tx_builder = TxBuilder.new(box_selection, tx_outputs, 0, fee, change_address, min_change_value);
   tx_builder.set_data_inputs(data_inputs);
   const tx = tx_builder.build();
   assert(tx != null);
@@ -47,7 +49,9 @@ it('sign transaction', async () => {
   const fee = BoxValue.from_u32(1000000);
   const change_address = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
   const min_change_value = BoxValue.SAFE_USER_MIN();
-  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value);
+  const box_selector = new SimpleBoxSelector();
+  const box_selection = box_selector.select(unspent_boxes, BoxValue.from_u32(11000000), new Tokens());
+  const tx_builder = TxBuilder.new(box_selection, 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();
@@ -55,3 +59,81 @@ it('sign transaction', async () => {
   const signed_tx = wallet.sign_transaction(dummy_ctx, tx, unspent_boxes, tx_data_inputs);
   assert(signed_tx != null);
 });
+
+it('TxBuilder mint token test', async () => {
+  const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+  const unspent_boxes = ErgoBoxes.from_boxes_json([
+    {
+      "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e",
+      "value": 67500000000,
+      "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301",
+      "assets": [],
+      "creationHeight": 284761,
+      "additionalRegisters": {},
+      "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9",
+      "index": 1
+    }
+  ]);
+  const box_selector = new SimpleBoxSelector();
+  const box_selection = box_selector.select(unspent_boxes, BoxValue.from_u32(11000000), new Tokens());
+  const contract = Contract.pay_to_address(recipient);
+  const token_id = TokenId.from_box_id(box_selection.boxes().get(0).box_id());
+  const box_builder = new ErgoBoxCandidateBuilder(BoxValue.from_u32(10000000), contract, 0);
+  const token = new Token(token_id, TokenAmount.from_u32(1));
+  box_builder.mint_token(token, "TKN", "token desc", 2)
+  const outbox = box_builder.build();
+  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.SAFE_USER_MIN();
+  const data_inputs = new DataInputs();
+  const tx_builder = TxBuilder.new(box_selection, tx_outputs, 0, fee, change_address, min_change_value);
+  tx_builder.set_data_inputs(data_inputs);
+  const tx = tx_builder.build();
+  assert(tx != null);
+});
+
+it('TxBuilder burn token test', async () => {
+  const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+  const unspent_boxes = ErgoBoxes.from_boxes_json([
+    {
+      "boxId": "0cf7b9e71961cc473242de389c8e594a4e5d630ddd2e4e590083fb0afb386341",
+      "value": 11491500000,
+      "ergoTree": "100f040005c801056404000e2019719268d230fd9093e4db0e2e42a07883ffe976e77c7419efc1bb218a05d4ba04000500043c040204c096b10204020101040205c096b1020400d805d601b2a5730000d602e4c6a70405d6039c9d720273017302d604b5db6501fed9010463ededed93e4c67204050ec5a7938cb2db6308720473030001730492e4c672040605997202720390e4c6720406059a72027203d605b17204ea02d1edededededed93cbc27201e4c6a7060e917205730593db63087201db6308a793e4c6720104059db072047306d9010641639a8c720601e4c68c72060206057e72050593e4c6720105049ae4c6a70504730792c1720199c1a77e9c9a720573087309058cb072048602730a730bd901063c400163d802d6088c720601d6098c72080186029a7209730ceded8c72080293c2b2a5720900d0cde4c68c720602040792c1b2a5720900730d02b2ad7204d9010663cde4c672060407730e00",
+      "assets": [
+        {
+          "tokenId": "19475d9a78377ff0f36e9826cec439727bea522f6ffa3bda32e20d2f8b3103ac",
+          "amount": 1
+        }
+      ],
+      "creationHeight": 348198,
+      "additionalRegisters": {
+        "R4": "059acd9109",
+        "R5": "04f2c02a",
+        "R6": "0e20277c78751ff6f68d4dcd082eeea9506324911a875b6b9cd4d177d4fcab061327"
+      },
+      "transactionId": "5ed0e572a8c097b053965519a696f413f7be02754345e8ed650377e29a6dedb3",
+      "index": 0
+    }
+  ]);
+  const token_id = TokenId.from_str("19475d9a78377ff0f36e9826cec439727bea522f6ffa3bda32e20d2f8b3103ac");
+  const token = new Token(token_id, TokenAmount.from_u32(1));
+  const box_selector = new SimpleBoxSelector();
+  let tokens = new Tokens();
+  tokens.add(token);
+  // select tokens from inputs
+  const box_selection = box_selector.select(unspent_boxes, BoxValue.from_u32(11000000), tokens);
+  const contract = Contract.pay_to_address(recipient);
+  // but don't put selected tokens in the output box (burn them)
+  const box_builder = new ErgoBoxCandidateBuilder(BoxValue.from_u32(10000000), contract, 0);
+  const outbox = box_builder.build();
+  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.SAFE_USER_MIN();
+  const data_inputs = new DataInputs();
+  const tx_builder = TxBuilder.new(box_selection, tx_outputs, 0, fee, change_address, min_change_value);
+  tx_builder.set_data_inputs(data_inputs);
+  const tx = tx_builder.build();
+  assert(tx != null);
+});
diff --git a/bindings/ergo-lib-wasm/tests/transaction.rs b/bindings/ergo-lib-wasm/tests/transaction.rs
index ff7b72005..ba49cc7a3 100644
--- a/bindings/ergo-lib-wasm/tests/transaction.rs
+++ b/bindings/ergo-lib-wasm/tests/transaction.rs
@@ -2,120 +2,14 @@
 
 extern crate wasm_bindgen_test;
 
-use std::{convert::TryInto, rc::Rc};
-
-use ergo_lib::{
-    ast::Expr, chain::ergo_box::register::NonMandatoryRegisters, chain::ergo_box::ErgoBox,
-    chain::transaction::TxId, sigma_protocol::sigma_boolean::SigmaProp,
-    sigma_protocol::DlogProverInput, ErgoTree,
-};
-use ergo_lib_wasm::ergo_box::box_builder::ErgoBoxCandidateBuilder;
-use ergo_lib_wasm::{
-    address::Address, box_coll::ErgoBoxCandidates, box_coll::ErgoBoxes, box_selector::BoxSelector,
-    contract::Contract, ergo_box::BoxValue, ergo_state_ctx::ErgoStateContext,
-    secret_key::SecretKey, tx_builder::TxBuilder, wallet::Wallet,
-};
-use wasm_bindgen::JsValue;
+use ergo_lib_wasm::secret_key::SecretKey;
 use wasm_bindgen_test::*;
 
 wasm_bindgen_test_configure!(run_in_browser);
 
-#[wasm_bindgen_test]
-fn test_sign_transaction() {
-    let pi = DlogProverInput::random();
-    let sk = SecretKey::from(ergo_lib::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 contract = Contract::pay_to_address(&recipient).expect("failed");
-    let outbox = ErgoBoxCandidateBuilder::new(&BoxValue::from_u32(10000000).unwrap(), &contract, 0)
-        .build()
-        .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::SAFE_USER_MIN();
-    let tx_builder = TxBuilder::new(
-        BoxSelector::Simple,
-        &tx_inputs,
-        &tx_outputs,
-        0,
-        &fee,
-        &change_address,
-        &min_change_value,
-    );
-    let tx = tx_builder.build().unwrap();
-    let wallet = Wallet::from_secret(&sk);
-    let dummy_ctx = ErgoStateContext::dummy();
-    let res = wallet.sign_transaction(
-        &dummy_ctx,
-        &tx,
-        &tx_inputs,
-        &ErgoBoxes::from_boxes_json(Box::new([])).unwrap(),
-    );
-    let _signed_tx = res.unwrap();
-}
-
 #[wasm_bindgen_test]
 fn test_random() {
     let sk1 = SecretKey::random_dlog();
     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 = ErgoBoxCandidateBuilder::new(&BoxValue::from_u32(10000000).unwrap(), &contract, 0)
-        .build()
-        .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::SAFE_USER_MIN();
-    let tx_builder = TxBuilder::new(
-        BoxSelector::Simple,
-        &tx_inputs,
-        &tx_outputs,
-        0,
-        &fee,
-        &change_address,
-        &min_change_value,
-    );
-    let _tx = tx_builder.build().unwrap();
-}