From 3cd9835382220bf1baeba62faeb637ef1d492aa7 Mon Sep 17 00:00:00 2001
From: "daniil.frolov" <daniil.frolov@demlabs.net>
Date: Tue, 16 Jan 2024 04:56:32 +0000
Subject: [PATCH] hotfix-9667

---
 modules/common/CMakeLists.txt                 |    2 +-
 modules/common/dap_chain_datum.c              |   37 +-
 modules/common/dap_chain_datum_tx.c           |    1 +
 modules/common/dap_chain_datum_tx_items.c     |   45 +
 modules/common/dap_chain_datum_tx_voting.c    |  223 +++
 modules/common/dap_chain_datum_vote.c         |   22 -
 modules/common/include/dap_chain_common.h     |    4 +
 .../common/include/dap_chain_datum_tx_items.h |    7 +
 .../include/dap_chain_datum_tx_voting.h       |   90 ++
 modules/common/include/dap_chain_datum_vote.h |   22 -
 modules/net/dap_chain_ledger.c                |  179 +++
 modules/net/dap_chain_net.c                   |    2 +
 modules/net/dap_chain_net_voting.c            | 1360 +++++++++++++++++
 modules/net/include/dap_chain_ledger.h        |    9 +-
 modules/net/include/dap_chain_net_voting.h    |   39 +
 15 files changed, 1994 insertions(+), 48 deletions(-)
 create mode 100644 modules/common/dap_chain_datum_tx_voting.c
 delete mode 100644 modules/common/dap_chain_datum_vote.c
 create mode 100644 modules/common/include/dap_chain_datum_tx_voting.h
 delete mode 100644 modules/common/include/dap_chain_datum_vote.h
 create mode 100644 modules/net/dap_chain_net_voting.c
 create mode 100644 modules/net/include/dap_chain_net_voting.h

diff --git a/modules/common/CMakeLists.txt b/modules/common/CMakeLists.txt
index 7b6258a44b..79ce7eb3e4 100644
--- a/modules/common/CMakeLists.txt
+++ b/modules/common/CMakeLists.txt
@@ -22,4 +22,4 @@ INSTALL(TARGETS ${PROJECT_NAME}
         ARCHIVE DESTINATION lib/modules/common/
         PUBLIC_HEADER DESTINATION include/modules/common/
 )
-endif()
\ No newline at end of file
+endif()
diff --git a/modules/common/dap_chain_datum.c b/modules/common/dap_chain_datum.c
index 4fec72ab3c..1548f1698f 100644
--- a/modules/common/dap_chain_datum.c
+++ b/modules/common/dap_chain_datum.c
@@ -31,6 +31,7 @@
 #include "dap_chain_datum_tx_items.h"
 #include "dap_chain_datum_decree.h"
 #include "dap_chain_datum_anchor.h"
+#include "dap_chain_datum_tx_voting.h"
 #include "dap_chain_datum_hashtree_roots.h"
 #include "dap_enc_base58.h"
 
@@ -356,7 +357,7 @@ bool dap_chain_datum_dump_tx(dap_chain_datum_tx_t *a_datum,
             dap_chain_addr_t l_sender_addr;
             dap_chain_addr_fill_from_sign(&l_sender_addr, l_sign_tmp, a_net_id);
             const char *l_addr_str = dap_chain_addr_to_str(&l_sender_addr);
-            dap_string_append_printf(a_str_out, "\tSender addr: %s", l_addr_str);
+            dap_string_append_printf(a_str_out, "\tSender addr: %s\n", l_addr_str);
             DAP_DELETE(l_addr_str);
         } break;
         case TX_ITEM_TYPE_RECEIPT: {
@@ -549,6 +550,40 @@ bool dap_chain_datum_dump_tx(dap_chain_datum_tx_t *a_datum,
             DAP_DELETE(l_value_str);
             DAP_DELETE(l_coins_str);
         } break;
+        case TX_ITEM_TYPE_VOTING:{
+            int l_tsd_size = 0;
+            dap_chain_tx_tsd_t *l_item = (dap_chain_tx_tsd_t *)dap_chain_datum_tx_item_get(a_datum, 0, TX_ITEM_TYPE_TSD, &l_tsd_size);
+            if (!l_item || !l_tsd_size)
+                    break;
+            dap_chain_datum_tx_voting_params_t *l_voting_params = dap_chain_voting_parse_tsd(a_datum);
+            dap_string_append_printf(a_str_out, "\t VOTING:\n\tVoting question: %s\n\t Answer options:\n", l_voting_params->voting_question);
+            dap_list_t *l_temp = l_voting_params->answers_list;
+            uint8_t l_index = 0;
+            while (l_temp){
+                    dap_string_append_printf(a_str_out, "\t\t %i) %s\n", l_index, (char*)l_temp->data);
+                    l_index++;
+                    l_temp = l_temp->next;
+            }
+
+            if(l_voting_params->voting_expire)
+                    dap_string_append_printf(a_str_out, "\t Voting expire: %s\n", dap_ctime_r(&l_voting_params->voting_expire, l_tmp_buf));
+            if (l_voting_params->votes_max_count)
+                    dap_string_append_printf(a_str_out, "\t Votes max count: %"DAP_UINT64_FORMAT_U"\n", l_voting_params->votes_max_count);
+            dap_string_append_printf(a_str_out, "\t Changing vote is %s available.\n", l_voting_params->vote_changing_allowed ? "" : "not");
+            dap_string_append_printf(a_str_out, "\t A delegated key is%s required to participate in voting. \n", l_voting_params->delegate_key_required ? "" : " not");
+
+            dap_list_free_full(l_voting_params->answers_list, NULL);
+            DAP_DELETE(l_voting_params->voting_question);
+            DAP_DELETE(l_voting_params);
+        } break;
+        case TX_ITEM_TYPE_VOTE:{
+            dap_chain_tx_vote_t *l_vote_item = (dap_chain_tx_vote_t *)item;
+            char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_vote_item->voting_hash);
+            dap_string_append_printf(a_str_out, "\t VOTE: \n"
+                                                "\t Voting hash: %s\n"
+                                                "\t Vote answer idx: %"DAP_UINT64_FORMAT_U"\n", l_hash_str, l_vote_item->answer_idx);
+            DAP_DELETE(l_hash_str);
+        } break;
         default:
             dap_string_append_printf(a_str_out, " This transaction have unknown item type \n");
             break;
diff --git a/modules/common/dap_chain_datum_tx.c b/modules/common/dap_chain_datum_tx.c
index 7da9de1563..32b2777732 100644
--- a/modules/common/dap_chain_datum_tx.c
+++ b/modules/common/dap_chain_datum_tx.c
@@ -28,6 +28,7 @@
 #include "dap_sign.h"
 #include "dap_chain_datum_tx_items.h"
 #include "dap_chain_datum_tx.h"
+#include "dap_chain_datum_tx_voting.h"
 
 #define LOG_TAG "dap_chain_datum_tx"
 
diff --git a/modules/common/dap_chain_datum_tx_items.c b/modules/common/dap_chain_datum_tx_items.c
index 2a0611b9c4..253cf0495d 100644
--- a/modules/common/dap_chain_datum_tx_items.c
+++ b/modules/common/dap_chain_datum_tx_items.c
@@ -32,6 +32,8 @@
 #include "dap_hash.h"
 #include "dap_chain_datum_tx.h"
 #include "dap_chain_datum_tx_items.h"
+#include "dap_chain_datum_tx_voting.h"
+
 
 static size_t dap_chain_tx_in_get_size(const dap_chain_tx_in_t *a_item)
 {
@@ -112,6 +114,19 @@ static size_t dap_chain_tx_tsd_get_size(const dap_chain_tx_tsd_t *a_item)
     return sizeof(dap_chain_tx_tsd_t) + a_item->header.size;
 }
 
+static size_t dap_chain_tx_voting_get_size(const dap_chain_tx_voting_t *a_item)
+{
+    (void) a_item;
+    size_t size = sizeof(dap_chain_tx_voting_t);
+    return size;
+}
+
+static size_t dap_chain_tx_vote_get_size(const dap_chain_tx_vote_t *a_item)
+{
+    (void) a_item;
+    size_t size = sizeof(dap_chain_tx_vote_t);
+    return size;
+}
 /**
  * Get item type by item name
  *
@@ -144,6 +159,10 @@ dap_chain_tx_item_type_t dap_chain_datum_tx_item_str_to_type(const char *a_datum
         return TX_ITEM_TYPE_RECEIPT;
     else if(!dap_strcmp(a_datum_name, "data"))
         return TX_ITEM_TYPE_TSD;
+    else if(!dap_strcmp(a_datum_name, "voting"))
+        return TX_ITEM_TYPE_VOTING;
+    else if(!dap_strcmp(a_datum_name, "vote"))
+        return TX_ITEM_TYPE_VOTE;
     return TX_ITEM_TYPE_UNKNOWN;
 }
 
@@ -225,6 +244,12 @@ size_t dap_chain_datum_item_tx_get_size(const void *a_item)
     case TX_ITEM_TYPE_TSD:
         size = dap_chain_tx_tsd_get_size((const dap_chain_tx_tsd_t*)a_item);
         break;
+    case TX_ITEM_TYPE_VOTING:
+        size = dap_chain_tx_voting_get_size((const dap_chain_tx_voting_t*)a_item);
+        break;
+    case TX_ITEM_TYPE_VOTE:
+        size = dap_chain_tx_vote_get_size((const dap_chain_tx_vote_t*)a_item);
+        break;
     default:
         return 0;
     }
@@ -653,3 +678,23 @@ dap_chain_tx_out_cond_t *dap_chain_datum_tx_out_cond_get(dap_chain_datum_tx_t *a
     }
     return l_res;
 }
+
+uint8_t *dap_chain_datum_tx_out_get_by_out_idx(dap_chain_datum_tx_t *a_tx, int a_out_num)
+{
+    uint8_t *l_ret = NULL;
+    dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_OUT_ALL, NULL), *l_item;
+    if (!l_list_out_items)
+        return NULL;
+
+    l_item = dap_list_nth(l_list_out_items, a_out_num);
+
+    if(!l_item){
+        dap_list_free(l_list_out_items);
+        return NULL;
+    }
+
+    l_ret = l_item->data;
+    dap_list_free(l_list_out_items);
+    return l_ret;
+
+}
diff --git a/modules/common/dap_chain_datum_tx_voting.c b/modules/common/dap_chain_datum_tx_voting.c
new file mode 100644
index 0000000000..510499137c
--- /dev/null
+++ b/modules/common/dap_chain_datum_tx_voting.c
@@ -0,0 +1,223 @@
+/*
+ * Authors:
+ * Daniil Frolov <daniil.frolov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Copyright  (c) 2020, All rights reserved.
+
+ This file is part of CellFrame SDK the open source project
+
+    CellFrame SDK is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    CellFrame SDK is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dap_chain_datum_tx_voting.h"
+#include "dap_chain_common.h"
+
+
+#define LOG_TAG "datum_tx_voting"
+
+
+dap_chain_datum_tx_voting_params_t* dap_chain_voting_parse_tsd(dap_chain_datum_tx_t* a_tx)
+{
+    if (!a_tx)
+        return NULL;
+
+    dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_TSD, NULL);
+    dap_chain_datum_tx_voting_params_t *l_voting_parms = DAP_NEW_Z_SIZE(dap_chain_datum_tx_voting_params_t,
+                                                                        sizeof(dap_chain_datum_tx_voting_params_t));
+    char *l_buf_string = NULL;
+    dap_list_t* l_temp = l_tsd_list;
+    while (l_temp){
+        dap_tsd_t *l_tsd = (dap_tsd_t *)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
+        switch(l_tsd->type){
+        case VOTING_TSD_TYPE_QUESTION:
+            l_buf_string = DAP_NEW_Z_SIZE(char, l_tsd->size + 1);
+            memcpy(l_buf_string, l_tsd->data, l_tsd->size);
+            l_buf_string[l_tsd->size] = '\0';
+            l_voting_parms->voting_question = l_buf_string;
+            break;
+        case VOTING_TSD_TYPE_ANSWER:
+            l_buf_string = DAP_NEW_Z_SIZE(char, l_tsd->size + 1);
+            memcpy(l_buf_string, l_tsd->data, l_tsd->size);
+            l_buf_string[l_tsd->size] = '\0';
+            l_voting_parms->answers_list = dap_list_append(l_voting_parms->answers_list, l_buf_string);
+            l_voting_parms->answers_count++;
+            break;
+        case VOTING_TSD_TYPE_EXPIRE:
+            l_voting_parms->voting_expire = *(dap_time_t*)l_tsd->data;
+            break;
+        case VOTING_TSD_TYPE_MAX_VOTES_COUNT:
+            l_voting_parms->votes_max_count = *(uint64_t*)l_tsd->data;
+            break;
+        case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED:
+            l_voting_parms->delegate_key_required = *(bool*)l_tsd->data;
+            break;
+        case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED:
+            l_voting_parms->vote_changing_allowed = *(bool*)l_tsd->data;
+            break;
+        default:
+            break;
+        }
+
+        l_temp = l_temp->next;
+    }
+    dap_list_free(l_tsd_list);
+
+    return l_voting_parms;
+}
+
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_question_tsd_create(const char* a_question_str, size_t str_len)
+{
+    if (!a_question_str || !str_len)
+        return NULL;
+
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create((void*)a_question_str, VOTING_TSD_TYPE_QUESTION, str_len);
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_answer_tsd_create(const char* a_answer_str, size_t str_len)
+{
+    if (!a_answer_str || !str_len)
+        return NULL;
+
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create((void*)a_answer_str, VOTING_TSD_TYPE_ANSWER, str_len);
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_expire_tsd_create(dap_time_t a_expire)
+{
+    if (!a_expire)
+        return NULL;
+
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_expire, VOTING_TSD_TYPE_EXPIRE, sizeof(dap_time_t));
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_max_votes_count_tsd_create(uint64_t a_max_count)
+{
+    if (!a_max_count)
+        return NULL;
+
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_max_count, VOTING_TSD_TYPE_MAX_VOTES_COUNT, sizeof(uint64_t));
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_delegated_key_required_tsd_create(bool a_delegate_key_required)
+{
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_delegate_key_required, VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED, sizeof(bool));
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_changing_allowed_tsd_create(bool a_vote_changing_allowed)
+{
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_vote_changing_allowed, VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED, sizeof(bool));
+
+    return l_tsd;
+}
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_tx_cond_tsd_create(dap_chain_hash_fast_t a_tx_hash, int a_out_idx)
+{
+    dap_chain_tx_voting_tx_cond_t l_temp = {
+        .tx_hash = a_tx_hash,
+        .out_idx = a_out_idx
+    };
+
+    dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&l_temp, VOTING_TSD_TYPE_VOTE_TX_COND, sizeof(dap_chain_tx_voting_tx_cond_t));
+
+    return l_tsd;
+}
+
+dap_chain_tx_voting_t *dap_chain_datum_tx_item_voting_create(void)
+{
+    dap_chain_tx_voting_t * l_item = DAP_NEW_Z(dap_chain_tx_voting_t);
+    l_item->type = TX_ITEM_TYPE_VOTING;
+    return l_item;
+}
+
+json_object *dap_chain_datum_tx_item_voting_tsd_to_json(dap_chain_datum_tx_t* a_tx)
+{
+    if (!a_tx)
+        return NULL;
+
+    json_object *l_object = json_object_new_object();
+    json_object *l_answer_array_object = json_object_new_array();
+    json_object *l_json_obj = NULL;
+    dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_TSD, NULL);
+    dap_list_t* l_temp = l_tsd_list;
+    while (l_temp){
+        dap_tsd_t* l_tsd = (dap_tsd_t *)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
+        switch(l_tsd->type){
+        case VOTING_TSD_TYPE_QUESTION:
+            l_json_obj = json_object_new_string_len((char*)l_tsd->data, l_tsd->size);
+            json_object_object_add(l_object, "question", l_json_obj);
+            break;
+        case VOTING_TSD_TYPE_ANSWER:
+            l_json_obj = json_object_new_string_len((char*)l_tsd->data, l_tsd->size);
+            json_object_array_add(l_answer_array_object, l_json_obj);
+            break;
+        case VOTING_TSD_TYPE_EXPIRE:
+            l_json_obj = json_object_new_uint64(*(uint64_t*)l_tsd->data);
+            json_object_object_add(l_object, "exired", l_json_obj);
+            break;
+        case VOTING_TSD_TYPE_MAX_VOTES_COUNT:
+            l_json_obj = json_object_new_uint64(*(uint64_t*)l_tsd->data);
+            json_object_object_add(l_object, "maxVotes", l_json_obj);
+            break;
+        case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED:
+            l_json_obj = json_object_new_boolean(*(bool*)l_tsd->data);
+            json_object_object_add(l_object, "delegateKeyRequired", l_json_obj);
+            break;
+        case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED:
+            l_json_obj = json_object_new_boolean(*(bool*)l_tsd->data);
+            json_object_object_add(l_object, "voteChangingAllowed", l_json_obj);
+            break;
+        default:
+            break;
+        }
+        l_temp = l_temp->next;
+    }
+    dap_list_free_full(l_tsd_list, NULL);
+
+    json_object_object_add(l_object, "answers", l_answer_array_object);
+    return l_object;
+}
+
+dap_chain_tx_vote_t *dap_chain_datum_tx_item_vote_create(dap_chain_hash_fast_t *a_voting_hash, uint64_t *a_answer_idx)
+{
+    if (!a_voting_hash)
+        return NULL;
+
+    dap_chain_tx_vote_t * l_item = DAP_NEW_Z(dap_chain_tx_vote_t);
+    l_item->type = TX_ITEM_TYPE_VOTE;
+    l_item->answer_idx = *a_answer_idx;
+    l_item->voting_hash = *a_voting_hash;
+    return l_item;
+}
+
+json_object *dap_chain_datum_tx_item_vote_to_json(dap_chain_tx_vote_t *a_vote)
+{
+    json_object *l_object = json_object_new_object();
+    char *l_voting_hash_str = dap_hash_fast_to_str_new(&a_vote->voting_hash);
+    json_object *l_voting_hash = json_object_new_string(l_voting_hash_str);
+    DAP_DELETE(l_voting_hash_str);
+    json_object *l_answer_idx = json_object_new_uint64(a_vote->answer_idx);
+    json_object_object_add(l_object, "votingHash", l_voting_hash);
+    json_object_object_add(l_object, "answer_idx", l_answer_idx);
+    return l_object;
+}
diff --git a/modules/common/dap_chain_datum_vote.c b/modules/common/dap_chain_datum_vote.c
deleted file mode 100644
index 7d7eef1b52..0000000000
--- a/modules/common/dap_chain_datum_vote.c
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Authors:
- * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
- * DeM Labs Inc.   https://demlabs.net
- * Copyright  (c) 2020, All rights reserved.
-
- This file is part of CellFrame SDK the open source project
-
-    CellFrame SDK is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    CellFrame SDK is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
diff --git a/modules/common/include/dap_chain_common.h b/modules/common/include/dap_chain_common.h
index fc2924bdfe..2e59ae9b82 100644
--- a/modules/common/include/dap_chain_common.h
+++ b/modules/common/include/dap_chain_common.h
@@ -200,6 +200,10 @@ enum dap_chain_tx_item_type {
     TX_ITEM_TYPE_RECEIPT = 0x70,
     TX_ITEM_TYPE_TSD = 0x80,
 
+    /// @brief Transaction: voting and vote
+    TX_ITEM_TYPE_VOTING = 0x90,
+    TX_ITEM_TYPE_VOTE = 0x91,
+
     /// @brief Virtual types for items enumearting
     TX_ITEM_TYPE_IN_EMS_LOCK = 0xf1,
     TX_ITEM_TYPE_IN_ALL = 0xfd,
diff --git a/modules/common/include/dap_chain_datum_tx_items.h b/modules/common/include/dap_chain_datum_tx_items.h
index 7ab06e5c3b..dfb41acc1f 100644
--- a/modules/common/include/dap_chain_datum_tx_items.h
+++ b/modules/common/include/dap_chain_datum_tx_items.h
@@ -77,6 +77,10 @@ DAP_STATIC_INLINE const char * dap_chain_datum_tx_item_type_to_str(dap_chain_tx_
         case TX_ITEM_TYPE_OUT_COND: return "TX_ITEM_TYPE_OUT_COND"; // 256
         case TX_ITEM_TYPE_RECEIPT: return "TX_ITEM_TYPE_RECEIPT";
         case TX_ITEM_TYPE_TSD: return "TX_ITEM_TYPE_TSD";
+        case TX_ITEM_TYPE_OUT_ALL: return "TX_ITEM_TYPE_OUT_OLDALL";
+        case TX_ITEM_TYPE_ANY: return "TX_ITEM_TYPE_ANY";
+        case TX_ITEM_TYPE_VOTING: return "TX_ITEM_TYPE_VOTING";
+        case TX_ITEM_TYPE_VOTE: return "TX_ITEM_TYPE_VOTE";
         default: return "UNDEFINED";
     }
 }
@@ -239,3 +243,6 @@ uint8_t *dap_chain_datum_tx_item_get_nth(dap_chain_datum_tx_t *a_tx, dap_chain_t
 dap_list_t* dap_chain_datum_tx_items_get(dap_chain_datum_tx_t *a_tx, dap_chain_tx_item_type_t a_type, int *a_item_count);
 // Get conditional out item with it's idx
 dap_chain_tx_out_cond_t *dap_chain_datum_tx_out_cond_get(dap_chain_datum_tx_t *a_tx, dap_chain_tx_item_type_t a_cond_type, int *a_out_num);
+// Get output by output index
+uint8_t *dap_chain_datum_tx_out_get_by_out_idx(dap_chain_datum_tx_t *a_tx, int a_out_num);
+
diff --git a/modules/common/include/dap_chain_datum_tx_voting.h b/modules/common/include/dap_chain_datum_tx_voting.h
new file mode 100644
index 0000000000..1eaf8b5b42
--- /dev/null
+++ b/modules/common/include/dap_chain_datum_tx_voting.h
@@ -0,0 +1,90 @@
+/*
+ * Authors:
+ * Daniil Frolov <daniil.frolov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Copyright  (c) 2020, All rights reserved.
+
+ This file is part of CellFrame SDK the open source project
+
+    CellFrame SDK is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    CellFrame SDK is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <stdint.h>
+#include "dap_chain_common.h"
+#include "dap_chain_datum_tx.h"
+#include "dap_chain_datum_tx_items.h"
+#include "dap_time.h"
+#include "dap_list.h"
+#include "dap_tsd.h"
+#include "json.h"
+
+#define DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH 200
+#define DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH 100
+#define DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT 10
+
+
+typedef enum dap_chain_datum_voting_tsd_type {
+    VOTING_TSD_TYPE_QUESTION = 0x01,
+    VOTING_TSD_TYPE_ANSWER,
+    VOTING_TSD_TYPE_EXPIRE,
+    VOTING_TSD_TYPE_MAX_VOTES_COUNT,
+    VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED,
+    VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED,
+    VOTING_TSD_TYPE_VOTE_TX_COND
+} dap_chain_datum_voting_tsd_type_t;
+
+typedef struct dap_chain_tx_voting {
+    dap_chain_tx_item_type_t type;
+} DAP_ALIGN_PACKED dap_chain_tx_voting_t;
+
+typedef struct dap_chain_tx_voting_tx_cond {
+    dap_chain_hash_fast_t tx_hash;
+    int out_idx;
+} DAP_ALIGN_PACKED dap_chain_tx_voting_tx_cond_t;
+
+typedef struct dap_chain_tx_vote {
+    dap_chain_tx_item_type_t type;
+    dap_chain_hash_fast_t voting_hash;
+    uint64_t answer_idx;
+} DAP_ALIGN_PACKED dap_chain_tx_vote_t;
+
+
+typedef struct dap_chain_datum_tx_voting_params {
+    char *voting_question;
+    dap_list_t *answers_list;
+    uint8_t    answers_count;
+    dap_time_t voting_expire;
+    uint64_t   votes_max_count;
+    bool       delegate_key_required;
+    bool       vote_changing_allowed;
+} dap_chain_datum_tx_voting_params_t;
+
+
+dap_chain_datum_tx_voting_params_t *dap_chain_voting_parse_tsd(dap_chain_datum_tx_t* a_tx);
+
+dap_chain_tx_tsd_t* dap_chain_datum_voting_question_tsd_create(const char* a_question_str, size_t str_len);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_answer_tsd_create(const char* a_answer_str, size_t str_len);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_expire_tsd_create(dap_time_t a_expire);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_max_votes_count_tsd_create(uint64_t a_max_count);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_delegated_key_required_tsd_create(bool a_delegate_key_required);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_changing_allowed_tsd_create(bool a_vote_changing_allowed);
+dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_tx_cond_tsd_create(dap_chain_hash_fast_t a_tx_hash, int a_out_idx);
+
+dap_chain_tx_voting_t *dap_chain_datum_tx_item_voting_create(void);
+json_object *dap_chain_datum_tx_item_voting_tsd_to_json(dap_chain_datum_tx_t* a_tx);
+
+
+dap_chain_tx_vote_t *dap_chain_datum_tx_item_vote_create(dap_chain_hash_fast_t *a_voting_hash, uint64_t *a_answer_idx);
+json_object *dap_chain_datum_tx_item_vote_to_json(dap_chain_tx_vote_t *a_vote);
diff --git a/modules/common/include/dap_chain_datum_vote.h b/modules/common/include/dap_chain_datum_vote.h
deleted file mode 100644
index d023c26b51..0000000000
--- a/modules/common/include/dap_chain_datum_vote.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Authors:
- * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
- * DeM Labs Inc.   https://demlabs.net
- * Copyright  (c) 2020, All rights reserved.
-
- This file is part of CellFrame SDK the open source project
-
-    CellFrame SDK is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    CellFrame SDK is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with any CellFrame SDK based project.  If not, see <http://www.gnu.org/licenses/>.
-*/
-#pragma once
diff --git a/modules/net/dap_chain_ledger.c b/modules/net/dap_chain_ledger.c
index f6d78a3b01..49244b1163 100644
--- a/modules/net/dap_chain_ledger.c
+++ b/modules/net/dap_chain_ledger.c
@@ -70,6 +70,8 @@ typedef struct dap_ledger_verificator {
 static dap_ledger_verificator_t *s_verificators;
 static  pthread_rwlock_t s_verificators_rwlock;
 
+static dap_chain_ledger_voting_callback_t s_voting_callback;
+
 #define MAX_OUT_ITEMS   10
 
 static const char * const s_ledger_tx_check_err_str[] = {
@@ -4203,6 +4205,30 @@ int dap_ledger_tx_cache_check(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx
         }
     }
 
+
+    if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTING, NULL)){
+        if (s_voting_callback){
+            if (!s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTING, a_tx, false)){
+                debug_if(s_debug_more, L_WARNING, "Verificator check error for voting.");
+                l_err_num = DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE;
+            }
+        } else {
+            debug_if(s_debug_more, L_WARNING, "Verificator check error for voting item");
+            l_err_num = DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET;
+        }
+    }else if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTE, NULL)){
+        if (s_voting_callback){
+            if (!s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTE, a_tx, false)){
+                debug_if(s_debug_more, L_WARNING, "Verificator check error for vote.");
+                l_err_num = DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE;
+            }
+        } else {
+            debug_if(s_debug_more, L_WARNING, "Verificator check error for vote item");
+            l_err_num = DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET;
+        }
+    }
+
+
     // 8. Check sovereign tax
     if (l_fee_check && SUBTRACT_256_256(l_taxed_value, l_fee_sum, &l_taxed_value)) {
         log_it(L_WARNING, "Fee is greater than sum of inputs");
@@ -4627,6 +4653,11 @@ int dap_ledger_tx_add(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_ha
         DAP_DELETE (l_addr_str);
     }
 
+    if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTING, NULL) && s_voting_callback)
+        s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTING, a_tx, true);
+    else if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTE, NULL) && s_voting_callback)
+        s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTE, a_tx, true);
+
     // add transaction to the cache list
     dap_ledger_tx_item_t *l_tx_item = DAP_NEW_Z(dap_ledger_tx_item_t);
     if ( !l_tx_item ) {
@@ -5436,6 +5467,81 @@ dap_list_t *dap_ledger_get_list_tx_outs_with_val(dap_ledger_t *a_ledger, const c
     return l_list_used_out;
 }
 
+dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from,
+                                        uint256_t *a_value_transfer)
+{
+    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
+    dap_chain_hash_fast_t l_tx_cur_hash = { 0 };
+    uint256_t l_value_transfer = {};
+    dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from,
+                                                            &l_tx_cur_hash);
+
+    while(l_tx)
+    {
+        // Get all item from transaction by type
+        dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_ALL, NULL);
+
+        uint32_t l_out_idx_tmp = 0; // current index of 'out' item
+        for (dap_list_t *it = l_list_out_items; it; it = dap_list_next(it), l_out_idx_tmp++) {
+            dap_chain_tx_item_type_t l_type = *(uint8_t *)it->data;
+            uint256_t l_value = {};
+            switch (l_type) {
+            case TX_ITEM_TYPE_OUT_OLD: {
+                dap_chain_tx_out_old_t *l_out = (dap_chain_tx_out_old_t *)it->data;
+                if (!l_out->header.value || memcmp(a_addr_from, &l_out->addr, sizeof(dap_chain_addr_t)))
+                    continue;
+                l_value = GET_256_FROM_64(l_out->header.value);
+            } break;
+            case TX_ITEM_TYPE_OUT: {
+                dap_chain_tx_out_t *l_out = (dap_chain_tx_out_t *)it->data;
+                if (memcmp(a_addr_from, &l_out->addr, sizeof(dap_chain_addr_t)) ||
+                    dap_strcmp(dap_ledger_tx_get_token_ticker_by_hash(a_ledger, &l_tx_cur_hash), a_token_ticker) ||
+                    IS_ZERO_256(l_out->header.value))
+                    continue;
+                l_value = l_out->header.value;
+            } break;
+            case TX_ITEM_TYPE_OUT_EXT: {
+                dap_chain_tx_out_ext_t *l_out_ext = (dap_chain_tx_out_ext_t *)it->data;
+                if (memcmp(a_addr_from, &l_out_ext->addr, sizeof(dap_chain_addr_t)) ||
+                    strcmp((char *)a_token_ticker, l_out_ext->token) ||
+                    IS_ZERO_256(l_out_ext->header.value) ) {
+                    continue;
+                }
+                l_value = l_out_ext->header.value;
+            } break;
+            case TX_ITEM_TYPE_OUT_COND:
+            default:
+                continue;
+            }
+            // Check whether used 'out' items
+            if (!dap_ledger_tx_hash_is_used_out_item (a_ledger, &l_tx_cur_hash, l_out_idx_tmp, NULL)) {
+                dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t);
+                if ( !l_item ) {
+                    log_it(L_CRITICAL, "Out of memory");
+                    if (l_list_used_out)
+                        dap_list_free_full(l_list_used_out, NULL);
+                    dap_list_free(l_list_out_items);
+                    return NULL;
+                }
+                l_item->tx_hash_fast = l_tx_cur_hash;
+                l_item->num_idx_out = l_out_idx_tmp;
+                l_item->value = l_value;
+                l_list_used_out = dap_list_append(l_list_used_out, l_item);
+                SUM_256_256(l_value_transfer, l_item->value, &l_value_transfer);
+            }
+        }
+        dap_list_free(l_list_out_items);
+        l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from,
+                                          &l_tx_cur_hash);
+    }
+
+    if (a_value_transfer) {
+        *a_value_transfer = l_value_transfer;
+    }
+    return l_list_used_out;
+}
+
+
 // Add new verificator callback with associated subtype. Returns 1 if callback replaced, -1 error, overwise returns 0
 int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_ledger_verificator_callback_t a_callback, dap_ledger_updater_callback_t a_callback_added)
 {
@@ -5462,6 +5568,20 @@ int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_le
     return 0;
 }
 
+int dap_chain_ledger_voting_verificator_add(dap_chain_ledger_voting_callback_t a_callback)
+{
+    if (!a_callback)
+        return -1;
+
+    if (s_voting_callback){
+        s_voting_callback = a_callback;
+        return 1;
+    }
+
+    s_voting_callback = a_callback;
+    return 0;
+}
+
 dap_list_t *dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only)
 {
     dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger);
@@ -5561,6 +5681,65 @@ dap_list_t *dap_ledger_get_list_tx_cond_outs_with_val(dap_ledger_t *a_ledger, co
     return l_list_used_out;
 }
 
+dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, const char *a_token_ticker,  const dap_chain_addr_t *a_addr_from,
+                                             dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t *a_value_transfer)
+{
+    dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items
+    dap_chain_hash_fast_t l_tx_cur_hash = { 0 };
+    uint256_t l_value_transfer = { };
+    dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, &l_tx_cur_hash);
+    while(l_tx)
+    {
+        // Get all item from transaction by type
+        dap_list_t *l_list_out_cond_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_COND, NULL);
+
+        uint32_t l_out_idx_tmp = 0; // current index of 'out' item
+        for(dap_list_t *it = l_list_out_cond_items; it; it = dap_list_next(it), l_out_idx_tmp++) {
+            dap_chain_tx_item_type_t l_type = *(uint8_t*) it->data;
+            uint256_t l_value = { };
+            switch (l_type) {
+            case TX_ITEM_TYPE_OUT_COND: {
+                dap_chain_tx_out_cond_t *l_out_cond = (dap_chain_tx_out_cond_t*) it->data;
+                if(IS_ZERO_256(l_out_cond->header.value) || a_subtype != l_out_cond->header.subtype) {
+                    continue;
+                }
+                l_value = l_out_cond->header.value;
+            }
+            break;
+            default:
+                continue;
+            }
+            if (!IS_ZERO_256(l_value)) {
+                dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t);
+                if ( !l_item ) {
+                    if (l_list_used_out)
+                        dap_list_free_full(l_list_used_out, NULL);
+                    dap_list_free(l_list_out_cond_items);
+                    return NULL;
+                }
+                l_item->tx_hash_fast = l_tx_cur_hash;
+                l_item->num_idx_out = l_out_idx_tmp;
+                l_item->value = l_value;
+                l_list_used_out = dap_list_append(l_list_used_out, l_item);
+                SUM_256_256(l_value_transfer, l_item->value, &l_value_transfer);
+            }
+        }
+        dap_list_free(l_list_out_cond_items);
+        l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, &l_tx_cur_hash);
+    }
+
+    // nothing to tranfer (not enough funds)
+    if(!l_list_used_out) {
+        dap_list_free_full(l_list_used_out, NULL);
+        return NULL;
+    }
+
+    if (a_value_transfer) {
+        *a_value_transfer = l_value_transfer;
+    }
+    return l_list_used_out;
+}
+
 void dap_ledger_tx_add_notify(dap_ledger_t *a_ledger, dap_ledger_tx_add_notify_t a_callback, void *a_arg) {
     if (!a_ledger) {
         log_it(L_ERROR, "NULL ledger passed to dap_ledger_tx_add_notify()");
diff --git a/modules/net/dap_chain_net.c b/modules/net/dap_chain_net.c
index 3544354dec..a94d103d59 100644
--- a/modules/net/dap_chain_net.c
+++ b/modules/net/dap_chain_net.c
@@ -118,6 +118,7 @@
 #include "dap_chain_net_srv_xchange.h"
 #include "dap_chain_node_net_ban_list.h"
 #include "dap_chain_cs_esbocs.h"
+#include "dap_chain_net_voting.h"
 
 #include <stdio.h>
 #include <sys/types.h>
@@ -270,6 +271,7 @@ int dap_chain_net_init()
     dap_stream_ch_chain_init();
     dap_stream_ch_chain_net_init();
     dap_chain_node_client_init();
+    dap_chain_net_voting_init();
     dap_chain_node_net_ban_list_init();
     dap_cli_server_cmd_add ("net", s_cli_net, "Network commands",
         "net list [chains -net <chain net name>]\n"
diff --git a/modules/net/dap_chain_net_voting.c b/modules/net/dap_chain_net_voting.c
new file mode 100644
index 0000000000..00855faa3f
--- /dev/null
+++ b/modules/net/dap_chain_net_voting.c
@@ -0,0 +1,1360 @@
+/*
+ * Authors:
+ * Daniil Frolov <daniil.frolov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * DeM Labs Open source community https://github.com/demlabsinc
+ * Copyright  (c) 2017-2019
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+ DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ DAP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "dap_chain_net_voting.h"
+#include "dap_chain_net_srv_stake_pos_delegate.h"
+#include "dap_chain_node_cli.h"
+#include "dap_chain_mempool.h"
+#include "uthash.h"
+#include "utlist.h"
+
+#define LOG_TAG "chain_net_voting"
+
+typedef struct dap_chain_net_voting_params_offsets{
+    dap_chain_datum_tx_t* voting_tx;
+    size_t voting_question_offset;
+    size_t voting_question_length;
+    dap_list_t* option_offsets_list;
+    size_t voting_expire_offset;
+    size_t votes_max_count_offset;
+    size_t delegate_key_required_offset;
+    size_t vote_changing_allowed_offset;
+} dap_chain_net_voting_params_offsets_t;
+
+typedef struct dap_chain_net_vote_option {
+    size_t vote_option_offset;
+    size_t vote_option_length;
+} dap_chain_net_vote_option_t;
+
+typedef struct dap_chain_net_voting_cond_outs {
+    dap_chain_hash_fast_t tx_hash;
+    int out_idx;
+
+    UT_hash_handle hh;
+} dap_chain_net_voting_cond_outs_t;
+
+typedef struct dap_chain_net_vote {
+    dap_chain_hash_fast_t vote_hash;
+    dap_chain_hash_fast_t pkey_hash;
+    uint64_t answer_idx;
+    uint256_t weight;
+} dap_chain_net_vote_t;
+
+typedef struct dap_chain_net_votings {
+    dap_chain_hash_fast_t voting_hash;
+    dap_chain_net_voting_params_offsets_t voting_params;
+    dap_list_t *votes;
+    dap_chain_net_id_t net_id;
+
+    pthread_rwlock_t s_tx_outs_rwlock;
+    dap_chain_net_voting_cond_outs_t *voting_spent_cond_outs;
+
+    UT_hash_handle hh;
+} dap_chain_net_votings_t;
+
+static dap_chain_net_votings_t *s_votings;
+static pthread_rwlock_t s_votings_rwlock;
+
+static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx);
+/// -1 error, 0 - unspent, 1 - spent
+static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx);
+static bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply);
+static int s_cli_voting(int argc, char **argv, void **a_str_reply);
+
+int dap_chain_net_voting_init()
+{
+    pthread_rwlock_init(&s_votings_rwlock, NULL);
+    dap_chain_ledger_voting_verificator_add(s_datum_tx_voting_verification_callback);
+    dap_cli_server_cmd_add("voting", s_cli_voting, "Voting commands.", ""
+                                                                       "voting create -net <net_name> -question <\"Question_string\"> -options <\"Option1\", \"Option2\" ... \"OptionN\"> [-expire <voting_expire_time_in_RCF822>] [-max_votes_count <Votes_count>] [-delegated_key_required] [-vote_changing_allowed] -fee <value_datoshi> -w <fee_wallet_name>\n"
+                                                                       "voting vote -net <net_name> -hash <voting_hash> -option_idx <option_index> [-cert <delegate_cert_name>] -fee <value_datoshi> -w <fee_wallet_name>\n"
+                                                                       "voting list -net <net_name>\n"
+                                                                       "voting dump -net <net_name> -hash <voting_hash>\n");
+    return 0;
+}
+
+uint64_t* dap_chain_net_voting_get_result(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_voting_hash)
+{
+    if(!a_voting_hash){
+        return NULL;
+    }
+
+    uint64_t* l_voting_results = NULL;
+
+    dap_chain_net_votings_t * l_voting = NULL;
+    pthread_rwlock_rdlock(&s_votings_rwlock);
+    HASH_FIND(hh, s_votings, a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
+    pthread_rwlock_unlock(&s_votings_rwlock);
+    if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64){
+        char* l_hash_str = dap_hash_fast_to_str_new(a_voting_hash);
+        log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_ledger->net->pub.name);
+        DAP_DEL_Z(l_hash_str);
+        return NULL;
+    }
+
+    l_voting_results = DAP_NEW_Z_SIZE(uint64_t, sizeof(uint64_t)*dap_list_length(l_voting->voting_params.option_offsets_list));
+    if (!l_voting_results){
+        log_it(L_CRITICAL, "Memory allocation error");
+        return NULL;
+    }
+
+    dap_list_t* l_temp = l_voting->votes;
+    while(l_temp){
+        dap_chain_net_vote_t* l_vote = l_temp->data;
+        if (l_vote->answer_idx >= dap_list_length(l_voting->voting_params.option_offsets_list))
+            continue;
+
+        l_voting_results[l_vote->answer_idx]++;
+
+        l_temp = l_temp->next;
+    }
+
+
+    return l_voting_results;
+}
+
+
+
+bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply)
+{
+
+    dap_hash_fast_t l_hash = {};
+    dap_hash_fast(a_tx_in, dap_chain_datum_tx_get_size(a_tx_in), &l_hash);
+
+    if (a_type == TX_ITEM_TYPE_VOTING){
+        if (!a_apply){
+            dap_chain_net_votings_t * l_voting = NULL;
+            pthread_rwlock_rdlock(&s_votings_rwlock);
+            HASH_FIND(hh, s_votings, &l_hash, sizeof(dap_hash_fast_t), l_voting);
+            pthread_rwlock_unlock(&s_votings_rwlock);
+            if(l_voting && l_voting->net_id.uint64 == a_ledger->net->pub.id.uint64){
+                char* l_hash_str = dap_hash_fast_to_str_new(&l_hash);
+                log_it(L_ERROR, "Voting with hash %s is already presents in net %s", l_hash_str, a_ledger->net->pub.name);
+                DAP_DEL_Z(l_hash_str);
+                return false;
+            }
+
+            dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
+            dap_list_t* l_temp = l_tsd_list;
+            size_t l_question_len = 0;
+            size_t l_options_count = 0;
+            while (l_temp){
+                dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
+                dap_chain_net_vote_option_t *l_vote_option = NULL;
+                switch(l_tsd->type){
+                case VOTING_TSD_TYPE_QUESTION:
+                    l_question_len = l_tsd->size;
+                    break;
+                case VOTING_TSD_TYPE_ANSWER:
+                    l_options_count++;
+                    break;
+                default:
+                    break;
+                }
+                l_temp = l_temp->next;
+            }
+            dap_list_free(l_tsd_list);
+
+            if (!l_question_len || !l_options_count)
+                return false;
+
+            return true;
+        }
+
+        dap_chain_net_votings_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_votings_t, sizeof(dap_chain_net_votings_t));
+        l_item->voting_hash = l_hash;
+        l_item->voting_params.voting_tx = a_tx_in;
+        l_item->net_id = a_ledger->net->pub.id;
+        pthread_rwlock_init(&l_item->s_tx_outs_rwlock, NULL);
+
+        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
+        dap_list_t* l_temp = l_tsd_list;
+        while (l_temp){
+            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd;
+            dap_chain_net_vote_option_t *l_vote_option = NULL;
+            switch(l_tsd->type){
+            case VOTING_TSD_TYPE_QUESTION:
+                l_item->voting_params.voting_question_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                l_item->voting_params.voting_question_length = l_tsd->size;
+                break;
+            case VOTING_TSD_TYPE_ANSWER:
+                l_vote_option = DAP_NEW_Z(dap_chain_net_vote_option_t);
+                l_vote_option->vote_option_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                l_vote_option->vote_option_length = l_tsd->size;
+                l_item->voting_params.option_offsets_list = dap_list_append(l_item->voting_params.option_offsets_list, l_vote_option);
+                break;
+            case VOTING_TSD_TYPE_EXPIRE:
+                l_item->voting_params.voting_expire_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                break;
+            case VOTING_TSD_TYPE_MAX_VOTES_COUNT:
+                l_item->voting_params.votes_max_count_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                break;
+            case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED:
+                l_item->voting_params.delegate_key_required_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                break;
+            case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED:
+                l_item->voting_params.vote_changing_allowed_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx);
+                break;
+            default:
+                break;
+            }
+            l_temp = l_temp->next;
+        }
+        dap_list_free(l_tsd_list);
+
+        pthread_rwlock_wrlock(&s_votings_rwlock);
+        HASH_ADD(hh, s_votings, voting_hash, sizeof(dap_hash_fast_t), l_item);
+        pthread_rwlock_unlock(&s_votings_rwlock);
+        return true;
+    } else if (a_type == TX_ITEM_TYPE_VOTE){
+        dap_chain_tx_vote_t *l_vote_tx_item = (dap_chain_tx_vote_t *)dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_VOTE, NULL);
+        if(!l_vote_tx_item){
+            log_it(L_ERROR, "Can't find vote item");
+            return false;
+        }
+
+        dap_chain_net_votings_t * l_voting = NULL;
+        pthread_rwlock_wrlock(&s_votings_rwlock);
+        HASH_FIND(hh, s_votings, &l_vote_tx_item->voting_hash, sizeof(dap_hash_fast_t), l_voting);
+        pthread_rwlock_unlock(&s_votings_rwlock);
+        if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64) {
+            char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_hash);
+            log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_ledger->net->pub.name);
+            DAP_DELETE(l_hash_str);
+            return false;
+        }
+
+        if (!a_apply){
+            if (l_vote_tx_item->answer_idx > dap_list_length(l_voting->voting_params.option_offsets_list)){
+                log_it(L_ERROR, "Invalid vote option index.");
+                return false;
+            }
+
+            if(l_voting->voting_params.votes_max_count_offset){
+                uint64_t l_votes_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
+                if (l_votes_max_count && dap_list_length(l_voting->votes) >= l_votes_max_count){
+                    log_it(L_ERROR, "The required number of votes has been collected.");
+                    return false;
+                }
+            }
+
+            if(l_voting->voting_params.voting_expire_offset){
+                dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
+                if( l_expire && l_expire <= a_tx_in->header.ts_created){
+                    log_it(L_ERROR, "The voting has been expired.");
+                    return false;
+                }
+            }
+
+            dap_hash_fast_t pkey_hash = {};
+            dap_chain_tx_sig_t *l_vote_sig = NULL;
+            int l_item_cnt = 0;
+            dap_list_t* l_signs_list = NULL;
+            l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt);
+
+            if(!l_signs_list){
+                log_it(L_ERROR, "Can't get sign.");
+                return false;
+            }
+            l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data);
+            dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash);
+            if (l_voting->voting_params.delegate_key_required_offset &&
+                *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset)){
+                if (!dap_chain_net_srv_stake_check_pkey_hash(&pkey_hash)){
+                    log_it(L_ERROR, "The voting required a delegated key.");
+                    return false;
+                }
+            }
+
+            dap_list_t *l_temp = l_voting->votes;
+            while(l_temp){
+                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){
+                    if(l_voting->voting_params.vote_changing_allowed_offset &&
+                        *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){
+                        //delete conditional outputs
+                        dap_chain_datum_tx_t *l_old_tx = dap_ledger_tx_find_by_hash(a_ledger, &((dap_chain_net_vote_t *)l_temp->data)->vote_hash);
+
+                        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(l_old_tx, TX_ITEM_TYPE_TSD, NULL);
+                        dap_list_t* l_tsd_temp = l_tsd_list;
+                        while (l_tsd_temp){
+                            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd;
+                            dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash;
+                            if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){
+                                dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL;
+                                pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
+                                HASH_FIND(hh, l_voting->voting_spent_cond_outs, &l_hash, sizeof(dap_hash_fast_t), l_tx_outs);
+                                if(l_tx_outs)
+                                    HASH_DELETE(hh, l_voting->voting_spent_cond_outs, l_tx_outs);
+                                pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);
+                            }
+                            l_tsd_temp = l_tsd_temp->next;
+                        }
+                        dap_list_free(l_tsd_list);
+
+
+                        //delete vote
+                        l_voting->votes = dap_list_remove(l_voting->votes, l_temp->data);
+                        break;
+                    } else {
+                        log_it(L_ERROR, "The voting don't allow change your vote.");
+                        return false;
+                    }
+                }
+                l_temp = l_temp->next;
+            }
+        }
+
+        uint256_t l_weight = {};
+
+        // check out conds
+        dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL);
+        dap_list_t* l_tsd_temp = l_tsd_list;
+        while (l_tsd_temp){
+            dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd;
+            dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash;
+            int l_out_idx = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->out_idx;
+            if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){
+                if (s_datum_tx_voting_coin_check_cond_out(a_ledger->net, l_vote_tx_item->voting_hash,
+                                                          l_hash, l_out_idx) != 0){
+                    l_tsd_temp = l_tsd_temp->next;
+                    continue;
+                }
+                dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_hash);
+                dap_chain_tx_out_cond_t *l_prev_out = (dap_chain_tx_out_cond_t*)dap_chain_datum_tx_item_get(l_tx_prev_temp, &l_out_idx, TX_ITEM_TYPE_OUT_COND, NULL);
+                if(!l_prev_out || l_prev_out->header.subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK){
+                    l_tsd_temp = l_tsd_temp->next;
+                    continue;
+                }
+                SUM_256_256(l_weight, l_prev_out->header.value, &l_weight);
+
+                dap_chain_net_voting_cond_outs_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_voting_cond_outs_t, sizeof(dap_chain_net_voting_cond_outs_t));
+                l_item->tx_hash = l_hash;
+                l_item->out_idx = l_out_idx;
+                pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
+                HASH_ADD(hh, l_voting->voting_spent_cond_outs, tx_hash, sizeof(dap_hash_fast_t), l_item);
+                pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);
+            }
+            l_tsd_temp = l_tsd_temp->next;
+        }
+        dap_list_free(l_tsd_list);
+        // check inputs
+        dap_list_t *l_ins_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_IN, NULL);
+        if (!l_ins_list){
+            log_it(L_ERROR, "Can't get inputs from tx");
+            return -1;
+        }
+        dap_list_t *l_in_temp = l_ins_list;
+        while(l_in_temp){
+            dap_chain_tx_in_t *l_tx_in = (dap_chain_tx_in_t *)l_in_temp->data;
+            if (s_datum_tx_voting_coin_check_spent(a_ledger->net, l_vote_tx_item->voting_hash, l_tx_in->header.tx_prev_hash, l_tx_in->header.tx_out_prev_idx) == 0){
+                dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_tx_in->header.tx_prev_hash);
+                int l_out_prev_idx = (int)l_tx_in->header.tx_out_prev_idx;
+                dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t *)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx);
+                if (!l_prev_out_union){
+                    l_in_temp = l_in_temp->next;
+                    continue;
+                }
+
+                switch (l_prev_out_union->header.type) {
+                case TX_ITEM_TYPE_OUT:
+                case TX_ITEM_TYPE_OUT_EXT:
+                    SUM_256_256(l_weight, l_prev_out_union->header.value, &l_weight);
+                }
+            }
+            l_in_temp = l_in_temp->next;
+        }
+        dap_list_free(l_ins_list);
+
+        if (IS_ZERO_256(l_weight)){
+            log_it(L_ERROR, "No unspent coins");
+            return false;
+        }
+
+
+        if (a_apply){
+            dap_hash_fast_t pkey_hash = {};
+            dap_chain_tx_sig_t *l_vote_sig = NULL;
+            int l_item_cnt = 0;
+            dap_list_t* l_signs_list = NULL;
+            l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt);
+
+            if(!l_signs_list){
+                log_it(L_ERROR, "Can't get sign.");
+                return false;
+            }
+            l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data);
+            dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash);
+
+            dap_chain_net_vote_t *l_vote_item = DAP_NEW_Z(dap_chain_net_vote_t);
+            if (!l_vote_item){
+                log_it(L_CRITICAL, "Memory allocate_error!");
+                return false;
+            }
+            l_vote_item->vote_hash = l_hash;
+            l_vote_item->pkey_hash = pkey_hash;
+            l_vote_item->answer_idx = l_vote_tx_item->answer_idx;
+            l_vote_item->weight = l_weight;
+
+            dap_list_t *l_temp = l_voting->votes;
+            while(l_temp){
+                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){
+                    if(l_voting->voting_params.vote_changing_allowed_offset &&
+                        *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){
+
+                        l_voting->votes = dap_list_append(l_voting->votes, l_vote_item);
+
+                        log_it(L_ERROR, "Vote is changed.");
+                        return true;
+                    } else {
+                        log_it(L_ERROR, "The voting don't allow change your vote.");
+                        DAP_DELETE(l_vote_item);
+                        return false;
+                    }
+                }
+                l_temp = l_temp->next;
+            }
+            log_it(L_INFO, "Vote is accepted.");
+            l_voting->votes = dap_list_append(l_voting->votes, l_vote_item);
+        }
+        return true;
+    } else {
+        log_it(L_ERROR, "Item is not supported in votings.");
+    }
+
+    return false;
+}
+
+static dap_list_t* s_get_options_list_from_str(const char* a_str)
+{
+    dap_list_t* l_ret = NULL;
+    char * l_options_str_dup = strdup(a_str);
+    if (!l_options_str_dup) {
+        log_it(L_ERROR, "Memory allocation error in %s, line %d", __PRETTY_FUNCTION__, __LINE__);
+        return 0;
+    }
+
+    size_t l_opt_str_len = strlen(l_options_str_dup);
+    char* l_option_start_ptr = l_options_str_dup;
+    dap_string_t* l_option_str = dap_string_new(NULL);
+    for (size_t i = 0; i <= l_opt_str_len; i++){
+        if(i == l_opt_str_len){
+            l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr);
+            char* l_option = dap_string_free(l_option_str, false);
+            l_option = dap_strstrip(l_option);// removes leading and trailing spaces
+            l_ret = dap_list_append(l_ret, l_option);
+            break;
+        }
+        if (l_options_str_dup [i] == ','){
+            if(i > 0 && l_options_str_dup [i-1] == '\\'){
+                l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, i-1);
+                l_option_start_ptr = &l_options_str_dup [i];
+                continue;
+            }
+            l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr);
+            l_option_start_ptr = &l_options_str_dup [i+1];
+            char* l_option = dap_string_free(l_option_str, false);
+            l_option_str = dap_string_new(NULL);
+            l_option = dap_strstrip(l_option);// removes leading and trailing spaces
+            l_ret = dap_list_append(l_ret, l_option);
+        }
+    }
+
+    free(l_options_str_dup);
+
+    return l_ret;
+}
+
+static int s_cli_voting(int a_argc, char **a_argv, void **a_str_reply)
+{
+    enum {CMD_NONE=0, CMD_CREATE, CMD_VOTE, CMD_LIST, CMD_DUMP};
+
+    const char* l_net_str = NULL;
+    int arg_index = 1;
+    dap_chain_net_t *l_net = NULL;
+
+    const char *l_hash_out_type = NULL;
+    dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-H", &l_hash_out_type);
+    if(!l_hash_out_type)
+        l_hash_out_type = "hex";
+    if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type, "base58"))
+        return -1;
+
+
+    dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str);
+    // Select chain network
+    if(!l_net_str) {
+        dap_cli_server_cmd_set_reply_text(a_str_reply, "command requires parameter '-net'");
+        return -2;
+    } else {
+        if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
+            dap_cli_server_cmd_set_reply_text(a_str_reply,
+                                              "command requires parameter '-net' to be valid chain network name");
+            return -3;
+        }
+    }
+
+    int l_cmd = CMD_NONE;
+    if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "create", NULL))
+        l_cmd = CMD_CREATE;
+    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "vote", NULL))
+        l_cmd = CMD_VOTE;
+    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "list", NULL))
+        l_cmd = CMD_LIST;
+    else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "dump", NULL))
+        l_cmd = CMD_DUMP;
+
+
+    switch(l_cmd){
+    case CMD_CREATE:{
+        const char* l_question_str = NULL;
+        const char* l_options_list_str = NULL;
+        const char* l_voting_expire_str = NULL;
+        const char* l_max_votes_count_str = NULL;
+        const char* l_fee_str = NULL;
+        const char* l_wallet_str = NULL;
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-question", &l_question_str);
+        if (!l_question_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid.");
+            return -100;
+        }
+
+        if (strlen(l_question_str) > DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "The question must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH);
+            return -101;
+        }
+
+        dap_list_t *l_options_list = NULL;
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-options", &l_options_list_str);
+        if (!l_options_list_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid.");
+            return -101;
+        }
+        // Parse options list
+        l_options_list = s_get_options_list_from_str(l_options_list_str);
+        if(!l_options_list || dap_list_length(l_options_list) < 2){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Number of options must be 2 or greater.");
+            return -102;
+        }
+
+        if(dap_list_length(l_options_list)>DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting can to contain no more than %d options", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT);
+            return -102;
+        }
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-expire", &l_voting_expire_str);
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-max_votes_count", &l_max_votes_count_str);
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str);
+        if (!l_fee_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires paramete -fee to be valid.");
+            return -102;
+        }
+        uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str);
+        if (IS_ZERO_256(l_value_fee)) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply,
+                                              "command requires parameter '-fee' to be valid uint256");
+            return -103;
+        }
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str);
+        if (!l_wallet_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires parameter -w to be valid.");
+            return -103;
+        }
+
+        dap_enc_key_t *l_priv_key = NULL;
+        const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+        dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path);
+        if (!l_wallet_fee) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str);
+            return -112;
+        }
+        l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0);
+
+        const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id);
+
+        if(!l_addr_from) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid");
+            return -113;
+        }
+
+        const char *l_native_ticker = l_net->pub.native_ticker;
+        uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer;
+        dap_chain_addr_t l_addr_fee = {};
+        bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee);
+        SUM_256_256(l_net_fee, l_value_fee, &l_total_fee);
+
+        dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name);
+        dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs_with_val(l_ledger, l_native_ticker,
+                                                                           l_addr_from, l_total_fee, &l_value_transfer);
+        if (!l_list_used_out) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer");
+            return -113;
+        }
+        // create empty transaction
+        dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();
+
+        // Add Voting item
+        dap_chain_tx_voting_t* l_voting_item = dap_chain_datum_tx_item_voting_create();
+
+        dap_chain_datum_tx_add_item(&l_tx, l_voting_item);
+        DAP_DELETE(l_voting_item);
+
+        // Add question to tsd data
+        dap_chain_tx_tsd_t* l_question_tsd = dap_chain_datum_voting_question_tsd_create(l_question_str, strlen(l_question_str));
+        dap_chain_datum_tx_add_item(&l_tx, l_question_tsd);
+        DAP_DELETE(l_question_tsd);
+
+        // Add options to tsd
+        dap_list_t *l_temp = l_options_list;
+        while(l_temp){
+            if(strlen((char*)l_temp->data) > DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_list_free_full(l_options_list, NULL);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "The option must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH);
+                return -114;
+            }
+            dap_chain_tx_tsd_t* l_option = dap_chain_datum_voting_answer_tsd_create((char*)l_temp->data, strlen((char*)l_temp->data));
+            if(!l_option){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_list_free_full(l_options_list, NULL);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create option tsd item.");
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_option);
+            DAP_DEL_Z(l_option);
+
+            l_temp = l_temp->next;
+        }
+        dap_list_free_full(l_options_list, NULL);
+
+        // add voting expire time if needed
+        if(l_voting_expire_str){
+            dap_time_t l_expired_time = dap_time_from_str_rfc822(l_voting_expire_str);
+            if(!l_expired_time){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't parse expire time");
+                return -114;
+            }
+            if (l_expired_time < dap_time_now()){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create voting with expired time");
+                return -114;
+            }
+
+            dap_chain_tx_tsd_t* l_expired_item = dap_chain_datum_voting_expire_tsd_create(l_expired_time);
+            if(!l_expired_item){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item.");
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_expired_item);
+            DAP_DEL_Z(l_expired_item);
+        }
+
+        // Add vote max count if needed
+        if(l_max_votes_count_str){
+            uint64_t l_max_votes_count = atoll(l_max_votes_count_str);
+            dap_chain_tx_tsd_t* l_max_votes_item = dap_chain_datum_voting_max_votes_count_tsd_create(l_max_votes_count);
+            if(!l_max_votes_item){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item.");
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_max_votes_item);
+            DAP_DEL_Z(l_max_votes_item);
+        }
+
+        if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-delegated_key_required", NULL)){
+            dap_chain_tx_tsd_t* l_delegated_key_req_item = dap_chain_datum_voting_delegated_key_required_tsd_create(true);
+            if(!l_delegated_key_req_item){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item.");
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_delegated_key_req_item);
+            DAP_DEL_Z(l_delegated_key_req_item);
+        }
+
+        if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-vote_changing_allowed", NULL)){
+            dap_chain_tx_tsd_t* l_vote_changing_item = dap_chain_datum_voting_vote_changing_allowed_tsd_create(true);
+            if(!l_vote_changing_item){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item.");
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_vote_changing_item);
+            DAP_DEL_Z(l_vote_changing_item);
+        }
+
+        // add 'in' items
+        uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out);
+        assert(EQUAL_256(l_value_to_items, l_value_transfer));
+        dap_list_free_full(l_list_used_out, NULL);
+        uint256_t l_value_pack = {};
+        // Network fee
+        if (l_net_fee_used) {
+            if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1)
+                SUM_256_256(l_value_pack, l_net_fee, &l_value_pack);
+            else {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
+                return -114;
+            }
+        }
+        // Validator's fee
+        if (!IS_ZERO_256(l_value_fee)) {
+            if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1)
+                SUM_256_256(l_value_pack, l_value_fee, &l_value_pack);
+            else {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
+                return -115;
+            }
+        }
+        // coin back
+        uint256_t l_value_back;
+        SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back);
+        if(!IS_ZERO_256(l_value_back)) {
+            if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back");
+                return -116;
+            }
+        }
+
+        // add 'sign' items
+        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) {
+            dap_chain_datum_tx_delete(l_tx);
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
+            return -117;
+        }
+
+        size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
+        dap_hash_fast_t l_tx_hash;
+        dap_hash_fast(l_tx, l_tx_size, &l_tx_hash);
+        dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
+        DAP_DELETE(l_tx);
+        dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX);
+
+        char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type);
+        if (l_ret)
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret);
+        else
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool");
+        DAP_DELETE(l_datum);
+    }break;
+    case CMD_VOTE:{
+        const char* l_cert_name = NULL;
+        const char* l_fee_str = NULL;
+        const char* l_wallet_str = NULL;
+        const char* l_hash_str = NULL;
+        const char* l_option_idx_str = NULL;
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str);
+        if(!l_hash_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' require the parameter -hash");
+            return -110;
+        }
+
+        dap_hash_fast_t l_voting_hash = {};
+        dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash);
+
+
+        dap_chain_hash_fast_t l_pkey_hash;
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-cert", &l_cert_name);
+        dap_cert_t * l_cert = dap_cert_find_by_name(l_cert_name);
+        if (l_cert_name){
+            if (l_cert == NULL) {
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find \"%s\" certificate", l_cert_name);
+                return -7;
+            }
+            if (l_cert->enc_key == NULL) {
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "No key found in \"%s\" certificate", l_cert_name );
+                return -8;
+            }
+            // Get publivc key hash
+            size_t l_pub_key_size = 0;
+            uint8_t *l_pub_key = dap_enc_key_serialize_pub_key(l_cert->enc_key, &l_pub_key_size);;
+            if (l_pub_key == NULL) {
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't serialize public key of certificate \"%s\"", l_cert_name);
+                return -9;
+            }
+
+            dap_hash_fast(l_pub_key, l_pub_key_size, &l_pkey_hash);
+        }
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str);
+        if (!l_fee_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires paramete -fee to be valid.");
+            return -102;
+        }
+        uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str);
+        if (IS_ZERO_256(l_value_fee)) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply,
+                                              "command requires parameter '-fee' to be valid uint256");
+            return -103;
+        }
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str);
+        if (!l_wallet_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -w to be valid.");
+            return -103;
+        }
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-option_idx", &l_option_idx_str);
+        if (!l_option_idx_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -option_idx to be valid.");
+            return -103;
+        }
+
+        dap_chain_net_votings_t *l_voting = NULL;
+        pthread_rwlock_rdlock(&s_votings_rwlock);
+        HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting);
+        pthread_rwlock_unlock(&s_votings_rwlock);
+        if(!l_voting || l_voting->net_id.uint64 != l_net->pub.id.uint64){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str);
+            return -111;
+        }
+
+        if(l_voting->voting_params.votes_max_count_offset){
+            uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
+            if (l_max_count && dap_list_length(l_voting->votes) >= l_max_count){
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already received the required number of votes.");
+                return -111;
+            }
+        }
+
+        if(l_voting->voting_params.voting_expire_offset){
+            dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
+            dap_time_t l_time_now = dap_time_now();
+            if (l_expire && l_time_now > l_expire){
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already expired.");
+                return -111;
+            }
+        }
+
+        if(l_voting->voting_params.delegate_key_required_offset &&
+            *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset) ){
+            if (!l_cert){
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting required a delegated key.");
+                return -111;
+            } else if(!dap_chain_net_srv_stake_check_pkey_hash(&l_pkey_hash)){
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Your key is not delegated.");
+                return -111;
+            }
+        }
+
+        if(l_cert){
+            dap_list_t *l_temp = l_voting->votes;
+            while(l_temp){
+                if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &l_pkey_hash)){
+                    if(!l_voting->voting_params.vote_changing_allowed_offset ||
+                        !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){
+                        dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting doesn't allow change your vote.");
+                        return -113;
+                    }
+                }
+                l_temp = l_temp->next;
+            }
+        }
+
+        dap_enc_key_t *l_priv_key = NULL;
+        const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
+        dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path);
+        if (!l_wallet_fee) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str);
+            return -112;
+        }
+        l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0);
+
+        const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id);
+
+        if(!l_addr_from) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid");
+            return -113;
+        }
+
+        const char *l_native_ticker = l_net->pub.native_ticker;
+        uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer;
+        dap_chain_addr_t l_addr_fee = {};
+        bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee);
+        SUM_256_256(l_net_fee, l_value_fee, &l_total_fee);
+
+        dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name);
+        dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs(l_ledger, l_native_ticker, l_addr_from, &l_value_transfer);
+        if (!l_list_used_out || compare256(l_value_transfer, l_total_fee) <= 0) {
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer");
+            return -113;
+        }
+
+
+        // check outputs UTXOs
+        dap_list_t *l_utxo_temp = l_list_used_out;
+        uint256_t l_value_transfer_new = {};
+        while(l_utxo_temp){
+            dap_chain_tx_used_out_item_t *l_out = (dap_chain_tx_used_out_item_t *)l_utxo_temp->data;
+            if (s_datum_tx_voting_coin_check_spent(l_net, l_voting_hash, l_out->tx_hash_fast, l_out->num_idx_out) != 0 &&
+                (!l_voting->voting_params.vote_changing_allowed_offset ||
+                 !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset))){
+                dap_list_t *l_temp = l_utxo_temp;
+                l_utxo_temp = l_utxo_temp->next;
+                dap_list_delete_link(l_list_used_out, l_temp);
+                continue;
+            }
+            SUM_256_256(l_value_transfer_new, l_out->value, &l_value_transfer_new);
+            l_utxo_temp = l_utxo_temp->next;
+        }
+
+        if (IS_ZERO_256(l_value_transfer_new) || compare256(l_value_transfer_new, l_total_fee) <= 0){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "You have not unspent UTXO for participation in this voting.");
+            return -113;
+        }
+
+        l_value_transfer = l_value_transfer_new;
+
+        // create empty transaction
+        dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create();
+
+        // Add vote item
+        uint64_t l_option_idx_count = atoll(l_option_idx_str);
+        if (l_option_idx_count > dap_list_length(l_voting->voting_params.option_offsets_list)){
+            dap_chain_datum_tx_delete(l_tx);
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Invalid option index.");
+            return -114;
+        }
+        dap_chain_tx_vote_t* l_vote_item = dap_chain_datum_tx_item_vote_create(&l_voting_hash, &l_option_idx_count);
+        if(!l_vote_item){
+            dap_chain_datum_tx_delete(l_tx);
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create vote item.");
+            return -114;
+        }
+        dap_chain_datum_tx_add_item(&l_tx, l_vote_item);
+        DAP_DEL_Z(l_vote_item);
+
+        // add stake out conds items
+        dap_list_t *l_outs = dap_ledger_get_list_tx_cond_outs(l_ledger, l_net->pub.native_ticker,  l_addr_from,
+                                                              DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK, NULL);
+        dap_list_t *l_temp = l_outs;
+        while(l_temp){
+            dap_chain_tx_used_out_item_t *l_out_item = (dap_chain_tx_used_out_item_t *)l_temp->data;
+            if (dap_ledger_tx_hash_is_used_out_item(l_net->pub.ledger, &l_out_item->tx_hash_fast, l_out_item->num_idx_out, NULL) ||
+                s_datum_tx_voting_coin_check_cond_out(l_net, l_voting_hash, l_out_item->tx_hash_fast, l_out_item->num_idx_out ) != 0){
+                l_temp = l_temp->next;
+                continue;
+            }
+            dap_chain_tx_tsd_t *l_item = dap_chain_datum_voting_vote_tx_cond_tsd_create(l_out_item->tx_hash_fast, l_out_item->num_idx_out);
+            if(!l_item){
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create tsd tx cond item.");
+                dap_list_free_full(l_outs, NULL);
+                return -114;
+            }
+            dap_chain_datum_tx_add_item(&l_tx, l_item);
+            DAP_DEL_Z(l_item);
+            l_temp = l_temp->next;
+        }
+        dap_list_free_full(l_outs, NULL);
+
+        // add 'in' items
+        uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out);
+        assert(EQUAL_256(l_value_to_items, l_value_transfer));
+        dap_list_free_full(l_list_used_out, NULL);
+        uint256_t l_value_pack = {};
+        // Network fee
+        if (l_net_fee_used) {
+            if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1)
+                SUM_256_256(l_value_pack, l_net_fee, &l_value_pack);
+            else {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
+                return -114;
+            }
+        }
+        // Validator's fee
+        if (!IS_ZERO_256(l_value_fee)) {
+            if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1)
+                SUM_256_256(l_value_pack, l_value_fee, &l_value_pack);
+            else {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out.");
+                return -115;
+            }
+        }
+        // coin back
+        uint256_t l_value_back;
+        SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back);
+        if(!IS_ZERO_256(l_value_back)) {
+            if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back");
+                return -116;
+            }
+        }
+
+        // add 'sign' items with wallet sign
+        if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) {
+            dap_chain_datum_tx_delete(l_tx);
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
+            return -117;
+        }
+
+        // add 'sign' items with delegated key if needed
+        if(l_cert){
+            if(dap_chain_datum_tx_add_sign_item(&l_tx, l_cert->enc_key) != 1) {
+                dap_chain_datum_tx_delete(l_tx);
+                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx");
+                return -117;
+            }
+        }
+
+        size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx);
+        dap_hash_fast_t l_tx_hash;
+        dap_hash_fast(l_tx, l_tx_size, &l_tx_hash);
+        dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size);
+        DAP_DELETE(l_tx);
+        dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX);
+
+        char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type);
+        if (l_ret)
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret);
+        else
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool");
+        DAP_DELETE(l_datum);
+
+
+    }break;
+    case CMD_LIST:{
+        dap_string_t *l_str_out = dap_string_new(NULL);
+        dap_string_append_printf(l_str_out, "List of votings in net %s:\n\n", l_net->pub.name);
+        dap_chain_net_votings_t *l_voting = NULL, *l_tmp;
+        pthread_rwlock_rdlock(&s_votings_rwlock);
+        HASH_ITER(hh, s_votings, l_voting, l_tmp){
+            if (l_voting->net_id.uint64 != l_net->pub.id.uint64)
+                continue;
+
+            char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_voting->voting_hash);
+            dap_string_append_printf(l_str_out, "Voting hash: %s\n", l_hash_str);
+            DAP_DELETE(l_hash_str);
+            dap_string_append(l_str_out, "Voting question:\n");
+            char* l_voting_question = (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset);
+            dap_string_append_len(l_str_out,
+                                  l_voting_question,
+                                  l_voting->voting_params.voting_question_length > strlen(l_voting_question) ? strlen(l_voting_question) : l_voting->voting_params.voting_question_length);
+            dap_string_append(l_str_out, "\n\n");
+        }
+        pthread_rwlock_unlock(&s_votings_rwlock);
+
+        dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str);
+        dap_string_free(l_str_out, true);
+    }break;
+    case CMD_DUMP:{
+        const char* l_hash_str = NULL;
+
+        dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str);
+        if(!l_hash_str){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'results' require the parameter -hash");
+            return -110;
+        }
+
+        dap_hash_fast_t l_voting_hash = {};
+        dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash);
+        dap_chain_net_votings_t *l_voting = NULL;
+        pthread_rwlock_rdlock(&s_votings_rwlock);
+        HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting);
+        pthread_rwlock_unlock(&s_votings_rwlock);
+        if(!l_voting){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str);
+            return -111;
+        }
+
+        uint64_t l_options_count = 0;
+        l_options_count = dap_list_length(l_voting->voting_params.option_offsets_list);
+        if(!l_options_count){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "No options. May be datum is crashed.");
+            return -111;
+        }
+
+        struct voting_results {uint64_t num_of_votes; uint256_t weights;};
+
+        struct voting_results* l_results = DAP_NEW_Z_SIZE(struct voting_results, sizeof(struct voting_results)*l_options_count);
+        if(!l_results){
+            dap_cli_server_cmd_set_reply_text(a_str_reply, "Memlory allocation error!");
+            return -111;
+        }
+        dap_list_t* l_list_tmp = l_voting->votes;
+        uint256_t l_total_weight = {};
+        while(l_list_tmp){
+            dap_chain_net_vote_t *l_vote = l_list_tmp->data;
+            l_results[l_vote->answer_idx].num_of_votes++;
+            SUM_256_256(l_results[l_vote->answer_idx].weights, l_vote->weight, &l_results[l_vote->answer_idx].weights);
+            l_list_tmp = l_list_tmp->next;
+            SUM_256_256(l_total_weight, l_vote->weight, &l_total_weight);
+        }
+
+        uint64_t l_votes_count = 0;
+        l_votes_count = dap_list_length(l_voting->votes);
+        dap_string_t *l_str_out = dap_string_new(NULL);
+        dap_string_append_printf(l_str_out, "Dump of voting %s:\n\n", l_hash_str);
+        dap_string_append_len(l_str_out,
+                              (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset),
+                              l_voting->voting_params.voting_question_length);
+        dap_string_append(l_str_out, "\n\n");
+
+        if(l_voting->voting_params.voting_expire_offset){
+            char l_tmp_buf[70];
+            dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset);
+            dap_string_append_printf(l_str_out, "\t Voting expire: %s", dap_ctime_r(&l_expire, l_tmp_buf));
+            dap_string_truncate(l_str_out, l_str_out->len - 1);
+            dap_string_append_printf(l_str_out, " (%s)\n", l_expire > dap_time_now() ? "active" : "expired");
+        }
+        if (l_voting->voting_params.votes_max_count_offset){
+            uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset);
+            dap_string_append_printf(l_str_out, "\t Votes max count: %"DAP_UINT64_FORMAT_U" (%s)\n", l_max_count, l_max_count <= l_votes_count ? "closed" : "active");
+        }
+        dap_string_append_printf(l_str_out, "\t Changing vote is %s available.\n", l_voting->voting_params.vote_changing_allowed_offset ? "" : "not");
+        dap_string_append_printf(l_str_out, "\t A delegated key is%s required to participate in voting. \n", l_voting->voting_params.delegate_key_required_offset ? "" : " not");
+        dap_string_append_printf(l_str_out, "\n\nResults:\n\n");
+        for (uint64_t i = 0; i < dap_list_length(l_voting->voting_params.option_offsets_list); i++){
+            dap_string_append_printf(l_str_out, "%"DAP_UINT64_FORMAT_U")  ", i);
+            dap_list_t* l_option = dap_list_nth(l_voting->voting_params.option_offsets_list, (uint64_t)i);
+            dap_chain_net_vote_option_t* l_vote_option = (dap_chain_net_vote_option_t*)l_option->data;
+            dap_string_append_len(l_str_out,
+                                  (char*)((byte_t*)l_voting->voting_params.voting_tx + l_vote_option->vote_option_offset),
+                                  l_vote_option->vote_option_length);
+            float l_percentage = l_votes_count ? ((float)l_results[i].num_of_votes/l_votes_count)*100 : 0;
+            uint256_t l_weight_percentage = {};
+
+            DIV_256_COIN(l_results[i].weights, l_total_weight, &l_weight_percentage);
+            MULT_256_COIN(l_weight_percentage, dap_chain_coins_to_balance("100.0"), &l_weight_percentage);
+            char *l_weight_percentage_str = dap_uint256_decimal_to_round_char(l_weight_percentage, 2, true);
+            dap_string_append_printf(l_str_out, "\nVotes: %"DAP_UINT64_FORMAT_U" (%.2f%%)\nWeight: %s (%s) %s (%s%%)\n", l_results[i].num_of_votes, l_percentage,
+                                     dap_chain_balance_to_coins(l_results[i].weights), dap_chain_balance_print(l_results[i].weights), l_net->pub.native_ticker,
+                                     l_weight_percentage_str);
+        }
+        DAP_DELETE(l_results);
+        dap_string_append_printf(l_str_out, "\nTotal number of votes: %"DAP_UINT64_FORMAT_U, l_votes_count);
+        dap_string_append_printf(l_str_out, "\nTotal weight: %s (%s) %s\n\n", dap_chain_balance_to_coins(l_total_weight), dap_chain_balance_print(l_total_weight), l_net->pub.native_ticker);
+        dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str);
+        dap_string_free(l_str_out, true);
+    }break;
+    default:{
+
+    }break;
+    }
+
+    return 0;
+}
+
+static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx)
+{
+    int l_coin_is_spent = 0;
+
+
+    dap_ledger_t *l_ledger = a_net->pub.ledger;
+    if(!l_ledger){
+        log_it(L_ERROR, "Can't find ledger");
+        return -1;
+    }
+
+    dap_chain_datum_tx_t *l_voting_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_voting_hash);
+    const char *l_native_ticker = a_net->pub.native_ticker;
+
+    dap_list_t *l_tx_list = NULL; // "stack" for saving txs on up level
+    dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_tx_prev_hash);
+    if (!l_tx){
+        log_it(L_ERROR, "Can't find tx");
+        return -1;
+    }
+
+    if (l_tx->header.ts_created < l_voting_tx->header.ts_created){
+        return 0;
+    }
+
+    if (s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, a_tx_prev_hash, a_out_idx) != 0){
+        return 1;
+    }
+
+    dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx, NULL, TX_ITEM_TYPE_VOTE, NULL);
+    if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){
+        dap_chain_net_votings_t *l_voting = NULL;
+        pthread_rwlock_wrlock(&s_votings_rwlock);
+        HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
+        pthread_rwlock_unlock(&s_votings_rwlock);
+        if (l_voting)
+        {
+            dap_list_t *l_temp = l_voting->votes;
+            while (l_temp){
+                dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data;
+                if (dap_hash_fast_compare(&l_vote->vote_hash, &a_tx_prev_hash)){
+                    l_coin_is_spent = 1;
+                    return 1;
+                }
+                l_temp = l_temp->next;
+            }
+        }
+    }
+
+
+    dap_list_t *l_ins_list_temp = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
+    dap_list_t *l_cond_ins_list = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN_COND, NULL);
+    if (!l_ins_list_temp && !l_cond_ins_list){
+        log_it(L_ERROR, "Can't get inputs from tx");
+        return -1;
+    }
+
+    dap_list_t *l_ins_list = NULL;
+    l_ins_list = dap_list_concat(l_ins_list, l_ins_list_temp);
+    l_ins_list = dap_list_concat(l_ins_list, l_cond_ins_list);
+
+    l_tx_list = dap_list_append(l_tx_list, l_ins_list);
+    dap_list_t* l_tx_temp = dap_list_last(l_tx_list);
+
+    while(l_tx_temp && !l_coin_is_spent){
+        if (l_tx_temp->data == NULL){
+            l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
+            l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
+            continue;
+        }
+        dap_list_t *l_ins_list = (dap_list_t*)l_tx_temp->data;
+        dap_chain_tx_in_t* l_temp_in = (dap_chain_tx_in_t*)l_ins_list->data;
+        dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash);
+        int l_out_prev_idx = (int)l_temp_in->header.tx_out_prev_idx;
+
+        const char* l_tx_token = NULL;
+        dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t*)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx);
+        if (!l_prev_out_union){
+            l_tx_temp->data = dap_list_remove(l_tx_temp->data, l_temp_in);
+            if (l_tx_temp->data == NULL){
+                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
+                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
+            }
+            continue;
+        }
+
+        switch (l_prev_out_union->header.type) {
+        case TX_ITEM_TYPE_OUT:{
+            l_tx_token = dap_ledger_tx_get_token_ticker_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash);
+        }break;
+        case TX_ITEM_TYPE_OUT_EXT:{
+            dap_chain_tx_out_ext_t *l_temp_out = (dap_chain_tx_out_ext_t *)l_prev_out_union;
+            l_tx_token = l_temp_out->token;
+        }break;
+        case TX_ITEM_TYPE_OUT_COND:{
+            dap_chain_tx_out_cond_t *l_temp_out = (dap_chain_tx_out_cond_t*)l_prev_out_union;
+            if (l_temp_out->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK ||
+                s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, l_temp_in->header.tx_prev_hash, l_temp_in->header.tx_out_prev_idx) == 0)
+                break;
+        }
+        default:
+            l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
+            if (l_tx_temp->data == NULL){
+                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
+                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
+            }
+            continue;
+        }
+
+        if (l_tx_prev_temp->header.ts_created < l_voting_tx->header.ts_created ||
+            dap_strcmp(l_tx_token, l_native_ticker)){
+            l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
+            if (l_tx_temp->data == NULL){
+                l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
+                l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
+            }
+            continue;
+        }
+
+
+        dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx_prev_temp, NULL, TX_ITEM_TYPE_VOTE, NULL);
+        if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){
+            dap_chain_net_votings_t *l_voting = NULL;
+            pthread_rwlock_wrlock(&s_votings_rwlock);
+            HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
+            pthread_rwlock_unlock(&s_votings_rwlock);
+            dap_list_t *l_temp = NULL;
+            while (l_temp){
+                dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data;
+                if (dap_hash_fast_compare(&l_vote->vote_hash, &l_temp_in->header.tx_prev_hash)){
+                    l_coin_is_spent = 1;
+                    break;
+                }
+                l_temp = l_temp->next;
+            }
+        }
+
+
+        l_ins_list = dap_chain_datum_tx_items_get(l_tx_prev_temp, TX_ITEM_TYPE_IN, NULL);
+        l_tx_list = dap_list_append(l_tx_list, l_ins_list);
+        l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in);
+        l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL;
+
+    }
+
+    if(l_tx_list){
+        l_tx_temp = l_tx_list;
+        while(l_tx_temp){
+            if (l_tx_temp->data)
+                dap_list_free((dap_list_t*)l_tx_temp->data);
+            l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp);
+            l_tx_temp = dap_list_first(l_tx_list);
+        }
+    }
+
+    return l_coin_is_spent;
+}
+
+static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx)
+{
+
+    dap_chain_net_votings_t * l_voting = NULL;
+    pthread_rwlock_wrlock(&s_votings_rwlock);
+    HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting);
+    pthread_rwlock_unlock(&s_votings_rwlock);
+    if(!l_voting || l_voting->net_id.uint64 != a_net->pub.id.uint64) {
+        char *l_hash_str = dap_chain_hash_fast_to_str_new(&a_voting_hash);
+        log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_net->pub.name);
+        DAP_DELETE(l_hash_str);
+        return -1;
+    }
+
+    dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL;
+    pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock);
+    HASH_FIND(hh, l_voting->voting_spent_cond_outs, &a_tx_cond_hash, sizeof(dap_hash_fast_t), l_tx_outs);
+    pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock);
+
+    if (!l_tx_outs || l_tx_outs->out_idx != a_cond_out_idx){
+        return 0;
+    }
+
+    return 1;
+}
diff --git a/modules/net/include/dap_chain_ledger.h b/modules/net/include/dap_chain_ledger.h
index 7fa2f5d3e9..129086c4b0 100644
--- a/modules/net/include/dap_chain_ledger.h
+++ b/modules/net/include/dap_chain_ledger.h
@@ -120,6 +120,7 @@ typedef void (* dap_ledger_tx_add_notify_t)(void *a_arg, dap_ledger_t *a_ledger,
 typedef void (* dap_ledger_bridged_tx_notify_t)(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash, void *a_arg);
 typedef bool (*dap_ledger_cache_tx_check_callback_t)(dap_hash_fast_t *a_tx_hash);
 typedef struct dap_chain_net dap_chain_net_t;
+typedef bool (*dap_chain_ledger_voting_callback_t)(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx, bool a_apply);
 
 //Change this UUID to automatically reload ledger cache on next node startup
 #define DAP_LEDGER_CACHE_RELOAD_ONCE_UUID "0c92b759-a565-448f-b8bd-99103dacf7fc"
@@ -331,15 +332,19 @@ uint256_t dap_ledger_tx_cache_get_out_cond_value(dap_ledger_t *a_ledger, dap_cha
 // Put this summary value to a_value_transfer
 dap_list_t *dap_ledger_get_list_tx_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from,
                                                        uint256_t a_value_need, uint256_t *a_value_transfer);
-
+dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from,
+                                        uint256_t *a_value_transfer);
 // Get the list of 'out_cond' items with summary value >= than a_value_need
 dap_list_t *dap_ledger_get_list_tx_cond_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker,  const dap_chain_addr_t *a_addr_from,
         dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t a_value_need, uint256_t *a_value_transfer);
 
+dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, const char *a_token_ticker,  const dap_chain_addr_t *a_addr_from,
+                                             dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t *a_value_transfer);
 // Add new verificator callback with associated subtype. Returns 1 if callback replaced, overwise returns 0
 int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_ledger_verificator_callback_t a_callback,
                                      dap_ledger_updater_callback_t a_callback_added);
-
+// Add new verificator callback for voting. Returns 1 if callback replaced, overwise returns 0
+int dap_chain_ledger_voting_verificator_add(dap_chain_ledger_voting_callback_t a_callback);
 // Getting a list of transactions from the ledger.
 dap_list_t * dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only);
 
diff --git a/modules/net/include/dap_chain_net_voting.h b/modules/net/include/dap_chain_net_voting.h
new file mode 100644
index 0000000000..be180c5015
--- /dev/null
+++ b/modules/net/include/dap_chain_net_voting.h
@@ -0,0 +1,39 @@
+/*
+ * Authors:
+ * Daniil Frolov <daniil.frolov@demlabs.net>
+ * DeM Labs Inc.   https://demlabs.net
+ * Cellframe Network https://cellframe.net
+ * Copyright  (c) 2022
+ * All rights reserved.
+
+ This file is part of DAP (Deus Applications Prototypes) the open source project
+
+    DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    DAP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+#include "dap_chain_datum_tx_voting.h"
+#include "dap_chain_ledger.h"
+#include "dap_chain_net.h"
+#include "dap_chain_common.h"
+
+
+typedef struct dap_chain_net_voting_result {
+    uint64_t answer_idx;
+    uint64_t votes_count;
+} dap_chain_net_voting_result_t;
+
+int dap_chain_net_voting_init();
+
+
+uint64_t* dap_chain_net_voting_get_result(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_voting_hash);
-- 
GitLab