diff --git a/dap-sdk/net/client/dap_client_pvt.c b/dap-sdk/net/client/dap_client_pvt.c
index 46cfeb6d4c46d1446cc673ac26e1580cf0573ffc..ae5397570f19101705fb6fa5b66ac13ac6e3df5b 100644
--- a/dap-sdk/net/client/dap_client_pvt.c
+++ b/dap-sdk/net/client/dap_client_pvt.c
@@ -875,14 +875,18 @@ static void s_request_response(void * a_response, size_t a_response_size, void *
             log_it(L_ERROR,"Client internal is NULL for s_request_response");
         else
             log_it(L_ERROR,"Client is NULL for s_request_response");
-
+        dap_client_pvt_hh_unlock();
         return;
     }
     l_client_pvt->refs_count--;
 
     if (l_client_pvt->is_to_delete){
-        if(l_client_pvt->refs_count==0) // Was requested to delete until we was working with request
+        if(l_client_pvt->refs_count==0) {// Was requested to delete until we was working with request
+            dap_client_pvt_hh_unlock();
             dap_client_delete_unsafe(l_client_pvt->client); // Init delete
+            return;
+        }
+        dap_client_pvt_hh_unlock();
         return;
     }
 
diff --git a/modules/modules_dynamic/cdb/CMakeLists.txt b/modules/modules_dynamic/cdb/CMakeLists.txt
index 26191138031bebd9bbaf9c54dc4fee0b0b519df5..28c1cb8042040e121d74350c7654dd11219daf7a 100644
--- a/modules/modules_dynamic/cdb/CMakeLists.txt
+++ b/modules/modules_dynamic/cdb/CMakeLists.txt
@@ -8,7 +8,7 @@ file(GLOB DAP_MODULES_DYNAMIC_CDB_HEADERS include/*.h)
 add_library(${PROJECT_NAME} STATIC ${DAP_MODULES_DYNAMIC_CDB_SRCS} ${DAP_MODULES_DYNAMIC_CDB_HEADERS})
 
 #target_link_libraries(dap_chain_net_srv dap_core dap_crypto dap_chain dap_chain_net dap_chain_wallet)
-target_link_libraries(${PROJECT_NAME} dap_core dap_http_server ${CMAKE_DL_LIBS})
+target_link_libraries(${PROJECT_NAME} dap_core dap_enc_server dap_chain_net dap_chain_net_srv dap_chain_net_srv_vpn dap_http_server zip ${CMAKE_DL_LIBS})
 
 target_include_directories(${PROJECT_NAME} INTERFACE .)
 target_include_directories(${PROJECT_NAME} PUBLIC include)
diff --git a/modules/modules_dynamic/cdb/dap_chain_net_bugreport.c b/modules/modules_dynamic/cdb/dap_chain_net_bugreport.c
new file mode 100644
index 0000000000000000000000000000000000000000..ae2c643a66439e0b04f2263da9581a88e3d5e89e
--- /dev/null
+++ b/modules/modules_dynamic/cdb/dap_chain_net_bugreport.c
@@ -0,0 +1,525 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.        https://demlabs.net
+ * CellFrame            https://cellframe.net
+ * Sources              https://gitlab.demlabs.net/cellframe
+ * Cellframe CDB lib    https://gitlab.demlabs.net/dap.support/cellframe-node-cdb-lib
+ * Copyrighted by Demlabs Limited, 2020
+ * All rights reserved.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <zip.h>
+#include <sys/stat.h>
+#include <json-c/json.h>
+#include <json-c/json_object.h>
+
+#include "dap_common.h"
+#include "dap_string.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "include/dap_enc_ks.h"
+#include "dap_enc_key.h"
+#include "dap_config.h"
+#include "rand/dap_rand.h"
+#include "dap_enc.h"
+
+#include "http_status_code.h"
+#include "dap_http_simple.h"
+#include "dap_enc_http.h"
+#include "dap_chain_net_bugreport.h"
+
+#define LOG_TAG "chain_net_bugreport"
+
+#define BUGREPORT_URL "/bugreport"
+
+enum {
+    BUGREPORT_STATUS_NOTDEFINED, BUGREPORT_STATUS_NEW, BUGREPORT_STATUS_IN_PROGRESS, BUGREPORT_STATUS_RESOLVED
+};
+
+void dap_chain_net_bugreport_add_proc(struct dap_http * sh);
+void bugreport_update_statuses(void);
+
+int dap_chain_net_bugreport_init(dap_http_t * a_http)
+{
+    dap_chain_net_bugreport_add_proc(a_http);
+    bugreport_update_statuses();
+    return 0;
+}
+
+const char *bugreport_get_status_text(int a_status)
+{
+    switch (a_status) {
+    case BUGREPORT_STATUS_NOTDEFINED:
+        return "";
+        break;
+    case BUGREPORT_STATUS_NEW:
+        return "new";
+        break;
+    case BUGREPORT_STATUS_IN_PROGRESS:
+        return "in progress";
+        break;
+    case BUGREPORT_STATUS_RESOLVED:
+        return "resolved";
+        break;
+    }
+    return NULL;
+}
+
+int bugreport_get_status_by_text(const char *a_status_str)
+{
+    if(!dap_strcmp(a_status_str, "new"))
+        return BUGREPORT_STATUS_NEW;
+    else if(!dap_strcmp(a_status_str, "in progress"))
+        return BUGREPORT_STATUS_IN_PROGRESS;
+    else if(!dap_strcmp(a_status_str, "in progress"))
+        return BUGREPORT_STATUS_RESOLVED;
+    return BUGREPORT_STATUS_NOTDEFINED;
+}
+
+struct json_object *bugreport_get_last_status(char *a_pkeyHash)//, int *a_status_out, char **a_status_date_out)
+{
+    dap_list_t *l_list_ret = NULL;
+    if(!a_pkeyHash || dap_strlen(a_pkeyHash)<1)
+        return NULL;
+    char *l_dir_str = dap_strdup_printf("%s/var/bugreport", g_sys_dir_path);
+    DIR * l_dir = opendir(l_dir_str);
+    if(l_dir) {
+        struct dirent * l_dir_entry;
+        while((l_dir_entry = readdir(l_dir)) != NULL ) {
+
+            const char *l_ext = dap_path_get_ext(l_dir_entry->d_name);
+            if(dap_strcmp(l_ext, "status"))
+                continue;
+            // read status file
+            char *l_status_content = NULL;
+            size_t l_status_content_len = 0;
+            char * l_full_path_status = dap_strdup_printf("%s/%s", l_dir_str, l_dir_entry->d_name);
+            if(dap_file_get_contents(l_full_path_status, &l_status_content, &l_status_content_len)) {
+                struct json_object *l_jobj = json_tokener_parse((char*) l_status_content);
+                DAP_DELETE(l_status_content);
+                // if file in json format
+                if(l_jobj) {
+                    struct json_object *l_obj_pkeyHash = json_object_object_get(l_jobj, "pkeyHash");
+                    const char *l_str_hash = json_object_get_string(l_obj_pkeyHash);
+                    // search file with pkeyHash = a_pkeyHash
+                    if(l_str_hash && !dap_strcmp(l_str_hash, a_pkeyHash)) {
+                        // get status from status file
+                        struct json_object *l_obj_status = json_object_object_get(l_jobj, "status");
+                        if(l_obj_status && json_object_get_type(l_obj_status) == json_type_array) {
+                            size_t l_num = json_object_array_length(l_obj_status);
+                            struct json_object *l_obj_last_status = l_num > 0 ? json_object_array_get_idx(l_obj_status, l_num - 1) : NULL;
+                            // form one item to return
+                            {
+                                // get bugreport id from filemane
+                                size_t l_shift_id = dap_strlen(l_dir_str) + 1 + 18;
+                                l_full_path_status[l_shift_id + 5] = '\0';
+                                json_object_object_add(l_obj_last_status, "id", json_object_new_string(l_full_path_status + l_shift_id));
+                            }
+
+                            // add item to return
+                            struct json_object *l_obj_last_status_copy;
+                            char* json_str = dap_strdup(json_object_to_json_string(l_obj_last_status));
+                            l_list_ret = dap_list_append(l_list_ret, json_str);
+
+                            //json_object_deep_copy(l_obj_last_status, &l_obj_last_status_copy, NULL);
+                            //json_object_array_add(l_obj_ret, l_obj_last_status);
+                            /*
+                            struct json_object *l_obj_last_status_text = l_obj_last_status ? json_object_object_get(l_obj_last_status, "status") : NULL;
+                            const char *l_str_status_text = json_object_get_string(l_obj_last_status_text);
+                            if(l_str_status_text && dap_strlen(l_str_status_text > 0)) {
+                                // status
+                                if(a_status_out)
+                                    *a_status_out = dap_strdup(l_str_status_text);
+                                // date
+                                if(a_status_date_out){
+                                    struct json_object *l_obj_last_date_text = l_obj_last_status ? json_object_object_get(l_obj_last_status, "date") : NULL;
+                                    *a_status_date_out = dap_strdup(json_object_get_string(l_obj_last_status_text));
+                                }
+                                // return = OK
+                                l_ret = 0;
+                            }
+                            else
+                                l_ret = -2;*/
+                        }
+                        //break;// if only one item return
+                    }
+                    // free
+                    json_object_put(l_jobj);
+                }
+            }
+            DAP_DELETE(l_full_path_status);
+        }
+        closedir(l_dir);
+    }
+    DAP_DELETE(l_dir_str);
+    if(l_list_ret) {
+        struct json_object *l_obj_ret = json_object_new_array();
+        dap_list_t *l_list = l_list_ret;
+        // create output array
+        while(l_list){
+            struct json_object *l_jobj = json_tokener_parse((char*) l_list->data);
+            json_object_array_add(l_obj_ret, l_jobj);
+            l_list = dap_list_next(l_list);
+        }
+        dap_list_free_full(l_list_ret, free);
+        return l_obj_ret;
+    }
+    return NULL ;
+}
+
+int bugreport_add_status(const char *a_filename_bugreport, const char *a_filename_status, const char *a_pkeyHash, int a_status)
+{
+    time_t l_status_date_time;
+    char *l_status_content = NULL;
+    size_t l_status_content_len = 0;
+    // if the status file exist, then the status time is current, otherwise = time of create bugreport
+    if(dap_file_test(a_filename_status)) {
+        l_status_date_time = time(NULL);
+        dap_file_get_contents(a_filename_status, &l_status_content, &l_status_content_len);
+    }
+    else {
+        struct stat st;
+        if(!stat(a_filename_bugreport, &st)) {
+            l_status_date_time = st.st_mtim.tv_sec;
+        }
+        else
+            l_status_date_time = time(NULL);
+    }
+    struct json_object *l_jobj = json_object_new_object();
+    // get exist json
+    if (l_status_content && l_status_content_len>0){
+        l_jobj = json_tokener_parse((char*)l_status_content);
+    }
+    DAP_DELETE(l_status_content);
+    if(!l_jobj)
+        l_jobj = json_object_new_object();
+    // find exist records
+    struct json_object *l_obj_pkey_hash = json_object_object_get(l_jobj, "pkeyHash");
+    struct json_object *l_obj_status = json_object_object_get(l_jobj, "status");
+
+/*    { "pkeyHash": "DKQ58CC6YFBFTTEJ",
+        "status": [ { "date": "Thu, 26 Nov 20 14:20:46 +0500", "status": "new" },
+                    { "date": "Thu, 26 Nov 20 15:05:12 +0500", "status": "in progress" },
+                    { "date": "Thu, 26 Nov 20 16:35:11 +0500", "status": "resolved" } ]
+    }*/
+
+    // pkeyHash
+    if(a_pkeyHash) {
+        const char *l_pkeyHash = l_obj_pkey_hash ? json_object_get_string(l_obj_pkey_hash) : NULL;
+        // create or update pkeyHash
+        if(!l_pkeyHash || dap_strcmp(l_pkeyHash, a_pkeyHash)) {
+            if(l_pkeyHash)
+                json_object_object_del(l_jobj, "pkeyHash");
+            json_object_object_add(l_jobj, "pkeyHash", json_object_new_string(a_pkeyHash));
+        }
+    }
+    // status
+    const char *l_status_str = bugreport_get_status_text(a_status);
+    if(l_status_str) {
+        char l_datetime_buf[1024];
+        dap_time_to_str_rfc822(l_datetime_buf, sizeof(l_datetime_buf), l_status_date_time);
+        //char *l_datetime_buf = dap_strdup_printf("%lu", l_status_date_time);
+
+        struct json_object *l_jobj_arr = l_obj_status ? l_obj_status : json_object_new_array();
+        struct json_object *l_jobj_item = json_object_new_object();
+        // date
+        json_object_object_add(l_jobj_item, "date", json_object_new_string(l_datetime_buf));
+        //status
+        json_object_object_add(l_jobj_item, "status", json_object_new_string(l_status_str));
+        json_object_array_add(l_jobj_arr, l_jobj_item);
+
+        if(!l_obj_status)
+            json_object_object_add(l_jobj, "status", l_jobj_arr);
+        //DAP_DELETE(l_datetime_buf);
+    }
+    const char* json_str = json_object_to_json_string(l_jobj);
+    // write json to file
+    if(json_str) {
+        FILE *l_file = fopen(a_filename_status, "wb");
+        if(l_file)
+        {
+
+            fwrite(json_str, 1, dap_strlen(json_str), l_file);
+            fwrite("\n", 1, 1, l_file);
+            fclose(l_file);
+
+        }
+    }
+    json_object_put(l_obj_pkey_hash);
+    json_object_put(l_jobj);
+    return 0;
+}
+
+void bugreport_update_statuses(void)
+{
+    char *l_dir_str = dap_strdup_printf("%s/var/bugreport", g_sys_dir_path);
+    DIR * l_dir = opendir(l_dir_str);
+    if(l_dir) {
+        struct dirent * l_dir_entry;
+        uint16_t l_acl_idx = 0;
+        while((l_dir_entry = readdir(l_dir)) != NULL) {
+            if(l_dir_entry->d_name[0] == '\0' || l_dir_entry->d_name[0] == '.')
+                continue;
+            // don't search in directories
+            char * l_full_path = dap_strdup_printf("%s/%s", l_dir_str, l_dir_entry->d_name);
+            if(dap_dir_test(l_full_path)) {
+                DAP_DELETE(l_full_path);
+                continue;
+            }
+            // read zip file
+            zip_stat_t l_sb;
+            zip_stat_init(&l_sb);
+            // open only archives
+            struct zip *l_za;
+            int err;
+            if((l_za = zip_open(l_full_path, 0, &err)) == NULL) {
+                DAP_DELETE(l_full_path);
+                continue;
+            }
+            // check the status file for exists
+            char * l_full_path_status = dap_strdup_printf("%s/%s.status", l_dir_str, l_dir_entry->d_name);
+            /*if(dap_file_test(l_full_path_status)){
+                zip_close(l_za);
+                DAP_DELETE(l_full_path);
+                DAP_DELETE(l_full_path_status);
+                continue;
+            }*/
+            zip_int64_t i;
+            for(i = 0; i < zip_get_num_entries(l_za, 0); i++) {
+                if(zip_stat_index(l_za, i, 0, &l_sb) == 0) {
+                    int l_data_txt = -1, l_data_json = -1;
+                    if((l_data_txt = dap_strcmp(l_sb.name, "data.txt")) == 0 ||
+                       (l_data_json = dap_strcmp(l_sb.name, "data.json")) == 0) {
+                        // read data.txt file from archive
+                        struct zip_file *l_zf = zip_fopen_index(l_za, i, 0);
+                        if(!l_zf)
+                            break;
+                        zip_int64_t l_buf_cur_pos = 0;
+                        char *l_buf = DAP_NEW_SIZE(char, l_sb.size + 1);
+                        l_buf[l_sb.size]='\0';
+                        while(l_buf_cur_pos != l_sb.size) {
+                            zip_int64_t len = zip_fread(l_zf, l_buf + l_buf_cur_pos, l_sb.size);
+                            if(len < 0) {
+                                break;
+                            }
+                            l_buf_cur_pos += len;
+                        }
+                        zip_fclose(l_zf);
+                        // if data file was read successfully
+                        if(l_buf_cur_pos == l_sb.size) {
+                            const char *pkeyHash = NULL;
+                            struct json_object *l_jobj = NULL;
+                            // found pkeyHash from data.txt file
+                            if(!l_data_txt) {
+                                char *l_end_of_str = dap_strstr_len(l_buf, l_buf_cur_pos, "\n");
+                                if(l_end_of_str) {
+                                    l_end_of_str[0] = '\0';
+                                    pkeyHash = l_buf;
+                                }
+                            }
+                            // found pkeyHash from data.json file
+                            else if(!l_data_json) {
+                                l_jobj = json_tokener_parse(l_buf);
+                                struct json_object *l_obj_pkey_hash = l_jobj ? json_object_object_get(l_jobj, "pKeyHash") : NULL;
+                                pkeyHash = l_obj_pkey_hash ? json_object_get_string(l_obj_pkey_hash) : NULL;
+                            }
+                            // write status file with pkeyHash
+                            if(pkeyHash) {
+                                bugreport_add_status(l_full_path, l_full_path_status, pkeyHash, BUGREPORT_STATUS_NEW);
+                            }
+                            json_object_put(l_jobj);
+                        }
+                        DAP_DELETE(l_buf);
+                    }
+
+                }
+            }
+            DAP_DELETE(l_full_path);
+            zip_close(l_za);
+        }
+        closedir(l_dir);
+    }
+    DAP_DELETE(l_dir_str);
+}
+
+static int64_t bugreport_write_to_file(byte_t *a_request_byte, size_t a_request_size)
+{
+    int64_t l_report_number = -2;
+    if(!a_request_byte || !a_request_size)
+        return -1;
+    char *l_dir_str = dap_strdup_printf("%s/var/bugreport", g_sys_dir_path);
+    dap_mkdir_with_parents(l_dir_str);
+
+    const time_t l_timer = time(NULL);
+    struct tm l_tm;
+    localtime_r(&l_timer, &l_tm);
+    // create unique number for bugreport
+    randombytes(&l_report_number, sizeof(int64_t));
+    if(l_report_number < 0)
+        l_report_number = -l_report_number;
+    //l_report_number 5 characters long
+    l_report_number %= 100000ll;
+    /*
+    // l_report_number 20 characters long
+    l_report_number -= l_report_number%1000000000000ll;
+    l_report_number+=(int64_t)(l_tm.tm_year - 100)*10000000000;
+    l_report_number+=(int64_t)(l_tm.tm_mon)*100000000;
+    l_report_number+=(int64_t)(l_tm.tm_mday)*1000000;
+    l_report_number+=(int64_t)(l_tm.tm_hour)*10000;
+    l_report_number+=(int64_t)(l_tm.tm_min)*100;
+    l_report_number+=(int64_t)(l_tm.tm_sec);
+    */
+    char *l_filename_str = dap_strdup_printf("%s/%02d-%02d-%02d_%02d:%02d:%02d_%05lld.brt", l_dir_str,
+            l_tm.tm_year - 100, l_tm.tm_mon + 1, l_tm.tm_mday,
+            l_tm.tm_hour, l_tm.tm_min, l_tm.tm_sec,
+            l_report_number);
+    FILE *l_fp;
+    if((l_fp = fopen(l_filename_str, "wb")) != NULL) {
+        if(fwrite(a_request_byte, 1, a_request_size, l_fp) != a_request_size)
+            l_report_number = -3;
+        fclose(l_fp);
+    }
+    DAP_DELETE(l_filename_str);
+    DAP_DELETE(l_dir_str);
+    return l_report_number;
+}
+
+
+static char* parse_query_string(const char *a_query_str, const char *a_str)
+{
+    if(!a_query_str)
+        return NULL;
+    char **l_items = dap_strsplit(a_query_str, "&", -1);
+    for(int l_i = 0; l_items[l_i] != NULL ; l_i++) {
+        char **l_value = dap_strsplit(l_items[l_i], "=", 2);
+        if(dap_str_countv(l_value) == 2) {
+            if(!dap_strcmp(a_str, l_value[0])) {
+                char *l_ret_str = dap_strdup(l_value[1]);
+                dap_strfreev(l_value);
+                dap_strfreev(l_items);
+                return l_ret_str;
+            }
+        }
+        dap_strfreev(l_value);
+    }
+    dap_strfreev(l_items);
+    return NULL;
+}
+
+
+/**
+ * @brief bugreport_http_proc
+ * @param a_http_simple
+ * @param a_arg
+ */
+static void bugreport_http_proc(struct dap_http_simple *a_http_simple, void * a_arg)
+{
+    // data:text/html,<form action=http://192.168.100.92:8079/bugreport/ method=post><input name=a></form>
+    // data:text/html,<form action=http://cdb.klvn.io/bugreport/ method=post><input name=a></form>
+    log_it(L_DEBUG, "bugreport_http_proc request");
+    http_status_code_t * return_code = (http_status_code_t*) a_arg;
+    //if(dap_strcmp(cl_st->http->url_path, BUGREPORT_URL) == 0 )
+    if(dap_strcmp(a_http_simple->http_client->action, "GET") == 0) {
+
+        /*dap_enc_key_t *l_key = dap_enc_ks_find_http(a_http_simple->http_client);
+        char *l_out_str[1024];
+        size_t test_len = dap_enc_code(l_key, "gsdg=323&pkeyhash=0xffdsg", strlen("gsdg=323&pkeyhash=0xffdsg"), l_out_str,
+                            sizeof(l_out_str), DAP_ENC_DATA_TYPE_B64_URLSAFE);
+        uint8_t *in_query_string = NULL;
+        size_t in_query_string_size = 0;
+        // decode bugreport
+        if(l_key) {
+            in_query_string_size = dap_strlen(a_http_simple->http_client->in_query_string) + 16;
+            in_query_string = DAP_NEW_Z_SIZE(uint8_t, in_query_string_size);
+            size_t l_size = dap_enc_decode(l_key, a_http_simple->http_client->in_query_string, a_http_simple->request_size, in_query_string,
+                    in_query_string_size, DAP_ENC_DATA_TYPE_B64_URLSAFE);
+        }*/
+
+
+        size_t l_url_len = dap_strlen(a_http_simple->http_client->url_path);
+        if(!l_url_len) {
+                    a_http_simple->reply = dap_strdup_printf("Unique Bug Report number required)");
+                    *return_code = Http_Status_NotFound;
+                }
+        else{
+            char *pkeyhash = parse_query_string(a_http_simple->http_client->in_query_string, "pkeyhash");
+            if(!pkeyhash){
+                a_http_simple->reply = dap_strdup("[{ \"error\": \"pkeyhash not found in request\"}]");
+            }
+            else{
+                struct json_object *l_jobj= bugreport_get_last_status(pkeyhash);//"DKQ58CC6YFBFTTEJ");//"0x845AC58041A72C25F40ACBBF54F2A93BABB91EB668ABE1F7B5750CD2DD26A666");//
+                const char* json_str = json_object_to_json_string(l_jobj);
+                a_http_simple->reply = dap_strdup(json_str);
+                // free
+                json_object_put(l_jobj);
+                DAP_DELETE(pkeyhash);
+            }
+
+            *return_code = Http_Status_OK;
+        }
+
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+    }
+    else if(dap_strcmp(a_http_simple->http_client->action, "POST") == 0) {
+        //a_http_simple->request_byte;
+        //a_http_simple->request_size;
+        //a_http_simple->http->in_content_length;
+
+        dap_enc_key_t *l_key = dap_enc_ks_find_http(a_http_simple->http_client);
+        uint8_t *l_request_byte = NULL;
+        size_t l_request_size = 0;
+        // decode bugreport
+        if(l_key) {
+            l_request_size = a_http_simple->request_size + 16;
+            l_request_byte = DAP_NEW_Z_SIZE(uint8_t, l_request_size);
+            l_request_size = dap_enc_decode(l_key, a_http_simple->request, a_http_simple->request_size, l_request_byte,
+                    l_request_size, DAP_ENC_DATA_TYPE_RAW);
+        }
+        else {
+            // key not found -> save without decoding
+            l_request_byte = a_http_simple->request_byte;
+            l_request_size = a_http_simple->request_size;
+        }
+        int64_t l_bugreport_number = bugreport_write_to_file(l_request_byte, l_request_size); //a_http_simple->request_byte, a_http_simple->request_size);
+        if(l_key) {
+            DAP_DELETE(l_request_byte);
+        }
+        if(l_bugreport_number >= 0) {
+            //l_report_number 5 characters long
+            a_http_simple->reply = dap_strdup_printf("Bug Report #%05lld saved successfully)", l_bugreport_number);
+            //l_report_number 20 characters long
+            //a_http_simple->reply = dap_strdup_printf("Bug Report #%020lld saved successfully)", l_bugreport_number);
+        }
+        else {
+            a_http_simple->reply = dap_strdup_printf("Bug Report not saved( code=%lld", l_bugreport_number);
+        }
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+        *return_code = Http_Status_OK;
+
+    } else {
+        log_it(L_ERROR, "Wrong action '%s' for the request. Must be 'POST' or 'GET'", a_http_simple->http_client->action);
+        a_http_simple->reply = dap_strdup_printf("[{ \"error\": \"Wrong action '%s' for the request. Must be 'POST' or 'GET'\"}]",
+                a_http_simple->http_client->action);
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+        *return_code = Http_Status_OK;
+    }
+    strcpy(a_http_simple->reply_mime, "application/json");
+}
+
+/**
+ * @brief dap_chain_net_bugreport_add_proc
+ * @param sh HTTP server instance
+ */
+void dap_chain_net_bugreport_add_proc(struct dap_http * sh)
+{
+    const char * url = BUGREPORT_URL;
+    dap_http_simple_proc_add(sh, url, 14096, bugreport_http_proc);
+}
+
diff --git a/modules/modules_dynamic/cdb/dap_chain_net_news.c b/modules/modules_dynamic/cdb/dap_chain_net_news.c
new file mode 100644
index 0000000000000000000000000000000000000000..446ad3eb1ed6f105356f1336d94384a611f06e53
--- /dev/null
+++ b/modules/modules_dynamic/cdb/dap_chain_net_news.c
@@ -0,0 +1,239 @@
+/*
+ * Authors:
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.        https://demlabs.net
+ * CellFrame            https://cellframe.net
+ * Sources              https://gitlab.demlabs.net/cellframe
+ * Cellframe CDB lib    https://gitlab.demlabs.net/dap.support/cellframe-node-cdb-lib
+ * Copyrighted by Demlabs Limited, 2020
+ * All rights reserved.
+ */
+
+#include <stddef.h>
+#include <json-c/json.h>
+#include <json-c/json_object.h>
+
+#include "dap_common.h"
+#include "dap_string.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+#include "dap_config.h"
+#include "dap_chain_node_cli.h"
+#include "rand/dap_rand.h"
+
+#include "http_status_code.h"
+#include "dap_http_simple.h"
+#include "dap_enc_http.h"
+//#include "<dap_chain_global_db_driver.h>
+#include "dap_chain_global_db.h"
+#include "dap_chain_net_news.h"
+#include "dap_chain_net_srv_vpn_cdb.h"
+#define LOG_TAG "cdb_news"
+
+#define NEWS_URL "/news"
+#define GROUP_NEWS "cdb.news"
+#define DEFAULT_LANG "en"
+
+static dap_http_url_proc_t * s_url_proc = NULL;
+static time_t s_cache_expire = 3600;
+
+int com_news(int a_argc, char ** a_argv, void *a_arg_func, char **a_str_reply);
+int dap_chain_net_news_write(const char *a_lang, char *a_data_news, size_t a_data_news_len);
+byte_t* dap_chain_net_news_read(const char *a_lang, size_t *a_news_len);
+void dap_chain_net_news_add_proc(struct dap_http * sh);
+
+int dap_chain_net_news_init(dap_http_t * a_http)
+{
+    s_cache_expire = dap_config_get_item_int32_default(g_dap_config_cdb, "cdb","cache_expire", s_cache_expire);
+
+    dap_chain_node_cli_cmd_item_create("news", com_news, NULL, "Add News for VPN clients. Language code is a text code like \"en\", \"ru\", \"fr\"",
+            "news [-text <news text> | -file <filename with news>] -lang <language code> \n");
+    dap_chain_net_news_add_proc(a_http);
+
+    return 0;
+}
+
+/**
+ * Add News for VPN clients
+ * news [-text <news text> | -file <filename with news>] -lang <language code>
+ */
+int com_news(int a_argc, char ** a_argv, void *a_arg_func, char **a_str_reply)
+{
+    int arg_index = 1;
+    const char * l_str_lang = NULL;
+    const char * l_str_text = NULL;
+    const char * l_str_file = NULL;
+
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-lang", &l_str_lang);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-text", &l_str_text);
+    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-file", &l_str_file);
+
+    if(!l_str_text && !l_str_file) {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "no source of news, add parameter -text or -file");
+        return -1;
+    }
+    char *l_data_news;
+    size_t l_data_news_len = 0;
+    const char *l_from = NULL;
+
+    if(l_str_text) {
+        l_data_news = dap_strdup(l_str_text);
+        l_data_news_len = dap_strlen(l_str_text);
+        l_from = "text";
+    }
+    else if(l_str_file) {
+        if(dap_file_get_contents(l_str_file, &l_data_news,&l_data_news_len)) {
+            l_from = "file";
+        }
+        else{
+                dap_chain_node_cli_set_reply_text(a_str_reply, "Can't read file %s", l_str_file);
+                return -2;
+            }
+    }
+
+    int l_res = dap_chain_net_news_write(l_str_lang, l_data_news, l_data_news_len);
+    if(l_res){
+        dap_chain_node_cli_set_reply_text(a_str_reply, "Error, News cannot be added from %s", l_from);
+        return -3;
+    }
+    dap_chain_node_cli_set_reply_text(a_str_reply, "News added from %s successfully", l_from);
+    return 0;
+}
+
+
+char* dap_chain_net_news_added_extra_info(const char *a_json_text)
+{
+    // parse existing news in json format
+    struct json_object *l_jobj_arr = json_tokener_parse(a_json_text);
+    if(json_object_is_type(l_jobj_arr, json_type_array)) {
+        int64_t l_timestamp = time(NULL);
+        // news may consist of several news blocks
+        size_t l_size = json_object_array_length(l_jobj_arr);
+        for(int i = 0; i < (int) l_size; i++) {
+            json_object *l_one_news = json_object_array_get_idx(l_jobj_arr, i);
+            if(json_object_is_type(l_one_news, json_type_object)) {
+                // add timestamp
+                json_object_object_add(l_one_news, "timestamp", json_object_new_int64(l_timestamp));
+                // create unique number for news
+                uint64_t l_id;
+                randombytes(&l_id, sizeof(int64_t));
+                l_id %= 100000ll; //l_id 5 characters long
+                // add unique id
+                json_object_object_add(l_one_news, "id", json_object_new_int64(l_id));
+            }
+        }
+
+        char* json_str = dap_strdup(json_object_to_json_string(l_jobj_arr));
+        json_object_put(l_jobj_arr);
+        return json_str;
+    }
+    return NULL;
+}
+
+/* Set news in the selected language
+ * a_lang - a language like "en", "ru", "fr"
+ * a_data_news - news data
+ * a_data_news_len length of news
+ */
+int dap_chain_net_news_write(const char *a_lang, char *a_data_news, size_t a_data_news_len)
+{
+    if(!a_data_news || !a_data_news_len)
+        return -2;
+    if(!a_lang)
+        a_lang = DEFAULT_LANG;
+    // insert timestamp and id into news
+    char *l_data_news_new = dap_chain_net_news_added_extra_info(a_data_news);
+    if(l_data_news_new){
+        size_t l_data_news_new_len = dap_strlen(l_data_news_new);
+        if(dap_chain_global_db_gr_set((char *)a_lang, l_data_news_new, l_data_news_new_len, GROUP_NEWS))
+            return 0;
+    }
+    if(dap_chain_global_db_gr_set((char *)a_lang, a_data_news, a_data_news_len, GROUP_NEWS))
+        return 0;
+    return -1;
+}
+
+/* Get news in the selected language
+ * a_lang - a language like "en", "ru", "fr"
+ */
+byte_t* dap_chain_net_news_read(const char *a_lang, size_t *a_news_len)
+{
+    if(!a_lang)
+        return NULL;
+    byte_t *l_ret_data = NULL;
+    size_t l_data_len_num = 0;
+    dap_store_obj_t *l_obj = dap_chain_global_db_obj_gr_get(a_lang, &l_data_len_num, GROUP_NEWS);
+    if(l_obj && l_obj->value_len) {
+        l_ret_data = DAP_NEW_Z_SIZE(byte_t, l_obj->value_len);
+        memcpy(l_ret_data, l_obj->value, l_obj->value_len);
+        if(a_news_len)
+            *a_news_len = l_obj->value_len;
+    }
+    dap_store_obj_free(l_obj, l_data_len_num);
+    return l_ret_data;
+}
+
+/**
+ * @brief news_http_proc
+ * @param a_http_simple
+ * @param a_arg
+ */
+static void news_http_proc(struct dap_http_simple *a_http_simple, void * a_arg)
+{
+    log_it(L_DEBUG, "news_http_proc request");
+    http_status_code_t * return_code = (http_status_code_t*) a_arg;
+    const char *l_lang = DEFAULT_LANG;
+    if(dap_strcmp(a_http_simple->http_client->url_path, NEWS_URL)) {
+        l_lang = a_http_simple->http_client->url_path;
+    }
+
+    if(l_lang)
+    {
+        size_t l_news_data_len = 0;
+        // get news in the selected language
+        byte_t *l_news_data = dap_chain_net_news_read(l_lang, &l_news_data_len);
+        // get news in the default language
+        if(!l_news_data && dap_strcmp(a_http_simple->http_client->in_query_string, "LocalNewsOnly"))
+            l_news_data = dap_chain_net_news_read(DEFAULT_LANG, &l_news_data_len);
+        if(!l_news_data){
+            a_http_simple->reply = l_news_data ;
+            a_http_simple->reply_size = l_news_data_len;
+        }else{
+            a_http_simple->reply = dap_strdup("[{ \"message\": \"no news\"}]");
+            a_http_simple->reply_size = dap_strlen((char*) a_http_simple->reply);
+        }
+        *return_code = Http_Status_OK;
+    }
+    else {
+        log_it(L_ERROR, "Wrong request. Must be %s/<lang_code>, example http:/<addr>%s/en", NEWS_URL, NEWS_URL);
+        a_http_simple->reply = dap_strdup_printf("[{ \"error\": \"Wrong request. Must be %s/<lang_code>, example http:/<addr>%s/en\"}]", NEWS_URL, NEWS_URL);
+        a_http_simple->reply_size = strlen(a_http_simple->reply);
+        *return_code = Http_Status_OK;//Http_Status_NotFound;
+    }
+    strcpy(a_http_simple->reply_mime, "application/json");
+    dap_http_simple_make_cache_from_reply(a_http_simple,time(NULL)+ s_cache_expire);
+}
+
+/**
+ * @brief dap_chain_net_news_add_proc
+ * @param sh HTTP server instance
+ */
+void dap_chain_net_news_add_proc(struct dap_http * sh)
+{
+    const char * url = NEWS_URL;
+    s_url_proc = dap_http_simple_proc_add(sh, url, 14096, news_http_proc);
+}
+
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_news_cache_reset
+ */
+void dap_chain_net_srv_vpn_cdb_news_cache_reset()
+{
+    if(s_url_proc){
+        pthread_rwlock_wrlock(&s_url_proc->cache_rwlock);
+        dap_http_cache_delete(s_url_proc->cache);
+        s_url_proc->cache = NULL;
+        pthread_rwlock_unlock(&s_url_proc->cache_rwlock);
+    }
+}
diff --git a/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb.c b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb.c
new file mode 100644
index 0000000000000000000000000000000000000000..cdd201699e7984faf0d6208c3d751a4c9979474b
--- /dev/null
+++ b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb.c
@@ -0,0 +1,444 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * DeM Labs Inc.        https://demlabs.net
+ * CellFrame            https://cellframe.net
+ * Sources              https://gitlab.demlabs.net/cellframe
+ * Cellframe CDB lib    https://gitlab.demlabs.net/dap.support/cellframe-node-cdb-lib
+ * Copyrighted by Demlabs Limited, 2020
+ * All rights reserved.
+*/
+#include "utlist.h"
+#include "dap_common.h"
+#include "dap_config.h"
+#include "dap_enc_http.h"
+#include "dap_enc_base64.h"
+#include "dap_http.h"
+
+#include "dap_chain.h"
+#include "dap_chain_net.h"
+#include "dap_chain_ledger.h"
+#include "dap_chain_wallet.h"
+#include "dap_chain_datum_tx.h"
+#include "dap_chain_datum_tx_in.h"
+#include "dap_chain_datum_tx_in_cond.h"
+#include "dap_chain_datum_tx_out_cond.h"
+#include "dap_chain_datum_tx_out.h"
+#include "dap_chain_datum_tx_pkey.h"
+#include "dap_chain_datum_tx_receipt.h"
+#include "dap_chain_datum_tx_sig.h"
+#include "dap_chain_global_db.h"
+
+#include "dap_chain_node_cli.h"
+
+#include "dap_chain_mempool.h"
+#include "dap_pkey.h"
+
+#include "dap_chain_net_srv_vpn.h"
+#include "dap_chain_net_srv_vpn_cdb.h"
+#include "dap_chain_net_srv_vpn_cdb_auth.h"
+#include "dap_chain_net_srv_vpn_cdb_server_list.h"
+#include "dap_chain_net_news.h"
+#include "dap_chain_net_bugreport.h"
+
+
+#define LOG_TAG "dap_chain_net_srv_vpn_cdb"
+
+#define DB_URL "/db"
+#define NODELIST_URL "/nodelist"
+
+typedef struct tx_cond_template{
+    char * wallet_name;
+    dap_chain_wallet_t * wallet;
+
+    long double value_coins;
+    uint128_t value_datoshi;
+
+    char token_ticker[DAP_CHAIN_TICKER_SIZE_MAX];
+    char * net_name;
+    dap_chain_net_t * net;
+    dap_ledger_t * ledger;
+    time_t min_time; // Minimum time between transactions
+    struct tx_cond_template *prev, *next;
+} tx_cond_template_t;
+
+static tx_cond_template_t *s_tx_cond_templates = NULL;
+dap_config_t * g_dap_config_cdb = NULL;
+const char *c_wallets_path = NULL;
+
+static int s_cli_vpn_cdb(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply);
+
+static const char * s_sensetive_fields[] = {
+    "--password",
+    ""
+};
+
+char* s_cdb_log_cmd_call_apply_filters_dup(const char * a_cmd){
+    char * l_filtred = dap_strdup(a_cmd);
+
+    for(int a = 0; s_sensetive_fields[a][0] != '\0'; a++){
+        char * l_found = strstr(l_filtred, s_sensetive_fields[a]);
+        if(l_found){
+            l_found += strlen(s_sensetive_fields[a]);
+            l_found += 1; //skipping '='
+            for(;*l_found != '\0' && *l_found != ';';l_found++){
+                *l_found = '*';
+            }
+        }
+    }
+
+    return l_filtred;
+}
+
+void s_cdb_log_cmd_call(const char* a_cmd){
+    char * l_filtred = s_cdb_log_cmd_call_apply_filters_dup(a_cmd);
+    //log_it(L_DEBUG,l_filtred);
+    DAP_DELETE(l_filtred);
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_init
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_init(dap_http_t * a_http)
+{
+    int ret=0;
+    g_dap_config_cdb = g_config;
+    c_wallets_path = dap_chain_wallet_get_path(g_config);
+    if (dap_config_get_item_bool_default( g_dap_config_cdb,
+                                                                "cdb",
+                                                                "servers_list_enabled",
+                                                                false)) {
+
+        if (dap_chain_net_srv_vpn_cdb_server_list_init() != 0) {
+            log_it(L_CRITICAL,"Can't init vpn servers list");
+            return -10;
+        }
+    }
+
+    dap_chain_node_cli_cmd_item_create ("vpn_cdb", s_cli_vpn_cdb, NULL, "VPN Central DataBase (CDB) commands",
+        "vpn_cdb user create --login <Login> --password <Password> [-password_base64] [--first_name <First Name>] [--last_name <Last Name>] [--email <Email>]"
+        "[--active_days <Setup active day thats left for user >]\n"
+            "\tCreate user with login, password and some more optional fields\n\n"
+        "vpn_cdb user update --login <Login> [--password <Password>] [-password_base64] [--first_name <First Name>] [--last_name <Last Name>] [--email <Email>]"
+                             "[--active_days <Setup active days that left for user >]\n"
+            "\tUpdate existent user\n"
+        "vpn_cdb user delete --login <Login>\n"
+            "\tDelete user by login\n"
+        "vpn_cdb user show --login <Login>\n"
+            "\tShow user fields by login\n"
+        "vpn_cdb user check --login <Login> --password <Password> [-password_base64]\n"
+            "\tCompare <Password> with stored in GDB for <Login>\n"
+        "vpn_cdb user list\n"
+            "\tShow all users\n"
+        "vpn_cdb serial generate -n <number of serial keys>] [-active_days <active days that left for serial>]\n"
+            "\tGenerate new serial keys\n"
+        "vpn_cdb serial add -file <file_name> [-active_days <active days that left for serial>]\n"
+            "\tAdd new serial keys from file\n"
+        "vpn_cdb serial list [-n <How many show serial keys>] [-shift <How many skip serial keys>] [-activated_only|-inactive_only] [-nototal|-total_only]\n"
+            "\tShow serial keys\n"
+        "vpn_cdb serial update -serial <serial keys> -active_days <active days that left for serial>\n"
+            "\tEdit serial key\n"
+        "vpn_cdb serial info -serial <serial keys>\n"
+            "\tInformation about serial key\n"
+        "vpn_cdb serial delete -serial <serial keys>\n"
+            "\tDelete serial key\n"
+        "vpn_cdb serial deactivate -serial <serial keys>\n"
+            "\tDeactivate serial key\n"
+        "vpn_cdb http_cache reset"
+            "\tReset HTTP cache for CDB urls"
+                                        );
+    dap_chain_node_cmd_item_func_overrides_t l_overrides = {NULL};
+    l_overrides.log_cmd_call = s_cdb_log_cmd_call;
+
+    dap_chain_node_cli_cmd_item_apply_overrides("vpn_cdb", &l_overrides);
+
+    // Load all chain networks
+    if (dap_config_get_item_bool_default( g_dap_config_cdb,
+                                                        "cdb",
+                                                        "servers_list_enabled",
+                                                        false)) {
+        dap_chain_net_srv_vpn_cdb_server_list_add_proc ( a_http, NODELIST_URL);
+    }
+    if (dap_config_get_item_bool_default( g_dap_config_cdb,"cdb_auth","enabled",false) ){
+
+        ret = dap_chain_net_srv_vpn_cdb_auth_init(  dap_config_get_item_str_default(g_dap_config_cdb,"cdb_auth","domain","cdb"),
+                                              dap_config_get_item_str_default(g_dap_config_cdb,"cdb_auth","mode","passwd"),
+                                              dap_config_get_item_bool_default(g_dap_config_cdb,"cdb_auth","registration_open",false));
+        if(ret<0)
+            return ret;
+        dap_chain_net_srv_vpn_cdb_auth_add_proc( a_http , DB_URL );
+
+        // Produce transaction for authorized users
+        if (dap_config_get_item_bool_default( g_dap_config_cdb,
+                                                            "cdb_auth",
+                                                            "tx_cond_create",
+                                                            false)) {
+            // Parse tx cond templates
+            uint16_t l_tx_cond_tpls_count = 0;
+
+            /* ! IMPORTANT ! This fetch is single-action and cannot be further reused, since it modifies the stored config data
+             * ! it also must NOT be freed within this module !
+             */
+            char **l_tx_cond_tpls = dap_config_get_array_str(g_dap_config_cdb, "cdb_auth", "tx_cond_templates", &l_tx_cond_tpls_count);
+            if (l_tx_cond_tpls_count == 0) {
+                log_it( L_ERROR, "No condition tpl, can't setup auth callback");
+                return -5;
+            }
+
+            for (size_t i = 0 ; i < l_tx_cond_tpls_count; i++) {
+                tx_cond_template_t *l_tx_cond_template = DAP_NEW_Z(tx_cond_template_t);
+
+                // Parse template entries
+                short l_step = 0;
+                char *ctx;
+                for (char *l_tpl_token = strtok_r(l_tx_cond_tpls[i], ":", &ctx); l_tpl_token || l_step == 5; l_tpl_token = strtok_r(NULL, ":", &ctx), ++l_step) {
+                    switch (l_step) {
+                    case 0:
+                        if(!(l_tx_cond_template->wallet = dap_chain_wallet_open(l_tpl_token, c_wallets_path))) {
+                            log_it(L_ERROR, "Can't open wallet \"%s\"", l_tpl_token);
+                            DAP_DELETE(l_tx_cond_template);
+                            break;
+                        }
+                        l_tx_cond_template->wallet_name = l_tpl_token;
+                        continue;
+                    case 1:
+                        if (!(l_tx_cond_template->value_coins = strtold(l_tpl_token, NULL))) {
+                            log_it(L_ERROR, "Error parsing tpl: text on 2nd position \"%s\" is not a number", l_tpl_token);
+                            DAP_DELETE(l_tx_cond_template->wallet);
+                            DAP_DELETE(l_tx_cond_template);
+                            l_step = 0;
+                            break;
+                        }
+                        l_tx_cond_template->value_datoshi = dap_chain_coins_to_datoshi(l_tx_cond_template->value_coins);
+                        continue;
+                    case 2:
+                        if (!(l_tx_cond_template->min_time = (time_t)atoll(l_tpl_token))) {
+                            log_it(L_ERROR, "Error parsing tpl: text on 3d position \"%s\" is not a number", l_tpl_token);
+                            DAP_DELETE(l_tx_cond_template->wallet);
+                            DAP_DELETE(l_tx_cond_template);
+                            l_step = 0;
+                            break;
+                        }
+                        continue;
+                    case 3:
+                        dap_stpcpy(l_tx_cond_template->token_ticker, l_tpl_token);
+                        continue;
+                    case 4:
+                        if (!(l_tx_cond_template->net = dap_chain_net_by_name(l_tpl_token))
+                                || !(l_tx_cond_template->ledger = dap_chain_ledger_by_net_name(l_tpl_token)))
+                        {
+                            log_it(L_ERROR, "Can't open network \"%s\" or ledger in it", l_tpl_token);
+                            DAP_DELETE(l_tx_cond_template->wallet);
+                            DAP_DELETE(l_tx_cond_template);
+                            l_step = 0;
+                            break;
+                        }
+                        l_tx_cond_template->net_name = l_tpl_token;
+                        continue;
+                    case 5:
+                        log_it(L_INFO, "Condition template correct, added to list");
+                        DL_APPEND(s_tx_cond_templates, l_tx_cond_template);
+                        break;
+                    default:
+                        break;
+                    }
+                    log_it(L_DEBUG, "Done with tpl item %d", i);
+                    break; // double break exits tokenizer loop and steps to next tpl item
+                }
+            }
+            if (!s_tx_cond_templates) ret = -1;
+        } else {
+            log_it(L_INFO, "No conditional transactions, provide VPN service for free");
+        }
+    }
+
+    bool l_bugreport_url_enabled = dap_config_get_item_bool_default(g_config, "server", "bugreport_url_enabled", false);
+    if(l_bugreport_url_enabled) {
+        dap_chain_net_bugreport_init(a_http);
+    }
+
+    bool l_news_url_enabled = dap_config_get_item_bool_default(g_config, "server", "news_url_enabled", false);
+    if(l_news_url_enabled) {
+        dap_chain_net_news_init(a_http);
+    }
+
+    return ret;
+}
+
+static int s_cli_vpn_cdb(int a_argc, char ** a_argv, void *arg_func, char **a_str_reply)
+{
+    const char *l_user_str = NULL;
+    const char *l_serial_add_param_str = NULL;
+
+    const char * l_http_cache_cmd = NULL;
+    int l_arg_index = 1;
+    int l_ret = -1;
+
+    int l_user_pos = dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "user", &l_user_str);
+    int l_serial_pos = dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "serial", &l_serial_add_param_str);
+
+    dap_chain_node_cli_find_option_val(a_argv, l_arg_index, a_argc, "http_cache", &l_http_cache_cmd);
+    if( l_http_cache_cmd){
+        if(dap_strcmp(l_http_cache_cmd,"reset") == 0) {
+            dap_chain_net_srv_vpn_cdb_news_cache_reset();
+            dap_chain_net_srv_vpn_cdb_server_list_cache_reset();
+            dap_chain_node_cli_set_reply_text( a_str_reply, "OK\nReset /news and /nodelist URL's" );
+        }
+    }else if ( l_user_str ){ // Selected 'user' subcommand
+        return dap_chain_net_srv_vpn_cdb_auth_cli_cmd_user(l_user_str,l_arg_index,  a_argc,  a_argv,a_str_reply);
+    }
+    // Selected 'serial' subcoummand
+    else if(l_serial_add_param_str) {
+        return dap_chain_net_srv_vpn_cdb_auth_cli_cmd_serial(l_serial_add_param_str, l_arg_index, a_argc, a_argv, a_str_reply);
+    }
+    else {
+        if(l_user_pos || l_user_pos)
+            dap_chain_node_cli_set_reply_text(a_str_reply, "require additional subcommand, see 'help vpn_cdb'");
+        else
+            dap_chain_node_cli_set_reply_text(a_str_reply, "unknown subcommand, use 'user' or 'serial'", l_user_str);
+    }
+    return l_ret;
+}
+
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_deinit
+ */
+void dap_chain_net_srv_vpn_cdb_deinit()
+{
+
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_after
+ * @param a_delegate
+ * @param a_login
+ * @param a_pkey_b64
+ */
+void dap_chain_net_srv_vpn_cdb_auth_after(enc_http_delegate_t* a_delegate, const char * a_login, const char * a_pkey_b64 )
+{
+#ifndef __ANDROID__
+
+    dap_enc_key_t *l_client_key = NULL;
+    byte_t *l_pkey_raw = NULL;
+    size_t l_pkey_raw_size = 0;
+    log_it( L_DEBUG, "Authorized, now need to create conditioned transaction if not present key_len=%d", dap_strlen( a_pkey_b64));
+    {
+        size_t l_pkey_b64_length = dap_strlen( a_pkey_b64);
+        l_pkey_raw = DAP_NEW_Z_SIZE(byte_t,l_pkey_b64_length);
+        memset(l_pkey_raw, 0, l_pkey_b64_length);
+        l_pkey_raw_size = dap_enc_base64_decode(a_pkey_b64, l_pkey_b64_length, l_pkey_raw, DAP_ENC_DATA_TYPE_B64_URLSAFE);
+        char *l_pkey_gdb_group =  dap_strdup_printf( "cdb.%s.pkey", DAP_CHAIN_NET_SRV_VPN_CDB_GDB_PREFIX);
+        log_it(L_DEBUG, "Pkey group '%s'", l_pkey_gdb_group);
+        dap_chain_global_db_gr_set((char *)a_login, l_pkey_raw, l_pkey_raw_size, l_pkey_gdb_group);
+
+        l_client_key = dap_enc_key_new(DAP_ENC_KEY_TYPE_SIG_TESLA);
+        int l_res = dap_enc_key_deserealize_pub_key(l_client_key, l_pkey_raw, l_pkey_raw_size);
+        // bad pkey
+        if(l_res){
+            log_it(L_WARNING, "dap_enc_key_deserealize_priv_key='%d'", l_res);
+            DAP_DELETE(l_pkey_raw);
+            l_pkey_raw_size = 0;
+            l_pkey_raw = NULL;
+        }
+        DAP_DELETE(l_pkey_gdb_group);
+    }
+
+    tx_cond_template_t *l_tpl;
+    DL_FOREACH(s_tx_cond_templates, l_tpl) {
+        size_t l_gdb_group_size = 0;
+
+        // Try to load from gdb
+        //char *l_tx_cond_gdb_group =  dap_strdup_printf( "%s.%s.tx_cond", l_tpl->net->pub.name, DAP_CHAIN_NET_SRV_VPN_CDB_GDB_PREFIX);
+        char *l_tx_cond_gdb_group =  dap_strdup_printf( "cdb.%s.tx_cond", DAP_CHAIN_NET_SRV_VPN_CDB_GDB_PREFIX);
+        log_it(L_DEBUG, "Checkout group %s", l_tx_cond_gdb_group);
+        // get key for tx_cond
+        char *l_user_key;
+        {
+            dap_chain_hash_fast_t l_hash = { 0 };
+            char *l_key_hash_str = NULL;
+            if(dap_hash_fast(a_pkey_b64, dap_strlen(a_pkey_b64), &l_hash))
+                l_key_hash_str = dap_chain_hash_fast_to_str_new(&l_hash);
+            l_user_key = dap_strdup_printf("%s/%s", a_login, l_key_hash_str);
+            DAP_DELETE(l_key_hash_str);
+        }
+        log_it(L_DEBUG, "\ndbg l_user_key=%s\n", l_user_key);
+        dap_chain_hash_fast_t *l_tx_cond_hash = (dap_chain_hash_fast_t*) dap_chain_global_db_gr_get(l_user_key, &l_gdb_group_size, l_tx_cond_gdb_group);
+
+        // Check for entry size
+        if (l_gdb_group_size && l_gdb_group_size != sizeof(dap_chain_hash_fast_t)) {
+            log_it(L_ERROR, "Wrong size of tx condition on database (%zd but expected %zd), may be old entry",
+                   l_gdb_group_size, sizeof(dap_chain_hash_fast_t));
+        }
+
+        time_t l_tx_cond_ts = 0;
+        // If loaded lets check is it spent or not
+        if ( l_tx_cond_hash ){
+            log_it(L_DEBUG, "2791: Search for unspent tx, net %s", l_tpl->net_name);
+            dap_chain_datum_tx_t *l_tx = dap_chain_net_get_tx_by_hash(l_tpl->net, l_tx_cond_hash, TX_SEARCH_TYPE_NET_UNSPENT);
+            if ( !l_tx ){ // If not found - all outs are used. Create new one
+                // pass all chains
+                l_tx = dap_chain_net_get_tx_by_hash(l_tpl->net, l_tx_cond_hash, TX_SEARCH_TYPE_NET);
+                DAP_DELETE(l_tx_cond_hash);
+                l_tx_cond_hash = NULL;
+                if ( l_tx ){
+                    l_tx_cond_ts =(time_t) l_tx->header.ts_created;
+                    log_it(L_DEBUG, "2791: got some tx, created %d", l_tx->header.ts_created);
+                }
+            }
+        }
+
+        // Try to create condition
+        if (! l_tx_cond_hash ) {
+            dap_chain_wallet_t *l_wallet_from = l_tpl->wallet;
+            log_it(L_DEBUG, "Create tx from wallet %s", l_wallet_from->name);
+            dap_enc_key_t *l_key_from = dap_chain_wallet_get_key(l_wallet_from, 0);
+
+
+            //dap_chain_cell_id_t *xccell = dap_chain_net_get_cur_cell(l_tpl->net);
+            //uint64_t uint64 =dap_chain_net_get_cur_cell(l_tpl->net)->uint64;
+
+
+            // where to take coins for service
+            dap_chain_addr_t *l_addr_from = dap_chain_wallet_get_addr(l_wallet_from, l_tpl->net->pub.id);
+            dap_chain_net_srv_price_unit_uid_t l_price_unit = { .enm = SERV_UNIT_SEC };
+            dap_chain_net_srv_uid_t l_srv_uid = { .uint64 = DAP_CHAIN_NET_SRV_VPN_ID };
+            l_tx_cond_hash = dap_chain_proc_tx_create_cond(l_tpl->net, l_key_from, l_client_key, l_addr_from, l_tpl->token_ticker,
+                                                       (uint64_t) l_tpl->value_datoshi, 0, l_price_unit, l_srv_uid, 0, l_pkey_raw, l_pkey_raw_size);
+            char *l_addr_from_str = dap_chain_addr_to_str( l_addr_from );
+            DAP_DELETE( l_addr_from);
+            if (!l_tx_cond_hash) {
+                log_it(L_ERROR, "Can't create condition for user");
+            } else {
+                // save transaction for login
+                dap_chain_global_db_gr_set(l_user_key, l_tx_cond_hash, sizeof(dap_chain_hash_fast_t),l_tx_cond_gdb_group);
+                log_it(L_NOTICE, "User \"%s\": created conditioned transaction from %s(%s) on "
+                                , a_login, l_tpl->wallet_name, l_addr_from_str);
+            }
+            DAP_DELETE( l_addr_from_str );
+        }
+        DAP_DELETE(l_user_key);
+        // dbg
+        //dap_ledger_t * l_ledger = dap_chain_ledger_by_net_name( l_tpl->net->pub.name);
+        //dap_chain_datum_tx_t *l_tx = dap_chain_ledger_tx_find_by_hash( l_ledger, l_tx_cond_hash);
+
+        // If we loaded or created hash
+        if( l_tx_cond_hash ){
+            char * l_tx_cond_hash_str = dap_chain_hash_fast_to_str_new(l_tx_cond_hash);
+            enc_http_reply_f(a_delegate,"\t<tx_cond_tpl>\n");
+            //enc_http_reply_f(a_delegate,"\t\t<net>%s</net>\n",l_tpl->net_name);
+            enc_http_reply_f(a_delegate,"\t\t<net>0x%x</net>\n",l_tpl->net->pub.id.uint64);
+            enc_http_reply_f(a_delegate,"\t\t<token>%s</token>\n",l_tpl->token_ticker);
+            enc_http_reply_f(a_delegate,"\t\t<tx_cond>%s</tx_cond>\n",l_tx_cond_hash_str);
+            enc_http_reply_f(a_delegate,"\t</tx_cond_tpl>\n");
+            DAP_DELETE(l_tx_cond_hash);
+            DAP_DELETE(l_tx_cond_hash_str);
+        }
+
+        DAP_DELETE(l_tx_cond_gdb_group);
+    }
+    if (l_client_key)
+        dap_enc_key_delete(l_client_key);
+#endif
+}
diff --git a/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_auth.c b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_auth.c
new file mode 100644
index 0000000000000000000000000000000000000000..aed83b936da71a86933fc3097c8d94db6e81c882
--- /dev/null
+++ b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_auth.c
@@ -0,0 +1,2029 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * DeM Labs Inc.        https://demlabs.net
+ * CellFrame            https://cellframe.net
+ * Sources              https://gitlab.demlabs.net/cellframe
+ * Cellframe CDB lib    https://gitlab.demlabs.net/dap.support/cellframe-node-cdb-lib
+ * Copyrighted by Demlabs Limited, 2020
+ * All rights reserved.
+*/
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <rand/dap_rand.h>
+
+#include <time.h>
+
+#include "dap_common.h"
+#include "dap_string.h"
+#include "dap_strfuncs.h"
+#include "dap_file_utils.h"
+
+#include "dap_http.h"
+#include "dap_http_client.h"
+#include "dap_http_simple.h"
+
+#include "dap_enc.h"
+#include "dap_enc_key.h"
+#include "dap_enc_ks.h"
+#include "dap_enc_http.h"
+#include "dap_enc_base64.h"
+#include "dap_server.h"
+
+#include "dap_chain_node_cli.h"
+#include "dap_chain_global_db.h"
+
+#include "http_status_code.h"
+
+#include "dap_chain_net_srv_vpn_cdb.h"
+#include "dap_chain_net_srv_vpn_cdb_auth.h"
+
+#define LOG_TAG "dap_chain_net_srv_vpn_cdb_auth"
+
+#define OP_CODE_NO_COOKIE "0xe0"
+#define OP_CODE_LOGIN_INCORRECT_SIGN_ALREADY_ACTIVATED  "0xf1"
+#define OP_CODE_LOGIN_INCORRECT_SIGN "0xf2"
+#define OP_CODE_LOGIN_INCORRECT_PSWD "0xf2"
+#define OP_CODE_NOT_FOUND_LOGIN_IN_DB "0xf3"
+#define OP_CODE_SUBSCRIBE_EXPIRIED "0xf4"
+#define OP_CODE_INCORRECT_SYMOLS "0xf6"
+#define OP_CODE_LOGIN_INACTIVE  "0xf7"
+#define OP_CODE_SERIAL_ACTIVED  "0xf8"
+
+
+dap_enc_http_callback_t s_callback_success = NULL;
+
+static char * s_domain = NULL;
+static char * s_group_users = NULL;
+static char * s_group_serials = NULL;
+static char * s_group_serials_activated = NULL;
+
+static char * s_group_password = NULL;
+static char * s_group_first_name = NULL;
+static char * s_group_last_name = NULL;
+static char * s_group_email = NULL;
+static char * s_group_ts_updated = NULL;
+static char * s_group_ts_last_login = NULL;
+static char * s_group_cookies = NULL;
+static char * s_group_cookie = NULL;
+static char * s_group_ts_active_till = NULL;
+
+static char * s_salt_str = "Ijg24GAS56h3hg7hj245b";
+
+static bool s_is_registration_open = false;
+
+enum {
+    MODE_UNKNOWN = 0,
+    MODE_PASSWD,
+    MODE_SERIAL,
+    MODE_BOTH
+};
+static int  s_mode_auth = -1;
+//static bool s_mode_passwd = true;
+
+// hook paths
+static char *s_hook_user_create = NULL;
+static char *s_hook_user_login = NULL;
+static char *s_hook_user_update = NULL;
+static char *s_hook_user_delete = NULL;
+
+static char *s_hook_serial_generate = NULL;
+static char *s_hook_serial_login = NULL;
+static char *s_hook_serial_activate = NULL;
+static char *s_hook_serial_update = NULL;
+static char *s_hook_serial_delete = NULL;
+static char *s_hook_serial_deactivate = NULL;
+
+static int s_input_validation(const char * str);
+static void s_http_enc_proc(enc_http_delegate_t *a_delegate, void * a_arg);
+static void s_http_enc_proc_key(enc_http_delegate_t *a_delegate, void * a_arg);
+static void s_http_enc_proc_key_deactivate(enc_http_delegate_t *a_delegate, void * a_arg);
+static void s_http_proc(dap_http_simple_t *a_http_simple, void * arg );
+
+static char *register_hook(const char *a_cfg_name)
+{
+    char *l_hook_path_ret = NULL;
+    const char *l_hook_path = dap_config_get_item_str(g_config, "cdb_auth", a_cfg_name);
+    if(dap_file_test(l_hook_path))
+        l_hook_path_ret = dap_strdup(l_hook_path);
+    else if(l_hook_path) {
+        log_it(L_WARNING, "file for %s = %s not found", a_cfg_name, l_hook_path);
+    }
+    return l_hook_path_ret;
+}
+
+static int run_hook(char *a_hook_path, char *a_format, ...)
+{
+    if(!a_hook_path)
+        return -1;
+    char *l_params = NULL;
+    va_list l_args;
+    va_start(l_args, a_format);
+    l_params = dap_strdup_vprintf(a_format, l_args);
+    va_end(l_args);
+    char *l_cmd = dap_strdup_printf("%s %s", a_hook_path, l_params);
+    int l_ret = system(l_cmd);
+    DAP_DELETE(l_params);
+    DAP_DELETE(l_cmd);
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_init
+ * @param a_domain
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_init (const char * a_domain, const char * a_mode, bool a_is_registration_open)
+{
+    s_is_registration_open = a_is_registration_open;
+
+    s_domain = dap_strdup(a_domain);
+
+    // Prefix for gdb groups
+    s_group_users = dap_strdup_printf("cdb.%s.users",s_domain);
+    s_group_serials = dap_strdup_printf("cdb.%s.serials",s_domain);
+    s_group_serials_activated = dap_strdup_printf("cdb.%s.serials_activated",s_domain);
+
+    // Cookie -> login
+    s_group_cookies = dap_strdup_printf("cdb.%s.cookies",s_domain);
+
+    // mode: passwd or serial
+    if(!dap_strcmp(a_mode, "serial"))
+        s_mode_auth = MODE_SERIAL;
+    else if(!dap_strcmp(a_mode, "passwd"))
+        s_mode_auth = MODE_PASSWD;
+    else if(!dap_strcmp(a_mode, "both"))
+        s_mode_auth = MODE_BOTH;
+    else{
+        log_it( L_ERROR, "Unknown cdb mode=%s", a_mode);
+        return -1;
+    }
+
+    // Login -> Password, First Name, Last Name, Email, Cookie,Timestamp Last Update, Timestamp Last Login
+    s_group_password = dap_strdup_printf("%s.password",s_group_users);
+    s_group_first_name = dap_strdup_printf("%s.first_name",s_group_users);
+    s_group_last_name = dap_strdup_printf("%s.last_name",s_group_users);
+    s_group_email = dap_strdup_printf("%s.email",s_group_users);
+    s_group_cookie  = dap_strdup_printf("%s.cookie",s_group_users);
+    s_group_ts_updated = dap_strdup_printf("%s.ts_updated",s_group_users);
+    s_group_ts_last_login = dap_strdup_printf("%s.ts_last_login",s_group_users);
+    s_group_ts_active_till = dap_strdup_printf("%s.ts_active_till",s_group_users);
+
+    // load hook paths
+    s_hook_user_create = register_hook("hook_user_create");
+    s_hook_user_login = register_hook("hook_user_login");
+    s_hook_user_update = register_hook("hook_user_update");
+    s_hook_user_delete = register_hook("hook_user_delete");
+
+    s_hook_serial_generate = register_hook("hook_serial_generate");
+    s_hook_serial_login = register_hook("hook_serial_login");
+    s_hook_serial_activate = register_hook("hook_serial_activate");
+    s_hook_serial_update = register_hook("hook_serial_update");
+    s_hook_serial_delete = register_hook("hook_serial_delete");
+    s_hook_serial_deactivate = register_hook("hook_serial_deactivate");
+    return 0;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_deinit
+ */
+void dap_chain_net_srv_vpn_cdb_auth_deinit()
+{
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_set_callback
+ * @param a_callback_success
+ */
+void dap_chain_net_srv_vpn_cdb_auth_set_callback(dap_enc_http_callback_t a_callback_success)
+{
+    s_callback_success = a_callback_success;
+}
+
+/*
+ * Convert XXXXXXXXXXXXXXXX -> XXXX-XXXX-XXXX-XXXX
+ */
+static char* make_fullserial(const char * a_serial)
+{
+    if(dap_strlen(a_serial)!=16)
+        return dap_strdup(a_serial);
+    return dap_strdup_printf("%c%c%c%c-%c%c%c%c-%c%c%c%c-%c%c%c%c",
+            a_serial[0], a_serial[1], a_serial[2], a_serial[3],
+            a_serial[4], a_serial[5], a_serial[6], a_serial[7],
+            a_serial[8], a_serial[9], a_serial[10], a_serial[11],
+            a_serial[12], a_serial[13], a_serial[14], a_serial[15]
+            );
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_check_password
+ * @param a_login
+ * @param a_password
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_check_login(const char * a_login, const char * a_password)
+{
+    int l_ret;
+
+    size_t l_tmp_size=0;
+    dap_chain_hash_fast_t *l_gdb_password_hash;
+    if ( (l_gdb_password_hash = (dap_chain_hash_fast_t*) dap_chain_global_db_gr_get (
+             a_login,&l_tmp_size  ,s_group_password ) ) ==NULL ){
+        // No user in database
+        return -1;
+    }
+
+    char * l_hash_str = dap_strdup_printf("%s%s",a_password, s_salt_str );
+    dap_chain_hash_fast_t l_password_hash = {0};
+    dap_hash_fast(l_hash_str,dap_strlen(l_hash_str), &l_password_hash );
+    DAP_DELETE(l_hash_str);
+
+    l_ret = (memcmp(&l_password_hash, l_gdb_password_hash,sizeof (l_password_hash) ) == 0)? 0: -2;
+
+#ifdef DAP_CHAIN_NET_SRV_VPN_CDB_MAY_CONTAIN_UNENCODED_PASSWORDS
+    //case: old user have his password stored unencoded in CDB, while client sends it in base64
+    if(l_ret != 0){
+        char * l_password_raw = dap_enc_strdup_from_base64(a_password);
+        if(l_password_raw){
+            //we can never know for sure if it was really a base64 password, but it won't hurt trying
+            l_hash_str = dap_strdup_printf("%s%s",l_password_raw, s_salt_str );
+            dap_chain_hash_fast_t l_password_hash_from_raw = {0};
+            dap_hash_fast(l_hash_str,dap_strlen(l_hash_str), &l_password_hash_from_raw );
+            DAP_DELETE(l_hash_str);
+            DAP_DELETE(l_password_raw);
+            l_ret = (memcmp(&l_password_hash_from_raw, l_gdb_password_hash,sizeof (l_password_hash_from_raw) ) == 0)? 0: -2;
+            if(l_ret == 0){
+                log_it(L_WARNING, "password check for '%s' (backward compatibility): success, but password in CDB is not in base64", a_login);
+                dap_chain_global_db_gr_set(dap_strdup(a_login), &l_password_hash, sizeof(l_password_hash), s_group_password);
+                log_it(L_INFO, "password for user '%s' was updated to base64\n", a_login);
+            }
+        }
+    }
+#endif
+
+#ifdef DAP_CHAIN_NET_SRV_VPN_CLIENT_MAY_SEND_UNENCODED_PASSWORD
+    //case: old version of client sends unencoded password, while new version of cdb has it in base64
+    if(l_ret != 0){
+        char * l_password_base64 = dap_enc_strdup_to_base64(a_password);
+        l_hash_str = dap_strdup_printf("%s%s",l_password_base64, s_salt_str );
+        dap_hash_fast(l_hash_str,dap_strlen(l_hash_str), &l_password_hash );
+        DAP_DELETE(l_hash_str);
+        DAP_DELETE(l_password_base64);
+        l_ret = (memcmp(&l_password_hash, l_gdb_password_hash,sizeof (l_password_hash) ) == 0)? 0: -2;
+        if(l_ret == 0)
+            log_it(L_WARNING, "password check for %s (backward compatibility): success, but password was passed not in base64", a_login);
+    }
+#endif
+    DAP_DELETE(l_gdb_password_hash);
+
+    // if password check passed lets see is it active or not
+    if ( l_ret == 0){
+        time_t *l_ts_active_till= (time_t*) dap_chain_global_db_gr_get( a_login, &l_tmp_size, s_group_ts_active_till );
+        if ( l_ts_active_till ){
+            if ( *l_ts_active_till < time(NULL) )
+                l_ret = -4;
+        }else
+            l_ret = -3;
+    }
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_activate_serial
+ * @param a_login
+ * @param a_password
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_activate_serial(const char * a_serial_raw, const char * a_serial, const char * a_sign, const char * a_pkey)
+{
+    int l_ret = -1;
+    if(!a_sign || !a_pkey)
+        return -2;//OP_CODE_LOGIN_INCORRECT_SIGN
+    dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(a_serial, NULL);
+    // not found
+    if(!l_serial_key)
+        return -1;//OP_CODE_NOT_FOUND_LOGIN_IN_DB
+    // already activated
+    if(l_serial_key->header.activated) {
+        l_ret = 0;// OK
+    }
+    else {
+        // check sign
+        int l_res = 0;
+        byte_t *l_pkey_raw = NULL;
+        size_t l_pkey_raw_size = 0;
+        dap_enc_key_type_t l_key_type;
+        {
+            // verify sign
+            byte_t *l_sign_raw = NULL;
+            size_t l_sign_length = dap_strlen(a_sign);
+            l_sign_raw = DAP_NEW_Z_SIZE(byte_t, l_sign_length * 2);
+            size_t l_sign_raw_size = dap_enc_base64_decode(a_sign, l_sign_length, l_sign_raw, DAP_ENC_DATA_TYPE_B64_URLSAFE);
+            dap_sign_t *l_sign = (dap_sign_t*) l_sign_raw; //dap_sign_pack(l_client_key, l_sign_raw, l_sign_raw_size, l_pkey_raw, l_pkey_length);
+            //get key type for pkey
+            dap_sign_type_t l_chain_sign_type;
+            l_chain_sign_type.raw = l_sign_raw_size > 0 ? l_sign->header.type.raw : SIG_TYPE_NULL;
+            l_key_type =  dap_sign_type_to_key_type(l_chain_sign_type);
+            size_t l_serial_len = dap_strlen(a_serial_raw);
+            if((l_sign->header.sign_size+l_sign->header.sign_pkey_size + sizeof(l_sign->header))>l_sign_length){
+                log_it(L_ERROR,"sign type=%d have inside incorrect size %u > size of sign %u", l_sign->header.type, l_sign->header.sign_size+l_sign->header.sign_pkey_size + sizeof(l_sign->header), l_sign_length);
+                l_res = 0;
+            }
+            else
+                l_res = dap_sign_verify(l_sign, a_serial_raw, l_serial_len);
+            if(!l_res){
+                DAP_DELETE(l_sign_raw);
+                return -2;//OP_CODE_LOGIN_INCORRECT_SIGN
+            }
+
+            // deserialize pkey
+            dap_enc_key_t *l_client_key = NULL;
+            size_t l_pkey_length = dap_strlen(a_pkey);
+            l_pkey_raw = DAP_NEW_Z_SIZE(byte_t, l_pkey_length);
+            memset(l_pkey_raw, 0, l_pkey_length);
+            l_pkey_raw_size = dap_enc_base64_decode(a_pkey, l_pkey_length, l_pkey_raw, DAP_ENC_DATA_TYPE_B64_URLSAFE);
+            l_client_key = dap_enc_key_new(l_key_type); //DAP_ENC_KEY_TYPE_SIG_TESLA
+            l_res = dap_enc_key_deserealize_pub_key(l_client_key, l_pkey_raw, l_pkey_raw_size);
+            // pkey from sign
+            size_t l_pkey_sign_size = 0;
+            uint8_t *l_pkey_sign = dap_sign_get_pkey(l_sign, &l_pkey_sign_size);
+            // activate serial key
+            if(l_pkey_sign_size == l_pkey_raw_size && !memcmp(l_pkey_sign, l_pkey_raw, l_pkey_sign_size)) {
+                // added pkey to serial
+                l_serial_key->header.ext_size = l_pkey_raw_size;
+                l_serial_key = DAP_REALLOC(l_serial_key, dap_serial_key_len(l_serial_key));
+                l_serial_key->header.activated = time(NULL);
+                if(l_serial_key->header.license_length)
+                    l_serial_key->header.expired = l_serial_key->header.activated + l_serial_key->header.license_length;
+                l_serial_key->header.pkey_type = l_key_type;
+                memcpy(l_serial_key->ext, l_pkey_raw, l_pkey_raw_size);
+                // save updated serial
+                if(dap_chain_global_db_gr_set(l_serial_key->header.serial, l_serial_key,
+                        dap_serial_key_len(l_serial_key),
+                        s_group_serials_activated)) {
+                    dap_chain_global_db_gr_del(l_serial_key->header.serial, s_group_serials);
+                    // save gdb
+                    dap_chain_global_db_flush();
+                    l_ret = 0; // OK
+                }
+            }
+            // bad pkey
+            else
+                l_ret = -2;//OP_CODE_LOGIN_INCORRECT_SIGN
+            DAP_DELETE(l_sign_raw);
+        }
+        DAP_DELETE(l_pkey_raw);
+    }
+    DAP_DELETE(l_serial_key);
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_activate_serial
+ * @param a_login
+ * @param a_password
+ * @return
+ */
+static int dap_chain_net_srv_vpn_cdb_auth_deactivate_serial(const char * a_serial, const char * a_pkey)
+{
+    int l_ret = -1;
+    if(!a_pkey)
+        return -2; //OP_CODE_LOGIN_INCORRECT_SIGN
+    dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(a_serial, NULL);
+    // not found
+    if(!l_serial_key)
+        return -1; //OP_CODE_NOT_FOUND_LOGIN_IN_DB
+    // already deactivated
+    if(!l_serial_key->header.activated) {
+        l_ret = 0; // OK
+    }
+    else {
+        // check pkey
+        dap_enc_key_t *l_client_key = NULL;
+        size_t l_pkey_length = dap_strlen(a_pkey);
+        byte_t *l_pkey_raw = DAP_NEW_Z_SIZE(byte_t, l_pkey_length);
+        memset(l_pkey_raw, 0, l_pkey_length);
+        size_t l_pkey_raw_size = dap_enc_base64_decode(a_pkey, l_pkey_length, l_pkey_raw,
+                DAP_ENC_DATA_TYPE_B64_URLSAFE);
+        // pkey from sign
+        size_t l_pkey_sign_size = l_serial_key->header.ext_size;
+        uint8_t *l_pkey_sign = l_serial_key->ext;
+        // compare pkeys
+        if(l_pkey_sign_size != l_pkey_raw_size || memcmp(l_pkey_sign, l_pkey_raw, l_pkey_sign_size)) {
+            return -2;//OP_CODE_LOGIN_INCORRECT_SIGN
+        }
+        DAP_DELETE(l_pkey_raw);
+
+        // modify serial
+        if(l_serial_key->header.expired) {
+            time_t l_cur_time = time(NULL);
+            if(l_serial_key->header.expired > 0) {
+                // if time already expired
+                if(l_cur_time > l_serial_key->header.expired)
+                    l_serial_key->header.expired = 1; // set to 1 sec, because 0 means no time expire never
+                else
+                    l_serial_key->header.expired = l_serial_key->header.expired - l_cur_time; //l_serial_key->header.activated;
+            }
+        }
+        l_serial_key->header.activated = 0;
+
+        // save updated serial
+        if(dap_chain_global_db_gr_set(l_serial_key->header.serial, l_serial_key,
+                dap_serial_key_len(l_serial_key), s_group_serials)) {
+            dap_chain_global_db_gr_del(l_serial_key->header.serial, s_group_serials_activated);
+            run_hook(s_hook_serial_deactivate, "serial=%s", l_serial_key->header.serial);
+            l_ret = 0; // OK
+            // save gdb
+            dap_chain_global_db_flush();
+        }
+        else {
+            log_it(L_ERROR, "Can't save serial '%s' to cdb", l_serial_key->header.serial);
+            l_ret = -3;
+        }
+    }
+    DAP_DELETE(l_serial_key);
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_check_password
+ * @param a_login
+ * @param a_password
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_check_serial(const char * a_serial, const char * a_pkey_b64)
+{
+    int l_ret = 0;
+    dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(a_serial, NULL);
+    // not found
+    if(!l_serial_key)
+        return -1;
+    // inactive serial key
+    if(!l_serial_key->header.activated) {
+        l_ret = -3;
+    }
+    // check time expired, if expired=0, then the time never expires
+    else if(l_serial_key->header.expired) {
+        if((l_serial_key->header.expired) < time(NULL))
+            l_ret = -4;
+    }
+    if(!l_ret) {
+        // check pkey
+        dap_enc_key_t *l_client_key = NULL;
+        size_t l_pkey_length = dap_strlen(a_pkey_b64);
+        byte_t *l_pkey_raw = DAP_NEW_Z_SIZE(byte_t, l_pkey_length);
+        memset(l_pkey_raw, 0, l_pkey_length);
+        size_t l_pkey_raw_size = dap_enc_base64_decode(a_pkey_b64, l_pkey_length, l_pkey_raw,
+                DAP_ENC_DATA_TYPE_B64_URLSAFE);
+        // pkey from sign
+        size_t l_pkey_sign_size = l_serial_key->header.ext_size;
+        uint8_t *l_pkey_sign = l_serial_key->ext;
+        // compare pkeys
+        if(l_pkey_sign_size != l_pkey_raw_size){
+            log_it(L_ERROR,"Different pkey sizes: expected %zd but got %zd",l_pkey_sign_size,l_pkey_raw_size);
+            l_ret = -2;
+        }
+        else if(memcmp(l_pkey_sign, l_pkey_raw, l_pkey_sign_size)){
+            l_ret = -5;
+        }
+        DAP_DELETE(l_pkey_raw);
+    }
+    DAP_DELETE(l_serial_key);
+    return l_ret;
+}
+
+/**
+ * @brief s_input_validation
+ * @param str
+ * @return
+ */
+static int s_input_validation(const char * str)
+{
+        // The compiler will stack "multiple" "strings" "end" "to" "end"
+        // into "multiplestringsendtoend", so we don't need one giant line.
+        static const char *nospecial="0123456789"
+                "abcdefghijklmnopqrstuvwxyz"
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                ".=@?_!#$%-";// /+
+        while(*str) // Loop until (*url) == 0.  (*url) is about equivalent to url[0].
+        {
+                // Can we find the character at *url in the string 'nospecial'?
+                // If not, it's a special character and we should return 0.
+                if(strchr(nospecial, *str) == NULL){
+                    return(0);
+                }
+                str++; // Jump to the next character.  Adding one to a pointer moves it ahead one element.
+        }
+
+        return(1); // Return 1 for success.
+}
+
+/**
+ * Generate serial number like xxx-xxx-xxx
+ * without symbols 0,1,L,I,O
+ * a_group_sepa may be NULL
+ */
+static char* generate_serial(int a_group_count, int a_group_len, const char *a_group_sepa)
+{
+    size_t l_group_sepa_len = a_group_sepa ? strlen(a_group_sepa) : 0;
+    char *l_serial = DAP_NEW_Z_SIZE(char, a_group_count * (a_group_len + l_group_sepa_len));
+    int l_serial_pos = 0;
+    for(int l_group_count = 0; l_group_count < a_group_count; l_group_count++) {
+        for(int l_group_len = 0; l_group_len < a_group_len; l_group_len++) {
+            uint32_t l_max_len = 'Z' - 'A' + 5; //['Z' - 'A' - 3]alpha + [10 - 2]digit
+            uint32_t l_value = random_uint32_t(l_max_len);
+            char l_sym;
+            if(l_value < 8)
+                l_sym = '2' + l_value;
+            // replace unused characters I,O,L
+            else if(l_value == 'I' - 'A' + 8)
+                l_sym = 'X';
+            else if(l_value == 'L' - 'A' + 8)
+                l_sym = 'Y';
+            else if(l_value == 'O' - 'A' + 8)
+                l_sym = 'Z';
+            else
+                l_sym = 'A' + l_value - 8;
+            l_serial[l_serial_pos] = l_sym;
+            l_serial_pos++;
+        }
+        // copy separator to serial
+        if(l_group_sepa_len && l_group_count < a_group_count - 1) {
+            dap_stpcpy(l_serial + l_serial_pos, a_group_sepa);
+            l_serial_pos += l_group_sepa_len;
+        }
+    }
+    return l_serial;
+}
+
+
+size_t dap_serial_key_len(dap_serial_key_t *a_serial_key)
+{
+    if(!a_serial_key)
+        return 0;
+    return sizeof(dap_serial_key_t) + a_serial_key->header.ext_size;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_get_serial_param
+ * @param a_serial_str
+ * @param a_group_out
+ * @return
+ */
+dap_serial_key_t* dap_chain_net_srv_vpn_cdb_auth_get_serial_param(const char *a_serial_str, const char **a_group_out)
+{
+    const char *l_group_out = s_group_serials_activated;
+    if(!a_serial_str)
+        return NULL;
+    size_t l_serial_data_len = 0;
+    dap_serial_key_t *l_serial_key = (dap_serial_key_t*)dap_chain_global_db_gr_get(a_serial_str, &l_serial_data_len, s_group_serials_activated);
+    if(!l_serial_key){
+        l_serial_key = (dap_serial_key_t*)dap_chain_global_db_gr_get(a_serial_str, &l_serial_data_len, s_group_serials);
+        l_group_out = s_group_serials;
+    }
+    if(l_serial_data_len>=sizeof(dap_serial_key_t)){
+        if(a_group_out)
+            *a_group_out = l_group_out;
+        return l_serial_key;
+    }
+    if (l_serial_key)
+	DAP_DELETE(l_serial_key);
+    return NULL;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_cli_cmd_serial
+ * @param a_serial_str
+ * @param a_arg_index
+ * @param a_argc
+ * @param a_argv
+ * @param a_str_reply
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_cli_cmd_serial(const char *a_serial_str, int a_arg_index, int a_argc, char ** a_argv, char **a_str_reply)
+{
+    int l_ret = 0;
+    // Command 'serial list'
+    if(!dap_strcmp(a_serial_str, "list")) {
+        const char * l_serial_count_str = NULL;
+        const char * l_serial_shift_str = NULL;
+        int l_serial_nototal = dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-nototal", NULL);
+        int l_serial_total_only = dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-total_only", NULL);
+        int l_serial_show_activated_only = dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-activated_only", NULL);
+        int l_serial_show_inactive_only = dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-inactive_only", NULL);
+        bool l_serial_show_all =  !l_serial_show_activated_only && !l_serial_show_inactive_only ? true : false;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-n", &l_serial_count_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-shift", &l_serial_shift_str);
+
+        if(l_serial_nototal && l_serial_total_only){
+            dap_chain_node_cli_set_reply_text(a_str_reply, "use only one option '-nototal' or '-total_only'");
+            return -1;
+        }
+        if(l_serial_show_activated_only && l_serial_show_inactive_only){
+            dap_chain_node_cli_set_reply_text(a_str_reply, "use only one option '-activated_only' or '-inactive_only'");
+            return -1;
+        }
+        long long l_serial_count_tmp = l_serial_count_str ? strtoll(l_serial_count_str, NULL, 10) : 0;
+        long long l_serial_shift_tmp = l_serial_shift_str ? strtoll(l_serial_shift_str, NULL, 10) : 0;
+        //size_t l_serial_shift = l_serial_shift_str ? strtoll(l_serial_shift_str, NULL, 10)+1 : 1;
+        //size_t l_total = dap_chain_global_db_driver_count(s_group_serials, l_serial_shift);
+        //l_serial_count = l_serial_count ? min(l_serial_count, l_total - l_serial_shift) : l_total;
+        size_t l_serial_count_noactivated = 0;
+        size_t l_serial_count_activated = 0;
+        // read inactive serials
+        dap_store_obj_t *l_obj = l_serial_show_inactive_only || l_serial_show_all ? dap_chain_global_db_driver_cond_read(s_group_serials, 0, &l_serial_count_noactivated) : NULL;
+        // read activated serials
+        dap_store_obj_t *l_obj_activated = l_serial_show_activated_only || l_serial_show_all ? dap_chain_global_db_driver_cond_read(s_group_serials_activated, 0, &l_serial_count_activated) : NULL;
+        size_t l_total = l_serial_count_noactivated + l_serial_count_activated;
+        size_t l_serial_count = l_serial_count_tmp > 0 ? l_serial_count_tmp : (!l_serial_count_tmp ? l_total : 0);
+        size_t l_serial_shift = l_serial_shift_tmp > 0 ? l_serial_shift_tmp : 0;
+        if(l_serial_count > 0) {
+            dap_string_t *l_keys = dap_string_new("");
+            if(!l_serial_total_only) {
+                l_keys =  l_serial_count > 1 ? dap_string_append(l_keys, "serial keys:\n") : dap_string_append(l_keys, "serial key: ");
+            }
+            size_t l_total_actual = 0;
+            for(size_t i = 0; i < l_serial_count_noactivated; i++) {
+                if((l_obj + i)->value_len < sizeof(dap_serial_key_t))
+                    continue;
+                if(l_serial_count > 0 && l_total_actual >= l_serial_count)
+                    break;
+                dap_serial_key_t *l_serial = (dap_serial_key_t*) (l_obj + i)->value;
+                if(l_serial_shift > 0)
+                    l_serial_shift--;
+                else {
+                    if(!l_serial_total_only) {
+                        dap_string_append(l_keys, l_serial->header.serial);
+                        dap_string_append(l_keys, " inactive");
+                        //if(i < l_serial_count - 1)
+                        dap_string_append(l_keys, "\n");
+                    }
+                    l_total_actual++;
+                }
+            }
+            for(size_t i = 0; i < l_serial_count_activated; i++) {
+                if((l_obj_activated + i)->value_len < sizeof(dap_serial_key_t))
+                    continue;
+                dap_serial_key_t *l_serial = (dap_serial_key_t*) (l_obj_activated + i)->value;
+                if(l_serial_count > 0 && l_total_actual >= l_serial_count)
+                    break;
+                if(l_serial_shift > 0)
+                    l_serial_shift--;
+                else {
+                    if(!l_serial_total_only) {
+                        dap_string_append(l_keys, l_serial->header.serial);
+                        dap_string_append(l_keys, " activated");
+                        //if(i < l_serial_count - 1)
+                        dap_string_append(l_keys, "\n");
+                    }
+                    l_total_actual++;
+                }
+            }
+            if(!l_serial_nototal){
+                char *l_total_str = l_total_actual == 1 ? dap_strdup_printf("total 1 key") : dap_strdup_printf("total %u keys", l_total_actual);
+                dap_string_append(l_keys, l_total_str);
+                DAP_DELETE(l_total_str);
+            }
+            dap_chain_node_cli_set_reply_text(a_str_reply, "%s", l_keys->str);
+            dap_string_free(l_keys, true);
+            dap_store_obj_free(l_obj, l_serial_count_noactivated);
+            dap_store_obj_free(l_obj_activated, l_serial_count_activated);
+        }
+        else
+            dap_chain_node_cli_set_reply_text(a_str_reply, "keys not found");
+        return 0;
+    }
+    else
+    // Command 'serial generate'
+    if(!dap_strcmp(a_serial_str, "generate")) {
+        const char * l_serial_count_str = NULL;
+        const char * l_active_days_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-n", &l_serial_count_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-active_days", &l_active_days_str);
+        uint32_t l_serial_count = l_serial_count_str ? strtoll(l_serial_count_str, NULL, 10) : 1;
+        size_t l_active_days = l_active_days_str ? strtoll(l_active_days_str, NULL, 10) : 0;
+        if(l_serial_count < 1)
+            l_serial_count = 1;
+        dap_string_t *l_keys = l_serial_count > 1 ? dap_string_new("serial keys:\n") : dap_string_new("serial key: ");
+        for(uint32_t i = 0; i < l_serial_count; i++) {
+            dap_serial_key_t l_serial;
+            memset(&l_serial, 0, sizeof(dap_serial_key_t));
+            while(1) {
+                char *l_serial_str = generate_serial(4, 4, "-");
+                uint8_t *l_serial_str_prev = dap_chain_global_db_gr_get(l_serial_str, NULL, s_group_serials);
+                if(l_serial_str_prev)
+                    DAP_DELETE(l_serial_str_prev);
+                else{
+                    strncpy(l_serial.header.serial, l_serial_str, sizeof(l_serial.header.serial));
+                    if(l_active_days)
+                        l_serial.header.license_length = l_active_days * 86400;// days to sec
+                    break;
+                }
+            };
+            l_serial.header.ext_size = 0;
+
+            if(dap_chain_global_db_gr_set(l_serial.header.serial, &l_serial, sizeof(l_serial), s_group_serials)) {
+                dap_string_append(l_keys, l_serial.header.serial);
+                if(i < l_serial_count - 1)
+                    dap_string_append(l_keys, "\n");
+            }
+            run_hook(s_hook_serial_generate, "serial=%s active_days=%lld", l_serial.header.serial, l_active_days);
+        }
+        dap_chain_node_cli_set_reply_text(a_str_reply, "generated new %s", l_keys->str);
+        dap_string_free(l_keys, true);
+        // save gdb
+        dap_chain_global_db_flush();
+        return 0;
+    }
+    else
+    // Command 'serial add' to add serials from file
+    if(!dap_strcmp(a_serial_str, "add")) {
+        const char * l_file_name_str = NULL;
+        const char * l_active_days_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-active_days", &l_active_days_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-file", &l_file_name_str);
+        if(!l_file_name_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-file <file_name>' is not defined");
+            return -1;
+        }
+        size_t l_active_days = l_active_days_str ? strtoll(l_active_days_str, NULL, 10) : 0;
+        char *l_buffer;
+        size_t l_buffer_size = 0;
+        bool r_res = dap_file_get_contents(l_file_name_str, &l_buffer, &l_buffer_size);
+        if(r_res) {
+            if(!l_buffer_size) {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "file <%s> empty", l_file_name_str);
+                l_ret = -2;
+            }
+            // read buffer from file
+            else {
+                dap_string_t *l_ret_str = dap_string_new("serial keys:\n");
+                int l_keys_num = 0;
+                char *l_string_begin = l_buffer;
+                while(l_string_begin) {
+                    // read one string
+                    char *l_string_end = dap_strstr_len(l_string_begin, l_buffer_size, "\n");
+                    if(l_string_end){
+                        l_string_end[0] = '\0';
+                    }
+                    // removes leading and trailing spaces
+                    char *l_serial_str = dap_strstrip(l_string_begin);
+                    //add serial
+                    if(dap_strlen(l_string_begin) == 19 && l_string_begin[4] == '-' && l_string_begin[9] == '-'
+                            && l_string_begin[14] == '-') {
+                        dap_serial_key_t l_serial;
+                        memset(&l_serial, 0, sizeof(dap_serial_key_t));
+                        uint8_t *l_serial_str_prev1 = dap_chain_global_db_gr_get(l_serial_str, NULL, s_group_serials);
+                        uint8_t *l_serial_str_prev2 = dap_chain_global_db_gr_get(l_serial_str, NULL, s_group_serials_activated);
+                        // if serial already present
+                        if(l_serial_str_prev1 || l_serial_str_prev2) {
+                            DAP_DELETE(l_serial_str_prev1);
+                            DAP_DELETE(l_serial_str_prev2);
+                            // serial already present
+                            dap_string_append_printf(l_ret_str, "'%s' already present\n", l_serial_str);
+                        }
+                        else {
+                            strncpy(l_serial.header.serial, l_serial_str, sizeof(l_serial.header.serial));
+                            if(l_active_days)
+                                l_serial.header.license_length = l_active_days * 86400; // days to sec
+                            l_serial.header.ext_size = 0;
+                            //save serial
+                            if(dap_chain_global_db_gr_set(l_serial.header.serial, &l_serial,
+                                    sizeof(l_serial), s_group_serials)) {
+                                dap_string_append_printf(l_ret_str, "'%s' added, days %u\n", l_serial.header.serial,l_active_days);
+                                run_hook(s_hook_serial_generate, "serial=%s active_days=%lld", l_serial_str,l_active_days);
+                                l_keys_num++;
+                            }
+                        }
+                    }
+                    else {
+                        if(dap_strlen(l_string_begin)>0)
+                            dap_string_append_printf(l_ret_str, "'%s' invalid, skipped\n", l_string_begin);
+                    }
+                    // go to next string
+                    l_string_begin = l_string_end ? l_string_end + 1 : NULL;
+                };
+                dap_string_append_printf(l_ret_str, "%d keys added", l_keys_num);
+                dap_chain_node_cli_set_reply_text(a_str_reply, "added %s", l_ret_str->str);
+                dap_string_free(l_ret_str, true);
+            }
+        }
+        else {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "can't open file <%s>", l_file_name_str);
+            l_ret = -3;
+        }
+        DAP_DELETE(l_buffer);
+        return l_ret;
+    }
+    else
+    // Command 'serial update'
+    if(!dap_strcmp(a_serial_str, "update")) {
+        const char * l_serial_number_str = NULL;
+        const char * l_active_days_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-serial", &l_serial_number_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-active_days", &l_active_days_str);
+        size_t l_active_days = l_active_days_str ? strtoll(l_active_days_str, NULL, 10) : 0;
+        if(!l_serial_number_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-serial XXXX-XXXX-XXXX-XXXX' is not defined");
+        }
+        else if(!l_active_days_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-active_days <active days that left for serial after activation>' is not defined");
+        }
+        else {
+            const char *l_group;
+            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial_number_str, &l_group);
+            if(l_serial_key){
+                // if serial inactive, then header.activated=0
+                if(l_serial_key->header.activated){
+                    if(l_active_days > 0)
+                        l_serial_key->header.expired = l_serial_key->header.activated + l_active_days * 86400; // 24*3600 = days to sec;
+                }
+                else
+                    l_serial_key->header.license_length = l_active_days * 86400; // 24*3600 = days to sec;
+                // save updated serial
+                if(dap_chain_global_db_gr_set(l_serial_key->header.serial, l_serial_key, dap_serial_key_len(l_serial_key), l_group)) {
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' successfully updated", l_serial_key->header.serial);
+                    // save gdb
+                    dap_chain_global_db_flush();
+                    run_hook(s_hook_serial_update, "serial=%s status=%s active_days=%lld", l_serial_key->header.serial, l_serial_key->header.activated ? "activated" : "inactive", l_active_days);
+                    DAP_DELETE(l_serial_key);
+                    return 0;
+                }
+                else{
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' can't updated", l_serial_key->header.serial);
+                }
+                DAP_DELETE(l_serial_key);
+            }
+            else{
+                dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not found", l_serial_number_str);
+            }
+            return 0;
+        }
+    }
+    else
+    // Command 'serial info'
+    if(!dap_strcmp(a_serial_str, "info")) {
+        int l_ret = 0;
+        const char * l_serial_number_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-serial", &l_serial_number_str);
+        if(!l_serial_number_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-serial XXXX-XXXX-XXXX-XXXX' is not defined");
+            l_ret = -1;
+        }
+        else {
+            const char *l_group;
+            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial_number_str, &l_group);
+            if(l_serial_key) {
+                char l_out_str[121];
+                char *l_str_message;
+                // form full string with serial info
+                if(l_serial_key->header.activated) {
+                    if(dap_time_to_str_rfc822(l_out_str, 120, l_serial_key->header.activated) > 0) {// instead of strftime
+                        // form expired time string
+                        char *l_expired_txt = NULL;
+                        if(l_serial_key->header.expired) {
+                            time_t l_expired_sec = l_serial_key->header.expired - time(NULL);
+                            if(l_expired_sec <= 0)
+                                l_expired_txt = dap_strdup("0 days");
+                            else
+                                l_expired_txt = dap_strdup_printf("%lld days", l_expired_sec/(24*3600));
+                        }
+                        else
+                            l_expired_txt = dap_strdup("no time limit");
+                        l_str_message = dap_strdup_printf("serial %s activated %s\nexpired: %s", l_serial_key->header.serial, l_out_str, l_expired_txt);
+                        DAP_DELETE(l_expired_txt);
+                    }
+                    else {
+                        l_str_message = dap_strdup_printf("serial %s activated ???", l_serial_key->header.serial);
+                        l_ret = -3;
+                    }
+                }
+                // not activated serial
+                else {
+                    // form expired time string
+                    char *l_expired_txt = NULL;
+                    if(l_serial_key->header.license_length) {
+                        l_expired_txt = dap_strdup_printf("%lld days", l_serial_key->header.license_length/(24*3600));
+                    }
+                    else
+                        l_expired_txt = dap_strdup("no time limit");
+                    l_str_message = dap_strdup_printf("serial %s not activated\nlicense length: %s", l_serial_key->header.serial, l_expired_txt);
+                    DAP_DELETE(l_expired_txt);
+                }
+                dap_chain_node_cli_set_reply_text(a_str_reply, l_str_message);
+
+                DAP_DELETE(l_str_message);
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not found", l_serial_number_str);
+                l_ret = -2;
+            }
+
+            DAP_DELETE(l_serial_key);
+        }
+        return l_ret;
+
+    }
+    else
+    // Command 'serial delete'
+    if(!dap_strcmp(a_serial_str, "delete")) {
+        int l_ret = 0;
+        const char * l_serial_number_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-serial", &l_serial_number_str);
+        if(!l_serial_number_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-serial XXXX-XXXX-XXXX-XXXX' is not defined");
+            l_ret = -1;
+        }
+        else {
+            const char *l_group;
+            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial_number_str, &l_group);
+            if(l_serial_key) {
+                if(dap_chain_global_db_gr_del(l_serial_key->header.serial, l_group)){
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' deleted", l_serial_key->header.serial);
+                    run_hook(s_hook_serial_delete, "serial=%s", l_serial_key->header.serial);
+                    // save gdb
+                    dap_chain_global_db_flush();
+                }
+                else {
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not deleted", l_serial_key->header.serial);
+                    l_ret = -4;
+                }
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not found", l_serial_number_str);
+                l_ret = -2;
+            }
+
+            DAP_DELETE(l_serial_key);
+        }
+        return l_ret;
+    }
+    else
+    // Command 'serial deactivate'
+    if(!dap_strcmp(a_serial_str, "deactivate")) {
+        int l_ret = 0;
+        const char * l_serial_number_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-serial", &l_serial_number_str);
+        if(!l_serial_number_str) {
+            dap_chain_node_cli_set_reply_text(a_str_reply, "option '-serial XXXX-XXXX-XXXX-XXXX' is not defined");
+            l_ret = -1;
+        }
+        else {
+            const char *l_group;
+            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial_number_str, &l_group);
+            if(l_serial_key) {
+                if(!l_serial_key->header.activated){
+                    dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' already deactivated", l_serial_number_str);
+                }
+                else{
+                    if(l_serial_key->header.expired)
+                        l_serial_key->header.expired = l_serial_key->header.expired - l_serial_key->header.activated;
+                    l_serial_key->header.activated = 0;
+
+                    // pkey in l_serial_key->ext remains
+                    // save updated serial
+                    if(dap_chain_global_db_gr_set(l_serial_key->header.serial, l_serial_key, dap_serial_key_len(l_serial_key), s_group_serials)) {
+                        dap_chain_global_db_gr_del(l_serial_key->header.serial, s_group_serials_activated);
+                        dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' deactivated successfully", l_serial_number_str);
+                        run_hook(s_hook_serial_deactivate, "serial=%s", l_serial_key->header.serial);
+                        l_ret = 0; // OK
+                        // save gdb
+                        dap_chain_global_db_flush();
+                    }
+                    else{
+                        l_ret = -5;
+                        dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not deactivated", l_serial_number_str);
+                    }
+                }
+            }
+            else {
+                dap_chain_node_cli_set_reply_text(a_str_reply, "serial '%s' not found", l_serial_number_str);
+                l_ret = -2;
+            }
+
+            DAP_DELETE(l_serial_key);
+        }
+        return l_ret;
+    }
+    else {
+        dap_chain_node_cli_set_reply_text(a_str_reply, "unknown subcommand %s, use 'generate', 'list', 'update', 'info', 'delete' or 'deactivate'", a_serial_str);
+    }
+    return -1;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_cli_cmd_user
+ * @param a_user_str
+ * @param a_arg_index
+ * @param a_argc
+ * @param a_argv
+ * @param a_str_reply
+ * @return
+ */
+int dap_chain_net_srv_vpn_cdb_auth_cli_cmd_user(const char *a_user_str, int a_arg_index, int a_argc, char ** a_argv, char **a_str_reply)
+{
+    int l_ret = 0;
+    dap_string_t * l_ret_str = dap_string_new("");
+    // Command 'user create'
+    bool l_is_user_create = (dap_strcmp(a_user_str, "create") == 0 );
+    bool l_is_user_update = (dap_strcmp(a_user_str, "update") == 0 );
+    if ( l_is_user_create  || l_is_user_update ){
+        const char * l_login_str = NULL;
+        const char * l_password_str = NULL;
+        const char * l_first_name_str = NULL;
+        const char * l_last_name_str = NULL;
+        const char * l_email_str = NULL;
+        const char * l_active_days_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--login", &l_login_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--password", &l_password_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--first_name", &l_first_name_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--last_name", &l_last_name_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--email", &l_email_str);
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--active_days", &l_active_days_str);
+        int l_password_base64 = dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "-password_base64", NULL);
+
+        if ( ( l_is_user_create && l_login_str && l_password_str ) ||
+             ( l_is_user_update && l_login_str && ( l_password_str || l_first_name_str || l_last_name_str || l_email_str || l_active_days_str) ) ){
+
+            if (l_password_str){
+                char * l_hash_str = NULL;
+
+                /* TODO: besides explicitly specifying base64 password, it may've been encoded by cli tool (if it contained ';') */
+                if(!l_password_base64){
+                    char * l_password_str_base64 = dap_enc_strdup_to_base64(l_password_str);
+                    l_hash_str = dap_strdup_printf("%s%s",l_password_str_base64, s_salt_str );
+                    DAP_DELETE(l_password_str_base64);
+                }else{
+                    l_hash_str = dap_strdup_printf("%s%s",l_password_str, s_salt_str );
+                }
+
+                dap_chain_hash_fast_t *l_password_hash = DAP_NEW_Z(dap_chain_hash_fast_t);
+                dap_hash_fast(l_hash_str,dap_strlen(l_hash_str), l_password_hash );
+                DAP_DELETE(l_hash_str);
+                dap_chain_global_db_gr_set((char *)l_login_str, l_password_hash,sizeof(*l_password_hash),s_group_password );
+            }
+
+            if ( l_first_name_str )
+                dap_chain_global_db_gr_set((char *)l_login_str, (char *)l_first_name_str, strlen(l_first_name_str) + 1, s_group_first_name);
+
+            if ( l_last_name_str )
+                dap_chain_global_db_gr_set((char *)l_login_str, (char *)l_last_name_str, strlen(l_last_name_str) + 1, s_group_last_name);
+
+            if ( l_email_str )
+                dap_chain_global_db_gr_set((char *)l_login_str, (char *)l_email_str, strlen(l_email_str) + 1, s_group_email);
+
+            // Update timestamp
+            dap_chain_time_t *l_time = DAP_NEW_Z(dap_chain_time_t);
+            *l_time = dap_chain_time_now();
+            dap_chain_global_db_gr_set((char *)l_login_str, l_time,sizeof (*l_time),s_group_ts_updated );
+            l_time = NULL; // to prevent usage uleased memory that could be free in any moment
+
+            uint64_t l_active_days = 0;
+            if ( l_active_days_str ){
+                l_active_days = strtoull(l_active_days_str,NULL,10);
+                if ( l_active_days ){
+                    l_time = DAP_NEW_Z(dap_chain_time_t);
+                    *l_time = dap_chain_time_now() + (dap_chain_time_t) l_active_days*86400ull;
+                    dap_chain_global_db_gr_set((char *)l_login_str, l_time, sizeof (*l_time), s_group_ts_active_till);
+                }else
+                    dap_string_append_printf(l_ret_str,"WARNING: Wrong --active_time format\n");
+            }
+
+            if (l_is_user_create){
+                run_hook(s_hook_user_create, "login=%s pass=%s active_days=%lld first_name=%s last_name=%s email=%s", l_login_str, l_password_str, l_active_days,
+                        l_first_name_str ? l_first_name_str : "-",
+                        l_last_name_str ? l_last_name_str : "-",
+                        l_email_str ? l_email_str : "-");
+                dap_string_append_printf(l_ret_str,"OK: Created user '%s'\n",l_login_str );
+                l_ret = 0;
+            }else if (l_is_user_update){
+                run_hook(s_hook_user_update, "login=%s pass=%s active_days=%lld first_name=%s last_name=%s email=%s", l_login_str, l_password_str, l_active_days,
+                                        l_first_name_str ? l_first_name_str : "-",
+                                        l_last_name_str ? l_last_name_str : "-",
+                                        l_email_str ? l_email_str : "-");
+                dap_string_append_printf(l_ret_str,"OK: Updated user '%s'\n",l_login_str );
+                l_ret = 0;
+            }else{
+                dap_string_append_printf(l_ret_str,"OK: Unknown action success\n");
+                l_ret = 0;
+            }
+        }else{
+            if (l_is_user_create){
+                dap_string_append_printf(l_ret_str,"ERROR: Need at least --login and --password options\n" );
+                l_ret = -2;
+            }else if (l_is_user_update){
+                dap_string_append_printf(l_ret_str,"ERROR: Need at least --login and one of next options: --password, --first_name, --last_name or --email\n" );
+                l_ret = -3;
+            }else{
+                dap_string_append_printf(l_ret_str,"ERROR: Unknown error in options\n");
+                l_ret = -4;
+            }
+        }
+
+    }else if ( dap_strcmp(a_user_str, "delete") == 0 ){
+        const char * l_login_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--login", &l_login_str);
+        if ( l_login_str ) {
+            if ( dap_chain_global_db_gr_del((char *)l_login_str, s_group_password) ){
+                dap_chain_global_db_gr_del((char *)l_login_str, s_group_last_name);
+                dap_chain_global_db_gr_del((char *)l_login_str, s_group_first_name);
+                dap_chain_global_db_gr_del((char *)l_login_str, s_group_email);
+                dap_chain_global_db_gr_del((char *)l_login_str, s_group_cookie);
+
+                // Find if present cookie and delete it
+                size_t l_cookie_size = 0;
+                char * l_cookie = (char*) dap_chain_global_db_gr_get(l_login_str,&l_cookie_size, s_group_cookie );
+                if ( l_cookie ){
+                    dap_chain_global_db_gr_del( l_cookie,s_group_cookies );
+                    log_it(L_WARNING,"Deleted user but its cookie is active in table. Deleted that but better also to close session");
+                    // TODO close session when cookie deleted
+                }
+
+                dap_string_append_printf(l_ret_str,"OK: Deleted user '%s'\n",l_login_str );
+                run_hook(s_hook_user_delete, "login=%s", l_login_str);
+                l_ret = 0;
+            }else{
+                l_ret = -6;
+                dap_string_append_printf(l_ret_str,"ERROR: Can't find login '%s' in database\n", l_login_str );
+            }
+        }else{
+            l_ret = -5;
+            dap_string_append_printf(l_ret_str,"ERROR: Need --login option\n" );
+        }
+    }else if ( dap_strcmp(a_user_str, "check") == 0 ){
+        const char * l_login_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--login", &l_login_str);
+        const char * l_password_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--password", &l_password_str);
+        if ( l_login_str && l_password_str) {
+            int l_check = dap_chain_net_srv_vpn_cdb_auth_check_login (l_login_str, l_password_str);
+            if ( l_check == 0){
+                dap_string_append_printf(l_ret_str,"OK: Passed password check for '%s'\n",l_login_str );
+                l_ret = 0;
+            }else if (l_check == -1){
+                l_ret = -7;
+                dap_string_append_printf(l_ret_str,"ERROR: Can't find login '%s' in database\n", l_login_str );
+            }else if (l_check == -2){
+                l_ret = -8;
+                dap_string_append_printf(l_ret_str,"ERROR: Wrong password for login '%s'\n", l_login_str );
+            }else if (l_check == -3){
+                l_ret = -10;
+                dap_string_append_printf(l_ret_str,"ERROR: Login '%s' is not activated\n", l_login_str );
+            }else if (l_check == -4){
+                l_ret = -11;
+                dap_string_append_printf(l_ret_str,"ERROR: Login '%s' activation is overdue\n", l_login_str );
+            }else {
+                l_ret = -9;
+                dap_string_append_printf(l_ret_str,"ERROR: Unknown error in password check for login '%s'\n", l_login_str );
+            }
+        }else{
+            l_ret = -5;
+            dap_string_append_printf(l_ret_str,"ERROR: Need --login option\n" );
+        }
+    }else if ( dap_strcmp(a_user_str, "show") == 0 ){
+        const char * l_login_str = NULL;
+        dap_chain_node_cli_find_option_val(a_argv, a_arg_index, a_argc, "--login", &l_login_str);
+        if ( l_login_str ) {
+            size_t l_password_hash_size=0;
+            dap_chain_hash_fast_t *l_password_hash;
+            if ( (l_password_hash = (dap_chain_hash_fast_t*) dap_chain_global_db_gr_get (
+                     l_login_str,&l_password_hash_size  ,s_group_password ) ) !=NULL ){
+                dap_string_append_printf(l_ret_str,"OK: Find user '%s'\n",l_login_str );
+
+                size_t l_first_name_size=0;
+                char * l_first_name =(char *) dap_chain_global_db_gr_get (  l_login_str,&l_first_name_size  ,s_group_first_name ) ;
+                if ( l_first_name ){
+                    dap_string_append_printf(l_ret_str,"\tFirst_name: %s\n", l_first_name);
+                    DAP_DELETE( l_first_name );
+                }
+
+                size_t l_last_name_size=0;
+                char * l_last_name =(char *) dap_chain_global_db_gr_get (  l_login_str,&l_last_name_size  ,s_group_last_name ) ;
+                if (l_last_name){
+                    dap_string_append_printf(l_ret_str,"\tLast_name: %s\n", l_last_name);
+                    DAP_DELETE( l_last_name );
+                }
+
+                size_t l_email_size=0;
+                char * l_email =(char *) dap_chain_global_db_gr_get (  l_login_str,&l_email_size  ,s_group_email ) ;
+                if (l_email){
+                    dap_string_append_printf(l_ret_str,"\tEmail: %s\n", l_email);
+                    DAP_DELETE( l_email );
+                }
+
+                size_t l_ts_active_till_size = 0;
+                time_t *l_ts_active_till = (time_t*) dap_chain_global_db_gr_get(l_login_str, &l_ts_active_till_size, s_group_ts_active_till);
+                if(l_ts_active_till_size) {
+                    double l_dt_days = difftime(*l_ts_active_till, time(NULL)) / 86400;
+
+                    if(l_dt_days < 1) {
+                        if(l_dt_days < 0)
+                            l_dt_days = 0;
+                        dap_string_append_printf(l_ret_str, "\tActive hours: %.2lf\n", l_dt_days * 24);
+                    }
+                    else
+                        dap_string_append_printf(l_ret_str, "\tActive days: %.2lf\n", l_dt_days);
+                    DAP_DELETE(l_ts_active_till);
+                }
+
+                l_ret = 0;
+            }else{
+                l_ret = -6;
+                dap_string_append_printf(l_ret_str,"ERROR: Can't find login '%s' in database\n", l_login_str );
+            }
+        }else{
+            l_ret = -5;
+            dap_string_append_printf(l_ret_str,"ERROR: Need --login option\n" );
+        }
+    }else if ( dap_strcmp(a_user_str, "list") == 0 ){
+        size_t l_users_size = 0;
+        dap_global_db_obj_t* l_users = dap_chain_global_db_gr_load(s_group_password,&l_users_size);
+        if (l_users_size){
+            dap_string_append_printf(l_ret_str,"OK: %zd users in DB\n",l_users_size);
+            for ( size_t i = 0; i < l_users_size; i++ ){
+                dap_string_append_printf(l_ret_str,"\t%s\n",l_users[i].key);
+            }
+            dap_chain_global_db_objs_delete(l_users, l_users_size);
+        }else{
+            dap_string_append_printf(l_ret_str,"OK: 0 users in DB\n");
+        }
+    }else {
+        dap_string_append_printf(l_ret_str,"ERROR: Unknown command 'user %s'\n", a_user_str );
+        l_ret = -1;
+    }
+    dap_chain_node_cli_set_reply_text( a_str_reply, l_ret_str->str );
+    dap_string_free( l_ret_str, false );
+    return l_ret;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_auth_add_proc
+ * @param a_http
+ * @param a_url
+ */
+void dap_chain_net_srv_vpn_cdb_auth_add_proc(dap_http_t * a_http, const char * a_url)
+{
+    dap_http_simple_proc_add(a_http,a_url,24000, s_http_proc);
+}
+
+/**
+ * @brief s_http_proc Process auth request
+ * @param a_http_simple HTTP simple client instance
+ * @param a_arg Return if ok
+ */
+static void s_http_proc(dap_http_simple_t *a_http_simple, void * a_arg )
+{
+    http_status_code_t * l_return_code = (http_status_code_t*)a_arg;
+    enc_http_delegate_t * l_delegate;
+    strcpy(a_http_simple->reply_mime,"application/octet-stream");
+
+    l_delegate = enc_http_request_decode(a_http_simple);
+    if(l_delegate){
+        if (l_delegate->url_path){
+            if(strcmp(l_delegate->url_path, "auth") == 0) {
+                s_http_enc_proc(l_delegate, a_arg);
+            }
+            else if(strcmp(l_delegate->url_path, "auth_key") == 0) {
+                s_http_enc_proc_key(l_delegate, a_arg);
+            }
+            else if(strcmp(l_delegate->url_path, "auth_deactivate") == 0) {
+                s_http_enc_proc_key_deactivate(l_delegate, a_arg);
+            }
+            else {
+
+                if(l_delegate->url_path)
+                    log_it(L_ERROR,"Wrong auth request %s",l_delegate->url_path);
+                else
+                    log_it(L_ERROR,"Wrong auth request: nothing after / ");
+
+                *l_return_code = Http_Status_BadRequest;
+            }
+        }else{
+            log_it(L_ERROR,"Delegate has no url_path");
+            *l_return_code = Http_Status_InternalServerError;
+        }
+
+        enc_http_reply_encode(a_http_simple,l_delegate);
+        enc_http_delegate_delete(l_delegate);
+    }else{
+        *l_return_code = Http_Status_Unauthorized;
+        log_it(L_WARNING,"No KeyID in the request");
+    }
+}
+
+/**
+ * Select mode for current connection
+ * @param a_request_str
+ */
+static int mode_auto_select(char *a_request_str)
+{
+    int l_mode_auth = MODE_UNKNOWN;// default mode
+    if(!a_request_str)
+        return l_mode_auth;
+
+    size_t l_pkey_size_min = 1024;
+    char **l_str_array = dap_strsplit(a_request_str, " ", 5);
+    if(!l_str_array)
+        return l_mode_auth;
+
+    size_t l_str_array_size = dap_str_countv(l_str_array);
+    if(l_str_array_size == 3) {
+        // passwd l_login, l_password, l_pkey
+        // serial l_serial, l_domain, l_pkey
+        size_t l_login_serial_size = dap_strlen(l_str_array[0]);
+        size_t l_pkey_size = dap_strlen(l_str_array[2]);
+        /*size_t l_str_domain_size;
+        {
+            char **l_str_domain = dap_strsplit(l_str_array[1], " ", -1);
+            l_str_domain_size = dap_str_countv(l_str_array);
+            dap_strfreev(l_str_domain);
+        }*/
+        if(l_pkey_size > l_pkey_size_min) {
+            if(l_login_serial_size == 16)
+                l_mode_auth = MODE_SERIAL;
+            else
+                l_mode_auth = MODE_PASSWD;
+        }
+    }
+    else if(l_str_array_size == 4) {
+        // passwd l_login, l_password, l_domain,l_pkey
+        // serial l_serial_raw, l_serial_sign, l_domain, l_pkey
+        size_t l_login_or_serial_size = dap_strlen(l_str_array[0]);
+        size_t l_passwd_or_serial_sign_size = dap_strlen(l_str_array[1]);
+        size_t l_pkey_size = dap_strlen(l_str_array[3]);
+        if(l_pkey_size > l_pkey_size_min) {
+            if(l_login_or_serial_size == 16 && l_passwd_or_serial_sign_size>l_pkey_size_min)
+                l_mode_auth = MODE_SERIAL;
+            else
+                l_mode_auth = MODE_PASSWD;
+        }
+    }
+    else if(l_str_array_size == 5) {
+        // passwd l_login, l_password, l_domain,l_pkey, l_domain2
+        size_t l_pkey_size = dap_strlen(l_str_array[3]);
+        if(l_pkey_size > l_pkey_size_min)
+            l_mode_auth = MODE_PASSWD;
+    }
+    dap_strfreev(l_str_array);
+    return l_mode_auth;
+}
+
+/**
+ * @brief s_http_enc_proc Auth http interface
+ * @param a_delegate HTTP Simple client instance
+ * @param a_arg Pointer to bool with okay status (true if everything is ok, by default)
+ */
+static void s_http_enc_proc(enc_http_delegate_t *a_delegate, void * a_arg)
+{
+    http_status_code_t * l_return_code = (http_status_code_t*)a_arg;
+
+    if( a_delegate->request && dap_strncmp(a_delegate->action,"POST",sizeof (a_delegate->action)-1 )==0  ){
+        if(a_delegate->in_query==NULL){
+            log_it(L_WARNING,"Empty auth action");
+            *l_return_code = Http_Status_BadRequest;
+            return;
+        }else{
+            if(strcmp(a_delegate->in_query,"logout")==0 ){
+                if(a_delegate->cookie== NULL){
+                    log_it(L_WARNING,"Logout request without cookie");
+                    enc_http_reply_f(a_delegate, OP_CODE_NO_COOKIE );
+                    *l_return_code = Http_Status_BadRequest;
+                }else{
+                    if(dap_chain_global_db_gr_del(dap_strdup(a_delegate->cookie), s_group_cookies)){
+                        enc_http_reply_f(a_delegate,
+                                         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                         "<return>Successfuly logouted</return>\n"
+                                         );
+                        *l_return_code = Http_Status_OK;
+                    }else{
+                        log_it(L_NOTICE,"Logout action: cookie %s is already logouted (by timeout?)", a_delegate->cookie);
+                        enc_http_reply_f(a_delegate,
+                                         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                         "<err_str>No session in table</err_str>\n"
+                                         );
+                        *l_return_code = Http_Status_OK;
+                    }
+                }
+
+            }else if(dap_strcmp(a_delegate->in_query,"login")==0 || dap_strcmp(a_delegate->in_query,"serial")==0 ){
+                char l_login[128]={0};
+                char l_password[256]={0};
+                char l_pkey[6001]={0};//char l_pkey[4096]={0};
+
+                char l_domain[64]={0}, l_domain2[64]={0};
+
+                if(a_delegate->request_str == NULL){
+                    log_it(L_WARNING, "Request string is empty ");
+                    enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                    *l_return_code = Http_Status_BadRequest;
+                    return;
+                }
+
+                if(a_delegate->request_size<= 1){
+                    log_it(L_WARNING, "Request string is too short ");
+                    enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                    *l_return_code = Http_Status_BadRequest;
+                    return;
+                }
+                if(a_delegate->request_str[a_delegate->request_size] != '\0'){
+                    log_it(L_WARNING, "Request (size %zd) is not null terminated string", a_delegate->request_size);
+                    enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                    *l_return_code = Http_Status_BadRequest;
+                    return;
+                }
+
+
+                //log_it(L_DEBUG, "request_size=%d request_str='%s'\n",a_delegate->request_size, a_delegate->request_str);
+
+                int l_mode_auth;
+                if(s_mode_auth == MODE_BOTH)
+                    l_mode_auth = mode_auto_select(a_delegate->request_str);
+                else
+                    l_mode_auth = s_mode_auth;
+
+                if(l_mode_auth == MODE_UNKNOWN){
+                    log_it(L_ERROR, "Can't recognize auth method");
+                    *l_return_code = Http_Status_BadRequest;
+                }
+                // password mode
+                if(l_mode_auth == MODE_PASSWD) {
+
+                    if(dap_sscanf(a_delegate->request_str, "%127s %255s %63s %6000s %63s", l_login, l_password, l_domain,
+                            l_pkey, l_domain2) >= 4 ||
+                            sscanf(a_delegate->request_str, "%127s %255s %6000s ", l_login, l_password, l_pkey) >= 3) {
+                        log_it(L_INFO, "Trying to login with username '%s'", l_login);
+
+                        if(s_input_validation(l_login) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in username");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            return;
+                        }
+                        if(s_input_validation(l_password) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in password");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            return;
+                        }
+                        if(s_input_validation(l_pkey) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in base64 pkey string");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            return;
+                        }
+
+                        int l_login_result = dap_chain_net_srv_vpn_cdb_auth_check_login(l_login, l_password);
+                        switch (l_login_result) {
+                        case 0: {
+                            run_hook(s_hook_user_login, "login=%s pass=%s result=true", l_login, l_password);
+                            size_t l_tmp_size;
+                            char * l_first_name = (char*) dap_chain_global_db_gr_get(l_login, &l_tmp_size,
+                                    s_group_first_name);
+                            char * l_last_name = (char*) dap_chain_global_db_gr_get(l_login, &l_tmp_size,
+                                    s_group_last_name);
+                            char * l_email = (char*) dap_chain_global_db_gr_get(l_login, &l_tmp_size, s_group_email);
+                            dap_chain_time_t * l_ts_last_logined = (dap_chain_time_t*) dap_chain_global_db_gr_get(
+                                    l_login, &l_tmp_size, s_group_ts_last_login);
+                            dap_chain_time_t *l_ts_active_till = (dap_chain_time_t*) dap_chain_global_db_gr_get(l_login,
+                                    &l_tmp_size, s_group_ts_active_till);
+
+                            enc_http_reply_f(a_delegate,
+                                    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                            "<auth_info>\n"
+                                    );
+                            enc_http_reply_f(a_delegate, "\t<login>%s</login>\n", l_login);
+                            if(l_first_name)
+                                enc_http_reply_f(a_delegate, "\t<first_name>%s</first_name>\n", l_first_name);
+                            if(l_last_name)
+                                enc_http_reply_f(a_delegate, "\t<last_name>%s</last_name>\n", l_last_name);
+                            if(l_email)
+                                enc_http_reply_f(a_delegate, "\t<email>%s</email>\n", l_email);
+                            if(l_ts_last_logined)
+                                enc_http_reply_f(a_delegate, "\t<ts_prev_login>%llu</ts_prev_login>\n", (long long unsigned) *l_ts_last_logined);
+                            if(l_ts_active_till)
+                                enc_http_reply_f(a_delegate, "\t<ts_active_till>%llu</ts_active_till>\n", (long long unsigned) *l_ts_active_till);
+
+                            if(a_delegate->cookie)
+                                enc_http_reply_f(a_delegate, "\t<cookie>%s</cookie>\n", a_delegate->cookie);
+                            dap_chain_net_srv_vpn_cdb_auth_after(a_delegate, l_login, l_pkey); // Here if smbd want to add smth to the output
+                            enc_http_reply_f(a_delegate, "</auth_info>");
+                            log_it(L_INFO, "Login: Successfuly logined user %s", l_login);
+                            *l_return_code = Http_Status_OK;
+                            //log_it(L_DEBUG, "response_size='%d'",a_delegate->response_size);
+                            DAP_DELETE(l_first_name);
+                            DAP_DELETE(l_last_name);
+                            DAP_DELETE(l_email);
+                            DAP_DELETE(l_ts_last_logined);
+                            DAP_DELETE(l_ts_active_till);
+
+                            // Update last logined
+                            l_ts_last_logined = DAP_NEW_Z(dap_chain_time_t);
+                            *l_ts_last_logined = dap_chain_time_now();
+                            dap_chain_global_db_gr_set(l_login, l_ts_last_logined, sizeof(time_t), s_group_ts_last_login);
+                            DAP_DELETE(l_ts_last_logined);
+                        }
+                            break;
+                        case -1:
+                            run_hook(s_hook_user_login, "login=%s pass=%s result=false error=user_no_found", l_login, l_password);
+                            enc_http_reply_f(a_delegate, OP_CODE_NOT_FOUND_LOGIN_IN_DB);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -2:
+                            run_hook(s_hook_user_login, "login=%s pass=%s result=false error=passwd_not_correct", l_login, l_password);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_PSWD);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -3:
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INACTIVE);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -4:
+                            run_hook(s_hook_user_login, "login=%s pass=%s result=false error=expired", l_login, l_password);
+                            enc_http_reply_f(a_delegate, OP_CODE_SUBSCRIBE_EXPIRIED);
+                            *l_return_code = Http_Status_PaymentRequired;
+                            break;
+                        default:
+                            log_it(L_WARNING, "Login: Unknown authorize error for login '%s'", l_login);
+                            *l_return_code = Http_Status_BadRequest;
+                            break;
+                        }
+                    } else {
+                        log_it(L_DEBUG, "Login: wrong auth's request body ");
+                        *l_return_code = Http_Status_BadRequest;
+                    }
+                }
+                // serial mode
+                else if(l_mode_auth == MODE_SERIAL)
+                {
+                    char l_serial_tmp[64]={0};
+                    if(sscanf(a_delegate->request_str, "%63s %63s %6000s", l_serial_tmp, l_domain, l_pkey) >= 3) {
+                        char *l_serial = make_fullserial(l_serial_tmp);
+
+                        // Hash pkey
+                        dap_chain_hash_fast_t *l_pkey_hash = DAP_NEW_Z(dap_chain_hash_fast_t);
+                        dap_hash_fast(l_pkey, dap_strlen(l_pkey), l_pkey_hash);
+                        char l_pkey_hash_str[71];
+                        dap_chain_hash_fast_to_str(l_pkey_hash,l_pkey_hash_str,70);
+                        DAP_DELETE(l_pkey_hash);
+                        log_it(L_INFO, "Trying to login with serial '%s' and pkey_hash '%s'", l_serial, l_pkey_hash_str);
+                        if(s_input_validation(l_serial) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in serial");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_domain) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in l_domain");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_pkey) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in base64 pkey string");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        int l_login_result = dap_chain_net_srv_vpn_cdb_auth_check_serial(l_serial, l_pkey);
+                        log_it(L_INFO, "Check serial '%s' with code %d (Ok=0)", l_serial, l_login_result);
+                        switch (l_login_result) {
+                        case 0: {
+                            run_hook(s_hook_serial_login, "serial=%s result=true", l_serial);
+                            size_t l_tmp_size;
+                            enc_http_reply_f(a_delegate,
+                                    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                            "<auth_info>\n"
+                                    );
+                            enc_http_reply_f(a_delegate, "\t<serial>%s</serial>\n", l_serial);
+
+                            dap_chain_time_t * l_ts_last_logined = (dap_chain_time_t*) dap_chain_global_db_gr_get(l_serial, &l_tmp_size, s_group_ts_last_login);
+                            //dap_chain_time_t *l_ts_active_till = (dap_chain_time_t*) dap_chain_global_db_gr_get(l_serial, &l_tmp_size, s_group_ts_active_till);
+                            if(l_ts_last_logined)
+                                enc_http_reply_f(a_delegate, "\t<ts_prev_login>%llu</ts_prev_login>\n", (long long unsigned) *l_ts_last_logined);
+
+                            // get active_seconds for serial
+                            //if(l_ts_active_till)
+                            dap_chain_time_t l_active_seconds = 0;
+                            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial, NULL);
+                            if(l_serial_key) {
+                                l_active_seconds = l_serial_key->header.expired;
+                                DAP_DELETE(l_serial_key);
+                            }
+                            enc_http_reply_f(a_delegate, "\t<ts_active_till>%llu</ts_active_till>\n", (long long unsigned)l_active_seconds);
+                            if(a_delegate->cookie)
+                                enc_http_reply_f(a_delegate, "\t<cookie>%s</cookie>\n", a_delegate->cookie);
+                            dap_chain_net_srv_vpn_cdb_auth_after(a_delegate, l_serial, l_pkey); // Here if smbd want to add smth to the output
+                            enc_http_reply_f(a_delegate, "</auth_info>");
+                            log_it(L_INFO, "Login: Successfuly logined serial %s", l_serial);
+                            *l_return_code = Http_Status_OK;
+                            //log_it(L_DEBUG, "response_size='%d'",a_delegate->response_size);
+
+                            DAP_DELETE(l_ts_last_logined);
+                            //DAP_DELETE(l_ts_active_till);
+
+                            // Update last logined
+                            l_ts_last_logined = DAP_NEW_Z(dap_chain_time_t);
+                            *l_ts_last_logined = dap_chain_time_now();
+                            dap_chain_global_db_gr_set(l_serial, l_ts_last_logined, sizeof(time_t),s_group_ts_last_login);
+                            DAP_DELETE(l_ts_last_logined);
+                        }
+                            break;
+                        case -1:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=serial_no_found", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_NOT_FOUND_LOGIN_IN_DB);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -2:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=bad_pkey", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_SIGN);// incorrect pkey size
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -3:
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INACTIVE);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -4:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=expired", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_SUBSCRIBE_EXPIRIED);
+                            *l_return_code = Http_Status_PaymentRequired;
+                            break;
+                        case -5:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=other_device", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_SIGN_ALREADY_ACTIVATED); // incorrect pkey
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        default:
+                            log_it(L_WARNING, "Login: Unknown authorize error for serial '%s'", l_serial);
+                            *l_return_code = Http_Status_BadRequest;
+                            break;
+                        }
+                        DAP_DELETE(l_serial);
+                    }
+                }
+            }else if (s_is_registration_open && strcmp(a_delegate->in_query,"register")==0){
+                char l_login[128];
+                char l_password[256];
+                char l_first_name[128];
+                char l_last_name[128];
+                char l_email[256];
+
+                log_it(L_INFO, "Request str = %s", a_delegate->request_str);
+                if(sscanf(a_delegate->request_str,"%127s %255s %127s %127s %255s"
+                          ,l_login,l_password,l_email,l_first_name,l_last_name)>=3){
+                    if(s_input_validation(l_login)==0){
+                        log_it(L_WARNING,"Registration: Wrong symbols in the username '%s'",l_login);
+                        *l_return_code = Http_Status_BadRequest;
+                        return;
+                    }
+                    if(s_input_validation(l_password)==0){
+                        log_it(L_WARNING,"Registration: Wrong symbols in the password");
+                        *l_return_code = Http_Status_BadRequest;
+                        return;
+                    }
+                    if(s_input_validation(l_first_name)==0){
+                        log_it(L_WARNING,"Registration: Wrong symbols in the first name '%s'",l_first_name);
+                        *l_return_code = Http_Status_BadRequest;
+                        return;
+                    }
+                    if(s_input_validation(l_last_name)==0){
+                        log_it(L_WARNING,"Registration: Wrong symbols in the last name '%s'",l_last_name);
+                        *l_return_code = Http_Status_BadRequest;
+                        return;
+                    }
+                    if(s_input_validation(l_email)==0){
+                        log_it(L_WARNING,"Registration: Wrong symbols in the email '%s'",l_email);
+                        *l_return_code = Http_Status_BadRequest;
+                        return;
+                    }
+                    if ( l_login[0] && l_password[0] && l_email[0] ){
+
+                        // Hash password with salt
+                        char * l_hash_str = dap_strdup_printf("%s%s",l_password, s_salt_str );
+                        dap_chain_hash_fast_t *l_password_hash = DAP_NEW_Z(dap_chain_hash_fast_t);
+                        dap_hash_fast(l_hash_str,dap_strlen(l_hash_str), l_password_hash );
+                        DAP_DELETE(l_hash_str);
+                        dap_chain_global_db_gr_set(l_login, l_password_hash,sizeof(*l_password_hash),s_group_password );
+
+                        // Write email in db
+                        dap_chain_global_db_gr_set(l_login, l_email,strlen(l_email)+1,s_group_email );
+
+                        enc_http_reply_f(a_delegate,
+                                         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                         "<auth_info>\n"
+                                         );
+
+                        enc_http_reply_f(a_delegate,"\t<login>%s</login>\n",l_login);
+                        // Write first and last names in db if present
+                        if ( l_first_name[0] ){
+                            dap_chain_global_db_gr_set( l_login, l_first_name,strlen(l_first_name)+1,
+                                                        s_group_first_name );
+                            enc_http_reply_f(a_delegate,"\t<first_name>%s</first_name>\n",l_first_name);
+                        }
+
+                        if ( l_last_name[0] ){
+                            dap_chain_global_db_gr_set( l_login,  l_last_name , strlen( l_last_name)+1,
+                                                        s_group_last_name );
+                            enc_http_reply_f(a_delegate,"\t<last_name>%s</last_name>\n",l_last_name);
+                        }
+
+                        // If cookie present - report it
+                        if ( a_delegate->cookie )
+                            enc_http_reply_f(a_delegate,"\t<cookie>%s</cookie>\n",a_delegate->cookie );
+                        enc_http_reply_f(a_delegate,"</auth_info>");
+
+                        // sync global_db
+                        dap_chain_global_db_flush();
+                        log_it(L_NOTICE,"Registration: new user %s \"%s %s\"<%s> is registred",l_login,l_first_name,l_last_name,l_email);
+                    }
+                }else{
+                    log_it(L_ERROR, "Registration: Wrong auth's request body ");
+                    *l_return_code = Http_Status_BadRequest;
+                }
+            }else{
+                log_it(L_ERROR, "Unknown auth command was selected (query_string='%s')",a_delegate->in_query);
+                *l_return_code = Http_Status_BadRequest;
+            }
+        }
+    }else{
+        log_it(L_ERROR, "Wrong auth request action '%s'",a_delegate->action);
+        *l_return_code = Http_Status_BadRequest;
+    }
+}
+
+/**
+ * @brief s_http_enc_proc_key Auth http interface
+ * @param a_delegate HTTP Simple client instance
+ * @param a_arg Pointer to bool with okay status (true if everything is ok, by default)
+ */
+static void s_http_enc_proc_key(enc_http_delegate_t *a_delegate, void * a_arg)
+{
+    http_status_code_t * l_return_code = (http_status_code_t*) a_arg;
+
+    if((a_delegate->request) && (strcmp(a_delegate->action, "POST") == 0)) {
+        if(a_delegate->in_query == NULL) {
+            log_it(L_WARNING, "Empty auth action");
+            *l_return_code = Http_Status_BadRequest;
+            return;
+        } else {
+            if(strcmp(a_delegate->in_query, "serial") == 0) {
+                char l_serial_raw[64] = { 0 };
+                char l_serial_sign[12000] = { 0 };
+                char l_pkey[6001] = { 0 };
+                int l_mode_auth;
+                if(s_mode_auth == MODE_BOTH)
+                    // s_http_enc_proc_key() only for serial
+                    l_mode_auth = MODE_SERIAL;//mode_auto_select(a_delegate->request_str);
+                else
+                    l_mode_auth = s_mode_auth;
+                if(l_mode_auth == MODE_UNKNOWN){
+                    log_it(L_ERROR, "Can't recognize auth method");
+                    l_mode_auth = MODE_SERIAL;
+                    *l_return_code = Http_Status_BadRequest;
+                }
+                // only for serial mode
+                if(l_mode_auth == MODE_SERIAL)
+                {
+                    char l_domain[64];
+                    if(sscanf(a_delegate->request_str, "%63s %12000s %63s %6000s", l_serial_raw, l_serial_sign, l_domain, l_pkey) >= 4) {
+                        char *l_serial = make_fullserial(l_serial_raw);
+                        /*size_t a1 = dap_strlen(l_serial);
+                        size_t a2 = dap_strlen(l_serial_sign);
+                        size_t a3 = dap_strlen(l_pkey);*/
+
+                        // Hash pkey
+                        dap_chain_hash_fast_t *l_pkey_hash = DAP_NEW_Z(dap_chain_hash_fast_t);
+                        dap_hash_fast(l_pkey, dap_strlen(l_pkey), l_pkey_hash);
+                        char l_pkey_hash_str[71];
+                        dap_chain_hash_fast_to_str(l_pkey_hash, l_pkey_hash_str, 70);
+                        DAP_DELETE(l_pkey_hash);
+                        log_it(L_INFO, "Trying to activate with serial '%s' and pkey_hash '%s'", l_serial, l_pkey_hash_str);
+                        if(s_input_validation(l_serial) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in serial");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_pkey) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in base64 pkey string");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_serial_sign) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in base64 serial sign");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        int l_activate_result = dap_chain_net_srv_vpn_cdb_auth_activate_serial(l_serial_raw, l_serial, l_serial_sign, l_pkey);
+                        log_it(L_INFO, "Serial '%s' activated with code %d (Ok=0)", l_serial, l_activate_result);
+                        switch (l_activate_result) {
+                        case 0:
+                            run_hook(s_hook_serial_activate, "serial=%s result=true", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_SERIAL_ACTIVED);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -1:
+                            run_hook(s_hook_serial_activate, "serial=%s result=false error=serial_no_found", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_NOT_FOUND_LOGIN_IN_DB);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -2:
+                            run_hook(s_hook_serial_activate, "serial=%s result=false error=sign_incorrect", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_SIGN);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                            /*case -3:
+                             enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INACTIVE);
+                             *l_return_code = Http_Status_OK;
+                             break;*/
+                        case -4:
+                            run_hook(s_hook_serial_activate, "serial=%s result=false error=expired", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_SUBSCRIBE_EXPIRIED);
+                            *l_return_code = Http_Status_PaymentRequired;
+                            break;
+                        default:
+                            log_it(L_WARNING, "Login: Unknown authorize error for activate serial '%s'", l_serial);
+                            *l_return_code = Http_Status_BadRequest;
+                            break;
+                        }
+                        DAP_DELETE(l_serial);
+                    }
+                    else {
+                        log_it(L_ERROR, "Registration: Wrong auth_key's request body ");
+                        *l_return_code = Http_Status_BadRequest;
+                    }
+                }
+                else {
+                    log_it(L_ERROR, "Unknown auth method");
+                    *l_return_code = Http_Status_BadRequest;
+                }
+            } else {
+                log_it(L_ERROR, "Unknown auth command was selected (query_string='%s')", a_delegate->in_query);
+                *l_return_code = Http_Status_BadRequest;
+            }
+        }
+    } else {
+        log_it(L_ERROR, "Wrong auth request action '%s'", a_delegate->action);
+        *l_return_code = Http_Status_BadRequest;
+    }
+}
+
+/**
+ * @brief s_http_enc_proc_key_deactivate Auth http interface
+ * @param a_delegate HTTP Simple client instance
+ * @param a_arg Pointer to bool with okay status (true if everything is ok, by default)
+ */
+static void s_http_enc_proc_key_deactivate(enc_http_delegate_t *a_delegate, void * a_arg)
+{
+    http_status_code_t * l_return_code = (http_status_code_t*) a_arg;
+
+    if((a_delegate->request) && (strcmp(a_delegate->action, "POST") == 0)) {
+        if(a_delegate->in_query == NULL) {
+            log_it(L_WARNING, "Empty auth action");
+            *l_return_code = Http_Status_BadRequest;
+            return;
+        } else {
+            if(strcmp(a_delegate->in_query, "serial") == 0) {
+                char l_serial_raw[64] = { 0 };
+                char l_serial_sign[12000] = { 0 };
+                char l_pkey[6001] = { 0 };
+                int l_mode_auth;
+                if(s_mode_auth == MODE_BOTH)
+                    // s_http_enc_proc_key() only for serial
+                    l_mode_auth = MODE_SERIAL;//mode_auto_select(a_delegate->request_str);
+                else
+                    l_mode_auth = s_mode_auth;
+                if(l_mode_auth == MODE_UNKNOWN){
+                    log_it(L_ERROR, "Can't recognize auth method");
+                    l_mode_auth = MODE_SERIAL;
+                    *l_return_code = Http_Status_BadRequest;
+                }
+                // only for serial mode
+                else if(l_mode_auth == MODE_SERIAL) {
+                    char l_serial_tmp[64] = { 0 };
+                    char l_domain[64];
+                    if(sscanf(a_delegate->request_str, "%63s %63s %6000s", l_serial_tmp, l_domain, l_pkey) >= 3) {
+                        char *l_serial = make_fullserial(l_serial_tmp);
+                        log_it(L_INFO, "Trying to login with serial '%s'", l_serial);
+                        if(s_input_validation(l_serial) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in serial");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_domain) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in l_domain");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        if(s_input_validation(l_pkey) == 0) {
+                            log_it(L_WARNING, "Wrong symbols in base64 pkey string");
+                            enc_http_reply_f(a_delegate, OP_CODE_INCORRECT_SYMOLS);
+                            *l_return_code = Http_Status_BadRequest;
+                            DAP_DELETE(l_serial);
+                            return;
+                        }
+                        int l_result = dap_chain_net_srv_vpn_cdb_auth_deactivate_serial(l_serial, l_pkey);
+                        log_it(L_INFO, "Check serial '%s' with code %d (Ok=0)", l_serial, l_result);
+                        switch (l_result) {
+                        case 0: {
+                            run_hook(s_hook_serial_login, "serial=%s result=true", l_serial);
+                            size_t l_tmp_size;
+                            enc_http_reply_f(a_delegate,
+                                    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
+                                            "<auth_info>\n"
+                                    );
+                            enc_http_reply_f(a_delegate, "\t<serial>%s</serial>\n", l_serial);
+
+                            dap_chain_time_t * l_ts_last_logined = (dap_chain_time_t*) dap_chain_global_db_gr_get(l_serial, &l_tmp_size, s_group_ts_last_login);
+                            if(l_ts_last_logined)
+                                enc_http_reply_f(a_delegate, "\t<ts_prev_login>%llu</ts_prev_login>\n",
+                                        (long long unsigned) *l_ts_last_logined);
+
+                            // get active_seconds for serial
+                            dap_chain_time_t l_active_seconds = 0;
+                            dap_serial_key_t *l_serial_key = dap_chain_net_srv_vpn_cdb_auth_get_serial_param(l_serial, NULL);
+                            if(l_serial_key) {
+                                l_active_seconds = l_serial_key->header.expired;
+                                DAP_DELETE(l_serial_key);
+                            }
+                            enc_http_reply_f(a_delegate, "\t<ts_time_left>%llu</ts_time_left>\n", (long long unsigned) l_active_seconds);
+                            if(a_delegate->cookie)
+                                enc_http_reply_f(a_delegate, "\t<cookie>%s</cookie>\n", a_delegate->cookie);
+                            dap_chain_net_srv_vpn_cdb_auth_after(a_delegate, l_serial, l_pkey); // Here if smbd want to add smth to the output
+                            enc_http_reply_f(a_delegate, "</auth_info>");
+                            log_it(L_INFO, "Login: Successfuly deactivated serial %s", l_serial);
+                            *l_return_code = Http_Status_OK;
+                            //log_it(L_DEBUG, "response_size='%d'",a_delegate->response_size);
+
+                            DAP_DELETE(l_ts_last_logined);
+                            //DAP_DELETE(l_ts_active_till);
+
+                            // Update last logined
+                            l_ts_last_logined = DAP_NEW_Z(dap_chain_time_t);
+                            *l_ts_last_logined = dap_chain_time_now();
+                            dap_chain_global_db_gr_set(l_serial, l_ts_last_logined, sizeof(time_t),
+                                    s_group_ts_last_login);
+                            DAP_DELETE(l_ts_last_logined);
+                        }
+                            break;
+                        case -1:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=serial_no_found", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_NOT_FOUND_LOGIN_IN_DB);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -2:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=bad_pkey", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_SIGN); // incorrect pkey size
+                            *l_return_code = Http_Status_OK;
+                            break;
+                        case -3:
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INACTIVE);
+                            *l_return_code = Http_Status_OK;
+                            break;
+                       /*case -4:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=expired", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_SUBSCRIBE_EXPIRIED);
+                            *l_return_code = Http_Status_PaymentRequired;
+                            break;
+                        case -5:
+                            run_hook(s_hook_serial_login, "serial=%s result=false error=other_device", l_serial);
+                            enc_http_reply_f(a_delegate, OP_CODE_LOGIN_INCORRECT_SIGN_ALREADY_ACTIVATED); // incorrect pkey
+                            *l_return_code = Http_Status_OK;
+                            break;*/
+                        default:
+                            log_it(L_WARNING, "Unknown error=%d for deactivate serial '%s'", l_result, l_serial);
+                            *l_return_code = Http_Status_BadRequest;
+                            break;
+                        }
+                        DAP_DELETE(l_serial);
+                    }
+                }
+                else {
+                    log_it(L_ERROR, "Unknown auth method");
+                    *l_return_code = Http_Status_BadRequest;
+                }
+            } else {
+                log_it(L_ERROR, "Unknown auth command was selected (query_string='%s')", a_delegate->in_query);
+                *l_return_code = Http_Status_BadRequest;
+            }
+        }
+    } else {
+        log_it(L_ERROR, "Wrong auth request action '%s'", a_delegate->action);
+        *l_return_code = Http_Status_BadRequest;
+    }
+}
diff --git a/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_server_list.c b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_server_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..098f4330077de668536a623128e2ec3dddee544c
--- /dev/null
+++ b/modules/modules_dynamic/cdb/dap_chain_net_srv_vpn_cdb_server_list.c
@@ -0,0 +1,1215 @@
+/*
+ * Authors:
+ * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
+ * Alexander Lysikov <alexander.lysikov@demlabs.net>
+ * DeM Labs Inc.        https://demlabs.net
+ * CellFrame            https://cellframe.net
+ * Sources              https://gitlab.demlabs.net/cellframe
+ * Cellframe CDB lib    https://gitlab.demlabs.net/dap.support/cellframe-node-cdb-lib
+ * Copyrighted by Demlabs Limited, 2020
+ * All rights reserved.
+*/
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <json-c/json.h>
+#include <json-c/json_object.h>
+
+#include "dap_common.h"
+#include "dap_config.h"
+#include "dap_timerfd.h"
+#include "dap_list.h"
+#include "uthash.h"
+
+#include "dap_chain.h"
+#include "dap_chain_net.h"
+#include "dap_chain_net_srv.h"
+#include "dap_chain_net_srv_vpn.h"
+#include "dap_chain_net_srv_order.h"
+#include "dap_chain_net_srv_geoip.h"
+#include "dap_chain_net_vpn_client.h"
+
+#include "dap_http.h"
+#include "dap_http_simple.h"
+#include "http_status_code.h"
+
+#include "dap_chain_net_srv_vpn_cdb.h"
+#include "dap_chain_net_srv_vpn_cdb_server_list.h"
+
+#define LOG_TAG "dap_chain_net_srv_vpn_cdb_server_list"
+
+typedef struct dap_chain_net_srv_order_static
+{
+    uint8_t continent;
+    const char *region;
+    const char *net_str;
+    const char *order_name;
+    uint64_t node_addr_uint64;
+    const char *ipv4_str;
+    const char *ipv6_str;
+    uint16_t port;
+    const char *ext_str;
+    size_t ext_size;
+    const char *price_ticker;
+    int price_unit;
+    uint64_t price;
+    json_object *obj;
+} dap_chain_net_srv_order_static_t;
+
+
+// Exceptions for countries - first element: target country, rest elements: countries for ordering "auto", last element "", and then another target country, etc.
+static char *s_orders_exclusion[] = {"India", "Hong Kong", "",
+                                     "China", "India", "Hong Kong", ""
+                                    };
+//static char *s_orders_exclusion[] = {"China", "India", "Hong Kong", "" };
+
+
+
+static size_t s_cdb_net_count = 0;
+static dap_chain_net_t ** s_cdb_net = NULL;
+static bool *s_orders_use = NULL;
+static bool s_debug_more = false;
+static size_t s_orders_use_total;
+
+static bool s_server_list_static_auto=true;
+static bool s_server_list_static_no_shuffle= true;
+static time_t s_server_list_cache_expire = 3600;
+
+static dap_http_url_proc_t * s_url_proc = NULL;
+
+static void s_http_simple_proc(dap_http_simple_t *a_http_simple, void *a_arg);
+
+typedef struct dap_chain_net_item {
+    dap_chain_node_addr_t node_addr;
+    bool is_available;
+    UT_hash_handle hh;
+} dap_order_state_t;
+
+dap_order_state_t *s_order_state = NULL;
+
+static dap_order_state_t* find_order_state(dap_chain_node_addr_t a_node_addr)
+{
+    dap_order_state_t *l_order_state = NULL;
+
+    HASH_FIND(hh, s_order_state, &a_node_addr, sizeof(dap_chain_node_addr_t), l_order_state);
+    if(l_order_state){
+        int gsdg=532;
+    }
+    return l_order_state;
+}
+
+int get_order_state(dap_chain_node_addr_t a_node_addr)
+{
+    dap_order_state_t *l_order_state = find_order_state(a_node_addr);
+    if(!l_order_state)
+        return -1;
+    // if order off-line
+    if(l_order_state->is_available)
+        return 1;
+    // if order on-line
+    return 0;
+}
+
+static void save_order_state(dap_chain_node_addr_t a_node_addr, bool a_is_available)
+{
+    dap_order_state_t *l_order_state = find_order_state(a_node_addr);
+    // node_addr already in the hash?
+    if(!l_order_state) {
+        l_order_state = DAP_NEW_Z(dap_order_state_t);
+        l_order_state->node_addr.uint64 = a_node_addr.uint64;
+        HASH_ADD(hh, s_order_state, node_addr, sizeof(dap_chain_node_addr_t), l_order_state);
+    }
+    l_order_state->is_available = a_is_available;
+}
+
+static void delete_order_state(dap_order_state_t *l_order_state)
+{
+    HASH_DELETE(hh, s_order_state, l_order_state);
+    DAP_DELETE(l_order_state);
+}
+
+static bool callback_check_orders(void *a_arg)
+{
+    log_it(L_DEBUG, "callback_check_orders");
+    static int l_current_run = 0;
+    int l_multiplicity = DAP_POINTER_TO_INT(a_arg);
+    // default timeout 10ms
+    int l_timeout_test_ms = dap_config_get_item_int32_default( g_config,"cdb", "servers_list_check_timeout", 20) * 1000;// read settings
+    size_t l_orders_num_total = 0;
+    // read all orders
+    for(size_t i = 0; i < s_cdb_net_count; i++) {
+        dap_chain_net_t * l_net = s_cdb_net[i];
+        if(l_net) {
+            dap_chain_net_srv_order_t * l_orders = NULL;
+            size_t l_orders_num = 0;
+            dap_chain_net_srv_price_unit_uid_t l_unit_uid = { { 0 } };
+            dap_chain_net_srv_uid_t l_srv_uid = { .uint64 = DAP_CHAIN_NET_SRV_VPN_ID };
+            dap_chain_net_srv_order_find_all_by(l_net, SERV_DIR_SELL, l_srv_uid, l_unit_uid,
+            NULL, 0, 0, &l_orders, &l_orders_num);
+            log_it(L_DEBUG, "Found %zd orders in \"%s\" network", l_orders_num, l_net->pub.name);
+            //l_orders_num_total += l_orders_num;
+
+            // find the shift for each node
+            dap_chain_net_srv_order_t *l_orders_pos[l_orders_num];
+            size_t l_orders_size = 0;
+            for(size_t j = 0; j < l_orders_num; j++) {
+                l_orders_pos[j] = (dap_chain_net_srv_order_t*) ((char*) l_orders + l_orders_size);
+                l_orders_size += dap_chain_net_srv_order_get_size(l_orders_pos[j]);
+            }
+
+            //check active orders
+            for(size_t j = 0; j < l_orders_num; j++) {
+
+                dap_chain_net_srv_order_t *l_orders = l_orders_pos[j];
+                dap_order_state_t *l_order_state = find_order_state(l_orders->node_addr);
+                // filter of unavailable orders
+                if(l_order_state){
+                    // run check for unavailable orders only every l_multiplicity time
+                    if(!l_order_state->is_available && (l_current_run % (l_multiplicity ? l_multiplicity : 1)))
+                        continue;
+                }
+                // get ip from node addr
+                dap_chain_node_info_t *l_node_info = dap_chain_node_info_read(l_net, &(l_orders->node_addr));
+                if(!l_node_info){
+                    log_it(L_NOTICE,"Node addr "NODE_ADDR_FP_STR" not found in base", &l_orders->node_addr);
+                    continue;
+                }
+                char l_node_ext_ipv4_str[INET_ADDRSTRLEN] = { 0 };
+                //char l_node_ext_ipv4_str[INET_ADDRSTRLEN] = "192.168.100.93";
+                char l_node_ext_ipv6_str[INET6_ADDRSTRLEN] = { 0 };
+                if(l_node_info->hdr.ext_addr_v4.s_addr)
+                    inet_ntop(AF_INET, &l_node_info->hdr.ext_addr_v4, l_node_ext_ipv4_str,
+                            sizeof(l_node_ext_ipv4_str));
+                if(*((uint128_t *) l_node_info->hdr.ext_addr_v6.s6_addr))
+                    inet_ntop(AF_INET6, &l_node_info->hdr.ext_addr_v6, l_node_ext_ipv6_str,
+                            sizeof(l_node_ext_ipv6_str));
+                size_t l_data_size_to_send = 10240*2;
+                size_t l_data_size_to_recv = 0;
+                // check send speed
+                int l_res1 = dap_chain_net_vpn_client_check(l_net, l_node_ext_ipv4_str, l_node_ext_ipv6_str, l_node_info->hdr.ext_port,
+                        l_data_size_to_send, l_data_size_to_recv, l_timeout_test_ms);
+                int l_res2 = -1;
+                if(l_res1 == 0) {
+                    size_t l_data_size_to_send = 0;
+                    size_t l_data_size_to_recv = 10240*2;
+                    // check recv speed
+                    l_res2 = dap_chain_net_vpn_client_check(l_net, l_node_ext_ipv4_str, l_node_ext_ipv6_str, l_node_info->hdr.ext_port, l_data_size_to_send, l_data_size_to_recv, l_timeout_test_ms);
+                    //s_orders_use[i] = false;
+                }
+                // save availability of order
+                save_order_state(l_orders->node_addr, !l_res1 || !l_res2);
+            }
+            if (l_orders)
+                DAP_DELETE(l_orders);
+        }
+    }
+    l_current_run++;
+    // repeat callback
+    if(l_multiplicity)
+        return true;
+    // no repeat callback
+    return false;
+}
+
+int dap_chain_net_srv_vpn_cdb_server_list_init()
+{
+    char **l_cdb_networks;
+    uint16_t l_cdb_networks_count = 0;
+    log_it(L_NOTICE,"Initialized Server List Module");
+    l_cdb_networks = dap_config_get_array_str( g_dap_config_cdb, "cdb", "servers_list_networks", &l_cdb_networks_count );
+    s_debug_more = dap_config_get_item_bool_default( g_dap_config_cdb, "cdb", "debug_more", s_debug_more );
+    s_server_list_cache_expire = dap_config_get_item_int32_default(g_dap_config_cdb, "cdb","cache_expire", s_server_list_cache_expire);
+    s_server_list_static_auto = dap_config_get_item_bool_default( g_dap_config_cdb, "cdb", "server_list_static_auto", s_server_list_static_auto );
+    s_server_list_static_no_shuffle = dap_config_get_item_bool_default( g_dap_config_cdb,"cdb", "server_list_static_no_shuffle", s_server_list_static_no_shuffle);// read settings
+    if ( l_cdb_networks_count ){
+        s_cdb_net = DAP_NEW_Z_SIZE(dap_chain_net_t*, sizeof (dap_chain_net_t*)* l_cdb_networks_count );
+        s_cdb_net_count = l_cdb_networks_count;
+        for ( size_t i = 0; i < l_cdb_networks_count ; i++) {
+            s_cdb_net[i] = dap_chain_net_by_name( l_cdb_networks[i] );
+            if ( s_cdb_net[i] )
+                log_it( L_INFO, "Added \"%s\" network for server list fetchs", l_cdb_networks[i]);
+            else
+                log_it( L_WARNING, "Can't find \"%s\" network to add to server list fetchs", l_cdb_networks[i]);
+        }
+    } else
+        log_it( L_WARNING, "No chain networks listed in config");
+
+    return 0;
+}
+
+void dap_chain_net_srv_vpn_cdb_server_list_deinit(void)
+{
+}
+
+
+/**
+ * @brief order_info_print
+ * @param a_server_location for server name, NULL not used
+ * @param a_node_number for server name, <0 not use
+ */
+static int order_info_print(dap_string_t *a_reply_str, dap_chain_net_t * a_net, dap_chain_net_srv_order_t * a_order, const char *a_server_name, int a_node_number)
+{
+    dap_chain_node_info_t * l_node_info = dap_chain_node_info_read(a_net, &a_order->node_addr);
+    if(l_node_info) {
+        char l_node_ext_ipv4_str[INET_ADDRSTRLEN] = { 0 };
+        char l_node_ext_ipv6_str[INET6_ADDRSTRLEN] = { 0 };
+        if(l_node_info->hdr.ext_addr_v4.s_addr)
+            inet_ntop(AF_INET, &l_node_info->hdr.ext_addr_v4, l_node_ext_ipv4_str, sizeof(l_node_ext_ipv4_str));
+        if(*((uint128_t *) l_node_info->hdr.ext_addr_v6.s6_addr))
+            inet_ntop(AF_INET6, &l_node_info->hdr.ext_addr_v6, l_node_ext_ipv6_str, sizeof(l_node_ext_ipv6_str));
+
+        uint8_t l_continent_num = 0;
+        char *l_region = NULL;
+        dap_chain_net_srv_order_get_continent_region(a_order, &l_continent_num, &l_region);
+        const char *l_continent_str = dap_chain_net_srv_order_continent_to_str(l_continent_num);
+        // ext_out in hex view
+        char *l_ext_out = a_order->ext_size ? DAP_NEW_Z_SIZE(char, a_order->ext_size * 2 + 1) : NULL;
+        dap_bin2hex(l_ext_out, a_order->ext, a_order->ext_size);
+
+        dap_string_append_printf(a_reply_str, "    {\n");
+        dap_string_append_printf(a_reply_str, "        \"Location\":\"%s\",\n", l_region ? l_region : "None"); //NETHERLANDS
+                //l_continent_str ? l_continent_str : "None", l_region ? l_region : "None");
+
+
+        dap_string_append_printf(a_reply_str, "        \"ChainNet\":\"%s\",\n", a_net->pub.name);
+        //dap_string_append_printf(a_reply_str, "        \"Name\":\"%s.Cell-%lu.%zd\",\n", a_net->pub.name, l_node_info->hdr.cell_id.uint64, 0);
+        if(a_server_name)
+            dap_string_append_printf(a_reply_str, "        \"Name\":\"%s\",\n", a_server_name);
+        else
+            dap_string_append_printf(a_reply_str, "        \"Name\":\"%s.%s.%zd\",\n", l_continent_str ? l_continent_str : "", l_region ? l_region : "", a_node_number + 1);
+            //dap_string_append_printf(a_reply_str, "        \"Name\":\"%s.%s.Cell-%lu.%zd\",\n", l_continent_str ? l_continent_str : "", l_region ? l_region : "", l_node_info->hdr.cell_id.uint64, a_node_number + 1);
+        if(l_node_ext_ipv4_str[0])
+            dap_string_append_printf(a_reply_str, "        \"Address\":\"%s\",\n", l_node_ext_ipv4_str);
+        if(l_node_ext_ipv6_str[0])
+            dap_string_append_printf(a_reply_str, "        \"Address6\":\"%s\",\n", l_node_ext_ipv6_str);
+        dap_string_append_printf(a_reply_str, "        \"Port\":%hu,\n", l_node_info->hdr.ext_port ? l_node_info->hdr.ext_port : 80);
+
+        //dap_string_append_printf(a_reply_str, "        \"Ext\":\"%s-%s\",\n", l_continent_str ? l_continent_str : "", l_region ? l_region : "");
+        if(l_ext_out)
+            dap_string_append_printf(a_reply_str, "        \"Ext\":\"0x%s\",\n", l_ext_out);
+        else
+            dap_string_append_printf(a_reply_str, "        \"Ext\":\"0x0\",\n");
+        dap_string_append_printf(a_reply_str, "        \"Price\":%lu,\n", a_order->price);
+        dap_string_append_printf(a_reply_str, "        \"PriceUnits\":%u,\n", a_order->price_unit.uint32);
+        dap_string_append_printf(a_reply_str, "        \"PriceToken\":\"%s\",\n", a_order->price_ticker);
+        // order state
+        {
+            dap_order_state_t *l_order_state = find_order_state(a_order->node_addr);
+            // if order is not tested
+            if(!l_order_state)
+                dap_string_append_printf(a_reply_str, "        \"State\":\"unknown\"\n");
+            // if order off-line
+            else if(l_order_state->is_available)
+                dap_string_append_printf(a_reply_str, "        \"State\":\"available\"\n");
+            // if order on-line
+            else
+                dap_string_append_printf(a_reply_str, "        \"State\":\"not available\"\n");
+        }
+        dap_string_append_printf(a_reply_str, "    }");
+        DAP_DELETE(l_region);
+        DAP_DELETE(l_ext_out);
+
+
+    } else{
+        log_it(L_WARNING, "Order in \"%s\" network issued by node without ext_ipv4 field", a_net->pub.name);
+        return -1;
+    }
+    return 0;
+}
+
+static void s_http_simple_proc_default(dap_http_simple_t *a_http_simple, void *a_arg)
+{
+    http_status_code_t * l_ret_code = (http_status_code_t*)a_arg;
+    dap_string_t *l_reply_str = dap_string_new("[\n");
+
+    char *l_client_ip = a_http_simple->http_client->esocket->hostaddr;//"64.225.61.216"
+    //char *l_client_ip = "122.75.117.129";// china
+    geoip_info_t *l_geoip_info = chain_net_geoip_get_ip_info(l_client_ip);
+
+    if(s_debug_more)
+        log_it(L_DEBUG, "Have %zd chain networks for cdb lists", s_cdb_net_count );
+
+    for ( int i = 0; i < s_cdb_net_count ; i++ ) {
+        dap_chain_net_t * l_net = s_cdb_net[i];
+        if ( l_net ) {
+            dap_chain_net_srv_order_t * l_orders = NULL;
+            size_t l_orders_num = 0;
+            dap_chain_net_srv_price_unit_uid_t l_unit_uid = {{0}};
+            dap_chain_net_srv_uid_t l_srv_uid = { .uint64 =DAP_CHAIN_NET_SRV_VPN_ID };
+            dap_chain_net_srv_order_find_all_by( l_net, SERV_DIR_SELL,  l_srv_uid, l_unit_uid ,
+                                                 NULL,0,0, &l_orders, &l_orders_num );
+            if(s_debug_more)
+                log_it(L_DEBUG, "Found %zd orders in \"%s\" network", l_orders_num, l_net->pub.name );
+
+
+            // find the shift for each node
+            dap_chain_net_srv_order_t *l_orders_pos[l_orders_num];
+            int l_orders_available[l_orders_num];
+            size_t l_orders_size = 0;
+            for(int j = 0; j < l_orders_num; j++) {
+                l_orders_pos[j] = (dap_chain_net_srv_order_t*) ((char*) l_orders + l_orders_size);
+                l_orders_size += dap_chain_net_srv_order_get_size(l_orders_pos[j]);
+            }
+
+
+            // list of node numbers
+            int l_continents_count = dap_chain_net_srv_order_continents_count(); //int *l_node_numbering = DAP_NEW_Z_SIZE(int, l_orders_num * sizeof(int));
+            // list of the number of nodes in each continent
+            int l_continents_numbers[l_continents_count]; //int *l_continents_numbers = DAP_NEW_Z_SIZE(int, l_continents_count * sizeof(int));
+            int l_node_numbering[l_continents_count][l_orders_num];
+            // init arrays
+            for(int m1 = 0; m1 < l_continents_count; m1++) {
+                l_continents_numbers[m1] = 0;
+                for(int m2 = 0; m2 < l_orders_num; m2++)
+                    l_node_numbering[m1][m2] = -1;
+            }
+
+            // node numbering
+            int l_orders_used_num = 0;
+            {
+                // filling l_continents_numbers and l_node_numbering
+                for(int j = 0; j < l_orders_num; j++) {
+                    dap_chain_net_srv_order_t *l_order = l_orders_pos[j];
+                    // get order availability
+                    dap_order_state_t *l_order_state = find_order_state(l_order->node_addr);
+                    if(l_order_state){
+                        // if order on-line or off-line
+                        l_orders_available[j] = l_order_state->is_available;
+                    }
+                    else
+                        l_orders_available[j] = -1;
+                    if(l_orders_available[j] == 0)
+                        continue;
+                    uint8_t l_continent_num;
+                    if(!dap_chain_net_srv_order_get_continent_region(l_order, &l_continent_num, NULL))
+                        continue;
+                    l_node_numbering[l_continent_num][j] = l_continents_numbers[l_continent_num]++;
+                    l_orders_used_num++;
+                }
+                // shuffle nodes for each continent
+                for(int m1 = 0; m1 < l_continents_count; m1++) {
+                    int l_cont_num = l_continents_numbers[m1];
+                    if(l_cont_num <= 1)
+                        continue;
+                    // number of shuffles
+                    int l_shuffle_num = rand() % (l_cont_num + 1);
+                    for(int l_sh = 0; l_sh <= l_shuffle_num; l_sh++) {
+                        int l_pos1 = 0;
+                        int l_pos2 = 0;
+                        while(l_pos1 == l_pos2) {
+                            l_pos1 = rand() % l_cont_num;
+                            l_pos2 = rand() % l_cont_num;
+                        }
+                        for(size_t m2 = 0; m2 < l_orders_num; m2++) {
+                            if(l_node_numbering[m1][m2] == l_pos1)
+                                l_node_numbering[m1][m2] = l_pos2;
+                            else if(l_node_numbering[m1][m2] == l_pos2)
+                                l_node_numbering[m1][m2] = l_pos1;
+                        }
+                    }
+                }
+            }
+
+            int8_t l_client_continent = l_geoip_info ? dap_chain_net_srv_order_continent_to_num(l_geoip_info->continent) : 0;
+            bool l_is_auto_order = false;
+            // rule for countries - exclusion
+            if(l_geoip_info) {
+                for(size_t l_reg = 0; l_reg < sizeof(s_orders_exclusion) / sizeof(char*); l_reg++) {
+                    if(!dap_strcmp(s_orders_exclusion[l_reg], l_geoip_info->country_name)) {
+                        l_reg++;
+                        dap_list_t *l_list_reg = NULL;
+                        for(size_t j = 0; j < l_orders_num; j++) {
+                            dap_chain_net_srv_order_t *l_order = l_orders_pos[j];
+                            size_t l_region_size = l_order->ext_size - sizeof(uint8_t) - 1;
+                            char *l_region = (char*) l_order->ext + 1 + sizeof(uint8_t);
+                            for(size_t l_reg2 = l_reg; l_reg2 < sizeof(s_orders_exclusion) / sizeof(char*); l_reg2++) {
+                                if(!dap_strlen(s_orders_exclusion[l_reg2])){
+                                    if(j == l_orders_num - 1)
+                                        l_reg = l_reg2;
+                                    break;
+                                }
+                                if(l_region_size > 0 &&
+                                        !dap_strncmp(s_orders_exclusion[l_reg2], l_region, l_region_size))
+                                    l_list_reg = dap_list_prepend(l_list_reg, l_order);
+                            }
+
+                        }
+                        size_t l_num_reg = dap_list_length(l_list_reg);
+                        // random node from selected counties
+                        if(l_num_reg > 0) {
+                            size_t k = rand() % l_num_reg;
+                            dap_chain_net_srv_order_t *l_order = (dap_chain_net_srv_order_t*) dap_list_nth_data(
+                                    l_list_reg,
+                                    k);
+                            if(!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)) {
+                                dap_string_append_printf(l_reply_str, ",\n");
+                                l_is_auto_order = true;
+                            }
+                        }
+                        break;
+                    }
+                    else {
+                        for(; l_reg < sizeof(s_orders_exclusion) / sizeof(char*); l_reg++) {
+                            if(!dap_strlen(s_orders_exclusion[l_reg]))
+                                break;
+                        }
+                    }
+                }
+            }
+            // random node on client's continent
+            if(!l_is_auto_order && l_client_continent > 0 && l_continents_numbers[l_client_continent] > 1) {
+                int l_count = 0;
+                while(l_orders_num > 0) {
+                    size_t k = rand() % l_continents_numbers[l_client_continent];
+                    int l_node_pos = -1;
+                    for(size_t j2 = 0; j2 <= l_orders_num; j2++) {
+                        if(k == l_node_numbering[l_client_continent][j2]) {
+                            l_node_pos = j2;
+                            break;
+                        }
+                    }
+                    if(l_node_pos == -1) {
+                        // random node for the whole world
+                        l_node_pos = rand() % l_orders_num;
+                    }
+                    dap_chain_net_srv_order_t *l_order = l_orders_pos[l_node_pos];
+                    const char *country_code = dap_chain_net_srv_order_get_country_code(l_order);
+                    if(country_code) {
+                        // only for other countries
+                        if(dap_strcmp(l_geoip_info->country_code, country_code)) {
+                            if(!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)) {
+                                dap_string_append_printf(l_reply_str, ",\n");
+                                break;
+                            }
+                        }
+                    }
+                    if(l_count > 200)
+                        break;
+                    l_count++;
+                }
+
+            }
+            // random node for the whole world
+			else if(!l_is_auto_order) {
+				int l_count = 0;
+				while(l_orders_num > 0) {
+					// first random node
+					size_t k = rand() % l_orders_num;
+                    if(l_orders_available[k] != 0) {
+                        dap_chain_net_srv_order_t *l_order = l_orders_pos[k];
+                        if(!order_info_print(l_reply_str, l_net, l_order, "Auto", -1)) {
+                            dap_string_append_printf(l_reply_str, ",\n");
+                            break;
+                        }
+                    }
+					if (l_count>20)
+						break;
+					l_count++;
+				}
+            }
+            // random nodes for continents
+            int l_count = 0;
+            for(size_t l_c = 0; l_c < l_continents_count; l_c++) {
+                while(l_continents_numbers[l_c] > 0) {
+                    // random node for continent
+                    size_t k = rand() % l_continents_numbers[l_c];
+                    int l_node_pos = -1;
+                    for(size_t j2 = 0; j2 <= l_orders_num; j2++) {
+                        if(k == l_node_numbering[l_c][j2]) {
+                            l_node_pos = j2;
+                            break;
+                        }
+                    }
+                    if(l_node_pos == -1)
+                        break;
+                    dap_chain_net_srv_order_t *l_order = l_orders_pos[l_node_pos];
+                    char *l_server_name = dap_strdup_printf("%s", dap_chain_net_srv_order_continent_to_str(l_c));
+                    if(!order_info_print(l_reply_str, l_net, l_order, l_server_name, -1)) {
+                        dap_string_append_printf(l_reply_str, ",\n");
+                        DAP_DELETE(l_server_name);
+                        break;
+                    }
+                    else
+                        DAP_DELETE(l_server_name);
+                    if(l_count > 20)
+                        break;
+                    l_count++;
+                }
+            }
+
+            for(size_t l_c = 0; l_c < l_continents_count; l_c++) {
+                // print all nodes for continent
+                for(size_t l_n = 0; l_n < l_continents_numbers[l_c]; l_n++) {
+                    // since the nodes are shuffled, look for the desired node index
+                    for(size_t l_o = 0; l_o < l_orders_num; l_o++) {
+                        if(l_node_numbering[l_c][l_o] != l_n)
+                            continue;
+                        dap_chain_net_srv_order_t *l_order = l_orders_pos[l_o];
+                        if(!order_info_print(l_reply_str, l_net, l_order, NULL, l_n)) {
+                            dap_string_append_printf(l_reply_str, ",\n");
+                        }
+                        break;
+                    }
+                }
+            }
+        }else
+            log_it(L_ERROR,"No network in list!");
+    }
+    DAP_DELETE(l_geoip_info);
+    //delete trailing comma if exists
+    if(l_reply_str->str[l_reply_str->len - 2] == ','){
+        dap_string_truncate(l_reply_str, l_reply_str->len - 2);
+        dap_string_append_printf(l_reply_str, "\n");
+    }
+
+    dap_string_append_printf( l_reply_str, "]\n\n");
+    dap_http_simple_reply( a_http_simple, l_reply_str->str, l_reply_str->len );
+    strcpy( a_http_simple->reply_mime, "application/json" );
+    dap_string_free(l_reply_str, true);
+    if(s_debug_more)
+        log_it(L_DEBUG,"Reply in buffer: %s", a_http_simple->reply_str );
+    *l_ret_code = Http_Status_OK;
+    dap_http_simple_make_cache_from_reply(a_http_simple,time(NULL)+s_server_list_cache_expire);
+}
+
+static void s_http_simple_proc(dap_http_simple_t *a_http_simple, void *a_arg)
+{
+    http_status_code_t * l_ret_code = (http_status_code_t*) a_arg;
+    dap_string_t *l_reply_str = dap_string_new(NULL);
+
+
+
+    char *l_client_ip = a_http_simple->http_client->esocket->hostaddr; //"64.225.61.216"
+    //char *l_client_ip = "122.75.117.129";// china
+    geoip_info_t *l_geoip_info = chain_net_geoip_get_ip_info(l_client_ip);
+
+    // how many static nodelist processing
+    int l_net_processing_num = 0;
+    log_it(L_DEBUG, "Have %zd chain networks for cdb lists", s_cdb_net_count);
+    for(size_t i = 0; i < s_cdb_net_count; i++) {
+        dap_chain_net_t * l_net = s_cdb_net[i];
+        if(l_net) {
+            //dap_chain_net_srv_order_t * l_orders = NULL;
+            //size_t l_orders_num = 0;
+            //dap_chain_net_srv_price_unit_uid_t l_unit_uid = { { 0 } };
+            //dap_chain_net_srv_uid_t l_srv_uid = { .uint64 = DAP_CHAIN_NET_SRV_VPN_ID };
+            //dap_chain_net_srv_order_find_all_by(l_net, SERV_DIR_SELL, l_srv_uid, l_unit_uid,
+            //NULL, 0, 0, &l_orders, &l_orders_num);
+            //log_it(L_DEBUG, "Found %zd orders in \"%s\" network", l_orders_num, l_net->pub.name);
+
+            // get static nodelist
+            size_t l_static_nodelist_size = 0;
+            uint8_t *l_static_nodelist = dap_chain_global_db_gr_get(dap_strdup(l_net->pub.name), &l_static_nodelist_size, "cdb.static_nodelist");
+            if (!l_static_nodelist && s_server_list_static_auto)
+                dap_chain_net_srv_vpn_cdb_server_list_static_create(l_net);
+
+            // try to fetch again
+            l_static_nodelist = dap_chain_global_db_gr_get(dap_strdup(l_net->pub.name), &l_static_nodelist_size, "cdb.static_nodelist");
+
+            if(!l_static_nodelist)
+                continue;
+
+            // use only static part
+            if(s_server_list_static_no_shuffle){
+                // parse static nodelist in json format
+                struct json_object *l_jobj_arr = json_tokener_parse((char*) l_static_nodelist);
+                // added static part
+                const char* json_str = json_object_to_json_string(l_jobj_arr);
+                dap_string_append(l_reply_str, json_str);
+
+                if(l_static_nodelist)
+                    l_net_processing_num++;
+                DAP_DELETE(l_static_nodelist);
+                json_object_put(l_jobj_arr);
+                continue;
+            }
+
+
+            // orders list
+            size_t l_static_orders_num = 0;
+            dap_chain_net_srv_order_static_t * l_static_orders = NULL;
+
+            struct json_object *l_jobj_arr_new = json_object_new_array();
+            // parse static nodelist in json format
+            struct json_object *l_jobj_arr = json_tokener_parse((char*)l_static_nodelist);
+            if(json_object_is_type(l_jobj_arr, json_type_array)) {
+                // form l_static_orders
+                l_static_orders_num = json_object_array_length(l_jobj_arr);
+                l_static_orders = DAP_NEW_Z_SIZE(dap_chain_net_srv_order_static_t, sizeof(dap_chain_net_srv_order_static_t) * l_static_orders_num);
+                for(int i = 0; i < (int) l_static_orders_num; i++) {
+                    json_object *l_one_news = json_object_array_get_idx(l_jobj_arr, i);
+                    // parse json_object to dap_chain_net_srv_order_static_t
+                    if(json_object_is_type(l_one_news, json_type_object)) {
+                        const char *str;
+                        struct json_object *l_obj;
+                        dap_chain_net_srv_order_static_t *l_order = l_static_orders + i;
+                        l_obj = json_object_object_get(l_one_news, "Location");
+                        if(l_obj)
+                            l_order->region = json_object_get_string(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "ChainNet");
+                        if(l_obj)
+                            l_order->net_str = json_object_get_string(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "Name");
+                        if(l_obj){
+                            l_order->order_name = json_object_get_string(l_obj);
+                            // parse order_name to get continent (for example "Europe.Germany.2" -> "Europe")
+                            const char *l_dot = dap_strstr_len(l_order->order_name, -1, ".");
+                            if(l_dot){
+                                char *l_continent_str = dap_strdup(l_order->order_name);
+                                l_continent_str[l_dot-l_order->order_name]= '\0';
+                                l_order->continent = dap_chain_net_srv_order_continent_to_num(l_continent_str);
+                            }
+                        }
+                        l_obj = json_object_object_get(l_one_news, "Address");
+                        if(l_obj)
+                            l_order->ipv4_str = json_object_get_string(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "Address");
+                        if(l_obj)
+                            l_order->ipv6_str = json_object_get_string(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "Port");
+                        if(l_obj)
+                            l_order->port = json_object_get_int(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "Ext");
+                        if(l_obj){
+                            l_order->ext_str = json_object_get_string(l_obj);
+                            l_order->ext_size = dap_strlen(l_order->ext_str);
+                        }
+                        l_obj = json_object_object_get(l_one_news, "Price");
+                        if(l_obj)
+                            l_order->price = json_object_get_int64(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "PriceUnits");
+                        if(l_obj)
+                            l_order->price_unit = json_object_get_int(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "PriceToken");
+                        if(l_obj)
+                            l_order->price_ticker = json_object_get_string(l_obj);
+                        l_obj = json_object_object_get(l_one_news, "NodeAddress");
+                        if(l_obj)
+                            l_order->node_addr_uint64 = (uint64_t)json_object_get_int64(l_obj);
+                        l_order->obj = l_one_news;
+                    }
+                }
+
+                // added dynamic part
+                {
+                    // list of node numbers
+                    size_t l_continents_count = dap_chain_net_srv_order_continents_count();
+                    // list of the number of nodes in each continent
+                    int l_continents_numbers[l_continents_count]; //int *l_continents_numbers = DAP_NEW_Z_SIZE(int, l_continents_count * sizeof(int));
+                    int l_node_numbering[l_continents_count][l_static_orders_num];
+                    // init arrays
+                    for(size_t m1 = 0; m1 < l_continents_count; m1++) {
+                        l_continents_numbers[m1] = 0;
+                        for(size_t m2 = 0; m2 < l_static_orders_num; m2++)
+                            l_node_numbering[m1][m2] = -1;
+                    }
+
+                    int l_orders_available[l_static_orders_num];
+                    // filling l_continents_numbers and l_node_numbering
+                    for(size_t j = 0; j < l_static_orders_num; j++) {
+                        dap_chain_net_srv_order_static_t *l_order_static = l_static_orders + j;
+                        // get order availability
+                        dap_chain_node_addr_t l_node_addr;
+                        l_node_addr.uint64 = l_order_static->node_addr_uint64;
+                        dap_order_state_t *l_order_state = find_order_state(l_node_addr);
+                        if(l_order_state) {
+                            // if order on-line or off-line
+                            l_orders_available[j] = l_order_state->is_available;
+                        }
+                        else
+                            l_orders_available[j] = -1;
+                        if(l_orders_available[j] == 0)
+                            continue;
+                        uint8_t l_continent_num = l_order_static->continent;
+                        if(!l_continent_num)//dap_chain_net_srv_order_static_get_continent_region(l_order_static, &l_continent_num, NULL))
+                            continue;
+                        l_node_numbering[l_continent_num][j] = l_continents_numbers[l_continent_num]++;
+                    }
+
+                    // get client continent by client ip
+                    char *l_client_ip = a_http_simple->http_client->esocket->hostaddr;//"64.225.61.216"
+                    //char *l_client_ip = "122.75.117.129";// china
+                    geoip_info_t *l_geoip_info = chain_net_geoip_get_ip_info(l_client_ip);
+                    int8_t l_client_continent = l_geoip_info ? dap_chain_net_srv_order_continent_to_num(l_geoip_info->continent) : 0;
+
+                    bool l_is_auto_order = false;
+                    // rule for countries - exclusion
+                    if(l_geoip_info) {
+                        for(size_t l_reg = 0; l_reg < sizeof(s_orders_exclusion) / sizeof(char*); l_reg++) {
+                            if(!dap_strcmp(s_orders_exclusion[l_reg], l_geoip_info->country_name)) {
+                                l_reg++;
+                                dap_list_t *l_list_reg = NULL;
+                                for(size_t j = 0; j < l_static_orders_num; j++) {
+                                    dap_chain_net_srv_order_static_t *l_order_static = l_static_orders + j;
+                                    const char *l_region = l_order_static->region;
+                                    size_t l_region_size = dap_strlen(l_order_static->region);
+                                    for(size_t l_reg2 = l_reg; l_reg2 < sizeof(s_orders_exclusion) / sizeof(char*);
+                                            l_reg2++) {
+                                        if(!dap_strlen(s_orders_exclusion[l_reg2])) {
+                                            if(j == l_static_orders_num - 1)
+                                                l_reg = l_reg2;
+                                            break;
+                                        }
+                                        if(l_region_size > 0 &&
+                                                !dap_strncmp(s_orders_exclusion[l_reg2], l_region, l_region_size))
+                                            l_list_reg = dap_list_prepend(l_list_reg, l_order_static);
+                                    }
+
+                                }
+                                size_t l_num_reg = dap_list_length(l_list_reg);
+                                // random node from selected counties
+                                if(l_num_reg > 0) {
+                                    size_t k = rand() % l_num_reg;
+                                    dap_chain_net_srv_order_static_t *l_order_static =
+                                            (dap_chain_net_srv_order_static_t*) dap_list_nth_data(l_list_reg, k);
+                                    // create deep copy of order
+                                    const char* json_one_str = json_object_to_json_string(l_order_static->obj);
+                                    struct json_object *l_jobj = json_tokener_parse(json_one_str);
+                                    json_object_object_add(l_jobj, "Name", json_object_new_string("Auto"));
+                                    // added new json object for continent
+                                    json_object_array_add(l_jobj_arr_new, l_jobj);
+                                    l_is_auto_order = true;
+                                }
+                                break;
+                            }
+                            else {
+                                for(; l_reg < sizeof(s_orders_exclusion) / sizeof(char*); l_reg++) {
+                                    if(!dap_strlen(s_orders_exclusion[l_reg]))
+                                        break;
+                                }
+                            }
+                        }
+                    }
+
+                    // node 'auto' -> random node on client's continent
+                    if(!l_is_auto_order && l_client_continent > 0 && l_continents_numbers[l_client_continent] > 1) {
+                        int l_count = 0;
+                        while(l_static_orders_num > 0) {
+                            size_t k = rand() % l_continents_numbers[l_client_continent];
+                            int l_node_pos = -1;
+                            for(size_t j2 = 0; j2 <= l_static_orders_num; j2++) {
+                                if(k == l_node_numbering[l_client_continent][j2]) {
+                                    l_node_pos = j2;
+                                    break;
+                                }
+                            }
+                            if(l_node_pos == -1) {
+                                // random node for the whole world
+                                l_node_pos = rand() % l_static_orders_num;
+                            }
+                            dap_chain_net_srv_order_static_t *l_order_static = l_static_orders + l_node_pos;
+                            const char *country_code_str = dap_chain_net_srv_order_continent_to_str(l_order_static->continent);
+                            if(country_code_str) {
+                                // only for other countries
+                                if(dap_strcmp(l_geoip_info->country_code, country_code_str)) {
+
+                                    // create deep copy of order
+                                    const char* json_one_str = json_object_to_json_string(l_order_static->obj);
+                                    struct json_object *l_jobj = json_tokener_parse(json_one_str);
+                                    json_object_object_add(l_jobj, "Name", json_object_new_string("Auto"));
+                                    // added new json object for continent
+                                    json_object_array_add(l_jobj_arr_new, l_jobj);
+                                    break;
+                                }
+                            }
+                            if(l_count > 200)
+                                break;
+                            l_count++;
+                        }
+
+                    }
+                    // random node for the whole world
+                    else if(!l_is_auto_order) {
+                        int l_count = 0;
+                        while(l_static_orders_num > 0) {
+                            // first random node
+                            size_t k = rand() % l_static_orders_num;
+                            if(l_orders_available[k] != 0) {
+                                dap_chain_net_srv_order_static_t *l_order_static = l_static_orders + k;
+                                // create deep copy of order
+                                const char* json_one_str = json_object_to_json_string(l_order_static->obj);
+                                struct json_object *l_jobj = json_tokener_parse(json_one_str);
+                                json_object_object_add(l_jobj, "Name", json_object_new_string("Auto"));
+                                // added new json object for continent
+                                json_object_array_add(l_jobj_arr_new, l_jobj);
+                                break;
+                            }
+                            if(l_count > 20)
+                                break;
+                            l_count++;
+                        }
+                    }
+
+                    // random nodes for continents
+                    for(size_t l_c = 0; l_c < l_continents_count; l_c++) {
+                        while(l_continents_numbers[l_c] > 0) {
+                            // random node for continent
+                            size_t k = rand() % l_continents_numbers[l_c];
+                            int l_node_pos = -1;
+                            for(size_t j2 = 0; j2 <= l_static_orders_num; j2++) {
+                                if(k == l_node_numbering[l_c][j2]) {
+                                    l_node_pos = j2;
+                                    break;
+                                }
+                            }
+                            if(l_node_pos == -1)
+                                break;
+                            //dap_chain_net_srv_order_t *l_order = l_static_orders[l_node_pos];
+                            dap_chain_net_srv_order_static_t *l_order = l_static_orders + l_node_pos;
+                            char *l_server_name = dap_strdup_printf("%s", dap_chain_net_srv_order_continent_to_str(l_c));
+                            // create deep copy of order
+                            const char* json_one_str = json_object_to_json_string(l_order->obj);
+                            struct json_object *l_jobj = json_tokener_parse(json_one_str);
+                            json_object_object_add(l_jobj, "Name", json_object_new_string(l_server_name));
+                            // added new json object for continent
+                            json_object_array_add(l_jobj_arr_new, l_jobj);
+                            DAP_DELETE(l_server_name);
+                            break;
+                        }
+                    }
+                }
+
+                // copy static part to common list
+                for(int i = 0; i < (int) json_object_array_length(l_jobj_arr); i++) {
+                    json_object *l_one_news = json_object_array_get_idx(l_jobj_arr, i);
+                    if(!l_one_news)
+                        continue;
+                    json_object_array_add(l_jobj_arr_new, l_one_news);
+                }
+
+                //added order state to all orders
+                {
+                    size_t l_all_orders_num = json_object_array_length(l_jobj_arr_new);
+                    for(int i = 0; i < (int) l_all_orders_num; i++) {
+                        json_object *l_one_news = json_object_array_get_idx(l_jobj_arr_new, i);
+                        json_object *l_obj = json_object_object_get(l_one_news, "NodeAddress");
+                        if(!l_obj)
+                            continue;
+                        uint64_t l_node_addr_uint64 = (uint64_t) json_object_get_int64(l_obj);
+                        // get order availability
+                        dap_chain_node_addr_t l_node_addr;
+                        l_node_addr.uint64 = l_node_addr_uint64;
+                        dap_order_state_t *l_order_state = find_order_state(l_node_addr);
+                        if(l_order_state) {
+                            // if order on-line or off-line
+                            if(l_order_state->is_available)
+                                json_object_object_add(l_one_news, "State", json_object_new_string("available"));
+                            // if order on-line
+                            else
+                                json_object_object_add(l_one_news, "State", json_object_new_string("not available"));
+                        }
+                        else
+                            json_object_object_add(l_one_news, "State", json_object_new_string("unknown"));
+                    }
+                }
+            }
+
+            // added static+dinamic part
+            const char* json_str = json_object_to_json_string(l_jobj_arr_new);
+            dap_string_append(l_reply_str, json_str);
+
+            if(l_static_nodelist)
+                l_net_processing_num++;
+            DAP_DELETE(l_static_nodelist);
+            json_object_put(l_jobj_arr);
+            json_object_put(l_jobj_arr_new);
+        }
+    }
+    // if static node lists not found
+    if(!l_net_processing_num){
+        s_http_simple_proc_default(a_http_simple, a_arg);
+        return;
+    }
+
+
+    dap_http_simple_reply( a_http_simple, l_reply_str->str, l_reply_str->len );
+    strcpy(a_http_simple->reply_mime, "application/json");
+    dap_string_free(l_reply_str, true);
+    *l_ret_code = Http_Status_OK;
+    dap_http_simple_make_cache_from_reply(a_http_simple,time(NULL)+ s_server_list_cache_expire);
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_server_list_static_create
+ * @param sh
+ * @param url
+ */
+int dap_chain_net_srv_vpn_cdb_server_list_static_create(dap_chain_net_t *a_net)
+{
+    if(!a_net) {
+        return -1;
+    }
+    // main json object - array [{...},{...},{...}]
+    struct json_object *l_jarr = json_object_new_array();
+
+    dap_chain_net_srv_order_t * l_orders = NULL;
+    size_t l_orders_num = 0;
+    dap_chain_net_srv_price_unit_uid_t l_unit_uid = { { 0 } };
+    //dap_chain_net_srv_uid_t l_srv_uid = { .uint64 = DAP_CHAIN_NET_SRV_VPN_ID };
+    dap_chain_net_srv_uid_t l_srv_uid = { .uint64 = 0x0000000000000001 };
+    dap_chain_net_srv_order_find_all_by(a_net, SERV_DIR_SELL, l_srv_uid, l_unit_uid, NULL, 0, 0, &l_orders, &l_orders_num);
+    log_it(L_DEBUG, "Found %zd orders in \"%s\" network", l_orders_num, a_net->pub.name);
+
+
+    // find the shift for each node
+    dap_chain_net_srv_order_t *l_orders_pos[l_orders_num];
+    int l_orders_available[l_orders_num];
+    size_t l_orders_size = 0;
+    for(size_t j = 0; j < l_orders_num; j++) {
+        l_orders_pos[j] = (dap_chain_net_srv_order_t*) ((char*) l_orders + l_orders_size);
+        l_orders_size += dap_chain_net_srv_order_get_size(l_orders_pos[j]);
+    }
+
+    // list of node numbers
+    size_t l_continents_count = dap_chain_net_srv_order_continents_count();
+    // list of the number of nodes in each continent
+    int l_continents_numbers[l_continents_count];
+    int l_node_numbering[l_continents_count][l_orders_num];
+    // init arrays
+    for(size_t m1 = 0; m1 < l_continents_count; m1++) {
+        l_continents_numbers[m1] = 0;
+        for(size_t m2 = 0; m2 < l_orders_num; m2++)
+            l_node_numbering[m1][m2] = -1;
+    }
+
+    // node numbering
+    size_t l_orders_used_num = 0;
+    // filling l_continents_numbers and l_node_numbering
+    for(size_t j = 0; j < l_orders_num; j++) {
+        dap_chain_net_srv_order_t *l_order = l_orders_pos[j];
+        // get order availability
+        /*            dap_order_state_t *l_order_state = find_order_state(l_order->node_addr);
+         if(l_order_state) {
+         // if order on-line or off-line
+         l_orders_available[j] = l_order_state->is_available;
+         }
+         else
+         l_orders_available[j] = -1;
+         if(l_orders_available[j] == 0)
+         continue;*/
+        uint8_t l_continent_num;
+        if(!dap_chain_net_srv_order_get_continent_region(l_order, &l_continent_num, NULL))
+            continue;
+        l_node_numbering[l_continent_num][j] = l_continents_numbers[l_continent_num]++;
+        l_orders_used_num++;
+    }
+
+    // shuffle nodes for each continent
+    for(size_t m1 = 0; m1 < l_continents_count; m1++) {
+        int l_cont_num = l_continents_numbers[m1];
+        if(l_cont_num <= 1)
+            continue;
+        // number of shuffles
+        size_t l_shuffle_num = rand() % (l_cont_num + 1);
+        for(size_t l_sh = 0; l_sh <= l_shuffle_num; l_sh++) {
+            size_t l_pos1 = 0;
+            size_t l_pos2 = 0;
+            while(l_pos1 == l_pos2) {
+                l_pos1 = rand() % l_cont_num;
+                l_pos2 = rand() % l_cont_num;
+            }
+            for(size_t m2 = 0; m2 < l_orders_num; m2++) {
+                if(l_node_numbering[m1][m2] == l_pos1)
+                    l_node_numbering[m1][m2] = l_pos2;
+                else if(l_node_numbering[m1][m2] == l_pos2)
+                    l_node_numbering[m1][m2] = l_pos1;
+            }
+        }
+    }
+
+    for(size_t l_c = 0; l_c < l_continents_count; l_c++) {
+        // print all nodes for continent
+        for(size_t l_n = 0; l_n < l_continents_numbers[l_c]; l_n++) {
+            // since the nodes are shuffled, look for the desired node index
+            for(size_t l_o = 0; l_o < l_orders_num; l_o++) {
+                if(l_node_numbering[l_c][l_o] != l_n)
+                    continue;
+                dap_chain_net_srv_order_t *l_order = l_orders_pos[l_o];
+
+                dap_chain_node_info_t * l_node_info = dap_chain_node_info_read(a_net, &l_order->node_addr);
+                if(!l_node_info)
+                    continue;
+                uint8_t l_continent_num = 0;
+                char *l_region = NULL;
+                dap_chain_net_srv_order_get_continent_region(l_order, &l_continent_num, &l_region);
+                const char *l_continent_str = dap_chain_net_srv_order_continent_to_str(l_continent_num);
+                // ext_out in hex view
+                char *l_ext_out = l_order->ext_size ? DAP_NEW_Z_SIZE(char, l_order->ext_size * 2 + 1) : NULL;
+                dap_bin2hex(l_ext_out, l_order->ext, l_order->ext_size);
+                // Order name
+                char *l_order_name = dap_strdup_printf("%s.%s.%llu", l_continent_str ? l_continent_str : "", l_region ? l_region : "",  l_n + 1);
+                // ip addresses
+                char l_node_ext_ipv4_str[INET_ADDRSTRLEN] = { 0 };
+                char l_node_ext_ipv6_str[INET6_ADDRSTRLEN] = { 0 };
+                if(l_node_info->hdr.ext_addr_v4.s_addr)
+                    inet_ntop(AF_INET, &l_node_info->hdr.ext_addr_v4, l_node_ext_ipv4_str, sizeof(l_node_ext_ipv4_str));
+                if(*((uint128_t *) l_node_info->hdr.ext_addr_v6.s6_addr))
+                    inet_ntop(AF_INET6, &l_node_info->hdr.ext_addr_v6, l_node_ext_ipv6_str,
+                            sizeof(l_node_ext_ipv6_str));
+                // ext
+                char *l_ext_out_str = dap_strdup_printf("0x%s", l_ext_out);
+
+
+                struct json_object *l_jobj = json_object_new_object();
+                json_object_array_add(l_jarr, l_jobj);
+                json_object_object_add(l_jobj, "Location", json_object_new_string(l_region ? l_region : "None"));
+                json_object_object_add(l_jobj, "ChainNet", json_object_new_string(a_net->pub.name));
+                json_object_object_add(l_jobj, "Name", json_object_new_string(l_order_name));
+                if(l_node_ext_ipv4_str[0])
+                    json_object_object_add(l_jobj, "Address", json_object_new_string(l_node_ext_ipv4_str));
+                if(l_node_ext_ipv6_str[0])
+                    json_object_object_add(l_jobj, "Address6", json_object_new_string(l_node_ext_ipv6_str));
+                json_object_object_add(l_jobj, "Port", json_object_new_int(l_node_info->hdr.ext_port ? l_node_info->hdr.ext_port : 8079));
+                json_object_object_add(l_jobj, "Ext", json_object_new_string(l_ext_out_str));
+                json_object_object_add(l_jobj, "Price", json_object_new_int64(l_order->price));
+                json_object_object_add(l_jobj, "PriceUnits", json_object_new_int(l_order->price_unit.uint32));
+                json_object_object_add(l_jobj, "PriceToken", json_object_new_string(l_order->price_ticker));
+                json_object_object_add(l_jobj, "NodeAddress", json_object_new_int64((int64_t)l_order->node_addr.uint64));
+                // order state
+                /*{
+                    dap_order_state_t *l_order_state = find_order_state(l_order->node_addr);
+                    // if order is not tested
+                    if(!l_order_state)
+                        json_object_object_add(l_jobj, "State", json_object_new_string("unknown"));
+                    // if order off-line
+                    else if(l_order_state->is_available)
+                        json_object_object_add(l_jobj, "State", json_object_new_string("available"));
+                    // if order on-line
+                    else
+                        json_object_object_add(l_jobj, "State", json_object_new_string("not available"));
+                }*/
+
+                DAP_DELETE(l_region);
+                DAP_DELETE(l_ext_out);
+                DAP_DELETE(l_order_name);
+                DAP_DELETE(l_ext_out_str);
+                break;
+            }
+        }
+    }
+
+    // get json string
+    const char* l_json_str = json_object_to_json_string(l_jarr);
+    int l_ret = 0;
+    // save to db
+    if(l_json_str) {
+        size_t l_orders_count = 0;
+        if(!dap_chain_global_db_gr_set(dap_strdup(a_net->pub.name), (void*) l_json_str, dap_strlen(l_json_str),
+                "cdb.static_nodelist")) {
+            log_it(L_DEBUG, "Error save static nodelist, %llu orders in \"%s\" network", l_orders_num, a_net->pub.name);
+            l_ret = -2;
+        }
+        else {
+            log_it(L_DEBUG, "Static nodelist saved successfully, %llu orders, \"%s\" network", l_orders_num,
+                    a_net->pub.name);
+        }
+    }
+    else {
+        log_it(L_DEBUG, "Error create static nodelist, %llu orders in \"%s\" network", l_orders_num, a_net->pub.name);
+        l_ret = -3;
+    }
+    //...
+    // free all json objects
+    json_object_put(l_jarr);
+    return l_ret;
+}
+
+
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_server_list_static_delete
+ * @param sh
+ * @param url
+ */
+
+int dap_chain_net_srv_vpn_cdb_server_list_static_delete(dap_chain_net_t *a_net)
+{
+    if(!a_net) {
+        return -1;
+    }
+    // delete from db
+    char * lgroup_str = dap_chain_net_srv_order_get_nodelist_group(a_net);
+    size_t l_orders_count = 0;
+    size_t l_static_nodelist_size = 0;
+    // check static nodelist
+    uint8_t *l_static_nodelist = dap_chain_global_db_gr_get(dap_strdup(a_net->pub.name), &l_static_nodelist_size, "cdb.static_nodelist");
+    if(!l_static_nodelist)
+        return 1;
+    else
+        DAP_DELETE(l_static_nodelist);
+    // delete static nodelist
+    if(!dap_chain_global_db_gr_del(a_net->pub.name, "cdb.static_nodelist")) {
+        log_it(L_DEBUG, "Error delete static nodelist for \"%s\" network", a_net->pub.name);
+        DAP_DELETE(lgroup_str);
+        return -2;
+    }
+    else {
+        log_it(L_DEBUG, "Static nodelist deleted successfully, \"%s\" network", a_net->pub.name);
+    }
+    DAP_DELETE(lgroup_str);
+    return 0;
+}
+
+int dap_chain_net_srv_vpn_cdb_server_list_check_orders(dap_chain_net_t * a_net)
+{
+    int l_multiplicity = 0;// no repeat callback
+    // run callback now and only one time
+    dap_timerfd_t *s_timerfd_check_orders = dap_timerfd_start(1, &callback_check_orders, DAP_INT_TO_POINTER(l_multiplicity));
+    return 0;
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_server_list_add_proc
+ * @param sh
+ * @param url
+ */
+void dap_chain_net_srv_vpn_cdb_server_list_add_proc(dap_http_t *a_http, const char *a_url)
+{
+    s_url_proc = dap_http_simple_proc_add(a_http,a_url,100000,s_http_simple_proc_default);
+    // added check of orders
+    if(dap_config_get_item_bool_default(g_config, "cdb", "servers_list_check", true)) {
+        int64_t l_timeout_ms = 0; // period between orders checks
+        int64_t l_timeout2_ms = 0; // period between checks for unavailable servers
+        uint16_t l_array_length = 0;
+        char **l_servers_list_check_periods_sec = dap_config_get_array_str(g_config, "cdb", "servers_list_check_periods", &l_array_length);
+        if(l_array_length > 0)
+            l_timeout_ms = strtoul(l_servers_list_check_periods_sec[0], NULL, 10) * 1000;
+        if(l_array_length > 1)
+            l_timeout2_ms = strtoul(l_servers_list_check_periods_sec[1], NULL, 10) * 1000;
+        // set default values ​​if necessary
+        if(!l_timeout_ms)
+            l_timeout_ms = 3600;// * 1000;
+        if(!l_timeout2_ms)
+            l_timeout2_ms = 12 * 3600;// * 1000;
+        int l_multiplicity = l_timeout2_ms / l_timeout_ms;
+        if(l_multiplicity < 1)
+            l_multiplicity = 1;
+
+        // add timer with l_timeout_ms millisecond timeout for check orders
+        dap_timerfd_t *s_timerfd_check_orders = dap_timerfd_start(l_timeout_ms, &callback_check_orders,
+                                                                  DAP_INT_TO_POINTER(l_multiplicity));
+    }
+
+}
+
+/**
+ * @brief dap_chain_net_srv_vpn_cdb_server_list_cache_reset
+ */
+void dap_chain_net_srv_vpn_cdb_server_list_cache_reset(void)
+{
+    if(s_url_proc){
+        pthread_rwlock_wrlock(&s_url_proc->cache_rwlock);
+        dap_http_cache_delete(s_url_proc->cache);
+        s_url_proc->cache = NULL;
+        pthread_rwlock_unlock(&s_url_proc->cache_rwlock);
+    }
+}
diff --git a/modules/modules_dynamic/cdb/dap_modules_dynamic_cdb.c b/modules/modules_dynamic/cdb/dap_modules_dynamic_cdb.c
index 079cd913b5732c513d22c670ed727c307a0ed3ed..bd731c985485577b5be7453dd507b44c1ab5ec15 100644
--- a/modules/modules_dynamic/cdb/dap_modules_dynamic_cdb.c
+++ b/modules/modules_dynamic/cdb/dap_modules_dynamic_cdb.c
@@ -35,7 +35,7 @@ static const char * s_default_path_modules = "var/modules";
 
 int dap_modules_dynamic_load_cdb(dap_http_t * a_server){
     char l_lib_path[MAX_PATH] = {'\0'};
-#if defined (DAP_OS_LINUX) && !defined (__ANDROID__)
+#if !defined (DAP_OS_LINUX) && !defined (__ANDROID__)
     const char * l_cdb_so_name = "libcellframe-node-cdb.so";
     dap_sprintf(l_lib_path, "%s/%s/%s", g_sys_dir_path, s_default_path_modules, l_cdb_so_name);
 
@@ -63,6 +63,8 @@ int dap_modules_dynamic_load_cdb(dap_http_t * a_server){
 
     return 0;
 #else
+    dap_chain_net_srv_vpn_cdb_init(a_server);
+    return 0;
     log_it(L_ERROR,"%s: module is not supported on current platfrom", __PRETTY_FUNCTION__);
     return -3;
 #endif
diff --git a/modules/net/dap_chain_net.c b/modules/net/dap_chain_net.c
index 18d66c704e554dee22bf5325c07f39f44ed05f23..9c5e4a39c01b14daabafe5d6f661f57c8086c7fa 100644
--- a/modules/net/dap_chain_net.c
+++ b/modules/net/dap_chain_net.c
@@ -619,7 +619,7 @@ static void s_net_state_link_prepare_success(dap_worker_t * a_worker,dap_chain_n
 {
     if(s_debug_more){
         char l_node_addr_str[INET_ADDRSTRLEN]={};
-        inet_ntop(AF_INET,&a_node_info->hdr.ext_addr_v4,l_node_addr_str,sizeof (a_node_info->hdr.ext_addr_v4));
+        inet_ntop(AF_INET,&a_node_info->hdr.ext_addr_v4,l_node_addr_str, INET_ADDRSTRLEN);
         log_it(L_DEBUG,"Link " NODE_ADDR_FP_STR " (%s) prepare success", NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address),
                                                                                      l_node_addr_str );
     }
diff --git a/modules/net/dap_chain_node_client.c b/modules/net/dap_chain_node_client.c
index 0080d96a30218adbaeeeb47a6175b7539850cfaf..d709fa4964a058f60c7f4b12641da935edc0d99d 100644
--- a/modules/net/dap_chain_node_client.c
+++ b/modules/net/dap_chain_node_client.c
@@ -165,40 +165,16 @@ static void s_stage_status_error_callback(dap_client_t *a_client, void *a_arg)
         pthread_mutex_unlock(&l_node_client->wait_mutex);
         l_node_client->own_esh.esocket = 0;
 
-        dap_chain_node_client_handle_t * l_client_handle = DAP_NEW_Z(dap_chain_node_client_handle_t);
-        l_client_handle->uuid = l_node_client->uuid;
-        l_client_handle->client = l_node_client;
-
-        dap_timerfd_start_on_worker(dap_events_worker_get_auto(),s_timer_update_states*1000,s_timer_update_states_callback, l_client_handle);
-
+        if (l_node_client->keep_connection) {
+            uint128_t *l_uuid = DAP_NEW(uint128_t);
+            memcpy(l_uuid, &l_node_client->uuid, sizeof(uint128_t));
+            dap_timerfd_start_on_worker(dap_events_worker_get_auto(),s_timer_update_states*1000,s_timer_update_states_callback, l_uuid);
+        }
         return;
     }
-
-    if(l_node_client && l_node_client->keep_connection &&
-            ((dap_client_get_stage(a_client) != STAGE_STREAM_STREAMING) ||
-                    (dap_client_get_stage_status(a_client) == STAGE_STATUS_ERROR))) {
-        log_it(L_NOTICE,"Some errors happends, current state is %s but we need to return back to STAGE_STREAM_STREAMING",
-                dap_client_get_stage_str(a_client) );
-
-        // TODO make different error codes
-        if(l_node_client->callbacks.error)
-            l_node_client->callbacks.error(l_node_client, EINVAL,l_node_client->callbacks_arg );
-
-        if (s_stream_ch_chain_debug_more)
-            log_it(L_DEBUG, "Wakeup all who waits");
-        pthread_mutex_lock(&l_node_client->wait_mutex);
-        l_node_client->state = NODE_CLIENT_STATE_ERROR;
-
-#ifndef _WIN32
-        pthread_cond_broadcast(&l_node_client->wait_cond);
-#else
-        SetEvent( l_node_client->wait_cond );
-#endif
-        pthread_mutex_unlock(&l_node_client->wait_mutex);
-        //dap_client_go_stage( a_client , STAGE_STREAM_STREAMING, s_stage_end_callback );
-    }
-
-    //printf("* tage_status_error_callback client=%x data=%x\n", a_client, a_arg);
+    // TODO make different error codes
+    if(l_node_client->callbacks.error)
+        l_node_client->callbacks.error(l_node_client, EINVAL,l_node_client->callbacks_arg );
 }
 
 /**
@@ -208,16 +184,17 @@ static void s_stage_status_error_callback(dap_client_t *a_client, void *a_arg)
  */
 static bool s_timer_update_states_callback(void * a_arg )
 {
-    dap_chain_node_client_handle_t * l_client_handle = (dap_chain_node_client_handle_t *) a_arg, *l_client_found = NULL;
-    assert(l_client_handle);
-    HASH_FIND(hh,s_clients,&l_client_handle->uuid,sizeof(l_client_handle->uuid),l_client_found);
-    if(! l_client_found){
-        log_it(L_DEBUG,"Chain node client %p was deleted before timer fired, nothing to do", l_client_handle->client);
-        DAP_DELETE(l_client_handle);
+    dap_chain_node_client_handle_t *l_client_found = NULL;
+    uint128_t *l_uuid = (uint128_t *)a_arg;
+    assert(l_uuid);
+    HASH_FIND(hh, s_clients, l_uuid, sizeof(*l_uuid), l_client_found);
+    if(!l_client_found){
+        log_it(L_DEBUG,"Chain node client %p was deleted before timer fired, nothing to do", l_uuid);
+        DAP_DELETE(l_uuid);
         return false;
     }
 
-    dap_chain_node_client_t *l_me = l_client_handle->client;
+    dap_chain_node_client_t *l_me = l_client_found->client;
     dap_worker_t * l_worker = dap_events_get_current_worker(dap_events_get_default());
     assert(l_worker);
     assert(l_me);
@@ -251,7 +228,7 @@ static bool s_timer_update_states_callback(void * a_arg )
                                                                         l_chain_id.uint64, l_net->pub.cell_id.uint64,
                                                               &l_sync_gdb, sizeof(l_sync_gdb));
                     }
-                    DAP_DELETE(l_client_handle);
+                    DAP_DELETE(l_uuid);
                     return true;
                 }
             }
@@ -263,7 +240,7 @@ static bool s_timer_update_states_callback(void * a_arg )
         log_it(L_INFO, "Reconnecting node client with peer "NODE_ADDR_FP_STR, NODE_ADDR_FP_ARGS_S(l_me->remote_node_addr));
         dap_chain_node_client_connect_internal(l_me, "CN"); // isn't always CN here?
     }
-    DAP_DELETE(l_client_handle);
+    DAP_DELETE(l_uuid);
     return false;
 }
 
@@ -291,7 +268,6 @@ static void s_stage_connected_callback(dap_client_t *a_client, void *a_arg)
         }
         if(l_node_client->callbacks.connected)
             l_node_client->callbacks.connected(l_node_client, l_node_client->callbacks_arg);
-        l_node_client->keep_connection = true;
         if(s_stream_ch_chain_debug_more)
             log_it(L_DEBUG, "Wakeup all who waits");
         l_node_client->state = NODE_CLIENT_STATE_ESTABLISHED;
@@ -300,11 +276,11 @@ static void s_stage_connected_callback(dap_client_t *a_client, void *a_arg)
         if (l_stream) {
             l_node_client->own_esh.esocket = l_stream->esocket;
             l_node_client->own_esh.uuid = l_stream->esocket->uuid;
-            dap_chain_node_client_handle_t * l_client_handle = DAP_NEW_Z(dap_chain_node_client_handle_t);
-            l_client_handle->uuid = l_node_client->uuid;
-            l_client_handle->client = l_node_client;
-
-            dap_timerfd_start_on_worker(l_stream->esocket->worker,s_timer_update_states*1000,s_timer_update_states_callback, l_client_handle);
+            if (l_node_client->keep_connection) {
+                uint128_t *l_uuid = DAP_NEW(uint128_t);
+                memcpy(l_uuid, &l_node_client->uuid, sizeof(uint128_t));
+                dap_timerfd_start_on_worker(l_stream->esocket->worker,s_timer_update_states*1000,s_timer_update_states_callback, l_uuid);
+            }
         }
 #ifndef _WIN32
         pthread_cond_broadcast(&l_node_client->wait_cond);
@@ -624,6 +600,10 @@ dap_chain_node_client_t* dap_chain_node_client_create_n_connect(dap_chain_net_t
     l_node_client->info = a_node_info;
     l_node_client->net = a_net;
     l_node_client->uuid = dap_uuid_generate_uint128();
+    dap_chain_node_client_handle_t * l_client_handle = DAP_NEW_Z(dap_chain_node_client_handle_t);
+    l_client_handle->uuid = l_node_client->uuid;
+    l_client_handle->client = l_node_client;
+    HASH_ADD(hh, s_clients, uuid, sizeof(l_client_handle->uuid), l_client_handle);
 
 #ifndef _WIN32
     pthread_condattr_t attr;
@@ -649,6 +629,7 @@ static bool dap_chain_node_client_connect_internal(dap_chain_node_client_t *a_no
 {
     a_node_client->client = dap_client_new(a_node_client->events, s_stage_status_callback,
             s_stage_status_error_callback);
+    a_node_client->keep_connection = true;
     dap_client_set_is_always_reconnect(a_node_client->client, true);
     a_node_client->client->_inheritor = a_node_client;
     dap_client_set_active_channels_unsafe(a_node_client->client, a_active_channels);
@@ -708,6 +689,9 @@ void dap_chain_node_client_reset(dap_chain_node_client_t *a_client)
 void dap_chain_node_client_close(dap_chain_node_client_t *a_client)
 {
     if (a_client && a_client->client) { // block tryes to close twice
+        char l_node_addr_str[INET_ADDRSTRLEN] = {};
+        inet_ntop(AF_INET, &a_client->info->hdr.ext_addr_v4, l_node_addr_str, INET_ADDRSTRLEN);
+        log_it(L_INFO, "Closing node client to uplink %s:%d", l_node_addr_str, a_client->info->hdr.ext_port);
         // clean client
         dap_client_delete_mt(a_client->client);
 #ifndef _WIN32
@@ -763,7 +747,7 @@ int dap_chain_node_client_wait(dap_chain_node_client_t *a_client, int a_waited_s
         log_it(L_ERROR, "Can't wait for status for (null) object");
         return -3;
     }
-
+    a_client->keep_connection = false;
     pthread_mutex_lock(&a_client->wait_mutex);
     // have waited
     if(a_client->state == a_waited_state) {