/*
 * Authors:
 * Alexander Lysikov <alexander.lysikov@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * Kelvin Project https://github.com/kelvinblockchain
 * Copyright  (c) 2019
 * All rights reserved.

 This file is part of DAP (Deus Applications Prototypes) the open source project

 DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 DAP is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdbool.h>
#include <stddef.h>
#include <pthread.h>

#include "dap_common.h"
#include "dap_enc_base58.h"
#include "dap_strfuncs.h"
#include "dap_string.h"
#include "dap_list.h"
#include "dap_hash.h"

#include "dap_chain_wallet.h"
#include "dap_chain_datum.h"
#include "dap_chain_datum_token.h"
#include "dap_chain_datum_tx_items.h"
#include "dap_chain_node_cli.h"
#include "dap_chain_node_cli_cmd_tx.h"

#define LOG_TAG "chain_node_cli_cmd_tx"

#include "uthash.h"
// for dap_db_history_filter()
typedef struct dap_tx_data {
    dap_chain_hash_fast_t tx_hash;
    char tx_hash_str[70];
    char token_ticker[DAP_CHAIN_TICKER_SIZE_MAX];
    //size_t obj_num;
    size_t pos_num;
    dap_chain_datum_t *datum;
    dap_chain_addr_t addr;
    bool is_use_all_cur_out;// find cur addr in prev OUT items
    UT_hash_handle hh;
} dap_tx_data_t;

typedef struct dap_chain_tx_hash_processed_ht{
    dap_chain_hash_fast_t hash;
    UT_hash_handle hh;
}dap_chain_tx_hash_processed_ht_t;
void _dap_chain_tx_hash_processed_ht_free(dap_chain_tx_hash_processed_ht_t *l_hash_processed){
    dap_chain_tx_hash_processed_ht_t *l_tmp;
    dap_chain_tx_hash_processed_ht_t *l_current_hash;
    HASH_ITER(hh, l_hash_processed, l_current_hash, l_tmp){
        DAP_FREE(&l_current_hash->hash);
        DAP_FREE(l_current_hash);
    }
    DAP_FREE(l_hash_processed);
}

void _dap_chain_datum_tx_out_data(dap_chain_datum_tx_t *a_datum,
                                  dap_ledger_t *a_ledger,
                                  dap_string_t *a_str_out,
                                  const char *a_hash_out_type,
                                  bool save_processed_tx,
                                  dap_chain_tx_hash_processed_ht_t **a_tx_hash_processed,
                                  size_t *l_tx_num){
    dap_chain_hash_fast_t l_tx_hash;
    dap_hash_fast(a_datum, dap_chain_datum_tx_get_size(a_datum), &l_tx_hash);
    if (save_processed_tx){
        dap_chain_tx_hash_processed_ht_t *l_sht = NULL;
        HASH_FIND(hh, *a_tx_hash_processed, &l_tx_hash, sizeof(dap_chain_hash_fast_t), l_sht);
        if (l_sht != NULL)
            return;
        l_sht = DAP_NEW_Z(dap_chain_tx_hash_processed_ht_t);
        memcpy(&l_sht->hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
        HASH_ADD(hh, *a_tx_hash_processed, hash, sizeof(dap_chain_hash_fast_t), l_sht);
        (*l_tx_num)++;
    }
    char *l_tx_hash_user_str;
    char l_tx_hash_str[70];
    dap_chain_hash_fast_to_str(&l_tx_hash, l_tx_hash_str, 70);
    time_t l_ts_create = (time_t)a_datum->header.ts_created;
    if(!dap_strcmp(a_hash_out_type, "hex"))
        l_tx_hash_user_str = dap_strdup(l_tx_hash_str);
    else
        l_tx_hash_user_str = dap_enc_base58_from_hex_str_to_str(l_tx_hash_str);
    dap_list_t *l_list_tx_any = dap_chain_datum_tx_items_get(a_datum, TX_ITEM_TYPE_TOKEN, NULL);
    if(a_ledger == NULL){
        dap_string_append_printf(a_str_out, "transaction: %s hash: %s\n Items:\n", l_list_tx_any ? "(emit)" : "", l_tx_hash_user_str);
    } else {
        char buf[50];
        dap_string_append_printf(a_str_out, "transaction: %s hash: %s\n TS Created: %s Token ticker: %s\n Items:\n",
                                 l_list_tx_any ? "(emit)" : "", l_tx_hash_user_str, dap_ctime_r(&l_ts_create, buf),
                                 dap_chain_ledger_tx_get_token_ticker_by_hash(a_ledger, &l_tx_hash));
    }
    DAP_DELETE(l_tx_hash_user_str);
    dap_list_free(l_list_tx_any);
    uint32_t l_tx_items_count = 0;
    uint32_t l_tx_items_size =a_datum->header.tx_items_size;
    char *l_hash_str_tmp = NULL;
    char l_tmp_buf[70];
    dap_sign_t *l_sign_tmp;
    dap_chain_hash_fast_t l_pkey_hash_tmp;
    dap_pkey_t *l_pkey_tmp;
    while(l_tx_items_count < l_tx_items_size){
        uint8_t *item = a_datum->tx_items + l_tx_items_count;
        size_t l_item_tx_size = dap_chain_datum_item_tx_get_size(item);
        switch(dap_chain_datum_tx_item_get_type(item)){
        case TX_ITEM_TYPE_IN:
            l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&((dap_chain_tx_in_t*)item)->header.tx_prev_hash);
            dap_string_append_printf(a_str_out, "\t IN:\nTx_prev_hash: %s\n"
                                                "\t\t Tx_out_prev_idx: %u\n",
                                        l_tx_hash_str,
                                        ((dap_chain_tx_in_t*)item)->header.tx_out_prev_idx);
            DAP_DELETE(l_hash_str_tmp);
            break;
        case TX_ITEM_TYPE_OUT:
            dap_string_append_printf(a_str_out, "\t OUT:\n"
                                                "\t\t Value: %s (%"DAP_UINT64_FORMAT_u")\n"
                                                "\t\t Address: %s\n",
                                        dap_chain_balance_to_coins(dap_chain_uint128_from(
                                                                       ((dap_chain_tx_out_t*)item)->header.value)
                                                                   ),
                                        ((dap_chain_tx_out_t*)item)->header.value,
                                        dap_chain_addr_to_str(&((dap_chain_tx_out_t*)item)->addr));
            break;
        case TX_ITEM_TYPE_TOKEN:
            l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&((dap_chain_tx_token_t*)item)->header.token_emission_hash);
            dap_string_append_printf(a_str_out, "\t TOKEN:\n"
                                                "\t\t ticker: %s \n"
                                                "\t\t token_emission_hash: %s\n"
                                                "\t\t token_emission_chain_id: 0x%016"DAP_UINT64_FORMAT_x"\n", ((dap_chain_tx_token_t*)item)->header.ticker, l_hash_str_tmp,
                                        ((dap_chain_tx_token_t*)item)->header.token_emission_chain_id.uint64);
            DAP_DELETE(l_hash_str_tmp);
            break;
        case TX_ITEM_TYPE_TOKEN_EXT:
            l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&((dap_chain_tx_token_ext_t*)item)->header.ext_tx_hash);
            dap_string_append_printf(a_str_out, "\t TOKEN EXT:\n"
                                         "\t\t Version: %u\n"
                                         "\t\t Ticker: %s\n"
                                         "\t\t Ext chain id: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                         "\t\t Ext net id: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                         "\t\t Ext tx hash: %s\n"
                                         "\t\t Ext tx out idx: %u\n",
                                     ((dap_chain_tx_token_ext_t*)item)->header.version,
                                     ((dap_chain_tx_token_ext_t*)item)->header.ticker,
                                     ((dap_chain_tx_token_ext_t*)item)->header.ext_chain_id.uint64,
                                     ((dap_chain_tx_token_ext_t*)item)->header.ext_net_id.uint64,
                                     l_hash_str_tmp,
                                     ((dap_chain_tx_token_ext_t*)item)->header.ext_tx_out_idx);
            DAP_FREE(l_hash_str_tmp);
            break;
        case TX_ITEM_TYPE_SIG:
            l_sign_tmp = dap_chain_datum_tx_item_sign_get_sig((dap_chain_tx_sig_t*)item);
            dap_string_append_printf(a_str_out, "\t SIG:\n"
                                                "\t sig_size: %u\n", ((dap_chain_tx_sig_t*)item)->header.sig_size);
            dap_sign_get_information(l_sign_tmp, a_str_out);
            break;
        case TX_ITEM_TYPE_RECEIPT:
            dap_string_append_printf(a_str_out, "\t Receipt:\n"
                                                "\t\t size: %u\n"
                                                "\t\t ext size:%u\n"
                                                "\t\t Info:"
                                                "\t\t\t   units: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                "\t\t\t   uid: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                "\t\t\t   units type: %s \n"
                                                "\t\t\t   value: %s (%"DAP_UINT64_FORMAT_u")\n",
                                     ((dap_chain_datum_tx_receipt_t*)item)->size,
                                     ((dap_chain_datum_tx_receipt_t*)item)->exts_size,
                                     ((dap_chain_datum_tx_receipt_t*)item)->receipt_info.units,
                                     ((dap_chain_datum_tx_receipt_t*)item)->receipt_info.srv_uid.uint64,
                                     serv_unit_enum_to_str(
                                         &((dap_chain_datum_tx_receipt_t*)item)->receipt_info.units_type.enm
                                         ),
                                     dap_chain_balance_to_coins(
                                         dap_chain_uint128_from(
                                             ((dap_chain_datum_tx_receipt_t*)item)->receipt_info.value_datoshi
                                         )
                                     ),
                                     ((dap_chain_datum_tx_receipt_t*)item)->receipt_info.value_datoshi);
            if (((dap_chain_datum_tx_receipt_t*)item)->exts_size == sizeof(dap_sign_t) + sizeof(dap_sign_t)){
                dap_sign_t *l_provider = DAP_NEW_Z(dap_sign_t);
                memcpy(l_provider, ((dap_chain_datum_tx_receipt_t*)item)->exts_n_signs, sizeof(dap_sign_t));
                dap_sign_t *l_client = DAP_NEW_Z(dap_sign_t);
                memcpy(l_client,
                       ((dap_chain_datum_tx_receipt_t*)item)->exts_n_signs + sizeof(dap_sign_t),
                       sizeof(dap_sign_t));
                dap_string_append_printf(a_str_out, "Exts:\n"
                                                    "   Provider:\n");
                dap_sign_get_information(l_provider, a_str_out);
                dap_string_append_printf(a_str_out, "   Client:\n");
                dap_sign_get_information(l_client, a_str_out);
            } else if (((dap_chain_datum_tx_receipt_t*)item)->exts_size == sizeof(dap_sign_t)) {
                dap_sign_t *l_provider = DAP_NEW_Z(dap_sign_t);
                memcpy(l_provider, ((dap_chain_datum_tx_receipt_t*)item)->exts_n_signs, sizeof(dap_sign_t));
                dap_string_append_printf(a_str_out, "Exts:\n"
                                                    "   Provider:\n");
                dap_sign_get_information(l_provider, a_str_out);
            }
            break;
        case TX_ITEM_TYPE_PKEY:
            l_pkey_tmp = (dap_pkey_t*)((dap_chain_tx_pkey_t*)item)->pkey;
            dap_hash_fast(l_pkey_tmp->pkey, l_pkey_tmp->header.size, &l_pkey_hash_tmp);
            l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&l_pkey_hash_tmp);
            dap_string_append_printf(a_str_out, "\t PKey: \n",
                                                "\t\t SIG type: %s\n"
                                                "\t\t SIG size: %i\n"
                                                "\t\t Sequence number: %i \n"
                                                "\t\t Key: \n"
                                                "\t\t\t Type: %s\n"
                                                "\t\t\t Size: %u\n"
                                                "\t\t\t Hash: %s\n",
                                     dap_sign_type_to_str(((dap_chain_tx_pkey_t*)item)->header.sig_type),
                                     ((dap_chain_tx_pkey_t*)item)->header.sig_size,
                                     ((dap_chain_tx_pkey_t*)item)->seq_no,
                                     dap_pkey_type_to_str(l_pkey_tmp->header.type),
                                     l_pkey_tmp->header.size,
                                     l_hash_str_tmp);
            DAP_FREE(l_hash_str_tmp);
            break;
        case TX_ITEM_TYPE_IN_COND:
            l_hash_str_tmp = dap_chain_hash_fast_to_str_new(&((dap_chain_tx_in_t*)item)->header.tx_prev_hash);
            dap_string_append_printf(a_str_out, "\t IN COND:\n\t\tReceipt_idx: %u\n"
                                                "\t\t Tx_prev_hash: %s\n"
                                                "\t\t Tx_out_prev_idx: %u\n",
                                     ((dap_chain_tx_in_cond_t*)item)->header.receipt_idx,
                                     l_hash_str_tmp,
                                     ((dap_chain_tx_in_cond_t*)item)->header.tx_out_prev_idx);
            DAP_FREE(l_hash_str_tmp);
            break;
        case TX_ITEM_TYPE_OUT_COND:
            dap_string_append_printf(a_str_out, "\t OUT COND:\n"
                                                "\t Header:\n"
                                                "\t\t\t ts_expires: %s\t"
                                                "\t\t\t value: %s (%"DAP_UINT64_FORMAT_u")\n"
                                                "\t\t\t subtype: %s\n"
                                                "\t\t SubType:\n",
                                     dap_ctime_r((time_t*)((dap_chain_tx_out_cond_t*)item)->header.ts_expires, l_tmp_buf),
                                     dap_chain_balance_to_coins(
                                         dap_chain_uint128_from(((dap_chain_tx_out_cond_t*)item)->header.value)
                                     ),
                                     ((dap_chain_tx_out_cond_t*)item)->header.value,
                                     dap_chain_tx_out_cond_subtype_to_str(((dap_chain_tx_out_cond_t*)item)->header.subtype));
            switch (((dap_chain_tx_out_cond_t*)item)->header.subtype) {
            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY:
                l_hash_str_tmp = dap_hash_fast_to_str_new(&((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.pkey_hash);
                dap_string_append_printf(a_str_out, "\t\t\t unit: 0x%08x\n"
                                                    "\t\t\t uid: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                    "\t\t\t pkey: %s\n"
                                                    "\t\t\t max price: %s (%"DAP_UINT64_FORMAT_u") \n",
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.unit.uint32,
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.srv_uid.uint64,
                                         l_hash_str_tmp,
                                         dap_chain_balance_to_coins(dap_chain_uint128_from(
                                                ((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.unit_price_max_datoshi)
                                                                    ),
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_pay.unit_price_max_datoshi);
                DAP_FREE(l_hash_str_tmp);
                break;
            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE:
                dap_string_append_printf(a_str_out, "\t\t\t uid:\n"
                                                    "\t\t\t addr:\n"
                                                    "\t\t\t value: %ld",
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_stake.srv_uid.uint64,
                                         dap_chain_addr_to_str(
                                             &((dap_chain_tx_out_cond_t*)item)->subtype.srv_stake.fee_addr
                                             ),
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_stake.fee_value);
                break;
            case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE:
                dap_string_append_printf(a_str_out, "\t\t\t uid: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                    "\t\t\t net id: 0x%016"DAP_UINT64_FORMAT_x"\n"
                                                    "\t\t\t token: %s\n"
                                                    "\t\t\t value: %s (%"DAP_UINT64_FORMAT_u")\n",
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.srv_uid.uint64,
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.net_id.uint64,
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.token,
                                         dap_chain_balance_to_coins(
                                             dap_chain_uint128_from(
                                                 ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.value
                                                 )
                                             ),
                                         ((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.value);
                break;
            }
            break;
        case TX_ITEM_TYPE_OUT_EXT:
            dap_string_append_printf(a_str_out, "\t OUT EXT:\n"
                                                "\t\t Addr: %s\n"
                                                "\t\t Token: %s\n"
                                                "\t\t Value: %s (%"DAP_UINT64_FORMAT_u")\n",
                                     dap_chain_addr_to_str(&((dap_chain_tx_out_ext_t*)item)->addr),
                                     ((dap_chain_tx_out_ext_t*)item)->token,
                                     dap_chain_balance_to_coins(dap_chain_uint128_from(
                                                                    ((dap_chain_tx_out_ext_t*)item)->header.value)
                                                                ),
                                     ((dap_chain_tx_out_ext_t*)item)->header.value);
            break;
        default:
            dap_string_append_printf(a_str_out, " This transaction have unknown item type \n");
            break;
        }
        l_tx_items_count += l_item_tx_size;

    }

    dap_string_append_printf(a_str_out, "\n");
}

/*static char* dap_db_new_history_timestamp()
{
    static pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER;
    // get unique key
    pthread_mutex_lock(&s_mutex);
    static time_t s_last_time = 0;
    static uint64_t s_suffix = 0;
    time_t l_cur_time = time(NULL);
    if(s_last_time == l_cur_time)
        s_suffix++;
    else {
        s_suffix = 0;
        s_last_time = l_cur_time;
    }
    char *l_str = dap_strdup_printf("%lld_%lld", (uint64_t) l_cur_time, s_suffix);
    pthread_mutex_unlock(&s_mutex);
    return l_str;
}*/

// for dap_db_history_tx & dap_db_history_addr()
static dap_chain_datum_t* get_prev_tx(dap_tx_data_t *a_tx_data)
{
    if(!a_tx_data)
        return NULL;
    dap_chain_datum_t *l_datum = a_tx_data->datum;
    return l_datum;
}

/**
 * Get data according the history log
 *
 * return history string
 */
char* dap_db_history_tx(dap_chain_hash_fast_t* a_tx_hash, dap_chain_t * a_chain, const char *a_hash_out_type)
{
    dap_string_t *l_str_out = dap_string_new(NULL);

    bool l_tx_hash_found = false;
    dap_tx_data_t *l_tx_data_hash = NULL;
    // load transactions
    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
    size_t l_atom_size = 0;
    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size);

    while(l_atom && l_atom_size) {
        dap_chain_datum_t *l_datum = (dap_chain_datum_t*) l_atom;
        if(!l_datum && l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
            // go to next transaction
            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
            continue;
        }
        dap_tx_data_t *l_tx_data = NULL;

        // transaction
        dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;

        // find Token items - present in emit transaction
        dap_list_t *l_list_tx_token = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);

        // find OUT items
        dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
        dap_list_t *l_list_tmp = l_list_out_items;
        while(l_list_tmp) {
            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
            // save OUT item l_tx_out - only for first OUT item
            if(!l_tx_data)
            {
                // save tx hash
                l_tx_data = DAP_NEW_Z(dap_tx_data_t);
                dap_chain_hash_fast_t l_tx_hash;
                dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
                memcpy(&l_tx_data->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
                memcpy(&l_tx_data->addr, &l_tx_out->addr, sizeof(dap_chain_addr_t));
                dap_chain_hash_fast_to_str(&l_tx_data->tx_hash, l_tx_data->tx_hash_str,
                        sizeof(l_tx_data->tx_hash_str));
                //l_tx_data->pos_num = l_count;
                //l_tx_data->datum = l_datum;
                l_tx_data->datum = DAP_NEW_SIZE(dap_chain_datum_t, l_atom_size);
                memcpy(l_tx_data->datum, l_datum, l_atom_size);
                // save token name
                if(l_list_tx_token) {
                    dap_chain_tx_token_t *tk = l_list_tx_token->data;
                    memcpy(l_tx_data->token_ticker, tk->header.ticker, sizeof(l_tx_data->token_ticker));
                }
                // take token from prev out item
                else {

                    // find IN items
                    dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
                    dap_list_t *l_list_tmp_in = l_list_in_items;
                    // find token_ticker in prev OUT items
                    while(l_list_tmp_in) {
                        const dap_chain_tx_in_t *l_tx_in =
                                (const dap_chain_tx_in_t*) l_list_tmp_in->data;
                        dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;

                        //find prev OUT item
                        dap_tx_data_t *l_tx_data_prev = NULL;
                        HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t),
                                l_tx_data_prev);
                        if(l_tx_data_prev != NULL) {
                            // fill token in l_tx_data from prev transaction
                            if(l_tx_data) {
                                // get token from prev tx
                                memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
                                        sizeof(l_tx_data->token_ticker));
                                break;
                            }
                            l_list_tmp_in = dap_list_next(l_list_tmp_in);
                        }
                    }
                    if(l_list_in_items)
                        dap_list_free(l_list_in_items);
                }
                HASH_ADD(hh, l_tx_data_hash, tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_data);
            }
            l_list_tmp = dap_list_next(l_list_tmp);
        }
        if(l_list_out_items)
            dap_list_free(l_list_out_items);

        // calc hash
        dap_chain_hash_fast_t l_tx_hash;
        dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
        // search tx with a_tx_hash
        if(!dap_hash_fast_compare(a_tx_hash, &l_tx_hash)) {
            // go to next transaction
            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
            continue;
        }
        // found a_tx_hash now

        // transaction time
        if(l_tx->header.ts_created > 0) {
            time_t rawtime = (time_t) l_tx->header.ts_created;
            struct tm l_timeinfo = {0};
            localtime_r(&rawtime, &l_timeinfo);
            dap_string_append_printf(l_str_out, " %s", asctime(&l_timeinfo));
        }

        // find all OUT items in transaction
        l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
        l_list_tmp = l_list_out_items;
        while(l_list_tmp) {
            const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
            //dap_tx_data_t *l_tx_data_prev = NULL;

            const char *l_token_str = NULL;
            if(l_tx_data)
                l_token_str = l_tx_data->token_ticker;
            char *l_dst_to_str =
                    (l_tx_out) ? dap_chain_addr_to_str(&l_tx_out->addr) :
                    NULL;
            if(l_tx_out)
                dap_string_append_printf(l_str_out, " OUT item %lld %s to %s\n",
                    l_tx_out->header.value,
                    dap_strlen(l_token_str) > 0 ? l_token_str : "?",
                    l_dst_to_str ? l_dst_to_str : "?"
                                   );
            DAP_DELETE(l_dst_to_str);
            l_list_tmp = dap_list_next(l_list_tmp);
        }
        // find all IN items in transaction
        dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
        l_list_tmp = l_list_in_items;
        // find cur addr in prev OUT items
        while(l_list_tmp) {
            const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_tmp->data;
            dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
            char l_tx_hash_str[70];
            char *tx_hash_base58_str = NULL;
            if(!dap_hash_fast_is_blank(&tx_prev_hash)){
                tx_hash_base58_str = dap_enc_base58_from_hex_str_to_str( l_tx_data->tx_hash_str);
                dap_chain_hash_fast_to_str(&tx_prev_hash, l_tx_hash_str, sizeof(l_tx_hash_str));
            }
            else{
                strcpy(l_tx_hash_str, "Null");
                tx_hash_base58_str = dap_strdup("Null");
            }
            if(!dap_strcmp(a_hash_out_type,"hex"))
                dap_string_append_printf(l_str_out, " IN item \n  prev tx_hash %s\n", l_tx_hash_str);
            else
                dap_string_append_printf(l_str_out, " IN item \n  prev tx_hash %s\n", tx_hash_base58_str);
            DAP_DELETE(tx_hash_base58_str);

            //find prev OUT item
            dap_tx_data_t *l_tx_data_prev = NULL;
            HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
            if(l_tx_data_prev != NULL) {

                dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
                dap_chain_datum_tx_t *l_tx_prev =
                        l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;

                // find OUT items in prev datum
                dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
                        TX_ITEM_TYPE_OUT, NULL);
                // find OUT item for IN item;
                dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
                        l_tx_in->header.tx_out_prev_idx);
                dap_chain_tx_out_t *l_tx_prev_out =
                        l_list_out_prev_item ?
                                               (dap_chain_tx_out_t*) l_list_out_prev_item->data :
                                               NULL;
                // print value from prev out item
                dap_string_append_printf(l_str_out, "  prev OUT item value=%lld",
                        l_tx_prev_out ? l_tx_prev_out->header.value : 0);
            }
            dap_string_append_printf(l_str_out, "\n");
            l_list_tmp = dap_list_next(l_list_tmp);
        }

        if(l_list_tx_token)
            dap_list_free(l_list_tx_token);
        if(l_list_out_items)
            dap_list_free(l_list_out_items);
        if(l_list_in_items)
            dap_list_free(l_list_in_items);
        l_tx_hash_found = true;
        break;

        // go to next transaction
        //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
        //l_atom_size = a_chain->callback_atom_get_size(l_atom);
    }
    a_chain->callback_atom_iter_delete(l_atom_iter);

    // delete hashes
    dap_tx_data_t *l_iter_current, *l_item_tmp;
    HASH_ITER(hh, l_tx_data_hash , l_iter_current, l_item_tmp)
    {
        HASH_DEL(l_tx_data_hash, l_iter_current);
        // delete datum
        DAP_DELETE(l_iter_current->datum);
        // delete struct
        DAP_DELETE(l_iter_current);
    }

    // if no history
    if(!l_str_out->len)
        dap_string_append(l_str_out, "empty");
    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
    return l_ret_str;
}

/**
 * Get data according the history log
 *
 * return history string
 */
char* dap_db_history_addr(dap_chain_addr_t * a_addr, dap_chain_t * a_chain, const char *a_hash_out_type)
{
    dap_string_t *l_str_out = dap_string_new(NULL);

    dap_tx_data_t *l_tx_data_hash = NULL;
    // load transactions
    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
    size_t l_atom_size=0;
    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size);
    if (!l_atom) {
        return NULL;
    }

    while(l_atom && l_atom_size) {
        size_t l_datums_count =0;
        dap_chain_datum_t **l_datums = a_chain->callback_atom_get_datums ? a_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count) :
                                                                          NULL;
        if (! l_datums){
            log_it(L_WARNING,"Not defined callback_atom_get_datums for chain \"%s\"", a_chain->name);
            break;
        }

        for (size_t d=0; d< l_datums_count; d++){
            dap_chain_datum_t *l_datum = l_datums && l_datums_count ? l_datums[d] :NULL;
            if(!l_datum || l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
                // go to next transaction
                l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
                continue;
            }
            // transaction
            dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;
            dap_list_t *l_records_out = NULL;
            // transaction time
            char *l_time_str = NULL;
            {
                if(l_tx->header.ts_created > 0) {
                    time_t rawtime = (time_t) l_tx->header.ts_created;
                    struct tm * timeinfo;
                    timeinfo = localtime(&rawtime);
                    if(timeinfo)
                        l_time_str = dap_strdup(asctime(timeinfo));
                }
                else
                    l_time_str = dap_strdup(" ");
            }

            // find Token items - present in emit transaction
            dap_list_t *l_list_tx_token = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);

            // list of dap_tx_data_t*; info about OUT item in current transaction
            dap_list_t *l_list_out_info = NULL;

            // find OUT items
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
            dap_list_t *l_list_out_items_tmp = l_list_out_items;
            while(l_list_out_items_tmp) {
                const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_out_items_tmp->data;
                // save OUT item l_tx_out
                {
                    // save tx hash
                    // info about OUT item in current transaction
                    dap_tx_data_t *l_tx_data = DAP_NEW_Z(dap_tx_data_t);
                    dap_chain_hash_fast_t l_tx_hash;
                    dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
                    memcpy(&l_tx_data->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
                    memcpy(&l_tx_data->addr, &l_tx_out->addr, sizeof(dap_chain_addr_t));
                    dap_chain_hash_fast_to_str(&l_tx_data->tx_hash, l_tx_data->tx_hash_str, sizeof(l_tx_data->tx_hash_str));
                    l_tx_data->datum = DAP_NEW_SIZE(dap_chain_datum_t, l_atom_size);
                    memcpy(l_tx_data->datum, l_datum, l_atom_size);
                    // save token name
                    if(l_tx_data && l_list_tx_token) {
                        dap_chain_tx_token_t *tk = l_list_tx_token->data;
                        memcpy(l_tx_data->token_ticker, tk->header.ticker, sizeof(l_tx_data->token_ticker));
                    }
                    HASH_ADD(hh, l_tx_data_hash, tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_data);

                    // save OUT items to list
                    l_records_out = dap_list_append(l_records_out, (void*) l_tx_out);
                    // save info about OUT items to list
                    l_list_out_info = dap_list_append(l_list_out_info, (void*) l_tx_data);
                }
                l_list_out_items_tmp = dap_list_next(l_list_out_items_tmp);
            }

            // find IN items
            dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
            dap_list_t *l_list_in_items_tmp = l_list_in_items;
            // find cur addr in prev OUT items
            //bool l_is_use_all_cur_out = false;
            {
                while(l_list_in_items_tmp) {
                    const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_in_items_tmp->data;
                    dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;

                    //find prev OUT item
                    dap_tx_data_t *l_tx_data_prev = NULL;
                    HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
                    if(l_tx_data_prev != NULL) {
                        // fill token in all l_tx_data from prev transaction

                        dap_list_t *l_list_out_info_tmp = l_list_out_info;
                        while(l_list_out_info_tmp) {
                            dap_tx_data_t *l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
                            if(l_tx_data) {
                                // get token from prev tx
                                memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
                                        sizeof(l_tx_data->token_ticker));
                                dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
                                dap_chain_datum_tx_t *l_tx_prev =
                                        l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;

                                // find OUT items in prev datum
                                dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
                                        TX_ITEM_TYPE_OUT, NULL);
                                // find OUT item for IN item;
                                dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
                                        l_tx_in->header.tx_out_prev_idx);
                                dap_chain_tx_out_t *l_tx_prev_out =
                                        l_list_out_prev_item ?
                                                               (dap_chain_tx_out_t*) l_list_out_prev_item->data :
                                                               NULL;
                                if(l_tx_prev_out && !memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
                                    l_tx_data->is_use_all_cur_out = true;

                            }
                            l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
                        }
                    }
                    l_list_in_items_tmp = dap_list_next(l_list_in_items_tmp);
                }
                // find prev OUT items for IN items
                dap_list_t *l_list_in_items2_tmp = l_list_in_items; // go to begin of list
                while(l_list_in_items2_tmp) {
                    const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_in_items2_tmp->data;
                    dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
                    // if first transaction - empty prev OUT item
                    if(dap_hash_fast_is_blank(&tx_prev_hash)) {

                        dap_tx_data_t *l_tx_data = NULL;
                        dap_list_t *l_list_out_info_tmp = l_list_out_info;
                        while(l_list_out_info_tmp) {
                            l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
                            if(l_tx_data->token_ticker && l_tx_data->token_ticker[0])
                                break;
                            l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
                        }

                        // add emit info to ret string
                        if(l_tx_data && !memcmp(&l_tx_data->addr, a_addr, sizeof(dap_chain_addr_t))) {
                            dap_list_t *l_records_tmp = l_records_out;
                            while(l_records_tmp) {
                                char *tx_hash_str;
                                if(!dap_strcmp(a_hash_out_type,"hex"))
                                    tx_hash_str = dap_strdup( l_tx_data->tx_hash_str);
                                else
                                    tx_hash_str = dap_enc_base58_from_hex_str_to_str( l_tx_data->tx_hash_str);
                                const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_records_tmp->data;

                                if(!dap_strcmp(a_hash_out_type,"hex")){
                                dap_string_append_printf(l_str_out, "tx hash %s \n emit %lu %s\n",
                                        tx_hash_str,//l_tx_data->tx_hash_str,
                                        l_tx_out->header.value,
                                        l_tx_data->token_ticker);
                                }
                                else {
                                    dap_string_append_printf(l_str_out, "tx hash %s \n emit %lu %s\n",
                                            l_tx_data->tx_hash_str,
                                            l_tx_out->header.value,
                                            l_tx_data->token_ticker);
                                }
                                DAP_DELETE(tx_hash_str);
                                l_records_tmp = dap_list_next(l_records_tmp);
                            }
                        }
                        //dap_list_free(l_records_out);
                    }
                    // in other transactions except first one
                    else {
                        //find prev OUT item
                        dap_tx_data_t *l_tx_data_prev = NULL;
                        HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
                        if(l_tx_data_prev != NULL) {
                            char *l_src_str = NULL;
                            bool l_src_str_is_cur = false;

                            dap_tx_data_t *l_tx_data = NULL;
                            dap_list_t *l_list_out_info_tmp = l_list_out_info;
                            while(l_list_out_info_tmp) {
                                l_tx_data = (dap_tx_data_t*) l_list_out_info_tmp->data;
                                if(l_tx_data->token_ticker && l_tx_data->token_ticker[0])
                                    break;
                                l_list_out_info_tmp = dap_list_next(l_list_out_info_tmp);
                            }
                            if(l_tx_data) {
                                // get token from prev tx
                                memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
                                        sizeof(l_tx_data->token_ticker));

                                dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
                                dap_chain_datum_tx_t *l_tx_prev =
                                        l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;

                                // find OUT items in prev datum
                                dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
                                        TX_ITEM_TYPE_OUT, NULL);
                                // find OUT item for IN item;
                                dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
                                        l_tx_in->header.tx_out_prev_idx);
                                dap_chain_tx_out_t *l_tx_prev_out =
                                        l_list_out_prev_item ?
                                                               (dap_chain_tx_out_t*) l_list_out_prev_item->data :
                                                               NULL;
                                // if use src addr
                                bool l_is_use_src_addr = false;
                                // find source addrs
                                dap_string_t *l_src_addr = dap_string_new(NULL);
                                {
                                    // find IN items in prev datum - for get destination addr
                                    dap_list_t *l_list_in_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
                                            TX_ITEM_TYPE_IN, NULL);
                                    dap_list_t *l_list_tmp = l_list_in_prev_items;
                                    while(l_list_tmp) {
                                        dap_chain_tx_in_t *l_tx_prev_in = l_list_tmp->data;
                                        dap_chain_hash_fast_t l_tx_prev_prev_hash =
                                                l_tx_prev_in->header.tx_prev_hash;
                                        //find prev OUT item
                                        dap_tx_data_t *l_tx_data_prev_prev = NULL;
                                        HASH_FIND(hh, l_tx_data_hash, &l_tx_prev_prev_hash,
                                                sizeof(dap_chain_hash_fast_t), l_tx_data_prev_prev);
                                        if(l_tx_data_prev_prev) {
                                            // if use src addr
                                            if(l_tx_data_prev_prev &&
                                                    !memcmp(&l_tx_data_prev_prev->addr, a_addr,
                                                            sizeof(dap_chain_addr_t)))
                                                l_is_use_src_addr = true;
                                            char *l_str = dap_chain_addr_to_str(&l_tx_data_prev_prev->addr);
                                            if(l_src_addr->len > 0)
                                                dap_string_append_printf(l_src_addr, "\n   %s", l_str);
                                            else
                                                dap_string_append_printf(l_src_addr, "%s", l_str); // first record
                                            DAP_DELETE(l_str);
                                        }
                                        l_list_tmp = dap_list_next(l_list_tmp);
                                    }
                                }

                                l_src_str_is_cur = l_is_use_src_addr;
                                if(l_src_addr->len <= 1) {
                                    l_src_str =
                                            (l_tx_data) ? dap_chain_addr_to_str(&l_tx_data->addr) :
                                            NULL;
                                    if(l_tx_prev_out && !memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
                                        l_src_str_is_cur = true;
                                    dap_string_free(l_src_addr, true);
                                }
                                else
                                    l_src_str = dap_string_free(l_src_addr, false);

                                if(l_tx_prev_out) {
                                    char *l_dst_to_str = dap_chain_addr_to_str(&l_tx_prev_out->addr);
                                    // if use dst addr
                                    bool l_is_use_dst_addr = false;
                                    if(!memcmp(&l_tx_prev_out->addr, a_addr, sizeof(dap_chain_addr_t)))
                                        l_is_use_dst_addr = true;
                                    char *tx_hash_str;
                                    if(!dap_strcmp(a_hash_out_type, "hex"))
                                        tx_hash_str = dap_strdup(l_tx_data->tx_hash_str);
                                    else
                                        tx_hash_str = dap_enc_base58_from_hex_str_to_str(l_tx_data->tx_hash_str);
                                    if(l_is_use_src_addr && !l_is_use_dst_addr) {
                                        dap_string_append_printf(l_str_out,
                                                "tx hash %s \n %s in send  %lu %s from %s\n to %s\n",
                                                tx_hash_str,//l_tx_data->tx_hash_str,
                                                l_time_str ? l_time_str : "",
                                                l_tx_prev_out->header.value,
                                                l_tx_data->token_ticker,
                                                l_src_str ? l_src_str : "",
                                                l_dst_to_str);
                                    } else if(l_is_use_dst_addr && !l_is_use_src_addr) {
                                        if(!l_src_str_is_cur)
                                            dap_string_append_printf(l_str_out,
                                                    "tx hash %s \n %s in recv %lu %s from %s\n",
                                                    tx_hash_str,//l_tx_data->tx_hash_str,
                                                    l_time_str ? l_time_str : "",
                                                    l_tx_prev_out->header.value,
                                                    l_tx_data->token_ticker,
                                                    l_src_str ? l_src_str : "");
                                    }
                                    DAP_DELETE(tx_hash_str);
                                    DAP_DELETE(l_dst_to_str);
                                }
                                dap_list_free(l_list_out_prev_items);
                            }

                            // OUT items
                            dap_list_t *l_records_tmp = l_records_out;
                            while(l_records_tmp) {

                                const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_records_tmp->data;

                                if(( l_tx_data && l_tx_data->is_use_all_cur_out )
                                        || !memcmp(&l_tx_out->addr, a_addr, sizeof(dap_chain_addr_t))) {

                                    char *l_addr_str = (l_tx_out) ? dap_chain_addr_to_str(&l_tx_out->addr) : NULL;

                                    char *tx_hash_str;
                                    if(!dap_strcmp(a_hash_out_type, "hex"))
                                        tx_hash_str = dap_strdup(l_tx_data->tx_hash_str);
                                    else
                                        tx_hash_str = dap_enc_base58_from_hex_str_to_str(l_tx_data->tx_hash_str);
                                    if(l_tx_out && a_addr &&  memcmp(&l_tx_out->addr, a_addr, sizeof(dap_chain_addr_t))==0) {
                                        if(!l_src_str_is_cur)
                                            dap_string_append_printf(l_str_out,
                                                    "tx hash %s \n %s recv %lu %s from %s\n",
                                                    tx_hash_str,//l_tx_data->tx_hash_str,
                                                    l_time_str ? l_time_str : "",
                                                    l_tx_out->header.value,
                                                    l_tx_data_prev->token_ticker,
                                                    l_src_str ? l_src_str : "?");
                                        // break search prev OUT items for IN items
                                        l_list_in_items2_tmp = NULL;
                                    }
                                    else {
                                        dap_string_append_printf(l_str_out,
                                                "tx hash %s \n %s send %lu %s to %s\n",
                                                tx_hash_str,//l_tx_data->tx_hash_str,
                                                l_time_str ? l_time_str : "",
                                                l_tx_out->header.value,
                                                l_tx_data_prev->token_ticker,
                                                l_addr_str ? l_addr_str : "");
                                        l_list_in_items2_tmp = NULL;
                                    }
                                    DAP_DELETE(tx_hash_str);
                                    DAP_DELETE(l_addr_str);
                                }

                                l_records_tmp = dap_list_next(l_records_tmp);
                            }
                            //dap_list_free(l_records_out);
                            DAP_DELETE(l_src_str);

                        }
                    }
                    l_list_in_items2_tmp = dap_list_next(l_list_in_items2_tmp);
                }
    //                l_list_in_items_tmp = dap_list_next(l_list_in_items_tmp);
    //            }
            }

            if(l_list_tx_token)
                dap_list_free(l_list_tx_token);
            if(l_list_out_items)
                dap_list_free(l_list_out_items);
            if(l_list_in_items)
                dap_list_free(l_list_in_items);
            dap_list_free(l_records_out);
            dap_list_free(l_list_out_info);
            DAP_DELETE(l_time_str);

            // go to next transaction
            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
        }
        DAP_DELETE(l_datums);
    }

    // delete hashes
    dap_tx_data_t *l_iter_current, *l_item_tmp;
    HASH_ITER(hh, l_tx_data_hash , l_iter_current, l_item_tmp)
    {
        // delete datum
        DAP_DELETE(l_iter_current->datum);
        // delete struct
        DAP_DELETE(l_iter_current);
        HASH_DEL(l_tx_data_hash, l_iter_current);
    }
    // if no history
    if(!l_str_out->len)
        dap_string_append(l_str_out, " empty");
    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
    return l_ret_str;
}

static char* dap_db_history_token_list(dap_chain_t * a_chain, const char *a_token_name, const char *a_hash_out_type, size_t *a_token_num)
{
    dap_string_t *l_str_out = dap_string_new(NULL);
    *a_token_num  = 0;
    bool l_tx_hash_found = false;
    // list all transactions
    dap_tx_data_t *l_tx_data_hash = NULL;
    // load transactions
    size_t l_atom_size = 0;
    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size);
    size_t l_datums_count = 0;
    dap_chain_datum_t **l_datums = (a_chain->callback_atom_get_datums && l_atom && l_atom_size) ?
                    a_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count) : NULL;
    if(!l_datums) {
        log_it(L_WARNING, "Not defined callback_atom_get_datums for chain \"%s\"", a_chain->name);
        return NULL ;
    }
    for(size_t l_datum_n = 0; l_datum_n < l_datums_count; l_datum_n++) {

        dap_chain_datum_t *l_datum = l_datums[l_datum_n];
        if(!l_datum ) {// || l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
            // go to next atom
            //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
            continue;
        }
        /*
            // transaction
            dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;
            dap_list_t *l_records_out = NULL;


        dap_chain_datum_t *l_datum =
                a_chain->callback_atom_get_datum ?
                        a_chain->callback_atom_get_datum(l_atom) : (dap_chain_datum_t*) l_atom;
        if(!l_datum) {
            // go to next transaction
            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
            l_atom_size = a_chain->callback_atom_get_size(l_atom);
            log_it(L_ERROR, "datum=NULL for atom=0x%x", l_atom);
            continue;
        }
*/
        char l_time_str[70];
        // get time of create datum
        if(dap_time_to_str_rfc822(l_time_str, 71, l_datum->header.ts_create) < 1)
            l_time_str[0] = '\0';
        if(l_datum->header.type_id==DAP_CHAIN_DATUM_TOKEN_DECL) {
            dap_chain_datum_token_t *l_token = (dap_chain_datum_token_t*) l_datum->data;
            if(!a_token_name || !dap_strcmp(l_token->ticker, a_token_name)) {
                dap_string_append_printf(l_str_out, "token %s, created: %s\n", l_token->ticker, l_time_str);
                switch (l_token->type) {
                // Simple private token decl
                case DAP_CHAIN_DATUM_TOKEN_TYPE_SIMPLE:
                    dap_string_append_printf(l_str_out, "  total_supply: %.0llf(%llu), signs: valid/total %02d/%02d \n",
                            l_token->header_private.total_supply / DATOSHI_LD,
                            l_token->header_private.total_supply,
                            l_token->header_private.signs_valid, l_token->header_private.signs_total);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PRIVATE_DECL:
                    dap_string_append_printf(l_str_out, "  tsd_total_size: %llu, flags: 0x%x \n",
                            l_token->header_private_decl.tsd_total_size,
                            l_token->header_private_decl.flags);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PRIVATE_UPDATE:
                    dap_string_append_printf(l_str_out, "  tsd_total_size: %llu, padding: 0x%x \n",
                            l_token->header_private_update.tsd_total_size,
                            l_token->header_private_update.padding);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PUBLIC: {
                    char *l_addr = dap_chain_addr_to_str(&l_token->header_public.premine_address);
                    dap_string_append_printf(l_str_out,
                            " total_supply: %.0llf(%llu), flags: 0x%x\n, premine_supply: %llu, premine_address '%s'\n",
                            l_token->header_public.total_supply / DATOSHI_LD,
                            l_token->header_public.total_supply,
                            l_token->header_public.flags,
                            l_token->header_public.premine_supply,
                            l_addr ? l_addr : "-");
                    DAP_DELETE(l_addr);
                }
                    break;
                default:
                    dap_string_append_printf(l_str_out, "unknown token type: 0x%x\n", l_token->type);
                    break;

                }
                dap_string_append_printf(l_str_out, "\n");
                (*a_token_num)++;
            }
        }

        // go to next transaction
        //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
        //l_atom_size = a_chain->callback_atom_get_size(l_atom);
    }

    a_chain->callback_atom_iter_delete(l_atom_iter);
    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
    return l_ret_str;
}

/**
 * Get data according the history log
 *
 * return history string
 */
static char* dap_db_history_filter(dap_chain_t * a_chain, dap_ledger_t *a_ledger, const char *a_filter_token_name, const char *a_filtr_addr_base58, const char *a_hash_out_type, long a_datum_start, long a_datum_end, long *a_total_datums, dap_chain_tx_hash_processed_ht_t *a_tx_hash_processed)
{
    dap_string_t *l_str_out = dap_string_new(NULL);

    bool l_tx_hash_found = false;
    // list all transactions
    dap_tx_data_t *l_tx_data_hash = NULL;
    // load transactions
    size_t l_atom_size = 0;
    dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter, &l_atom_size);
    size_t l_datum_num = 0, l_token_num = 0, l_emission_num = 0, l_tx_num = 0;
    size_t l_datum_num_global = a_total_datums ? *a_total_datums : 0;
    while(l_atom && l_atom_size) {
    size_t l_datums_count = 0;
    dap_chain_datum_t **l_datums =
            (a_chain->callback_atom_get_datums && l_atom && l_atom_size) ?
                    a_chain->callback_atom_get_datums(l_atom, l_atom_size, &l_datums_count) : NULL;
    if(!l_datums) {
        log_it(L_WARNING, "Not defined callback_atom_get_datums for chain \"%s\"", a_chain->name);
        return NULL ;
    }
    for(size_t l_datum_n = 0; l_datum_n < l_datums_count; l_datum_n++) {

        dap_chain_datum_t *l_datum = l_datums[l_datum_n];
        if(!l_datum) { // || l_datum->header.type_id != DAP_CHAIN_DATUM_TX) {
            // go to next atom
            //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
            continue;
        }

    /*dap_chain_atom_iter_t *l_atom_iter = a_chain->callback_atom_iter_create(a_chain);
    dap_chain_atom_ptr_t *l_atom = a_chain->callback_atom_iter_get_first(l_atom_iter);
    size_t l_atom_size = a_chain->callback_atom_get_size(l_atom);
    size_t l_datum_num = 0, l_token_num = 0, l_emission_num = 0, l_tx_num = 0;
    while(l_atom && l_atom_size) {
        dap_chain_datum_t *l_datum =
                a_chain->callback_atom_get_datum ?
                        a_chain->callback_atom_get_datum(l_atom) : (dap_chain_datum_t*) l_atom;
        if(!l_datum) {
            // go to next transaction
            l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
            l_atom_size = a_chain->callback_atom_get_size(l_atom);
            log_it(L_ERROR, "datum=NULL for atom=0x%x", l_atom);
            continue;
        }*/
        char l_time_str[70];
        // get time of create datum
        if(dap_time_to_str_rfc822(l_time_str, 71, l_datum->header.ts_create) < 1)
            l_time_str[0] = '\0';
        switch (l_datum->header.type_id) {

        // token
        case DAP_CHAIN_DATUM_TOKEN_DECL: {

            // no token necessary for addr
            if(a_filtr_addr_base58) {
                    break;
            }

            dap_chain_datum_token_t *l_token = (dap_chain_datum_token_t*) l_datum->data;
            //if(a_datum_start < 0 || (l_datum_num >= a_datum_start && l_datum_num < a_datum_end))
            // datum out of page
            if(a_datum_start >= 0 && (l_datum_num+l_datum_num_global < a_datum_start || l_datum_num+l_datum_num_global >= a_datum_end)){
                l_token_num++;
                break;
            }
            if(!a_filter_token_name || !dap_strcmp(l_token->ticker, a_filter_token_name)) {
                dap_string_append_printf(l_str_out, "token %s, created: %s\n", l_token->ticker, l_time_str);
                switch (l_token->type) {
                // Simple private token decl
                case DAP_CHAIN_DATUM_TOKEN_TYPE_SIMPLE:
                    dap_string_append_printf(l_str_out, "  total_supply: %.0llf(%llu), signs: valid/total %02d/%02d \n",
                            l_token->header_private.total_supply / DATOSHI_LD,
                            l_token->header_private.total_supply,
                            l_token->header_private.signs_valid, l_token->header_private.signs_total);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PRIVATE_DECL:
                    dap_string_append_printf(l_str_out, "  tsd_total_size: %llu, flags: 0x%x \n",
                            l_token->header_private_decl.tsd_total_size,
                            l_token->header_private_decl.flags);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PRIVATE_UPDATE:
                    dap_string_append_printf(l_str_out, "  tsd_total_size: %llu, padding: 0x%x \n",
                            l_token->header_private_update.tsd_total_size,
                            l_token->header_private_update.padding);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_TYPE_PUBLIC: {
                    char *l_addr = dap_chain_addr_to_str(&l_token->header_public.premine_address);
                    dap_string_append_printf(l_str_out,
                            " total_supply: %.0llf(%llu), flags: 0x%x\n, premine_supply: %llu, premine_address '%s'\n",
                            l_token->header_public.total_supply / DATOSHI_LD,
                            l_token->header_public.total_supply,
                            l_token->header_public.flags,
                            l_token->header_public.premine_supply,
                            l_addr ? l_addr : "-");
                    DAP_DELETE(l_addr);
                }
                    break;
                default:
                    dap_string_append_printf(l_str_out, "unknown token type: 0x%x\n", l_token->type);
                    break;

                }
                dap_string_append_printf(l_str_out, "\n");
                l_token_num++;
            }
        }
            break;

            // emission
        case DAP_CHAIN_DATUM_TOKEN_EMISSION: {
            // datum out of page
            if(a_datum_start >= 0 && (l_datum_num+l_datum_num_global < a_datum_start || l_datum_num+l_datum_num_global >= a_datum_end)) {
                 l_token_num++;
                 break;
            }
            dap_chain_datum_token_emission_t *l_token_em = (dap_chain_datum_token_emission_t*) l_datum->data;
            if(!a_filter_token_name || !dap_strcmp(l_token_em->hdr.ticker, a_filter_token_name)) {
                char * l_token_emission_address_str = dap_chain_addr_to_str(&(l_token_em->hdr.address));
                // filter for addr
                if(dap_strcmp(a_filtr_addr_base58,l_token_emission_address_str)) {
                     break;
                }

                dap_string_append_printf(l_str_out, "emission: %.0llf(%llu) %s, type: %s, version: %d\n",
                        l_token_em->hdr.value / DATOSHI_LD, l_token_em->hdr.value, l_token_em->hdr.ticker,
                        c_dap_chain_datum_token_emission_type_str[l_token_em->hdr.type],
                        l_token_em->hdr.version);
                dap_string_append_printf(l_str_out, "  to addr: %s\n", l_token_emission_address_str);

                DAP_DELETE(l_token_emission_address_str);
                switch (l_token_em->hdr.type) {
                case DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_UNDEFINED:
                    break;
                case DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_AUTH:
                    dap_string_append_printf(l_str_out, "  signs_count: %d\n", l_token_em->data.type_auth.signs_count);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_ALGO:
                    dap_string_append_printf(l_str_out, "  codename: %s\n", l_token_em->data.type_algo.codename);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_ATOM_OWNER:
                    dap_string_append_printf(l_str_out, " value_start: %.0llf(%llu), codename: %s\n",
                            l_token_em->data.type_atom_owner.value_start / DATOSHI_LD,
                            l_token_em->data.type_atom_owner.value_start,
                            l_token_em->data.type_atom_owner.value_change_algo_codename);
                    break;
                case DAP_CHAIN_DATUM_TOKEN_EMISSION_TYPE_SMART_CONTRACT: {
                    char *l_addr = dap_chain_addr_to_str(&l_token_em->data.type_presale.addr);
                    // get time of create datum
                    if(dap_time_to_str_rfc822(l_time_str, 71, l_token_em->data.type_presale.lock_time) < 1)
                            l_time_str[0] = '\0';
                    dap_string_append_printf(l_str_out, "  flags: 0x%x, lock_time: %s\n", l_token_em->data.type_presale.flags, l_time_str);
                    dap_string_append_printf(l_str_out, "  addr: %s\n", l_addr);
                    DAP_DELETE(l_addr);
                }
                    break;
                }
                dap_string_append_printf(l_str_out, "\n");
                l_emission_num++;
            }
        }
            break;

            // transaction
        case DAP_CHAIN_DATUM_TX:{

            // datum out of page
            if(a_datum_start >= 0 && (l_datum_num+l_datum_num_global < a_datum_start || l_datum_num+l_datum_num_global >= a_datum_end)) {
                l_tx_num++;
                break;
            }
            dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*)l_datum->data;
//            dap_chain_tx_hash_processed_ht_t *l_tx_hash_processed = a_tx_hash_processed;
            //calc tx hash
            _dap_chain_datum_tx_out_data(l_tx, a_ledger, l_str_out, a_hash_out_type, true, &a_tx_hash_processed, &l_tx_num);
//            a_tx_hash_processed = l_tx_hash_processed;
//            l_tx_num++;

            /*dap_chain_datum_tx_t *l_tx = (dap_chain_datum_tx_t*) l_datum->data;

            // find Token items - present in emit transaction
            dap_list_t *l_list_tx_token = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);
            // find OUT items
            dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);

            dap_tx_data_t *l_tx_data = NULL;

             // calc tx hash
            dap_chain_hash_fast_t l_tx_hash;
            dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
            char *tx_hash_str;
            char l_tx_hash_str[70];
            dap_chain_hash_fast_to_str(&l_tx_hash, l_tx_hash_str, 70);
            if(!dap_strcmp(a_hash_out_type, "hex"))
                tx_hash_str = dap_strdup(l_tx_hash_str);
            else
                tx_hash_str = dap_enc_base58_from_hex_str_to_str(l_tx_hash_str);

            dap_string_append_printf(l_str_out, "transaction: %s hash: %s\n", l_list_tx_token ? "(emit)" : "", tx_hash_str);
            DAP_DELETE(tx_hash_str);

            dap_list_t *l_list_tmp = l_list_out_items;
            while(l_list_tmp) {
                const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
                // save OUT item l_tx_out - only for first OUT item
                if(!l_tx_data)
                {
                    // save tx hash
                    l_tx_data = DAP_NEW_Z(dap_tx_data_t);
                    dap_chain_hash_fast_t l_tx_hash;
                    dap_hash_fast(l_tx, dap_chain_datum_tx_get_size(l_tx), &l_tx_hash);
                    memcpy(&l_tx_data->tx_hash, &l_tx_hash, sizeof(dap_chain_hash_fast_t));
                    memcpy(&l_tx_data->addr, &l_tx_out->addr, sizeof(dap_chain_addr_t));
                    dap_chain_hash_fast_to_str(&l_tx_data->tx_hash, l_tx_data->tx_hash_str,
                            sizeof(l_tx_data->tx_hash_str));
                    l_tx_data->datum = DAP_NEW_SIZE(dap_chain_datum_t, l_atom_size);
                    memcpy(l_tx_data->datum, l_datum, l_atom_size);
                    // save token name
                    if(l_list_tx_token) {
                        dap_chain_tx_token_t *tk = l_list_tx_token->data;
                        memcpy(l_tx_data->token_ticker, tk->header.ticker, sizeof(l_tx_data->token_ticker));
                    }
                    // take token from prev out item
                    else {

                        // find IN items
                        dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
                        dap_list_t *l_list_tmp_in = l_list_in_items;
                        // find token_ticker in prev OUT items
                        while(l_list_tmp_in) {
                            const dap_chain_tx_in_t *l_tx_in =
                                    (const dap_chain_tx_in_t*) l_list_tmp_in->data;
                            dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;

                            //find prev OUT item
                            dap_tx_data_t *l_tx_data_prev = NULL;
                            HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
                            if(l_tx_data_prev != NULL) {
                                // fill token in l_tx_data from prev transaction
                                if(l_tx_data) {
                                    // get token from prev tx
                                    memcpy(l_tx_data->token_ticker, l_tx_data_prev->token_ticker,
                                            sizeof(l_tx_data->token_ticker));
                                    break;
                                }
                            }
                            l_list_tmp_in = dap_list_next(l_list_tmp_in);
                        }
                        if(l_list_in_items)
                            dap_list_free(l_list_in_items);
                    }
                    HASH_ADD(hh, l_tx_data_hash, tx_hash, sizeof(dap_chain_hash_fast_t), l_tx_data);
                }
                l_list_tmp = dap_list_next(l_list_tmp);
            }

            if(l_list_out_items)
                dap_list_free(l_list_out_items);

            // found a_tx_hash now
            // transaction time
            if(l_tx->header.ts_created > 0) {
                time_t rawtime = (time_t) l_tx->header.ts_created;
                struct tm l_timeinfo = { 0 };
                localtime_r(&rawtime, &l_timeinfo);
                dap_string_append_printf(l_str_out, " %s", asctime(&l_timeinfo));
            }

            // find all OUT items in transaction
            l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT, NULL);
            l_list_tmp = l_list_out_items;
            while(l_list_tmp) {
                const dap_chain_tx_out_t *l_tx_out = (const dap_chain_tx_out_t*) l_list_tmp->data;
                dap_tx_data_t *l_tx_data_prev = NULL;

                const char *l_token_str = NULL;
                if(l_tx_data)
                    l_token_str = l_tx_data->token_ticker;
                char *l_dst_to_str =
                        (l_tx_out) ? dap_chain_addr_to_str(&l_tx_out->addr) :
                        NULL;
                dap_string_append_printf(l_str_out, " OUT item %lld %s to %s\n",
                        l_tx_out->header.value,
                        dap_strlen(l_token_str) > 0 ? l_token_str : "?",
                        l_dst_to_str ? l_dst_to_str : "?"
                                       );
                DAP_DELETE(l_dst_to_str);
                l_list_tmp = dap_list_next(l_list_tmp);
            }

            // find all IN items in transaction
            dap_list_t *l_list_in_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL);
            l_list_tmp = l_list_in_items;
            // find cur addr in prev OUT items
            while(l_list_tmp) {
                const dap_chain_tx_in_t *l_tx_in = (const dap_chain_tx_in_t*) l_list_tmp->data;
                dap_chain_hash_fast_t tx_prev_hash = l_tx_in->header.tx_prev_hash;
                char l_tx_hash_str[70];
                char *tx_hash_base58_str = NULL;
                if(!dap_hash_fast_is_blank(&tx_prev_hash)) {
                    tx_hash_base58_str = dap_enc_base58_from_hex_str_to_str(l_tx_data->tx_hash_str);
                    dap_chain_hash_fast_to_str(&tx_prev_hash, l_tx_hash_str, sizeof(l_tx_hash_str));
                }
                else {
                    strcpy(l_tx_hash_str, "Null");
                    tx_hash_base58_str = dap_strdup("Null");
                }
                if(!dap_strcmp(a_hash_out_type, "hex"))
                    dap_string_append_printf(l_str_out, " IN item \n  prev tx_hash %s\n", l_tx_hash_str);
                else
                    dap_string_append_printf(l_str_out, " IN item \n  prev tx_hash %s\n", tx_hash_base58_str);
                DAP_DELETE(tx_hash_base58_str);

                //find prev OUT item
                dap_tx_data_t *l_tx_data_prev = NULL;
                HASH_FIND(hh, l_tx_data_hash, &tx_prev_hash, sizeof(dap_chain_hash_fast_t), l_tx_data_prev);
                if(l_tx_data_prev != NULL) {

                    dap_chain_datum_t *l_datum_prev = get_prev_tx(l_tx_data_prev);
                    dap_chain_datum_tx_t *l_tx_prev =
                            l_datum_prev ? (dap_chain_datum_tx_t*) l_datum_prev->data : NULL;

                    // find OUT items in prev datum
                    dap_list_t *l_list_out_prev_items = dap_chain_datum_tx_items_get(l_tx_prev,
                            TX_ITEM_TYPE_OUT, NULL);
                    // find OUT item for IN item;
                    dap_list_t *l_list_out_prev_item = dap_list_nth(l_list_out_prev_items,
                            l_tx_in->header.tx_out_prev_idx);
                    dap_chain_tx_out_t *l_tx_prev_out =
                            l_list_out_prev_item ?
                                                   (dap_chain_tx_out_t*) l_list_out_prev_item->data :
                                                   NULL;
                    // print value from prev out item
                    dap_string_append_printf(l_str_out, "  prev OUT item value=%lld",
                            l_tx_prev_out ? l_tx_prev_out->header.value : 0);
                }
                l_list_tmp = dap_list_next(l_list_tmp);
            }

            //find SIG type
            dap_list_t *l_list_sig_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_SIG, NULL);
            unsigned int l_list_sig_items_len = dap_list_length(l_list_sig_items);
            //TX_ITEM_TYPE_SIG
            dap_string_append_printf(l_str_out, "Count SIGN: %i \n", l_list_sig_items_len);
            l_list_tmp = l_list_sig_items;
            while (l_list_tmp) {
                dap_chain_tx_sig_t *l_sig_tx = (dap_chain_tx_sig_t *)l_list_tmp->data;
                dap_chain_hash_fast_t *l_sign_hash_fast = DAP_NEW(dap_chain_hash_fast_t);
                dap_sign_t *l_sign = dap_chain_datum_tx_item_sign_get_sig(l_sig_tx);
                if (dap_sign_get_pkey_hash(l_sign, l_sign_hash_fast)){
                    char l_tx_sign_hash_str[70];
                    dap_chain_hash_fast_to_str(l_sign_hash_fast, l_tx_sign_hash_str, 70);
                    dap_string_append_printf(l_str_out, "%s\n", l_tx_sign_hash_str);
                }else{
                    dap_string_append_printf(l_str_out, "Can't get pkey for sign \n");
                }
                DAP_FREE(l_sign_hash_fast);
                l_list_tmp = dap_list_next(l_list_tmp);
            }
            dap_list_free(l_list_sig_items);
            //find PKEY
            dap_list_t *l_list_pkey_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_PKEY, NULL);
            unsigned int l_list_pkey_items_len = dap_list_length(l_list_pkey_items);
            dap_string_append_printf(l_str_out, "Count PKEY: %i \n", l_list_pkey_items_len);
            dap_list_free(l_list_pkey_items);
            //find TOKEN
            dap_list_t *l_list_token_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN, NULL);
            unsigned int l_list_token_items_len = dap_list_length(l_list_token_items);
            dap_string_append_printf(l_str_out, "Count TOKEN: %i \n", l_list_token_items_len);
            l_list_tmp = l_list_token_items;
            while(l_list_tmp){
                dap_chain_tx_token_t *l_token = (dap_chain_tx_token_t*)l_list_tmp->data;
                l_list_tmp = dap_list_next(l_list_tmp);
            }
            dap_list_free(l_list_token_items);
            //find IN_COND
            dap_list_t *l_list_in_cond_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN_COND, NULL);
            unsigned int l_list_in_cond_items_len = dap_list_length(l_list_in_cond_items);
            dap_string_append_printf(l_str_out, "Count IN_COND: %i \n", l_list_in_cond_items_len);
            dap_list_free(l_list_in_cond_items);
            //find OUT_COND
            dap_list_t *l_list_out_cond_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_COND, NULL);
            unsigned int l_list_out_cond_items_len = dap_list_length(l_list_out_cond_items);
            dap_string_append_printf(l_str_out, "Count OUT_COND: %i \n", l_list_out_cond_items_len);
            dap_list_free(l_list_out_cond_items);
            //find OUT_EXT
            dap_list_t *l_list_out_ext_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_EXT, NULL);
            unsigned int l_list_out_ext_items_len = dap_list_length(l_list_out_ext_items);
            dap_string_append_printf(l_str_out, "Count OUT_EXIT: %i \n", l_list_out_ext_items_len);
            dap_list_free(l_list_out_ext_items);
            //find RECEIPT
            dap_list_t *l_list_receipt_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_RECEIPT, NULL);
            unsigned int l_list_receipt_items_len = dap_list_length(l_list_receipt_items);
            dap_string_append_printf(l_str_out, "Count RECEIPT: %i \n", l_list_receipt_items_len);
            dap_list_free(l_list_receipt_items);
            //find TOKEN_EXT
            dap_list_t *l_list_token_ext_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_TOKEN_EXT, NULL);
            unsigned int l_list_token_ext_items_len = dap_list_length(l_list_token_ext_items);
            dap_string_append_printf(l_str_out, "Count TOKEN_EXT: %i \n", l_list_token_ext_items_len);
            dap_list_free(l_list_token_ext_items);

            dap_string_append_printf(l_str_out, "\n");



            if(l_list_tx_token)
                dap_list_free(l_list_tx_token);
            if(l_list_out_items)
                dap_list_free(l_list_out_items);
            if(l_list_in_items)
                dap_list_free(l_list_in_items);
            l_tx_hash_found = true;
            l_tx_num++;*/
        }
            break;
        default:
            dap_string_append_printf(l_str_out, "unknown datum type=%d %lld %s to %s\n", l_datum->header.type_id);
            break;
        }
        l_datum_num++;
    }
        // go to next transaction
        l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter, &l_atom_size);
        //l_atom = a_chain->callback_atom_iter_get_next(l_atom_iter);
        //l_atom_size = a_chain->callback_atom_get_size(l_atom);
    }
    a_chain->callback_atom_iter_delete(l_atom_iter);
    //total
    dap_string_append_printf(l_str_out,
            "---------------\ntokens: %u\nemissions: %u\ntransactions: %u\ntotal datums: %u", l_token_num,
            l_emission_num, l_tx_num, l_datum_num);

    // return total datums
    if(a_total_datums)
        *a_total_datums = l_datum_num;
    // delete hashes
    dap_tx_data_t *l_iter_current, *l_item_tmp;
    HASH_ITER(hh, l_tx_data_hash , l_iter_current, l_item_tmp)
    {
        HASH_DEL(l_tx_data_hash, l_iter_current);
        // delete datum
        DAP_DELETE(l_iter_current->datum);
        // delete struct
        DAP_DELETE(l_iter_current);
    }

    // if no history
    if(!l_str_out->len)
        dap_string_append(l_str_out, "empty");
    char *l_ret_str = l_str_out ? dap_string_free(l_str_out, false) : NULL;
    return l_ret_str;
}




/**
 * ledger command
 *
 */
int com_ledger(int a_argc, char ** a_argv, void *a_arg_func, char **a_str_reply)
{
    enum { CMD_NONE, CMD_LIST, CMD_TX_HISTORY, CMD_TX_INFO };
    int arg_index = 1;
    const char *l_addr_base58 = NULL;
    const char *l_wallet_name = NULL;
    const char *l_net_str = NULL;
    const char *l_chain_str = NULL;
    const char *l_tx_hash_str = NULL;

    dap_chain_t * l_chain = NULL;
    dap_chain_net_t * l_net = NULL;

    const char * l_hash_out_type = NULL;
    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-H", &l_hash_out_type);
    if(!l_hash_out_type)
        l_hash_out_type = "base58";
    if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type,"base58")) {
        dap_chain_node_cli_set_reply_text(a_str_reply, "invalid parameter -H, valid values: -H <hex | base58>");
        return -1;
    }

    int l_cmd = CMD_NONE;
    if (dap_chain_node_cli_find_option_val(a_argv, 1, 2, "list", NULL)){
        l_cmd = CMD_LIST;
    } else if (dap_chain_node_cli_find_option_val(a_argv, 1, 2, "tx", NULL)){
        l_cmd = CMD_TX_HISTORY;
        if (dap_chain_node_cli_find_option_val(a_argv, 2, 3, "info", NULL))
            l_cmd = CMD_TX_INFO;
    }
    // command tx_history
    if(l_cmd == CMD_TX_HISTORY) {
        bool l_is_all = dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-all", NULL);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-addr", &l_addr_base58);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_name);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-chain", &l_chain_str);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-tx", &l_tx_hash_str);
        dap_chain_tx_hash_processed_ht_t *l_list_tx_hash_processd = NULL;

        if(!l_is_all && !l_addr_base58 && !l_wallet_name && !l_tx_hash_str) {
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-all' or '-addr' or '-w'");
            return -1;
        }

        // Select chain network
        if(!l_net_str) {
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-net'");
            return -2;
        } else {
            if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
                dap_chain_node_cli_set_reply_text(a_str_reply,
                        "command requires parameter '-net' to be valid chain network name");
                return -3;
            }
        }
        //Select chain emission
        if(!l_chain_str) { // chain may be null -> then all chain use
            //dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-chain'");
            //return -4;
        } else {
            if((l_chain = dap_chain_net_get_chain_by_name(l_net, l_chain_str)) == NULL) { // Can't find such chain
                dap_chain_node_cli_set_reply_text(a_str_reply,
                        "command requires parameter '-chain' to be valid chain name in chain net %s",
                        l_net_str);
                return -5;
            }
        }
        //char *l_group_mempool = dap_chain_net_get_gdb_group_mempool(l_chain);
        //const char *l_chain_group = dap_chain_gdb_get_group(l_chain);

        dap_chain_hash_fast_t l_tx_hash;
        if(l_tx_hash_str) {
            if(dap_chain_hash_fast_from_str(l_tx_hash_str, &l_tx_hash) < 0) {
                l_tx_hash_str = NULL;
                dap_chain_node_cli_set_reply_text(a_str_reply, "tx hash not recognized");
                return -1;
            }
//        char hash_str[99];
//        dap_chain_hash_fast_to_str(&l_tx_hash, hash_str,99);
//        int gsdgsd=523;
        }
        dap_chain_addr_t *l_addr = NULL;
        // if need addr
        if(l_wallet_name || l_addr_base58) {
            if(l_wallet_name) {
                const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
                dap_chain_wallet_t * l_wallet = dap_chain_wallet_open(l_wallet_name, c_wallets_path);
                if(l_wallet) {
                    dap_chain_addr_t *l_addr_tmp = (dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet,
                            l_net->pub.id);
                    l_addr = DAP_NEW_SIZE(dap_chain_addr_t, sizeof(dap_chain_addr_t));
                    memcpy(l_addr, l_addr_tmp, sizeof(dap_chain_addr_t));
                    dap_chain_wallet_close(l_wallet);
                }
            }
            if(!l_addr && l_addr_base58) {
                l_addr = dap_chain_addr_from_str(l_addr_base58);
            }
            if(!l_addr && !l_tx_hash_str) {
                dap_chain_node_cli_set_reply_text(a_str_reply, "wallet address not recognized");
                return -1;
            }
        }

        dap_string_t *l_str_ret = dap_string_new(NULL); //char *l_str_ret = NULL;
        dap_chain_t *l_chain_cur;
        void *l_chain_tmp = (void*)0x1;
        int l_num = 0;
        // only one chain
        if(l_chain)
            l_chain_cur = l_chain;
        // all chain
        else
            l_chain_cur = dap_chain_enum(&l_chain_tmp);
        while(l_chain_cur) {
            // only selected net
            if(l_net->pub.id.uint64 == l_chain_cur->net_id.uint64) {
                // separator between chains
                if(l_num>0 && !l_chain)
                    dap_string_append(l_str_ret, "---------------\n");

                char *l_str_out = NULL;
                dap_string_append_printf(l_str_ret, "chain: %s\n", l_chain_cur->name);
                dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(l_net_str);
                if(l_is_all) {
                    // without filters
                    l_str_out = dap_db_history_filter(l_chain_cur, l_ledger, NULL, NULL, l_hash_out_type, -1, 0, NULL, l_list_tx_hash_processd);
                    dap_string_append_printf(l_str_ret, "all history:\n%s\n", l_str_out ? l_str_out : " empty");
                }
                else {
                    l_str_out = l_tx_hash_str ?
                                                dap_db_history_tx(&l_tx_hash, l_chain_cur, l_hash_out_type) :
                                                dap_db_history_addr(l_addr, l_chain_cur, l_hash_out_type);

                    if(l_tx_hash_str) {
                        dap_string_append_printf(l_str_ret, "history for tx hash %s:\n%s\n", l_tx_hash_str,
                                l_str_out ? l_str_out : " empty");
                    }
                    else if(l_addr) {
                        char *l_addr_str = dap_chain_addr_to_str(l_addr);
                        dap_string_append_printf(l_str_ret, "history for addr %s:\n%s\n", l_addr_str,
                                l_str_out ? l_str_out : " empty");
                        DAP_DELETE(l_addr_str);
                    }
                }
                DAP_DELETE(l_str_out);
                l_num++;
            }
            // only one chain use
            if(l_chain)
                break;
            dap_chain_enum_unlock();
            l_chain_cur = dap_chain_enum(&l_chain_tmp);
        }
        DAP_DELETE(l_addr);
        _dap_chain_tx_hash_processed_ht_free(l_list_tx_hash_processd);
        // all chain
        if(!l_chain)
            dap_chain_enum_unlock();
        dap_chain_node_cli_set_reply_text(a_str_reply, l_str_ret->str);
        dap_string_free(l_str_ret, true);
        return 0;
    }
    else if(l_cmd == CMD_LIST){
        enum {SUBCMD_NONE, SUBCMD_LIST_COIN};
        int l_sub_cmd = SUBCMD_NONE;
        if (dap_chain_node_cli_find_option_val(a_argv, 2, 3, "coins", NULL ))
                l_sub_cmd = SUBCMD_LIST_COIN;
        dap_chain_node_cli_find_option_val(a_argv, 4, a_argc, "-net", &l_net_str);
        if (l_net == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires key -net");
            return -1;
        }
        dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(l_net_str);
        if (l_ledger == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't get ledger for net %s", l_net_str);
            return -2;
        }
        //dap_chain_ledger_
    } else if (l_cmd == CMD_TX_INFO){
        //GET hash
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_tx_hash_str);
        //get net
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str);
        //check input
        if (l_tx_hash_str == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires key -hash");
            return -1;
        }
        if (l_net_str == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires key -net");
            return -1;
        }
        dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(l_net_str);
        if (l_ledger == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't get ledger for net %s", l_net_str);
            return -2;
        }
        dap_chain_hash_fast_t *l_tx_hash = DAP_NEW(dap_chain_hash_fast_t);
        if(dap_chain_hash_fast_from_str(l_tx_hash_str, l_tx_hash)){
            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't get hash_fast from %s", l_tx_hash_str);
            return -2;
        }
        dap_chain_datum_tx_t *l_datum_tx = dap_chain_ledger_tx_find_by_hash(l_ledger, l_tx_hash);
        if (l_datum_tx == NULL){
            dap_chain_node_cli_set_reply_text(a_str_reply, "Can't get datum from transaction hash %s", l_tx_hash_str);
            return -2;
        }
        dap_string_t *l_str = dap_string_new("");
        _dap_chain_datum_tx_out_data(l_datum_tx, l_ledger, l_str, l_hash_out_type, false, NULL, NULL);
        dap_chain_node_cli_set_reply_text(a_str_reply, l_str->str);
        dap_string_free(l_str, true);
    }
    else{
        dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter 'list' or 'tx' or 'info'");
        return -1;
    }
    return 0;
}

/**
 * token command
 *
 */
int com_token(int a_argc, char ** a_argv, void *a_arg_func, char **a_str_reply)
{
    enum { CMD_NONE, CMD_LIST, CMD_INFO, CMD_TX };
    int arg_index = 1;
    //const char *l_addr_base58 = NULL;
    //const char *l_wallet_name = NULL;
    const char *l_net_str = NULL;
    const char *l_chain_str = NULL;

    dap_chain_t * l_chain = NULL;
    dap_chain_net_t * l_net = NULL;
    dap_chain_tx_hash_processed_ht_t *l_list_tx_hash_processd = NULL;

    const char * l_hash_out_type = NULL;
    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-H", &l_hash_out_type);
    if(!l_hash_out_type)
        l_hash_out_type = "base58";
    if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type,"base58")) {
        dap_chain_node_cli_set_reply_text(a_str_reply, "invalid parameter -H, valid values: -H <hex | base58>");
        return -1;
    }

    //bool l_is_all = dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-all", NULL);
    //dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-addr", &l_addr_base58);
    //dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_name);
    dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str);
    //dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-chain", &l_chain_str);
    //dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-tx", &l_tx_hash_str);

    // Select chain network
    if(!l_net_str) {
        dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-net'");
        return -2;
    } else {
        if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network
            dap_chain_node_cli_set_reply_text(a_str_reply,
                    "command requires parameter '-net' to be valid chain network name");
            return -3;
        }
    }

    int l_cmd = CMD_NONE;
    if (dap_chain_node_cli_find_option_val(a_argv, 1, 2, "list", NULL))
        l_cmd = CMD_LIST;
    else if (dap_chain_node_cli_find_option_val(a_argv, 1, 2, "info", NULL))
        l_cmd = CMD_INFO;
    else if (dap_chain_node_cli_find_option_val(a_argv, 1, 2, "tx", NULL))
            l_cmd = CMD_TX;
    // token list
    if(l_cmd == CMD_LIST) {
        dap_string_t *l_str_out = dap_string_new(NULL);
        size_t l_token_num_total = 0;
        // get first chain
        void *l_chain_tmp = (void*)0x1;
        dap_chain_t *l_chain_cur = dap_chain_enum(&l_chain_tmp);
        while(l_chain_cur) {
            // only selected net
            if(l_net->pub.id.uint64 == l_chain_cur->net_id.uint64) {
                size_t l_token_num = 0;
                char *token_list_str = dap_db_history_token_list(l_chain_cur, NULL, l_hash_out_type, &l_token_num);
                if(token_list_str)
                    dap_string_append(l_str_out, token_list_str);
                l_token_num_total += l_token_num;
            }
            // next chain
            dap_chain_enum_unlock();
            l_chain_cur = dap_chain_enum(&l_chain_tmp);
        }
        dap_chain_enum_unlock();
        //total
        dap_string_append_printf(l_str_out, "---------------\ntokens: %u\n", l_token_num_total);
        dap_chain_node_cli_set_reply_text(a_str_reply, l_str_out->str);
        dap_string_free(l_str_out, true);
        return 0;

    }
    // token info
    else if(l_cmd == CMD_INFO) {
        const char *l_token_name_str = NULL;
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-name", &l_token_name_str);
        if(!l_token_name_str) {
                dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-name' <token name>");
                return -4;
            }

            dap_string_t *l_str_out = dap_string_new(NULL);
            size_t l_token_num_total = 0;
            // get first chain
            void *l_chain_tmp = (void*)0x1;
            dap_chain_t *l_chain_cur = dap_chain_enum(&l_chain_tmp);
            while(l_chain_cur) {
                // only selected net
                if(l_net->pub.id.uint64 == l_chain_cur->net_id.uint64) {
                    size_t l_token_num = 0;
                    // filter - token name
                    char *token_list_str = dap_db_history_token_list(l_chain_cur, l_token_name_str, l_hash_out_type, &l_token_num);
                    if(token_list_str)
                        dap_string_append(l_str_out, token_list_str);
                    l_token_num_total += l_token_num;
                }
                // next chain
                dap_chain_enum_unlock();
                l_chain_cur = dap_chain_enum(&l_chain_tmp);
            }
            dap_chain_enum_unlock();
            if(!l_token_num_total)
                dap_string_append_printf(l_str_out, "token '%s' not found\n", l_token_name_str);
            dap_chain_node_cli_set_reply_text(a_str_reply, l_str_out->str);
            dap_string_free(l_str_out, true);
            return 0;

    }
    // command tx history
    else if(l_cmd == CMD_TX) {

        enum { SUBCMD_TX_NONE, SUBCMD_TX_ALL, SUBCMD_TX_ADDR };
        // find subcommand
        int l_subcmd = CMD_NONE;
        const char *l_addr_base58_str = NULL;
        const char *l_wallet_name = NULL;
        if(dap_chain_node_cli_find_option_val(a_argv, 2, a_argc, "-all", NULL))
            l_subcmd = SUBCMD_TX_ALL;
        else if(dap_chain_node_cli_find_option_val(a_argv, 2, a_argc, "-addr", &l_addr_base58_str))
            l_subcmd = SUBCMD_TX_ADDR;
        else if(dap_chain_node_cli_find_option_val(a_argv, 2, a_argc, "-wallet", &l_wallet_name))
            l_subcmd = SUBCMD_TX_ADDR;

        const char *l_token_name_str = NULL;
        const char *l_page_start_str = NULL;
        const char *l_page_size_str = NULL;
        const char *l_page_str = NULL;
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-name", &l_token_name_str);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-page_start", &l_page_start_str);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-page_size", &l_page_size_str);
        dap_chain_node_cli_find_option_val(a_argv, arg_index, a_argc, "-page", &l_page_str);
        if(!l_token_name_str) {
            dap_chain_node_cli_set_reply_text(a_str_reply, "command requires parameter '-name' <token name>");
            return -4;
        }
        long l_page_start = -1;// not used if =-1
        long l_page_size = 10;
        long l_page = 2;
        long l_cur_datum = 0;
        if(l_page_start_str)
            l_page_start = strtol(l_page_start_str, NULL, 10);
        if(l_page_size_str) {
            l_page_size = strtol(l_page_size_str, NULL, 10);
            if(l_page_size < 1)
                l_page_size = 1;
        }
        if(l_page_str) {
            l_page = strtol(l_page_str, NULL, 10);
            if(l_page < 1)
                l_page = 1;
        }


         // tx all
        if(l_subcmd == SUBCMD_TX_ALL) {
            dap_string_t *l_str_out = dap_string_new(NULL);
            // get first chain
            void *l_chain_tmp = (void*) 0x1;
            dap_chain_t *l_chain_cur = dap_chain_enum(&l_chain_tmp);
            while(l_chain_cur) {
                // only selected net
                if(l_net->pub.id.uint64 == l_chain_cur->net_id.uint64) {
                    long l_chain_datum = l_cur_datum;
                    dap_ledger_t *l_ledger = dap_chain_ledger_by_net_name(l_net_str);
                    char *l_datum_list_str = dap_db_history_filter(l_chain_cur, l_ledger, l_token_name_str, NULL,
                            l_hash_out_type, l_page_start * l_page_size, (l_page_start+l_page)*l_page_size, &l_chain_datum, l_list_tx_hash_processd);
                    if(l_datum_list_str) {
                        l_cur_datum += l_chain_datum;
                        dap_string_append_printf(l_str_out, "Chain: %s\n", l_chain_cur->name);
                        dap_string_append_printf(l_str_out, "%s\n\n", l_datum_list_str);
                        DAP_DELETE(l_datum_list_str);
                    }
                }
                // next chain
                dap_chain_enum_unlock();
                l_chain_cur = dap_chain_enum(&l_chain_tmp);
            }
            dap_chain_enum_unlock();
            _dap_chain_tx_hash_processed_ht_free(l_list_tx_hash_processd);
            dap_chain_node_cli_set_reply_text(a_str_reply, l_str_out->str);
            dap_string_free(l_str_out, true);
            return 0;
        }
        // tx -addr or tx -wallet
        else if(l_subcmd == SUBCMD_TX_ADDR) {
            // parse addr from -addr <addr> or -wallet <wallet>
            dap_chain_addr_t *l_addr_base58 = NULL;
            if(l_addr_base58_str) {
                //l_addr_base58 = dap_strdup(l_addr_base58_str);
                l_addr_base58 = dap_chain_addr_from_str(l_addr_base58_str);
            }
            else if(l_wallet_name) {
                const char *c_wallets_path = dap_chain_wallet_get_path(g_config);
                dap_chain_wallet_t * l_wallet = dap_chain_wallet_open(l_wallet_name, c_wallets_path);
                if(l_wallet) {
                    dap_chain_addr_t *l_addr_tmp = (dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet,
                            l_net->pub.id);
                    l_addr_base58 = DAP_NEW_SIZE(dap_chain_addr_t, sizeof(dap_chain_addr_t));
                    memcpy(l_addr_base58, l_addr_tmp, sizeof(dap_chain_addr_t));
                    dap_chain_wallet_close(l_wallet);
                    char *ffl_addr_base58 = dap_chain_addr_to_str(l_addr_base58);
                    ffl_addr_base58 = 0;
                }
                else {
                    dap_chain_node_cli_set_reply_text(a_str_reply, "wallet '%s' not found", l_wallet_name);
                    return -2;
                }
            }
            if(!l_addr_base58) {
                dap_chain_node_cli_set_reply_text(a_str_reply, "address not recognized");
                return -3;
            }

            dap_string_t *l_str_out = dap_string_new(NULL);
            // get first chain
            void *l_chain_tmp = (void*) 0x1;
            dap_chain_t *l_chain_cur = dap_chain_enum(&l_chain_tmp);
            while(l_chain_cur) {
                // only selected net
                if(l_net->pub.id.uint64 == l_chain_cur->net_id.uint64) {
                    long l_chain_datum = l_cur_datum;
                    char *l_datum_list_str = dap_db_history_addr(l_addr_base58, l_chain_cur, l_hash_out_type);
                    if(l_datum_list_str) {
                        l_cur_datum += l_chain_datum;
                        dap_string_append_printf(l_str_out, "Chain: %s\n", l_chain_cur->name);
                        dap_string_append_printf(l_str_out, "%s\n\n", l_datum_list_str);
                        DAP_DELETE(l_datum_list_str);
                    }
                }
                // next chain
                dap_chain_enum_unlock();
                l_chain_cur = dap_chain_enum(&l_chain_tmp);
            }
            dap_chain_enum_unlock();
            dap_chain_node_cli_set_reply_text(a_str_reply, l_str_out->str);
            dap_string_free(l_str_out, true);
            DAP_DELETE(l_addr_base58);
            return 0;

        }
        else{
            dap_chain_node_cli_set_reply_text(a_str_reply, "not found parameter '-all', '-wallet' or '-addr'");
            return -1;
        }
        return 0;
    }

    dap_chain_node_cli_set_reply_text(a_str_reply, "unknown command code %d", l_cmd);
    return -5;
}