From 6ad25067bb0e0b97a09159547cccac07cc8e0460 Mon Sep 17 00:00:00 2001
From: Alexander Lysikov <alexander.lysikov@demlabs.net>
Date: Tue, 21 Jun 2022 21:49:27 +0500
Subject: [PATCH] features-6376 Add tx_create_json command to create a custom
 transaction

---
 dap-sdk/crypto/include/dap_hash.h             |   2 +-
 modules/common/dap_chain_datum_tx_items.c     |  50 ++
 modules/common/include/dap_chain_common.h     |   3 +-
 .../common/include/dap_chain_datum_tx_items.h |  22 +-
 .../include/dap_chain_datum_tx_out_cond.h     |   1 +
 modules/net/dap_chain_node_cli.c              |   2 +
 modules/net/dap_chain_node_cli_cmd.c          | 519 +++++++++++++++++-
 modules/net/include/dap_chain_node_cli_cmd.h  |   1 +
 modules/net/srv/dap_chain_net_srv.c           |  22 +
 modules/net/srv/include/dap_chain_net_srv.h   |  19 +
 .../service/stake/dap_chain_net_srv_stake.c   |  32 +-
 11 files changed, 658 insertions(+), 15 deletions(-)

diff --git a/dap-sdk/crypto/include/dap_hash.h b/dap-sdk/crypto/include/dap_hash.h
index 76d507087c..4565a72262 100755
--- a/dap-sdk/crypto/include/dap_hash.h
+++ b/dap-sdk/crypto/include/dap_hash.h
@@ -129,7 +129,7 @@ DAP_STATIC_INLINE int dap_chain_hash_fast_to_str( dap_hash_fast_t *a_hash, char
 DAP_STATIC_INLINE char *dap_chain_hash_fast_to_str_new(dap_hash_fast_t * a_hash)
 {
     const size_t c_hash_str_size = DAP_CHAIN_HASH_FAST_STR_SIZE;
-    char * ret = DAP_NEW_Z_SIZE(char, c_hash_str_size);
+    char * ret = DAP_NEW_Z_SIZE(char, c_hash_str_size + 1);
     if(dap_chain_hash_fast_to_str( a_hash, ret, c_hash_str_size ) < 0 )
         DAP_DEL_Z(ret);
     return ret;
diff --git a/modules/common/dap_chain_datum_tx_items.c b/modules/common/dap_chain_datum_tx_items.c
index c3f16a8ba9..10456b810b 100644
--- a/modules/common/dap_chain_datum_tx_items.c
+++ b/modules/common/dap_chain_datum_tx_items.c
@@ -104,6 +104,56 @@ static size_t dap_chain_datum_tx_receipt_get_size(const dap_chain_datum_tx_recei
     return size;
 }
 
+/**
+ * Get item type by item name
+ *
+ * return type, or TX_ITEM_TYPE_UNKNOWN
+ */
+dap_chain_tx_item_type_t dap_chain_datum_tx_item_str_to_type(const char *a_datum_name) {
+    if(!a_datum_name)
+        return TX_ITEM_TYPE_UNKNOWN;
+    if(!dap_strcmp(a_datum_name, "in"))
+        return TX_ITEM_TYPE_IN;
+    else if(!dap_strcmp(a_datum_name, "out"))
+        return TX_ITEM_TYPE_OUT;
+    else if(!dap_strcmp(a_datum_name, "out_ext"))
+            return TX_ITEM_TYPE_OUT_EXT;
+    else if(!dap_strcmp(a_datum_name, "pkey"))
+            return TX_ITEM_TYPE_PKEY;
+    else if(!dap_strcmp(a_datum_name, "sign"))
+            return TX_ITEM_TYPE_SIG;
+    else if(!dap_strcmp(a_datum_name, "token"))
+            return TX_ITEM_TYPE_TOKEN;
+    else if(!dap_strcmp(a_datum_name, "in_cond"))
+            return TX_ITEM_TYPE_IN_COND;
+    else if(!dap_strcmp(a_datum_name, "out_cond"))
+            return TX_ITEM_TYPE_OUT_COND;
+    else if(!dap_strcmp(a_datum_name, "receipt"))
+            return TX_ITEM_TYPE_RECEIPT;
+    return TX_ITEM_TYPE_UNKNOWN;
+}
+
+/**
+ * Get dap_chain_tx_out_cond_subtype_t by name
+ *
+ * return subtype, or DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED
+ */
+dap_chain_tx_out_cond_subtype_t dap_chain_tx_out_cond_subtype_from_str(const char *a_subtype_str) {
+    if(!a_subtype_str)
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED;
+    if(!dap_strcmp(a_subtype_str, "srv_pay"))
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY;
+    else if(!dap_strcmp(a_subtype_str, "srv_xchange"))
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE;
+    else if(!dap_strcmp(a_subtype_str, "srv_stake"))
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE;
+    else if(!dap_strcmp(a_subtype_str, "srv_stake_update"))
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_UPDATE;
+    else if(!dap_strcmp(a_subtype_str, "fee"))
+        return DAP_CHAIN_TX_OUT_COND_SUBTYPE_FEE;
+    return DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED;
+}
+
 /**
  * Get item type
  *
diff --git a/modules/common/include/dap_chain_common.h b/modules/common/include/dap_chain_common.h
index d226b56cc7..50061ba043 100644
--- a/modules/common/include/dap_chain_common.h
+++ b/modules/common/include/dap_chain_common.h
@@ -209,7 +209,8 @@ enum dap_chain_tx_item_type {
     TX_ITEM_TYPE_RECEIPT = 0x70,
 
     TX_ITEM_TYPE_OUT_ALL = 0xfe,
-    TX_ITEM_TYPE_ANY = 0xff
+    TX_ITEM_TYPE_ANY = 0xff,
+    TX_ITEM_TYPE_UNKNOWN = 0xff
 };
 typedef byte_t dap_chain_tx_item_type_t;
 
diff --git a/modules/common/include/dap_chain_datum_tx_items.h b/modules/common/include/dap_chain_datum_tx_items.h
index f71d6f514b..8aa908cc30 100644
--- a/modules/common/include/dap_chain_datum_tx_items.h
+++ b/modules/common/include/dap_chain_datum_tx_items.h
@@ -25,9 +25,9 @@
 
 #include <stdint.h>
 #include <string.h>
-//#include <glib.h>
 
 #include "dap_common.h"
+#include "dap_strfuncs.h"
 #include "dap_list.h"
 #include "dap_chain_common.h"
 #include "dap_sign.h"
@@ -48,6 +48,12 @@
  * return type, or TX_ITEM_TYPE_ANY if error
  */
 dap_chain_tx_item_type_t dap_chain_datum_tx_item_get_type(const uint8_t *a_item);
+
+/**
+ * Get item name by item type
+ *
+ * return name, or UNDEFINED
+ */
 DAP_STATIC_INLINE const char * dap_chain_datum_tx_item_type_to_str(dap_chain_tx_item_type_t a_item_type)
 {
     switch(a_item_type){
@@ -68,6 +74,20 @@ DAP_STATIC_INLINE const char * dap_chain_datum_tx_item_type_to_str(dap_chain_tx_
     }
 }
 
+/**
+ * Get item type by item name
+ *
+ * return type, or TX_ITEM_TYPE_UNKNOWN
+ */
+dap_chain_tx_item_type_t dap_chain_datum_tx_item_str_to_type(const char *a_datum_name);
+
+/**
+ * Get dap_chain_tx_out_cond_subtype_t by name
+ *
+ * return subtype, or DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED
+ */
+dap_chain_tx_out_cond_subtype_t dap_chain_tx_out_cond_subtype_from_str(const char *a_subtype_str);
+
 /**
  * Get item size
  *
diff --git a/modules/common/include/dap_chain_datum_tx_out_cond.h b/modules/common/include/dap_chain_datum_tx_out_cond.h
index 552841785a..004b1e0be3 100644
--- a/modules/common/include/dap_chain_datum_tx_out_cond.h
+++ b/modules/common/include/dap_chain_datum_tx_out_cond.h
@@ -34,6 +34,7 @@
 #define MAX_FEE_STAKE   GET_256_FROM_64(1000)
 
 enum dap_chain_tx_out_cond_subtype {
+    DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED = 0x0,
     DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY = 0x01,
     DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE = 0x02,
     DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE = 0x3,
diff --git a/modules/net/dap_chain_node_cli.c b/modules/net/dap_chain_node_cli.c
index b000278be9..40569b6c0f 100644
--- a/modules/net/dap_chain_node_cli.c
+++ b/modules/net/dap_chain_node_cli.c
@@ -1079,6 +1079,8 @@ int dap_chain_node_cli_init(dap_config_t * g_config)
     // Transaction commands
     dap_chain_node_cli_cmd_item_create ("tx_create", com_tx_create, "Make transaction",
             "tx_create -net <net name> -chain <chain name> {-from_wallet <name> -token <token ticker> -value <value> -to_addr <addr> | -from_emission <emission_hash>}[-fee <addr> -value_fee <val>]\n" );
+    dap_chain_node_cli_cmd_item_create ("tx_create_json", com_tx_create_json, "Make transaction",
+                "tx_create_json -net <net name> -chain <chain name> -json <json file path>\n" );
     dap_chain_node_cli_cmd_item_create ("tx_cond_create", com_tx_cond_create, "Make cond transaction",
                                         "tx_cond_create -net <net name> -token <token ticker> -wallet <from wallet> -cert <public cert> -value <value datoshi> -unit {mb | kb | b | sec | day} -srv_uid <numeric uid>\n" );
 
diff --git a/modules/net/dap_chain_node_cli_cmd.c b/modules/net/dap_chain_node_cli_cmd.c
index d1e0dc7de4..2f052563ae 100644
--- a/modules/net/dap_chain_node_cli_cmd.c
+++ b/modules/net/dap_chain_node_cli_cmd.c
@@ -3685,17 +3685,7 @@ int com_tx_cond_create(int a_argc, char ** a_argv, char **a_str_reply)
         return -8;
     }
 
-    dap_chain_net_srv_price_unit_uid_t l_price_unit = { .enm = SERV_UNIT_UNDEFINED };
-    if(!dap_strcmp(l_unit_str, "mb"))
-        l_price_unit.enm = SERV_UNIT_MB;
-    else if(!dap_strcmp(l_unit_str, "sec"))
-        l_price_unit.enm = SERV_UNIT_SEC;
-    else if(!dap_strcmp(l_unit_str, "day"))
-        l_price_unit.enm = SERV_UNIT_DAY;
-    else if(!dap_strcmp(l_unit_str, "kb"))
-        l_price_unit.enm = SERV_UNIT_KB;
-    else if(!dap_strcmp(l_unit_str, "b"))
-        l_price_unit.enm = SERV_UNIT_B;
+    dap_chain_net_srv_price_unit_uid_t l_price_unit = dap_chain_net_srv_price_unit_uid_from_str(l_unit_str);
 
     if(l_price_unit.enm == SERV_UNIT_UNDEFINED) {
         dap_chain_node_cli_set_reply_text(a_str_reply, "Can't recognize unit '%s'. Unit must look like {mb | kb | b | sec | day}",
@@ -3960,6 +3950,513 @@ int com_chain_ca_pub( int a_argc,  char ** a_argv, char ** a_str_reply)
     }
 }
 
+static const char* s_json_get_text(struct json_object *a_json, const char *a_key)
+{
+    if(!a_json || !a_key)
+        return NULL;
+    struct json_object *l_json = json_object_object_get(a_json, a_key);
+    if(l_json && json_object_is_type(l_json, json_type_string)) {
+        // Read text
+        return json_object_get_string(l_json);
+    }
+    return NULL;
+}
+
+static bool s_json_get_int64(struct json_object *a_json, const char *a_key, int64_t *a_out)
+{
+    if(!a_json || !a_key || !a_out)
+        return false;
+    struct json_object *l_json = json_object_object_get(a_json, a_key);
+    if(l_json) {
+        if(json_object_is_type(l_json, json_type_int)) {
+            // Read number
+            *a_out = json_object_get_int64(l_json);
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool s_json_get_unit(struct json_object *a_json, const char *a_key, dap_chain_net_srv_price_unit_uid_t *a_out)
+{
+    const char *l_unit_str = s_json_get_text(a_json, a_key);
+    if(!l_unit_str || !a_out)
+        return false;
+    dap_chain_net_srv_price_unit_uid_t l_unit = dap_chain_net_srv_price_unit_uid_from_str(l_unit_str);
+    if(l_unit.enm == SERV_UNIT_UNDEFINED)
+        return false;
+    a_out->enm = l_unit.enm;
+    return true;
+}
+
+static bool s_json_get_uint256(struct json_object *a_json, const char *a_key, uint256_t *a_out)
+{
+    const char *l_uint256_str = s_json_get_text(a_json, a_key);
+    if(!a_out || !l_uint256_str)
+        return false;
+    uint256_t l_value = dap_chain_balance_scan(l_uint256_str);
+    if(!IS_ZERO_256(l_value)) {
+        memcpy(a_out, &l_value, sizeof(uint256_t));
+        return true;
+    }
+    return false;
+}
+
+// service names: srv_stake, srv_vpn, srv_xchange
+static bool s_json_get_srv_uid(struct json_object *a_json, const char *a_key_service_id, const char *a_key_service, uint64_t *a_out)
+{
+    uint64_t l_srv_id;
+    if(!a_out)
+        return false;
+    // Read service id
+    if(s_json_get_int64(a_json, a_key_service_id, (int64_t*) &l_srv_id)) {
+        *a_out = l_srv_id;
+        return true;
+    }
+    else {
+        // Read service as name
+        const char *l_service = s_json_get_text(a_json, a_key_service);
+        if(l_service) {
+            dap_chain_net_srv_t *l_srv = dap_chain_net_srv_get_by_name(l_service);
+            if(!l_srv)
+                return false;
+            *a_out = l_srv->uid.uint64;
+            return true;
+        }
+    }
+    return false;
+}
+
+static dap_chain_wallet_t* s_json_get_wallet(struct json_object *a_json, const char *a_key)
+{
+    dap_enc_key_t *l_enc_key = NULL;
+    // From wallet
+    const char *l_wallet_str = s_json_get_text(a_json, a_key);
+    if(l_wallet_str) {
+        dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, dap_config_get_item_str_default(g_config, "resources", "wallets_path", NULL));
+        return l_wallet;
+    }
+    return NULL;
+}
+
+static const dap_cert_t* s_json_get_cert(struct json_object *a_json, const char *a_key)
+{
+    const char *l_cert_name = s_json_get_text(a_json, a_key);
+    if(l_cert_name) {
+        dap_cert_t *l_cert = dap_cert_find_by_name(l_cert_name);
+        return l_cert;
+    }
+    return NULL;
+}
+
+// Read pkey from wallet or cert
+static dap_pkey_t* s_json_get_pkey(struct json_object *a_json)
+{
+    dap_pkey_t *l_pub_key = NULL;
+    // From wallet
+    dap_chain_wallet_t *l_wallet = s_json_get_wallet(a_json, "wallet");
+    if(l_wallet) {
+        l_pub_key = dap_chain_wallet_get_pkey(l_wallet, 0);
+        dap_chain_wallet_close(l_wallet);
+        if(l_pub_key) {
+            return l_pub_key;
+        }
+    }
+    // From cert
+    const dap_cert_t *l_cert = s_json_get_cert(a_json, "cert");
+    if(l_cert) {
+        l_pub_key = dap_pkey_from_enc_key(l_cert->enc_key);
+    }
+    return l_pub_key;
+}
+
+
+/**
+ * @brief Create transaction from json file
+ * com_tx_create command
+ * @param argc
+ * @param argv
+ * @param arg_func
+ * @param str_reply
+ * @return int
+ */
+int com_tx_create_json(int a_argc, char ** a_argv, char **a_str_reply)
+{
+    int l_arg_index = 1;
+    int l_err_code = 0;
+    const char *l_net_name = NULL; // optional parameter
+    const char *l_chain_name = NULL; // optional parameter
+    const char *l_json_file_path = NULL;
+
+    dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-net", &l_net_name); // optional parameter
+    dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-chain", &l_chain_name); // optional parameter
+    dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "-json", &l_json_file_path);
+
+    if(!l_json_file_path) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Command requires one of parameters '-json <json file path>'");
+        return -1;
+    }
+    // Open json file
+    struct json_object *l_json = json_object_from_file(l_json_file_path);
+    if(!l_json) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Can't open json file: %s", json_util_get_last_err());
+        return -2;
+    }
+    if(!json_object_is_type(l_json, json_type_object)) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Wrong json format");
+        json_object_put(l_json);
+        return -3;
+    }
+
+    // Read network from json file
+    if(!l_net_name) {
+        struct json_object *l_json_net = json_object_object_get(l_json, "net");
+        if(l_json_net && json_object_is_type(l_json_net, json_type_string)) {
+            l_net_name = json_object_get_string(l_json_net);
+        }
+        if(!l_net_name) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Command requires parameter '-net' or set net in the json file");
+            json_object_put(l_json);
+            return -11;
+        }
+    }
+    dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_name);
+    if(!l_net) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Not found net by name '%s'", l_net_name);
+        json_object_put(l_json);
+        return -12;
+    }
+
+    // Read chain from json file
+    if(!l_chain_name) {
+        struct json_object *l_json_chain = json_object_object_get(l_json, "chain");
+        if(l_json_chain && json_object_is_type(l_json_chain, json_type_string)) {
+            l_chain_name = json_object_get_string(l_json_chain);
+        }
+    }
+    dap_chain_t *l_chain = dap_chain_net_get_chain_by_name(l_net, l_chain_name);
+    if(!l_chain) {
+        l_chain = dap_chain_net_get_chain_by_chain_type(l_net, CHAIN_TYPE_TX);
+    }
+    if(!l_chain) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Chain name '%s' not found, try use parameter '-chain' or set chain in the json file", l_chain_name);
+        json_object_put(l_json);
+        return -13;
+    }
+
+
+    // Read items from json file
+    struct json_object *l_json_items = json_object_object_get(l_json, "items");
+    size_t l_items_count = json_object_array_length(l_json_items);
+    bool a = (l_items_count = json_object_array_length(l_json_items));
+    if(!l_json_items || !json_object_is_type(l_json_items, json_type_array) || !(l_items_count = json_object_array_length(l_json_items))) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Wrong json format: not found array 'items' or array is empty");
+        json_object_put(l_json);
+        return -15;
+    }
+    // Create transaction
+    dap_chain_datum_tx_t *l_tx = DAP_NEW_Z_SIZE(dap_chain_datum_tx_t, sizeof(dap_chain_datum_tx_t));
+    l_tx->header.ts_created = time(NULL);
+    size_t l_items_ready = 0;
+    dap_list_t *l_sign_list = NULL;
+    // Creating and adding items to the transaction
+    for(size_t i = 0; i < l_items_count; ++i) {
+        struct json_object *l_json_item_obj = json_object_array_get_idx(l_json_items, i);
+        if(!l_json_item_obj || !json_object_is_type(l_json_item_obj, json_type_object)) {
+            continue;
+        }
+        struct json_object *l_json_item_type = json_object_object_get(l_json_item_obj, "type");
+        if(!l_json_item_type && json_object_is_type(l_json_item_type, json_type_string)) {
+            log_it(L_WARNING, "Item %zu without type", i);
+            continue;
+        }
+        const char *l_item_type_str = json_object_get_string(l_json_item_type);
+        dap_chain_tx_item_type_t l_item_type = dap_chain_datum_tx_item_str_to_type(l_item_type_str);
+        if(l_item_type == TX_ITEM_TYPE_UNKNOWN) {
+            log_it(L_WARNING, "Item %zu has invalid type '%s'", i, l_item_type_str);
+            continue;
+        }
+        // Create an item depending on its type
+        const uint8_t *l_item = NULL;
+        switch (l_item_type) {
+        case TX_ITEM_TYPE_IN: {
+            // Read prev_hash and out_prev_idx
+            const char *l_prev_hash_str = s_json_get_text(l_json_item_obj, "prev_hash");
+            int64_t l_out_prev_idx;
+            bool l_is_out_prev_idx = s_json_get_int64(l_json_item_obj, "out_prev_idx", &l_out_prev_idx);
+            // If prev_hash and out_prev_idx were read
+            if(l_prev_hash_str && l_is_out_prev_idx) {
+                dap_chain_hash_fast_t l_tx_prev_hash;
+                if(!dap_chain_hash_fast_from_str(l_prev_hash_str, &l_tx_prev_hash)) {
+                    // Create IN item
+                    dap_chain_tx_in_t *l_in_item = dap_chain_datum_tx_item_in_create(&l_tx_prev_hash, (uint32_t)l_out_prev_idx);
+                    l_item = (const uint8_t*) l_in_item;
+                }
+                else {
+                    log_it(L_WARNING, "Invalid 'in' item %zu", i);
+                    continue;
+                }
+            }
+        }
+            break;
+        case TX_ITEM_TYPE_OUT:
+            case TX_ITEM_TYPE_OUT_EXT: {
+            // Read address and value
+            uint256_t l_value = { };
+            const char *l_json_item_addr_str = s_json_get_text(l_json_item_obj, "addr");
+            bool l_is_value = s_json_get_uint256(l_json_item_obj, "value", &l_value);
+            if(l_is_value && l_json_item_addr_str) {
+                dap_chain_addr_t *l_addr = dap_chain_addr_from_str(l_json_item_addr_str);
+                if(l_addr && !IS_ZERO_256(l_value)) {
+                    if(l_item_type == TX_ITEM_TYPE_OUT) {
+                        // Create OUT item
+                        dap_chain_tx_out_t *l_out_item = dap_chain_datum_tx_item_out_create(l_addr, l_value);
+                        l_item = (const uint8_t*) l_out_item;
+                    }
+                    else if(l_item_type == TX_ITEM_TYPE_OUT_EXT) {
+                        // Read address and value
+                        const char *l_token = s_json_get_text(l_json_item_obj, "token");
+                        if(l_token) {
+                            // Create OUT_EXT item
+                            dap_chain_tx_out_ext_t *l_out_ext_item = dap_chain_datum_tx_item_out_ext_create(l_addr, l_value, l_token);
+                            l_item = (const uint8_t*) l_out_ext_item;
+                        }
+                        else {
+                            log_it(L_WARNING, "Invalid 'out_ext' item %zu", i);
+                            continue;
+                        }
+                    }
+                }
+                else {
+                    if(l_item_type == TX_ITEM_TYPE_OUT) {
+                        log_it(L_WARNING, "Invalid 'out' item %zu", i);
+                    }
+                    else if(l_item_type == TX_ITEM_TYPE_OUT_EXT) {
+                        log_it(L_WARNING, "Invalid 'out_ext' item %zu", i);
+                    }
+                    continue;
+                }
+            }
+        }
+            break;
+        case TX_ITEM_TYPE_IN_COND: {
+            // Read prev_hash and out_prev_idx
+            const char *l_prev_hash_str = s_json_get_text(l_json_item_obj, "prev_hash");
+            int64_t l_out_prev_idx;
+            int64_t l_receipt_idx;
+            bool l_is_out_prev_idx = s_json_get_int64(l_json_item_obj, "out_prev_idx", &l_out_prev_idx);
+            bool l_is_receipt_idx = s_json_get_int64(l_json_item_obj, "receipt_idx", &l_receipt_idx);
+
+            if(l_prev_hash_str && l_is_out_prev_idx && l_is_receipt_idx) {
+                dap_chain_hash_fast_t l_tx_prev_hash;
+                if(!dap_chain_hash_fast_from_str(l_prev_hash_str, &l_tx_prev_hash)) {
+                    // Create IN_COND item
+                    dap_chain_tx_in_cond_t *l_in_cond_item = dap_chain_datum_tx_item_in_cond_create(&l_tx_prev_hash, (uint32_t) l_out_prev_idx, (uint32_t) l_receipt_idx);
+                    l_item = (const uint8_t*) l_in_cond_item;
+                }
+            }
+        }
+            break;
+        case TX_ITEM_TYPE_OUT_COND: {
+            // Read subtype of item
+            const char *l_subtype_str = s_json_get_text(l_json_item_obj, "subtype");
+            dap_chain_tx_out_cond_subtype_t l_subtype = dap_chain_tx_out_cond_subtype_from_str(l_subtype_str);
+            switch (l_subtype) {
+
+            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY:{
+                uint256_t l_value = { };
+                bool l_is_value = s_json_get_uint256(l_json_item_obj, "value", &l_value);
+                if(!l_is_value || IS_ZERO_256(l_value)) {
+                    break;
+                }
+                uint256_t l_value_max_per_unit = { };
+                l_is_value = s_json_get_uint256(l_json_item_obj, "value_max_per_unit", &l_value_max_per_unit);
+                if(!l_is_value || IS_ZERO_256(l_value_max_per_unit)) {
+                    break;
+                }
+                dap_chain_net_srv_price_unit_uid_t l_price_unit;
+                if(!s_json_get_unit(l_json_item_obj, "price_unit", &l_price_unit)) {
+                    break;
+                }
+                dap_chain_net_srv_uid_t l_srv_uid;
+                if(!s_json_get_srv_uid(l_json_item_obj, "service_id", "service", &l_srv_uid.uint64)){
+                    // Default service DAP_CHAIN_NET_SRV_VPN_ID
+                    l_srv_uid.uint64 = 0x0000000000000001;
+                }
+
+                // From "wallet" or "cert"
+                dap_pkey_t *l_pkey = s_json_get_pkey(l_json_item_obj);
+                if(!l_pkey) {
+                    break;
+                }
+                const char *l_params_str = s_json_get_text(l_json_item_obj, "params");
+                size_t l_params_size = dap_strlen(l_params_str);
+                dap_chain_tx_out_cond_t *l_out_cond_item = dap_chain_datum_tx_item_out_cond_create_srv_pay(l_pkey, l_srv_uid, l_value, l_value_max_per_unit,
+                        l_price_unit, l_params_str, l_params_size);
+                l_item = (const uint8_t*) l_out_cond_item;
+                DAP_DELETE(l_pkey);
+            }
+                break;
+            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE: {
+
+                dap_chain_net_srv_uid_t l_srv_uid;
+                if(!s_json_get_srv_uid(l_json_item_obj, "service_id", "service", &l_srv_uid.uint64)) {
+                    // Default service DAP_CHAIN_NET_SRV_XCHANGE_ID
+                    l_srv_uid.uint64 = 0x2;
+                }
+                dap_chain_net_t *l_net = dap_chain_net_by_name(s_json_get_text(l_json_item_obj, "net"));
+                if(!l_net) {
+                    break;
+                }
+                const char *l_token = s_json_get_text(l_json_item_obj, "token");
+                if(!l_token) {
+                    break;
+                }
+                uint256_t l_value = { };
+                if(!s_json_get_uint256(l_json_item_obj, "value", &l_value) || IS_ZERO_256(l_value)) {
+                    break;
+                }
+                const char *l_params_str = s_json_get_text(l_json_item_obj, "params");
+                size_t l_params_size = dap_strlen(l_params_str);
+                dap_chain_tx_out_cond_t *l_out_cond_item = dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_srv_uid, l_net->pub.id, l_token, l_value, l_params_str, l_params_size);
+                l_item = (const uint8_t*) l_out_cond_item;
+            }
+                break;
+            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE:{
+                dap_chain_net_srv_uid_t l_srv_uid;
+                if(!s_json_get_srv_uid(l_json_item_obj, "service_id", "service", &l_srv_uid.uint64)) {
+                    // Default service DAP_CHAIN_NET_SRV_STAKE_ID
+                    l_srv_uid.uint64 = 0x13;
+                }
+                uint256_t l_value = { };
+                if(!s_json_get_uint256(l_json_item_obj, "value", &l_value) || IS_ZERO_256(l_value)) {
+                    break;
+                }
+                uint256_t l_fee_value = { };
+                if(!s_json_get_uint256(l_json_item_obj, "fee", &l_fee_value) || IS_ZERO_256(l_fee_value)) {
+                    break;
+                }
+                const char *l_fee_addr_str = s_json_get_text(l_json_item_obj, "fee_addr");
+                const char *l_hldr_addr_str = s_json_get_text(l_json_item_obj, "hldr_addr");
+                const char *l_signing_addr_str = s_json_get_text(l_json_item_obj, "signing_addr");
+                dap_chain_addr_t *l_fee_addr = dap_chain_addr_from_str(l_fee_addr_str);
+                dap_chain_addr_t *l_hldr_addr = dap_chain_addr_from_str(l_hldr_addr_str);
+                dap_chain_addr_t *l_signing_addr = dap_chain_addr_from_str(l_signing_addr_str);
+                if(!l_fee_addr || !l_hldr_addr || !l_signing_addr) {
+                    break;
+                }
+                dap_chain_node_addr_t l_signer_node_addr;
+                const char *l_node_addr_str = s_json_get_text(l_json_item_obj, "node_addr");
+                if(!l_node_addr_str || dap_chain_node_addr_from_str(&l_signer_node_addr, l_node_addr_str)) {
+                    break;
+                }
+                dap_chain_tx_out_cond_t *l_out_cond_item = dap_chain_datum_tx_item_out_cond_create_srv_stake(l_srv_uid, l_value, l_fee_value,
+                        l_fee_addr, l_hldr_addr, l_signing_addr, &l_signer_node_addr);
+                l_item = (const uint8_t*) l_out_cond_item;
+            }
+                break;
+            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_FEE: {
+                uint256_t l_value = { };
+                bool l_is_value = s_json_get_uint256(l_json_item_obj, "value", &l_value);
+                if(!IS_ZERO_256(l_value)) {
+                    dap_chain_tx_out_cond_t *l_out_cond_item = dap_chain_datum_tx_item_out_cond_create_fee(l_value);
+                    l_item = (const uint8_t*) l_out_cond_item;
+                }
+            }
+                break;
+            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_UNDEFINED:
+                log_it(L_WARNING, "Undefined subtype: '%s' of 'out_cond' item %zu ", l_subtype_str, i);
+                break;
+            }
+        }
+
+            break;
+        case TX_ITEM_TYPE_SIG:{
+            // Save item obj for sign
+            l_sign_list = dap_list_append(l_sign_list,l_json_item_obj);
+        }
+            break;
+        //case TX_ITEM_TYPE_PKEY:
+            //break;
+        //case TX_ITEM_TYPE_TOKEN:
+            //break;
+        //case TX_ITEM_TYPE_TOKEN_EXT:
+            //break;
+        //case TX_ITEM_TYPE_RECEIPT:
+            //break;
+        }
+        // Add item to transaction
+        if(l_item) {
+            dap_chain_datum_tx_add_item(&l_tx, (const uint8_t*) l_item);
+            l_items_ready++;
+        }
+    }
+
+    // Add signs
+    dap_list_t *l_list = l_sign_list;
+    while(l_list){
+        bool is_add = false;
+        struct json_object *l_json_item_obj = (struct json_object*) l_list->data;
+        // From wallet
+        dap_chain_wallet_t *l_wallet = s_json_get_wallet(l_json_item_obj, "wallet");
+        if(l_wallet) {
+            dap_enc_key_t *l_enc_key = dap_chain_wallet_get_key(l_wallet, 0);
+            // sign all previous items in transaction
+            if(dap_chain_datum_tx_add_sign_item(&l_tx, l_enc_key)>0){
+                is_add = true;
+                l_items_ready++;
+            }
+            dap_chain_wallet_close(l_wallet);
+        }
+        // If wallet not found
+        if(!is_add) {
+            // From cert
+            const dap_cert_t *l_cert = s_json_get_cert(l_json_item_obj, "cert");
+            if(l_cert && l_cert->enc_key) {
+                // sign all previous items in transaction
+                if(dap_chain_datum_tx_add_sign_item(&l_tx, l_cert->enc_key) > 0) {
+                    is_add = true;
+                    l_items_ready++;
+                }
+            }
+        }
+        l_list = dap_list_next(l_list);
+    }
+    dap_list_free(l_sign_list);
+    json_object_put(l_json);
+
+    if(l_items_ready<l_items_count) {
+        if(!l_items_ready)
+            dap_chain_node_cli_set_reply_text(a_str_reply, "No valid items found to create a transaction");
+        else
+            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't create transaction, because only %zu items out of %zu are valid",l_items_ready,l_items_count);
+        DAP_DELETE(l_tx);
+        return -30;
+    }
+
+    // Pack transaction into the datum
+    dap_chain_datum_t *l_datum_tx = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, dap_chain_datum_tx_get_size(l_tx));
+    size_t l_datum_tx_size = dap_chain_datum_size(l_datum_tx);
+    DAP_DELETE(l_tx);
+
+    // Add transaction to mempool
+    char *l_gdb_group_mempool_base_tx = dap_chain_net_get_gdb_group_mempool(l_chain);// get group name for mempool
+    dap_chain_hash_fast_t *l_datum_tx_hash = DAP_NEW(dap_hash_fast_t);
+    dap_hash_fast(l_datum_tx, l_datum_tx_size, l_datum_tx_hash);// Calculate datum hash
+    char *l_tx_hash_str = dap_chain_hash_fast_to_str_new(l_datum_tx_hash);
+    bool l_placed = dap_chain_global_db_gr_set(l_tx_hash_str, l_datum_tx, l_datum_tx_size, l_gdb_group_mempool_base_tx);
+
+    DAP_DELETE(l_tx_hash_str);
+    DAP_DELETE(l_datum_tx);
+    DAP_DELETE(l_gdb_group_mempool_base_tx);
+    if(!l_placed) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Can't add transaction to mempool");
+        return -90;
+    }
+    // Completed successfully
+    dap_chain_node_cli_set_reply_text(a_str_reply, "Transaction with %d items created and added to mempool successfully", l_items_ready);
+    return l_err_code;
+}
 
 /**
  * @brief Create transaction
diff --git a/modules/net/include/dap_chain_node_cli_cmd.h b/modules/net/include/dap_chain_node_cli_cmd.h
index ea22439219..36b75456e6 100644
--- a/modules/net/include/dap_chain_node_cli_cmd.h
+++ b/modules/net/include/dap_chain_node_cli_cmd.h
@@ -110,6 +110,7 @@ int com_tx_wallet(int argc, char ** argv, char **str_reply);
  * Create transaction
  */
 int com_tx_create(int argc, char ** argv, char **str_reply);
+int com_tx_create_json(int argc, char ** argv, char **str_reply);
 int com_tx_cond_create(int argc, char ** argv, char **str_reply);
 
 /**
diff --git a/modules/net/srv/dap_chain_net_srv.c b/modules/net/srv/dap_chain_net_srv.c
index 56114f311e..2cf389562e 100644
--- a/modules/net/srv/dap_chain_net_srv.c
+++ b/modules/net/srv/dap_chain_net_srv.c
@@ -76,6 +76,7 @@ static dap_chain_net_srv_uid_t * m_uid;
 typedef struct service_list {
     dap_chain_net_srv_uid_t uid;
     dap_chain_net_srv_t * srv;
+    char name[32];
     UT_hash_handle hh;
 } service_list_t;
 
@@ -732,6 +733,7 @@ dap_chain_net_srv_t* dap_chain_net_srv_add(dap_chain_net_srv_uid_t a_uid,
         pthread_mutex_init(&l_srv->banlist_mutex, NULL);
         l_sdata = DAP_NEW_Z(service_list_t);
         memcpy(&l_sdata->uid, &l_uid, sizeof(l_uid));
+        strncpy(l_sdata->name, a_config_section, sizeof(l_sdata->name));
         l_sdata->srv = l_srv;
         dap_chain_net_srv_parse_pricelist(l_srv, a_config_section);
         HASH_ADD(hh, s_srv_list, uid, sizeof(l_srv->uid), l_sdata);
@@ -877,6 +879,26 @@ dap_chain_net_srv_t * dap_chain_net_srv_get(dap_chain_net_srv_uid_t a_uid)
     return (l_sdata) ? l_sdata->srv : NULL;
 }
 
+/**
+ * @brief dap_chain_net_srv_get_by_name
+ * @param a_client
+ */
+dap_chain_net_srv_t* dap_chain_net_srv_get_by_name(const char *a_name)
+{
+    if(!a_name)
+        return NULL;
+    dap_chain_net_srv_t *l_srv = NULL;
+    service_list_t *l_sdata, *l_sdata_tmp;
+    pthread_mutex_lock(&s_srv_list_mutex);
+    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
+    {
+        if(!dap_strcmp(l_sdata->name, a_name))
+            l_srv = l_sdata->srv;
+    }
+    pthread_mutex_unlock(&s_srv_list_mutex);
+    return l_srv;
+}
+
 /**
  * @brief dap_chain_net_srv_count
  * @return
diff --git a/modules/net/srv/include/dap_chain_net_srv.h b/modules/net/srv/include/dap_chain_net_srv.h
index 741cde9068..faadfcdbbd 100755
--- a/modules/net/srv/include/dap_chain_net_srv.h
+++ b/modules/net/srv/include/dap_chain_net_srv.h
@@ -279,6 +279,7 @@ void dap_chain_net_srv_call_closed_all(dap_stream_ch_t * a_client);
 void dap_chain_net_srv_call_opened_all(dap_stream_ch_t * a_client);
 
 dap_chain_net_srv_t * dap_chain_net_srv_get(dap_chain_net_srv_uid_t a_uid);
+dap_chain_net_srv_t* dap_chain_net_srv_get_by_name(const char *a_name);
 size_t dap_chain_net_srv_count(void);
 const dap_chain_net_srv_uid_t * dap_chain_net_srv_list(void);
 dap_chain_datum_tx_receipt_t * dap_chain_net_srv_issue_receipt(dap_chain_net_srv_t *a_srv,
@@ -300,6 +301,24 @@ DAP_STATIC_INLINE const char * dap_chain_net_srv_price_unit_uid_to_str( dap_chai
     }
 }
 
+DAP_STATIC_INLINE dap_chain_net_srv_price_unit_uid_t dap_chain_net_srv_price_unit_uid_from_str( const char  *a_unit_str )
+{
+    dap_chain_net_srv_price_unit_uid_t l_price_unit = { .enm = SERV_UNIT_UNDEFINED };
+    if(!dap_strcmp(a_unit_str, "mb"))
+        l_price_unit.enm = SERV_UNIT_MB;
+    else if(!dap_strcmp(a_unit_str, "sec"))
+        l_price_unit.enm = SERV_UNIT_SEC;
+    else if(!dap_strcmp(a_unit_str, "day"))
+        l_price_unit.enm = SERV_UNIT_DAY;
+    else if(!dap_strcmp(a_unit_str, "kb"))
+        l_price_unit.enm = SERV_UNIT_KB;
+    else if(!dap_strcmp(a_unit_str, "b") || !dap_strcmp(a_unit_str, "bytes"))
+        l_price_unit.enm = SERV_UNIT_B;
+    else if(!dap_strcmp(a_unit_str, "pcs") || !dap_strcmp(a_unit_str, "pieces"))
+        l_price_unit.enm = SERV_UNIT_PCS;
+    return l_price_unit;
+}
+
 DAP_STATIC_INLINE bool dap_chain_net_srv_uid_compare(dap_chain_net_srv_uid_t a, dap_chain_net_srv_uid_t b)
 {
 #if DAP_CHAIN_NET_SRV_UID_SIZE == 8
diff --git a/modules/service/stake/dap_chain_net_srv_stake.c b/modules/service/stake/dap_chain_net_srv_stake.c
index 6538a04c0a..fba31770d7 100644
--- a/modules/service/stake/dap_chain_net_srv_stake.c
+++ b/modules/service/stake/dap_chain_net_srv_stake.c
@@ -37,6 +37,10 @@
 #define LOG_TAG "dap_chain_net_srv_stake"
 
 static int s_cli_srv_stake(int a_argc, char **a_argv, char **a_str_reply);
+static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size);
+static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size);
+static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size);
+static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size);
 
 static dap_chain_net_srv_stake_t *s_srv_stake = NULL;
 
@@ -73,7 +77,13 @@ int dap_chain_net_srv_stake_init()
     );
 
     s_srv_stake = DAP_NEW_Z(dap_chain_net_srv_stake_t);
-    
+
+    dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_STAKE_ID };
+    dap_chain_net_srv_t* l_srv = dap_chain_net_srv_add(l_uid, "srv_stake", s_callback_requested,
+                                                       s_callback_response_success, s_callback_response_error,
+                                                       s_callback_receipt_next_success, NULL);
+    l_srv->_internal = s_srv_stake;
+
     uint16_t l_net_count;
     dap_chain_net_t **l_net_list = dap_chain_net_list(&l_net_count);
     for (uint16_t i = 0; i < l_net_count; i++) {
@@ -1475,3 +1485,23 @@ static int s_cli_srv_stake(int a_argc, char **a_argv, char **a_str_reply)
     }
     return 0;
 }
+
+static int s_callback_requested(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size)
+{
+    return 0;
+}
+
+static int s_callback_response_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size)
+{
+    return 0;
+}
+
+static int s_callback_response_error(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size)
+{
+    return 0;
+}
+
+static int s_callback_receipt_next_success(dap_chain_net_srv_t *a_srv, uint32_t a_usage_id, dap_chain_net_srv_client_remote_t *a_srv_client, const void *a_data, size_t a_data_size)
+{
+    return 0;
+}
-- 
GitLab