diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eb3d82d8bcd989bf100fdfc5f675899be32ea5e..cad6340a777c69c7ec1f759153528b6bf3608e17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ endif() if(NOT DEFINED CELLFRAME_MODULES) include (dap-sdk/cmake/OS_Detection.cmake) - set(CELLFRAME_MODULES "core chains mining network cs-dag-poa cs-esbocs cs-none srv-stake srv-voting srv-bridge srv-xchange srv-emit-delegate") + set(CELLFRAME_MODULES "core chains mining network cs-dag-poa cs-esbocs cs-none srv-stake srv-voting srv-bridge srv-xchange srv-emit-delegate compose") if(LINUX OR DARWIN) set(CELLFRAME_MODULES "${CELLFRAME_MODULES} srv-vpn") @@ -91,6 +91,13 @@ if (CELLFRAME_MODULES MATCHES "network") dap_stream dap_stream_ch dap_client dap_cli_server dap_stream_ch_chain_net dap_chain_net dap_chain_net_srv dap_chain_mempool) endif() +# Compose +if (CELLFRAME_MODULES MATCHES "compose") + message("[+] Module 'compose'") + set(CELLFRAME_LIBS ${CELLFRAME_LIBS} dap_compose dap_json_rpc dap_enc_server dap_notify_srv dap_http_server dap_session + dap_stream dap_stream_ch dap_client dap_cli_server dap_stream_ch_chain_net dap_chain_net dap_chain_net_srv dap_chain_mempool) +endif() + # Chain net services if (CELLFRAME_MODULES MATCHES "srv-" ) set(CELLFRAME_LIBS ${CELLFRAME_LIBS} dap_stream_ch_chain_net_srv) diff --git a/dap-sdk b/dap-sdk index cda10ae53ead9930543447422b770298b4b24280..6cfac2c1c65127d8f9cf9cf4c65afb054af8ce1f 160000 --- a/dap-sdk +++ b/dap-sdk @@ -1 +1 @@ -Subproject commit cda10ae53ead9930543447422b770298b4b24280 +Subproject commit 6cfac2c1c65127d8f9cf9cf4c65afb054af8ce1f diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index fef7978aabbe7df81756376bdedad45cca6945e3..de7713fd242aab1321d17a172e0df7a33e27ad75 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -108,3 +108,8 @@ endif() if (CELLFRAME_MODULES MATCHES "srv-emit-delegate") add_subdirectory(service/emit-delegate) endif() + +# compose service +if (CELLFRAME_MODULES MATCHES "compose") + add_subdirectory(compose) +endif() diff --git a/modules/chain/include/dap_chain_policy.h b/modules/chain/include/dap_chain_policy.h index 59c494726396fe869608da0539889ded99f74f9d..1e177a4ecee62e85f7157a2c2cf0edc191a9b1fb 100644 --- a/modules/chain/include/dap_chain_policy.h +++ b/modules/chain/include/dap_chain_policy.h @@ -106,6 +106,6 @@ DAP_STATIC_INLINE const char *dap_chain_policy_to_str(dap_chain_policy_t *a_poli */ DAP_STATIC_INLINE bool dap_chain_policy_num_is_valid(uint64_t a_num) { - uint32_t l_num = dap_maxval(l_num); + uint32_t l_num = UINT32_MAX; return (a_num && a_num <= l_num); } diff --git a/modules/compose/CMakeLists.txt b/modules/compose/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..82f0ea9189f6e19123eeae1e07acb7446f9a3d56 --- /dev/null +++ b/modules/compose/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) +project (dap_compose) + +file(GLOB DAP_CHAIN_COMPOSE_SRCS *.c) +file(GLOB DAP_CHAIN_COMPOSE_HEADERS include/*.h) + +add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_COMPOSE_SRCS} ${DAP_CHAIN_COMPOSE_HEADERS}) + +target_link_libraries(${PROJECT_NAME} dap_cli_server dap_chain_net dap_chain_net_srv dap_enc_server dap_json_rpc dap_chain_net_srv_xchange dap_chain_net_srv_voting dap_app_cli dap_cli_server) +target_include_directories(${PROJECT_NAME} PUBLIC include/ ) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../../dap-sdk/3rdparty/json-c) + + +if (INSTALL_SDK) +set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${DAP_CHAIN_COMPOSE_HEADERS}") +INSTALL(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION lib/modules/compose/ + ARCHIVE DESTINATION lib/modules/compose/ + PUBLIC_HEADER DESTINATION include/modules/compose/ +) +endif() diff --git a/modules/compose/dap_chain_tx_compose.c b/modules/compose/dap_chain_tx_compose.c new file mode 100644 index 0000000000000000000000000000000000000000..2349aa5e5d69d6fe2a4fd58bd9f247eccc4a32dd --- /dev/null +++ b/modules/compose/dap_chain_tx_compose.c @@ -0,0 +1,4612 @@ +/* + * Authors: + * Roman Padenkov <roman.padenkov@demlabs.net> + * Olzhas Zharasbaev <oljas.jarasbaev@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2025-2026 + * All rights reserved. + + This file is part of DAP (Distributed Applications Platform) the open source project + + DAP (Distributed Applications Platform) 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 "dap_common.h" +#include "dap_chain_tx_compose.h" +#include "dap_chain_datum_tx_voting.h" +#include "dap_chain_net_srv_stake_lock.h" +#include "dap_chain_net_srv_voting.h" +#include "dap_chain_net_tx.h" +#include "dap_net.h" +#include "dap_app_cli.h" +#include "dap_json_rpc.h" +#include "dap_app_cli_net.h" +#include "dap_cli_server.h" +#include "dap_enc_base64.h" +#include "dap_chain_net_srv_order.h" +#include "dap_chain_net_srv_stake_pos_delegate.h" + +#include <json-c/json.h> + +static compose_config_t* s_compose_config_init(const char *a_net_name, const char *a_url_str, + uint16_t a_port) { + if (!a_net_name || !a_url_str || a_port == 0) { + return NULL; + } + compose_config_t *l_config = DAP_NEW_Z(compose_config_t); + if (!l_config) { + return NULL; + } + l_config->net_name = a_net_name; + l_config->url_str = a_url_str ? a_url_str : dap_compose_get_net_url(a_net_name); + l_config->port = a_port ? a_port : dap_compose_get_net_port(a_net_name); + l_config->response_handler = json_object_new_object(); + return l_config; +} + +static json_object* s_compose_config_return_response_handler(compose_config_t *a_config) { + if (!a_config || !a_config->response_handler) { + return NULL; + } + json_object* l_response_handler = a_config->response_handler; + DAP_DEL_Z(a_config); + return l_response_handler; +} + +static int s_compose_config_deinit(compose_config_t *a_config) { + json_object_put(a_config->response_handler); + DAP_DEL_Z(a_config); + return 0; +} + +const char* dap_compose_get_net_url(const char* name) { + for (int i = 0; i < NET_COUNT; i++) { + if (strcmp(netinfo[i].name, name) == 0) { + return netinfo[i].url; + } + } + return NULL; +} + +uint16_t dap_compose_get_net_port(const char* name) { + for (int i = 0; i < NET_COUNT; i++) { + if (strcmp(netinfo[i].name, name) == 0) { + return netinfo[i].port; + } + } + return 0; +} + +static const char* s_get_native_ticker(const char* name) { + for (int i = 0; i < NET_COUNT; i++) { + if (strcmp(netinfo[i].name, name) == 0) { + return netinfo[i].native_ticker; + } + } + return NULL; +} + +static dap_chain_net_id_t s_get_net_id(const char* name) { + for (int i = 0; i < NET_COUNT; i++) { + if (strcmp(netinfo[i].name, name) == 0) { + return netinfo[i].net_id; + } + } + dap_chain_net_id_t empty_id = {.uint64 = 0}; + return empty_id; +} + + +int dap_json_compose_error_add(json_object* a_json_obj_reply, int a_code_error, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + char *l_msg = dap_strdup_vprintf(msg, args); + va_end(args); + + if (!a_json_obj_reply || !json_object_is_type(a_json_obj_reply, json_type_object)) { + return -1; + } + + json_object *l_json_arr_errors = NULL; + if (!json_object_object_get_ex(a_json_obj_reply, "errors", &l_json_arr_errors)) { + l_json_arr_errors = json_object_new_array(); + json_object_object_add(a_json_obj_reply, "errors", l_json_arr_errors); + } + + json_object* l_obj_error = json_object_new_object(); + json_object_object_add(l_obj_error, "code", json_object_new_int(a_code_error)); + json_object_object_add(l_obj_error, "message", json_object_new_string(l_msg)); + json_object_array_add(l_json_arr_errors, l_obj_error); + + DAP_DEL_Z(l_msg); + return 0; +} + + +int dap_tx_json_tsd_add(json_object * json_tx, json_object * json_add) { + json_object *items_array; + if (!json_object_object_get_ex(json_tx, "items", &items_array)) { + fprintf(stderr, "Failed to get 'items' array\n"); + return 1; + } + json_object_array_add(items_array, json_add); + return 0; +} + +struct cmd_request { +#ifdef DAP_OS_WINDOWS + CONDITION_VARIABLE wait_cond; + CRITICAL_SECTION wait_crit_sec; +#else + pthread_cond_t wait_cond; + pthread_mutex_t wait_mutex; +#endif + char* response; + size_t response_size; + int error_code; +}; + +static struct cmd_request* s_cmd_request_init() +{ + struct cmd_request *l_cmd_request = DAP_NEW_Z(struct cmd_request); + if (!l_cmd_request) + return NULL; +#ifdef DAP_OS_WINDOWS + InitializeCriticalSection(&l_cmd_request->wait_crit_sec); + InitializeConditionVariable(&l_cmd_request->wait_cond); +#else + pthread_mutex_init(&l_cmd_request->wait_mutex, NULL); +#ifdef DAP_OS_DARWIN + pthread_cond_init(&l_cmd_request->wait_cond, NULL); +#else + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); + pthread_cond_init(&l_cmd_request->wait_cond, &attr); +#endif +#endif + return l_cmd_request; +} + +void s_cmd_request_free(struct cmd_request *a_cmd_request) +{ + if (!a_cmd_request) + return; + +#ifdef DAP_OS_WINDOWS + DeleteCriticalSection(&a_cmd_request->wait_crit_sec); +#else + pthread_mutex_destroy(&a_cmd_request->wait_mutex); + pthread_cond_destroy(&a_cmd_request->wait_cond); +#endif + DAP_DEL_MULTY(a_cmd_request->response, a_cmd_request); +} + +static void s_cmd_response_handler(void *a_response, size_t a_response_size, void *a_arg, + http_status_code_t http_status_code) { + (void)http_status_code; + struct cmd_request *l_cmd_request = (struct cmd_request *)a_arg; +#ifdef DAP_OS_WINDOWS + EnterCriticalSection(&l_cmd_request->wait_crit_sec); +#else + pthread_mutex_lock(&l_cmd_request->wait_mutex); +#endif + l_cmd_request->response = DAP_DUP_SIZE(a_response, a_response_size); + l_cmd_request->response_size = a_response_size; +#ifdef DAP_OS_WINDOWS + WakeConditionVariable(&l_cmd_request->wait_cond); + LeaveCriticalSection(&l_cmd_request->wait_crit_sec); +#else + pthread_cond_signal(&l_cmd_request->wait_cond); + pthread_mutex_unlock(&l_cmd_request->wait_mutex); +#endif +} + +static void s_cmd_error_handler(int a_error_code, void *a_arg){ + struct cmd_request * l_cmd_request = (struct cmd_request *)a_arg; +#ifdef DAP_OS_WINDOWS + EnterCriticalSection(&l_cmd_request->wait_crit_sec); + l_cmd_request->response = NULL; + l_cmd_request->error_code = a_error_code; + WakeConditionVariable(&l_cmd_request->wait_cond); + LeaveCriticalSection(&l_cmd_request->wait_crit_sec); +#else + pthread_mutex_lock(&l_cmd_request->wait_mutex); + l_cmd_request->response = NULL; + l_cmd_request->error_code = a_error_code; + pthread_cond_signal(&l_cmd_request->wait_cond); + pthread_mutex_unlock(&l_cmd_request->wait_mutex); +#endif +} + + +static int dap_chain_cmd_list_wait(struct cmd_request *a_cmd_request, int a_timeout_ms) { +#ifdef DAP_OS_WINDOWS + EnterCriticalSection(&a_cmd_request->wait_crit_sec); + if (a_cmd_request->response) + return LeaveCriticalSection(&a_cmd_request->wait_crit_sec), 0; + while (!a_cmd_request->response) { + if (!SleepConditionVariableCS(&a_cmd_request->wait_cond, &a_cmd_request->wait_crit_sec, a_timeout_ms)) { + a_cmd_request->error_code = GetLastError() == ERROR_TIMEOUT ? 1 : 2; + break; + } + } + LeaveCriticalSection(&a_cmd_request->wait_crit_sec); + return a_cmd_request->error_code; +#else + pthread_mutex_lock(&a_cmd_request->wait_mutex); + if(a_cmd_request->response) { + pthread_mutex_unlock(&a_cmd_request->wait_mutex); + return 0; + } + + struct timespec l_cond_timeout; +#ifdef DAP_OS_DARWIN + l_cond_timeout.tv_sec = a_timeout_ms / 1000; + l_cond_timeout.tv_nsec = (a_timeout_ms % 1000) * 1000000; +#else + clock_gettime(CLOCK_MONOTONIC, &l_cond_timeout); + l_cond_timeout.tv_sec += a_timeout_ms / 1000; + l_cond_timeout.tv_nsec += (a_timeout_ms % 1000) * 1000000; + if (l_cond_timeout.tv_nsec >= 1000000000) { + l_cond_timeout.tv_sec += l_cond_timeout.tv_nsec / 1000000000; + l_cond_timeout.tv_nsec %= 1000000000; + } +#endif + + int ret = 0; + while (!a_cmd_request->response) { + int cond_ret; +#ifdef DAP_OS_DARWIN + cond_ret = pthread_cond_timedwait_relative_np(&a_cmd_request->wait_cond, + &a_cmd_request->wait_mutex, &l_cond_timeout); +#else + cond_ret = pthread_cond_timedwait(&a_cmd_request->wait_cond, + &a_cmd_request->wait_mutex, &l_cond_timeout); +#endif + if (cond_ret == ETIMEDOUT) { + a_cmd_request->error_code = 1; + ret = 1; + break; + } else if (cond_ret != 0) { + a_cmd_request->error_code = 2; + ret = 2; + break; + } + } + pthread_mutex_unlock(&a_cmd_request->wait_mutex); + return ret; +#endif +} + +static int s_cmd_request_get_response(struct cmd_request *a_cmd_request, json_object **a_response_out, size_t *a_response_out_size) +{ + int ret = 0; + + if (a_cmd_request->error_code) { + ret = - 1; + } else if (a_cmd_request->response) { + *a_response_out = json_tokener_parse(a_cmd_request->response); + *a_response_out_size = a_cmd_request->response_size; + } else { + ret = -2; + } + + return ret; +} + + +static json_object* s_request_command_to_rpc(const char *request, compose_config_t *a_config) { + json_object * l_response = NULL; + size_t l_response_size = 0; + struct cmd_request* l_cmd_request = s_cmd_request_init(); + + if (!l_cmd_request) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_REQUEST_INIT_FAILED, "Failed to initialize command request"); + return NULL; + } + + dap_client_http_request(dap_worker_get_auto(), + a_config->url_str, + a_config->port, + "POST", "application/json", + NULL, request, strlen(request), NULL, + s_cmd_response_handler, s_cmd_error_handler, + l_cmd_request, NULL); + + int l_ret = dap_chain_cmd_list_wait(l_cmd_request, 15000); + + if (!l_ret){ + if (s_cmd_request_get_response(l_cmd_request, &l_response, &l_response_size)) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_REQUEST_FAILED, "Response error code: %d", l_cmd_request->error_code); + } + } else { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_REQUEST_TIMEOUT, "Request timed out"); + } + s_cmd_request_free(l_cmd_request); + return l_response; +} + +static json_object* s_request_command_parse(json_object *l_response, compose_config_t *a_config) { + if (!l_response) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is NULL"); + return NULL; + } + + json_object * l_result = NULL; + if (!json_object_object_get_ex(l_response, "result", &l_result)) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESULT_NOT_FOUND, "Failed to get 'result' from response"); + return NULL; + } + + json_object *errors_array = NULL; + if (json_object_is_type(l_result, json_type_array) && json_object_array_length(l_result) > 0) { + json_object *first_element = json_object_array_get_idx(l_result, 0); + if (json_object_object_get_ex(first_element, "errors", &errors_array)) { + int errors_len = json_object_array_length(errors_array); + for (int j = 0; j < errors_len; j++) { + json_object *error_obj = json_object_array_get_idx(errors_array, j); + json_object *error_code = NULL, *error_message = NULL; + if (json_object_object_get_ex(error_obj, "code", &error_code) && + json_object_object_get_ex(error_obj, "message", &error_message)) { + dap_json_compose_error_add(a_config->response_handler, json_object_get_int(error_code), json_object_get_string(error_message)); + } + } + l_result = NULL; + } + } + json_object_get(l_result); + return l_result; +} + +json_object* dap_request_command_to_rpc(const char *request, compose_config_t *a_config) { + json_object * l_response = s_request_command_to_rpc(request, a_config); + if (!l_response) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Failed to get response from RPC request"); + return NULL; + } + json_object * l_result = s_request_command_parse(l_response, a_config); + json_object_put(l_response); + if (!l_result) { + json_object_put(l_result); + return NULL; + } + return l_result; +} + + +bool dap_get_remote_net_fee_and_address(uint256_t *a_net_fee, dap_chain_addr_t **l_addr_fee, compose_config_t *a_config) { + char data[512]; + snprintf(data, sizeof(data), "{\"method\": \"net\",\"params\": [\"net;get;fee;-net;%s\"],\"id\": \"1\"}", a_config->net_name); + json_object * l_json_get_fee = dap_request_command_to_rpc(data, a_config); + if (!l_json_get_fee) { + return false; + } + + json_object *l_first_result = json_object_array_get_idx(l_json_get_fee, 0); + if (!l_first_result || !json_object_is_type(l_first_result, json_type_object)) { + json_object_put(l_json_get_fee); + return false; + } + + json_object *l_fees = NULL; + if (!json_object_object_get_ex(l_first_result, "fees", &l_fees) || + !json_object_is_type(l_fees, json_type_object)) { + json_object_put(l_json_get_fee); + return false; + } + + json_object *l_network = NULL; + if (!json_object_object_get_ex(l_fees, "network", &l_network) || + !json_object_is_type(l_network, json_type_object)) { + json_object_put(l_json_get_fee); + return false; + } + + json_object *l_balance = NULL; + if (!json_object_object_get_ex(l_network, "balance", &l_balance) || + !json_object_is_type(l_balance, json_type_string)) { + json_object_put(l_json_get_fee); + return false; + } + *a_net_fee = dap_chain_balance_scan(json_object_get_string(l_balance)); + + json_object *l_addr = NULL; + if (!json_object_object_get_ex(l_network, "addr", &l_addr) || + !json_object_is_type(l_addr, json_type_string)) { + json_object_put(l_json_get_fee); + return false; + } + *l_addr_fee = dap_chain_addr_from_str(json_object_get_string(l_addr)); + + json_object_put(l_json_get_fee); + return true; +} + +bool dap_get_remote_wallet_outs_and_count(dap_chain_addr_t *a_addr_from, const char *a_token_ticker, + json_object **l_outs, int *l_outputs_count, compose_config_t *a_config) { + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"wallet\",\"params\": [\"wallet;outputs;-addr;%s;-token;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_addr_to_str(a_addr_from), a_token_ticker, a_config->net_name); + json_object *l_json_outs = dap_request_command_to_rpc(data, a_config); + if (!l_json_outs) { + return false; + } + + if (!json_object_is_type(l_json_outs, json_type_array)) { + json_object_put(l_json_outs); + return false; + } + + if (json_object_array_length(l_json_outs) == 0) { + json_object_put(l_json_outs); + return false; + } + + json_object *l_first_array = json_object_array_get_idx(l_json_outs, 0); + if (!l_first_array || !json_object_is_type(l_first_array, json_type_array)) { + json_object_put(l_json_outs); + return false; + } + + json_object *l_first_item = json_object_array_get_idx(l_first_array, 0); + if (!l_first_item) { + json_object_put(l_json_outs); + return false; + } + + if (!json_object_object_get_ex(l_first_item, "outs", l_outs) || + !json_object_is_type(*l_outs, json_type_array)) { + json_object_put(l_json_outs); + return false; + } + + *l_outputs_count = json_object_array_length(*l_outs); + json_object_get(*l_outs); + json_object_put(l_json_outs); + return true; +} + + + + + + +typedef enum { + TX_CREATE_COMPOSE_OK = 0, + TX_CREATE_COMPOSE_MEMORY_ERROR = -1, + TX_CREATE_COMPOSE_ADDR_ERROR = -2, + TX_CREATE_COMPOSE_VALUE_ERROR = -3, + TX_CREATE_COMPOSE_WALLET_ERROR = -4, + TX_CREATE_COMPOSE_INVALID_PARAMS = -5, + TX_CREATE_COMPOSE_FEE_ERROR = -6, + TX_CREATE_COMPOSE_FUNDS_ERROR = -7, + TX_CREATE_COMPOSE_OUT_ERROR = -8 +} tx_create_compose_error_t; + +json_object* dap_tx_create_compose(const char *l_net_str, const char *l_token_ticker, const char *l_value_str, const char *l_fee_str, const char *addr_base58_to, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port) { + + compose_config_t *l_config = s_compose_config_init(l_net_str, l_url_str, l_port); + if (!l_config) { + json_object* l_json_obj_ret = json_object_new_object(); + dap_json_compose_error_add(l_json_obj_ret, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_FEE, "Can't create compose config"); + return l_json_obj_ret; + } + + uint256_t *l_value = NULL; + uint256_t l_value_fee = {}; + dap_chain_addr_t **l_addr_to = NULL; + size_t l_addr_el_count = 0; + size_t l_value_el_count = 0; + + + l_value_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_value_fee) && (l_fee_str && strcmp(l_fee_str, "0"))) { + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_FEE_ERROR, "tx_create requires parameter '-fee' to be valid uint256"); + return s_compose_config_return_response_handler(l_config); + } + + l_value_el_count = dap_str_symbol_count(l_value_str, ',') + 1; + + if (addr_base58_to) + l_addr_el_count = dap_str_symbol_count(addr_base58_to, ',') + 1; + else + l_addr_el_count = l_value_el_count; + + if (addr_base58_to && l_addr_el_count != l_value_el_count) { + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_INVALID_PARAMS, "num of '-to_addr' and '-value' should be equal"); + return s_compose_config_return_response_handler(l_config); + } + + l_value = DAP_NEW_Z_COUNT(uint256_t, l_value_el_count); + if (!l_value) { + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_MEMORY_ERROR, "Can't allocate memory"); + return s_compose_config_return_response_handler(l_config); + } + char **l_value_array = dap_strsplit(l_value_str, ",", l_value_el_count); + if (!l_value_array) { + DAP_DELETE(l_value); + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_ADDR_ERROR, "Can't read '-to_addr' arg"); + return s_compose_config_return_response_handler(l_config); + } + for (size_t i = 0; i < l_value_el_count; ++i) { + l_value[i] = dap_chain_balance_scan(l_value_array[i]); + if(IS_ZERO_256(l_value[i])) { + DAP_DEL_MULTY(l_value_array, l_value); + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_VALUE_ERROR, "tx_create requires parameter '-value' to be valid uint256 value"); + return s_compose_config_return_response_handler(l_config); + } + } + DAP_DELETE(l_value_array); + + if (addr_base58_to) { + l_addr_to = DAP_NEW_Z_COUNT(dap_chain_addr_t *, l_addr_el_count); + if (!l_addr_to) { + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_MEMORY_ERROR, "Can't allocate memory"); + DAP_DELETE(l_value); + return s_compose_config_return_response_handler(l_config); + } + char **l_addr_base58_to_array = dap_strsplit(addr_base58_to, ",", l_addr_el_count); + if (!l_addr_base58_to_array) { + DAP_DEL_MULTY(l_addr_to, l_value); + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_ADDR_ERROR, "Can't read '-to_addr' arg"); + return s_compose_config_return_response_handler(l_config); + } + for (size_t i = 0; i < l_addr_el_count; ++i) { + l_addr_to[i] = dap_chain_addr_from_str(l_addr_base58_to_array[i]); + if(!l_addr_to[i]) { + for (size_t j = 0; j < i; ++j) { + DAP_DELETE(l_addr_to[j]); + } + DAP_DEL_MULTY(l_addr_to, l_addr_base58_to_array, l_value); + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_ADDR_ERROR, "destination address is invalid"); + return s_compose_config_return_response_handler(l_config); + } + } + DAP_DELETE(l_addr_base58_to_array); + } + + dap_chain_wallet_t * l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL); + if(!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, TX_CREATE_COMPOSE_WALLET_ERROR, "Can't open wallet %s", l_wallet_str); + return s_compose_config_return_response_handler(l_config); + } + + + dap_chain_addr_t *l_addr_from = dap_chain_wallet_get_addr(l_wallet, s_get_net_id(l_net_str)); + for (size_t i = 0; l_addr_to && i < l_addr_el_count; ++i) { + if (dap_chain_addr_compare(l_addr_to[i], l_addr_from)) { + printf("The transaction cannot be directed to the same address as the source."); + for (size_t j = 0; j < l_addr_el_count; ++j) { + DAP_DELETE(l_addr_to[j]); + } + DAP_DEL_MULTY(l_addr_to, l_value); + return s_compose_config_return_response_handler(l_config); + } + } + + + dap_chain_datum_tx_t* l_tx = dap_chain_datum_tx_create_compose( l_addr_from, l_addr_to, l_token_ticker, l_value, l_value_fee, l_addr_el_count, l_config); + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + dap_chain_datum_tx_delete(l_tx); + DAP_DEL_MULTY(l_addr_to, l_value, l_addr_from); + return s_compose_config_return_response_handler(l_config); + } + + DAP_DEL_MULTY(l_addr_to, l_value, l_addr_from); + return s_compose_config_return_response_handler(l_config); +} + +int dap_chain_datum_tx_add_out_without_addr(dap_chain_datum_tx_t **a_tx, uint256_t a_value) { + if (IS_ZERO_256(a_value)) + return -1; + + dap_chain_tx_out_t *l_item = DAP_NEW_Z(dap_chain_tx_out_t); + if (!l_item) + return -1; + + l_item->header.type = TX_ITEM_TYPE_OUT; + l_item->header.value = a_value; + + int res = dap_chain_datum_tx_add_item(a_tx, l_item); + DAP_DELETE(l_item); + + return res; +} + + +int dap_chain_datum_tx_add_out_ext_item_without_addr(dap_chain_datum_tx_t **a_tx, uint256_t a_value, const char *a_token) +{ + if (!a_token || IS_ZERO_256(a_value)) + return -1; + + dap_chain_tx_out_ext_t *l_item = DAP_NEW_Z(dap_chain_tx_out_ext_t); + if (!l_item) + return -2; + l_item->header.type = TX_ITEM_TYPE_OUT_EXT; + l_item->header.value = a_value; + dap_strncpy((char*)l_item->token, a_token, sizeof(l_item->token) - 1); + + int result = dap_chain_datum_tx_add_item(a_tx, l_item); + DAP_DELETE(l_item); + return result; +} + + +dap_chain_datum_tx_t *dap_chain_datum_tx_create_compose(dap_chain_addr_t* a_addr_from, dap_chain_addr_t** a_addr_to, + const char* a_token_ticker, uint256_t *a_value, uint256_t a_value_fee, size_t a_tx_num, compose_config_t *a_config) +{ + if (!a_config) { + return NULL; + } + if (!a_addr_from || !a_token_ticker || !a_value) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_INVALID_PARAMS, "Invalid parameters"); + return NULL; + } + + if (dap_chain_addr_check_sum(a_addr_from)) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_ADDR_ERROR, "Invalid source address"); + return NULL; + } + + for (size_t i = 0; i < a_tx_num; ++i) { + // if (!a_addr_to || !a_addr_to[i]) { + // return NULL; + // } + if (a_addr_to && dap_chain_addr_check_sum(a_addr_to[i])) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_ADDR_ERROR, "Invalid destination address"); + return NULL; + } + if (IS_ZERO_256(a_value[i])) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_VALUE_ERROR, "Invalid value"); + return NULL; + } + } + const char * l_native_ticker = s_get_native_ticker(a_config->net_name); + bool l_single_channel = !dap_strcmp(a_token_ticker, l_native_ticker); + + uint256_t l_value_transfer = {}; // how many coins to transfer + uint256_t l_value_total = {}, l_total_fee = {}, l_fee_transfer = {}; + for (size_t i = 0; i < a_tx_num; ++i) { + SUM_256_256(l_value_total, a_value[i], &l_value_total); + } + uint256_t l_value_need = l_value_total; + + dap_list_t *l_list_fee_out = NULL; + uint256_t l_net_fee = {}; + dap_chain_addr_t *l_addr_fee = NULL; + if (!dap_get_remote_net_fee_and_address(&l_net_fee, &l_addr_fee, a_config)) { + return NULL; + } + + bool l_net_fee_used = !IS_ZERO_256(l_net_fee); + SUM_256_256(l_net_fee, a_value_fee, &l_total_fee); + json_object *l_outs = NULL; + int l_outputs_count = 0; + if (!dap_get_remote_wallet_outs_and_count(a_addr_from, a_token_ticker, &l_outs, &l_outputs_count, a_config)) { + return NULL; + } + + if (l_single_channel) + SUM_256_256(l_value_need, l_total_fee, &l_value_need); + else if (!IS_ZERO_256(l_total_fee)) { + l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_outputs_count, + l_total_fee, + &l_fee_transfer); + if (!l_list_fee_out) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_FEE_ERROR, "Not enough funds to pay fee"); + json_object_put(l_outs); + return NULL; + } + } + dap_list_t *l_list_used_out = NULL; + l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_outputs_count, + l_value_need, + &l_value_transfer); + json_object_put(l_outs); + if (!l_list_used_out) { + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_FUNDS_ERROR, "Not enough funds to transfer"); + return NULL; + } + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + // add 'in' items + { + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + assert(EQUAL_256(l_value_to_items, l_value_transfer)); + dap_list_free_full(l_list_used_out, NULL); + if (l_list_fee_out) { + uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); + assert(EQUAL_256(l_value_fee_items, l_fee_transfer)); + dap_list_free_full(l_list_fee_out, NULL); + } + + } + if (a_tx_num > 1) { + uint32_t l_tx_num = a_tx_num; + dap_chain_tx_tsd_t *l_out_count = dap_chain_datum_tx_item_tsd_create(&l_tx_num, DAP_CHAIN_DATUM_TRANSFER_TSD_TYPE_OUT_COUNT, sizeof(uint32_t)); + dap_chain_datum_tx_add_item(&l_tx, l_out_count); + } + + if (l_single_channel) { // add 'out' items + uint256_t l_value_pack = {}; // how much datoshi add to 'out' items + for (size_t i = 0; i < a_tx_num; ++i) { + if (a_addr_to) { + if (dap_chain_datum_tx_add_out_item(&l_tx, a_addr_to[i], a_value[i]) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'out' item"); + return NULL; + } + } else { + if (dap_chain_datum_tx_add_out_without_addr(&l_tx, a_value[i]) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'out' without address"); + return NULL; + } + } + SUM_256_256(l_value_pack, a_value[i], &l_value_pack); + } + // Network fee + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_item(&l_tx, l_addr_fee, l_net_fee) == 1) + SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_FEE_ERROR, "Can't add network 'fee' item"); + return NULL; + } + } + // Validator's fee + if (!IS_ZERO_256(a_value_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_value_fee) == 1) + SUM_256_256(l_value_pack, a_value_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_FEE_ERROR, "Can't add validator's 'fee' item"); + return NULL; + } + } + // coin back + uint256_t l_value_back; + SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); + if(!IS_ZERO_256(l_value_back)) { + if(dap_chain_datum_tx_add_out_item(&l_tx, a_addr_from, l_value_back) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'coin back' item"); + return NULL; + } + } + } else { // add 'out_ext' items + for (size_t i = 0; i < a_tx_num; ++i) { + if (a_addr_to) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, a_addr_to[i], a_value[i], a_token_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'out_ext' item"); + return NULL; + } + } else { + if (dap_chain_datum_tx_add_out_ext_item_without_addr(&l_tx, a_value[i], a_token_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'out_ext' without address"); + return NULL; + } + } + } + // coin back + uint256_t l_value_back; + SUBTRACT_256_256(l_value_transfer, l_value_total, &l_value_back); + if(!IS_ZERO_256(l_value_back)) { + if(dap_chain_datum_tx_add_out_ext_item(&l_tx, a_addr_from, l_value_back, a_token_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'coin back' item"); + return NULL; + } + } + // Network fee + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_fee, l_net_fee, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add network 'fee' item"); + return NULL; + } + } + // Validator's fee + if (!IS_ZERO_256(a_value_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_value_fee) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_FEE_ERROR, "Can't add validator's 'fee' item"); + return NULL; + } + } + // fee coin back + SUBTRACT_256_256(l_fee_transfer, l_total_fee, &l_value_back); + if(!IS_ZERO_256(l_value_back)) { + if(dap_chain_datum_tx_add_out_ext_item(&l_tx, a_addr_from, l_value_back, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, TX_CREATE_COMPOSE_OUT_ERROR, "Can't add 'coin back' item"); + return NULL; + } + } + } + DAP_DELETE(l_addr_fee); + return l_tx; +} + +dap_list_t *dap_ledger_get_list_tx_outs_from_json(json_object * a_outputs_array, int a_outputs_count, uint256_t a_value_need, uint256_t *a_value_transfer) +{ + dap_list_t *l_list_used_out = NULL; + uint256_t l_value_transfer = {}; + + for (int i = 0; i < a_outputs_count; i++) { + json_object *l_output = json_object_array_get_idx(a_outputs_array, i); + + json_object *l_value_datosi_obj = NULL; + json_object_object_get_ex(l_output, "value_datosi", &l_value_datosi_obj); + if (!l_value_datosi_obj) { + continue; + } + const char *l_value_str = json_object_get_string(l_value_datosi_obj); + uint256_t l_value = dap_chain_balance_scan(l_value_str); + + if (IS_ZERO_256(l_value)) { + continue; + } + + json_object *l_prev_hash_obj = NULL; + json_object_object_get_ex(l_output, "prev_hash", &l_prev_hash_obj); + if (!l_prev_hash_obj) { + continue; + } + const char *l_prev_hash_str = json_object_get_string(l_prev_hash_obj); + + json_object *l_out_prev_idx_obj = NULL; + json_object_object_get_ex(l_output, "out_prev_idx", &l_out_prev_idx_obj); + if (!l_out_prev_idx_obj) { + continue; + } + int l_out_idx = json_object_get_int(l_out_prev_idx_obj); + + dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); + if (!l_item) { + continue; + } + + dap_chain_hash_fast_from_str(l_prev_hash_str, &l_item->tx_hash_fast); + l_item->num_idx_out = l_out_idx; + l_item->value = l_value; + + l_list_used_out = dap_list_append(l_list_used_out, l_item); + + SUM_256_256(l_value_transfer, l_value, &l_value_transfer); + + if (compare256(l_value_transfer, a_value_need) >= 0) { + break; + } + } + + if (compare256(l_value_transfer, a_value_need) >= 0 && l_list_used_out) { + if (a_value_transfer) { + *a_value_transfer = l_value_transfer; + } + return l_list_used_out; + } else { + dap_list_free_full(l_list_used_out, NULL); + return NULL; + } +} + + +json_object *dap_get_remote_tx_outs(const char *a_token_ticker, dap_chain_addr_t * a_addr, compose_config_t *a_config) { + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"wallet\",\"params\": [\"wallet;outputs;-addr;%s;-token;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_addr_to_str(a_addr), a_token_ticker, a_config->net_name); + json_object *l_json_outs = dap_request_command_to_rpc(data, a_config); + if (!l_json_outs) { + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Failed to get response from RPC request"); + return NULL; + } + + if (!json_object_is_type(l_json_outs, json_type_array)) { + json_object_put(l_json_outs); + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is not an array"); + return NULL; + } + + if (json_object_array_length(l_json_outs) == 0) { + json_object_put(l_json_outs); + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is empty"); + return NULL; + } + + json_object *l_first_array = json_object_array_get_idx(l_json_outs, 0); + if (!l_first_array || !json_object_is_type(l_first_array, json_type_array)) { + json_object_put(l_json_outs); + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is not an array"); + return NULL; + } + + json_object *l_first_item = json_object_array_get_idx(l_first_array, 0); + if (!l_first_item) { + json_object_put(l_json_outs); + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is not an array"); + return NULL; + } + + json_object *l_outs = NULL; + if (!json_object_object_get_ex(l_first_item, "outs", &l_outs) || + !json_object_is_type(l_outs, json_type_array)) { + json_object_put(l_json_outs); + dap_json_compose_error_add(a_config->response_handler, DAP_COMPOSE_ERROR_RESPONSE_NULL, "Response is not an array"); + return NULL; + } + json_object_get(l_outs); + json_object_put(l_json_outs); + return l_outs; +} + +uint256_t get_balance_from_json(json_object *l_json_outs, const char *a_token_sell) { + uint256_t l_value = {}; + if (l_json_outs && json_object_is_type(l_json_outs, json_type_array)) { + for (size_t i = 0; i < json_object_array_length(l_json_outs); i++) { + json_object *outer_array = json_object_array_get_idx(l_json_outs, i); + if (json_object_is_type(outer_array, json_type_array)) { + for (size_t j = 0; j < json_object_array_length(outer_array); j++) { + json_object *addr_obj = json_object_array_get_idx(outer_array, j); + if (json_object_is_type(addr_obj, json_type_object)) { + json_object *tokens = NULL; + if (json_object_object_get_ex(addr_obj, "tokens", &tokens) && json_object_is_type(tokens, json_type_array)) { + for (size_t k = 0; k < json_object_array_length(tokens); k++) { + json_object *token_obj = json_object_array_get_idx(tokens, k); + json_object *token = NULL; + if (json_object_object_get_ex(token_obj, "token", &token) && json_object_is_type(token, json_type_object)) { + json_object *ticker = NULL; + if (json_object_object_get_ex(token, "ticker", &ticker) && json_object_is_type(ticker, json_type_string)) { + const char *ticker_str = json_object_get_string(ticker); + if (strcmp(ticker_str, a_token_sell) == 0) { + json_object *datoshi = NULL; + if (json_object_object_get_ex(token_obj, "datoshi", &datoshi) && json_object_is_type(datoshi, json_type_string)) { + const char *datoshi_str = json_object_get_string(datoshi); + l_value = dap_uint256_scan_uninteger(datoshi_str); + break; + } + } + } + } + } + } + } + } + } + } + } + return l_value; +} + +bool check_token_in_ledger(json_object *l_json_coins, const char *a_token) { + if (json_object_is_type(l_json_coins, json_type_array)) { + for (size_t i = 0; i < json_object_array_length(l_json_coins); i++) { + json_object *token_array = json_object_array_get_idx(l_json_coins, i); + if (json_object_is_type(token_array, json_type_array)) { + for (size_t j = 0; j < json_object_array_length(token_array); j++) { + json_object *token_obj = json_object_array_get_idx(token_array, j); + json_object *token_name = NULL; + if (json_object_object_get_ex(token_obj, "-->Token name", &token_name) && json_object_is_type(token_name, json_type_string)) { + const char *token_name_str = json_object_get_string(token_name); + if (strcmp(token_name_str, a_token) == 0) { + return true; + } + } + } + } + } + } + return false; +} + + +typedef enum dap_xchange_compose_error { + DAP_XCHANGE_COMPOSE_ERROR_NONE = 0, + DAP_XCHANGE_COMPOSE_ERROR_INVALID_ARGUMENT, + DAP_XCHANGE_COMPOSE_ERROR_RATE_IS_ZERO, + DAP_XCHANGE_COMPOSE_ERROR_FEE_IS_ZERO, + DAP_XCHANGE_COMPOSE_ERROR_VALUE_SELL_IS_ZERO, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_GET_TX_OUTS, + DAP_XCHANGE_COMPOSE_ERROR_TOKEN_TICKER_SELL_OR_BUY_IS_NOT_FOUND_LEDGER, + DAP_XCHANGE_COMPOSE_ERROR_INTEGER_OVERFLOW_WITH_SUM_OF_VALUE_AND_FEE, + DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_CASH_FOR_FEE_IN_SPECIFIED_WALLET, + DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_CASH_IN_SPECIFIED_WALLET, + DAP_XCHANGE_COMPOSE_ERROR_MEMORY_ALLOCATED, + DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_FUNDS_TO_PAY_FEE, + DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_FUNDS_TO_TRANSFER, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_COMPOSE_THE_TRANSACTION_INPUT, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_COMPOSE_THE_TRANSACTION_CONDITIONAL_OUTPUT, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_NETWORK_FEE_OUTPUT, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_VALIDATOR_FEE_OUTPUT, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_COIN_BACK_OUTPUT, + DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_FEE_BACK_OUTPUT, + DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE +} dap_xchange_compose_error_t; + +json_object* dap_tx_create_xchange_compose(const char *l_net_name, const char *l_token_buy, const char *l_token_sell, const char *l_wallet_name, const char *l_wallet_path, const char *l_value_str, const char *l_rate_str, const char *l_fee_str, const char *l_url_str, uint16_t l_port){ + compose_config_t *l_config = s_compose_config_init(l_net_name, l_url_str, l_port); + if (!l_config) { + json_object* l_json_obj_ret = json_object_new_object(); + dap_json_compose_error_add(l_json_obj_ret, DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE, "Can't create compose config"); + return l_json_obj_ret; + } + + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_name, l_wallet_path, NULL); + if(!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE, "wallet %s does not exist", l_wallet_name); + return s_compose_config_return_response_handler(l_config); + } + + + uint256_t l_value = dap_chain_balance_scan(l_value_str); + if (IS_ZERO_256(l_value)) { + dap_json_compose_error_add(l_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE, "Invalid parameter value, rate or fee is 0, use required format 1.0e+18 ot in datoshi"); + return s_compose_config_return_response_handler(l_config); + } + uint256_t l_rate = dap_chain_balance_scan(l_rate_str); + if (IS_ZERO_256(l_rate)) { + dap_json_compose_error_add(l_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE, "Invalid parameter rate"); + return s_compose_config_return_response_handler(l_config); + } + uint256_t l_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_fee)) { + dap_json_compose_error_add(l_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_FEE, "Invalid parameter fee"); + return s_compose_config_return_response_handler(l_config); + } + + dap_chain_datum_tx_t *l_tx = dap_chain_net_srv_xchange_create_compose(l_token_buy, + l_token_sell, l_value, l_rate, l_fee, l_wallet, l_config); + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + dap_chain_datum_tx_delete(l_tx); + return s_compose_config_return_response_handler(l_config); + } + + return s_compose_config_return_response_handler(l_config); +} + + +dap_chain_datum_tx_t* dap_chain_net_srv_xchange_create_compose(const char *a_token_buy, + const char *a_token_sell, uint256_t a_datoshi_sell, + uint256_t a_rate, uint256_t a_fee, dap_chain_wallet_t *a_wallet, compose_config_t *a_config){ + if (!a_config) { + return NULL; + } + if ( !a_token_buy || !a_token_sell || !a_wallet) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_ARGUMENT, "Invalid parameter"); + return NULL; + } + if (IS_ZERO_256(a_rate)) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_RATE_IS_ZERO, "Invalid parameter rate"); + return NULL; + } + if (IS_ZERO_256(a_fee)) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_FEE_IS_ZERO, "Invalid parameter fee"); + return NULL; + } + if (IS_ZERO_256(a_datoshi_sell)) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_VALUE_SELL_IS_ZERO, "Invalid parameter value sell"); + return NULL; + } + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;list;coins;-net;%s\"],\"id\": \"2\"}", a_config->net_name); + json_object *l_json_coins = dap_request_command_to_rpc(data, a_config); + if (!l_json_coins) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_GET_TX_OUTS, "Can't get tx outs"); + return NULL; + } + if (!check_token_in_ledger(l_json_coins, a_token_sell) || !check_token_in_ledger(l_json_coins, a_token_buy)) { + json_object_put(l_json_coins); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_TOKEN_TICKER_SELL_OR_BUY_IS_NOT_FOUND_LEDGER, "Token ticker sell or buy is not found in ledger"); + return NULL; + } + json_object_put(l_json_coins); + dap_chain_addr_t *l_wallet_addr = dap_chain_wallet_get_addr(a_wallet, s_get_net_id(a_config->net_name)); + snprintf(data, sizeof(data), + "{\"method\": \"wallet\",\"params\": [\"wallet;info;-addr;%s;-net;%s\"],\"id\": \"2\"}", + dap_chain_addr_to_str(l_wallet_addr), a_config->net_name); + DAP_DEL_Z(l_wallet_addr); + json_object *l_json_outs = dap_request_command_to_rpc(data, a_config); + uint256_t l_value = get_balance_from_json(l_json_outs, a_token_sell); + + uint256_t l_value_sell = a_datoshi_sell; + if (!dap_strcmp(s_get_native_ticker(a_config->net_name), a_token_sell)) { + if (SUM_256_256(l_value_sell, a_fee, &l_value_sell)) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INTEGER_OVERFLOW_WITH_SUM_OF_VALUE_AND_FEE, "Integer overflow with sum of value and fee"); + return NULL; + } + } else { // sell non-native ticker + uint256_t l_fee_value = get_balance_from_json(l_json_outs, s_get_native_ticker(a_config->net_name)); + if (compare256(l_fee_value, a_fee) == -1) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_CASH_FOR_FEE_IN_SPECIFIED_WALLET, "Not enough cash for fee in specified wallet"); + return NULL; + } + } + if (compare256(l_value, l_value_sell) == -1) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_CASH_IN_SPECIFIED_WALLET, "Not enough cash in specified wallet"); + return NULL; + } + // Create the price + dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); + if (!l_price) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_MEMORY_ALLOCATED, "Memory allocated"); + return NULL; + } + dap_stpcpy(l_price->token_sell, a_token_sell); + dap_stpcpy(l_price->token_buy, a_token_buy); + l_price->datoshi_sell = a_datoshi_sell; + l_price->rate = a_rate; + l_price->fee = a_fee; + dap_chain_datum_tx_t *l_tx = dap_xchange_tx_create_request_compose(l_price, a_wallet, s_get_native_ticker(a_config->net_name), a_config); + return l_tx; +} + + + +dap_chain_datum_tx_t *dap_xchange_tx_create_request_compose(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet, + const char *a_native_ticker, compose_config_t *a_config) +{ + if (!a_config) { + return NULL; + } + if (!a_price || !*a_price->token_sell || !*a_price->token_buy || !a_wallet) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_INVALID_ARGUMENT, "Invalid parameter"); + return NULL; + } + const char *l_native_ticker = s_get_native_ticker(a_config->net_name); + bool l_single_channel = !dap_strcmp(a_price->token_sell, l_native_ticker); + // find the transactions from which to take away coins + uint256_t l_value_transfer; // how many coins to transfer + uint256_t l_value_need = a_price->datoshi_sell, + l_net_fee, + l_total_fee = a_price->fee, + l_fee_transfer; + dap_chain_addr_t * l_addr_net_fee = NULL; + dap_list_t *l_list_fee_out = NULL; + + bool l_net_fee_used = dap_get_remote_net_fee_and_address(&l_net_fee, &l_addr_net_fee, a_config); + if (l_net_fee_used) + SUM_256_256(l_total_fee, l_net_fee, &l_total_fee); + + dap_chain_addr_t *l_wallet_addr = dap_chain_wallet_get_addr(a_wallet, s_get_net_id(a_config->net_name)); + dap_chain_addr_t l_seller_addr = *l_wallet_addr; + json_object *l_outs_native = dap_get_remote_tx_outs(a_native_ticker, l_wallet_addr, a_config); + if (!l_outs_native) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_GET_TX_OUTS, "Can't get tx outs"); + return NULL; + } + + json_object *l_outs = NULL; + if (!dap_strcmp(a_price->token_sell, a_native_ticker)) { + l_outs = l_outs_native; + } else { + l_outs = dap_get_remote_tx_outs(a_price->token_sell, l_wallet_addr, a_config); + } + DAP_DELETE(l_wallet_addr); + int l_out_native_count = json_object_array_length(l_outs_native); + int l_out_count = json_object_array_length(l_outs); + + if (l_single_channel) + SUM_256_256(l_value_need, l_total_fee, &l_value_need); + else if (!IS_ZERO_256(l_total_fee)) { + l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, + l_total_fee, + &l_fee_transfer); + if (!l_list_fee_out) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_FUNDS_TO_PAY_FEE, "Not enough funds to pay fee"); + json_object_put(l_outs_native); + json_object_put(l_outs); + return NULL; + } + } + dap_list_t *l_list_used_out = NULL; + l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_out_count, + l_value_need, + &l_value_transfer); + if (!l_list_used_out) { + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_NOT_ENOUGH_FUNDS_TO_TRANSFER, "Not enough funds to transfer"); + json_object_put(l_outs_native); + json_object_put(l_outs); + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + // add 'in' items to sell + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + dap_list_free_full(l_list_used_out, NULL); + if (!EQUAL_256(l_value_to_items, l_value_transfer) != 0) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_COMPOSE_THE_TRANSACTION_INPUT, "Can't compose the transaction input"); + return NULL; + } + if (!l_single_channel) { + // add 'in' items to fee + uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); + if (!EQUAL_256(l_value_fee_items, l_fee_transfer) != 0) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_COMPOSE_THE_TRANSACTION_INPUT, "Can't compose the transaction input"); + return NULL; + } + } + + // add 'out_cond' & 'out' items + + { + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_XCHANGE_ID }; + dap_chain_tx_out_cond_t *l_tx_out = dap_chain_datum_tx_item_out_cond_create_srv_xchange(l_uid, s_get_net_id(a_config->net_name), a_price->datoshi_sell, + s_get_net_id(a_config->net_name), a_price->token_buy, a_price->rate, + &l_seller_addr, NULL, 0); + if (!l_tx_out) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_COMPOSE_THE_TRANSACTION_CONDITIONAL_OUTPUT, "Can't compose the transaction conditional output"); + return NULL; + } + dap_chain_datum_tx_add_item(&l_tx, (const uint8_t *)l_tx_out); + DAP_DELETE(l_tx_out); + // Network fee + if (l_net_fee_used) { + if ((l_single_channel && + dap_chain_datum_tx_add_out_item(&l_tx, l_addr_net_fee, l_net_fee) != 1) || + (!l_single_channel && + dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_net_fee, l_net_fee, l_native_ticker) != 1)) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_NETWORK_FEE_OUTPUT, "Can't add network fee output"); + return NULL; + } + } + DAP_DELETE(l_addr_net_fee); + // Validator's fee + if (!IS_ZERO_256(a_price->fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_price->fee) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_VALIDATOR_FEE_OUTPUT, "Can't add validator's fee output"); + return NULL; + } + } + // coin back + uint256_t l_value_back = {}; + SUBTRACT_256_256(l_value_transfer, l_value_need, &l_value_back); + if (!IS_ZERO_256(l_value_back)) { + if ((l_single_channel && + dap_chain_datum_tx_add_out_item(&l_tx, &l_seller_addr, l_value_back) != 1) || + (!l_single_channel && + dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_seller_addr, l_value_back, a_price->token_sell) != 1)) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_COIN_BACK_OUTPUT, "Can't add coin back output"); + return NULL; + } + } + // Fee coinback + if (!l_single_channel) { + uint256_t l_fee_coinback = {}; + SUBTRACT_256_256(l_fee_transfer, l_total_fee, &l_fee_coinback); + if (!IS_ZERO_256(l_fee_coinback)) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_seller_addr, l_fee_coinback, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, DAP_XCHANGE_COMPOSE_ERROR_CAN_NOT_ADD_FEE_BACK_OUTPUT, "Can't add fee back output"); + return NULL; + } + } + } + } + return l_tx; +} + +// // tx_cond_create -net <net_name> -token <token_ticker> -w <wallet_name> -cert <pub_cert_name> -value <value_datoshi> -fee <value> -unit {B | SEC} -srv_uid <numeric_uid> +// int dap_tx_cond_create_compose(int argc, char ** argv) +// { +// int arg_index = 1; +// const char * l_token_ticker = NULL; +// const char * l_wallet_str = NULL; +// const char * l_cert_str = NULL; +// const char * l_value_datoshi_str = NULL; +// const char * l_value_fee_str = NULL; +// const char * l_net_name = NULL; +// const char * l_unit_str = NULL; +// const char * l_srv_uid_str = NULL; +// uint256_t l_value_datoshi = {}; +// uint256_t l_value_fee = {}; +// const char * l_url_str = NULL; +// const char * l_port_str = NULL; +// uint16_t l_port = 0; + +// const char *l_wallet_path = NULL; +// dap_cli_server_cmd_find_option_val(argv, 1, argc, "-wallet_path", &l_wallet_path); +// if (!l_wallet_path) { +// l_wallet_path = +// #ifdef DAP_OS_WINDOWS +// dap_strdup_printf("%s/var/lib/wallets", regGetUsrPath()); +// #elif defined DAP_OS_MAC +// dap_strdup_printf("Library/Application Support/CellframeNode/var/lib/wallets"); +// #elif defined DAP_OS_UNIX +// dap_strdup_printf("/opt/CellframeNode/var/lib/wallets"); +// #endif +// } + +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-token", &l_token_ticker); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-w", &l_wallet_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-cert", &l_cert_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-value", &l_value_datoshi_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-fee", &l_value_fee_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-net", &l_net_name); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-unit", &l_unit_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-srv_uid", &l_srv_uid_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-url", &l_url_str); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-port", &l_port_str); + +// if(!l_token_ticker) { +// printf("tx_cond_create requires parameter '-token'\n"); +// return -2; +// } +// if (!l_wallet_str) { +// printf("tx_cond_create requires parameter '-w'\n"); +// return -3; +// } +// if (!l_cert_str) { +// printf("tx_cond_create requires parameter '-cert'\n"); +// return -4; +// } +// if(!l_value_datoshi_str) { +// printf("tx_cond_create requires parameter '-value'\n"); +// return -5; +// } +// if(!l_value_fee_str){ +// printf("tx_cond_create requires parameter '-fee'\n"); +// return -6; +// } +// if(!l_net_name) { +// printf("tx_cond_create requires parameter '-net'\n"); +// return -7; +// } +// if(!l_url_str) { +// l_url_str = dap_compose_get_net_url(l_net_name); +// } +// if(!l_port_str) { +// l_port = dap_compose_get_net_port(l_net_name); +// } else { +// l_port = atoi(l_port_str); +// } +// if(!l_unit_str) { +// printf("tx_cond_create requires parameter '-unit'\n"); +// return -8; +// } + +// if(!l_srv_uid_str) { +// printf("tx_cond_create requires parameter '-srv_uid'\n"); +// return -9; +// } +// dap_chain_net_srv_uid_t l_srv_uid = {}; +// l_srv_uid.uint64 = strtoll(l_srv_uid_str, NULL, 10); +// if (!l_srv_uid.uint64) { +// printf("Can't find service UID %s\n", l_srv_uid_str); +// return -10; +// } + +// dap_chain_net_srv_price_unit_uid_t l_price_unit = { .enm = dap_chain_srv_str_to_unit_enum((char*)l_unit_str)}; + +// if(l_price_unit.enm == SERV_UNIT_UNDEFINED) { +// printf("Can't recognize unit '%s'. Unit must look like { B | SEC }\n", l_unit_str); +// return -11; +// } + +// l_value_datoshi = dap_chain_balance_scan(l_value_datoshi_str); +// if(IS_ZERO_256(l_value_datoshi)) { +// printf("Can't recognize value '%s' as a number\n", l_value_datoshi_str); +// return -12; +// } + +// l_value_fee = dap_chain_balance_scan(l_value_fee_str); +// if(IS_ZERO_256(l_value_fee)) { +// printf("Can't recognize value '%s' as a number\n", l_value_fee_str); +// return -13; +// } + +// dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL); +// if(!l_wallet) { +// printf("Can't open wallet '%s'\n", l_wallet_str); +// return -15; +// } + +// dap_cert_t *l_cert_cond = dap_cert_find_by_name(l_cert_str); +// if(!l_cert_cond) { +// dap_chain_wallet_close(l_wallet); +// printf("Can't find cert '%s'\n", l_cert_str); +// return -16; +// } + +// dap_enc_key_t *l_key_from = dap_chain_wallet_get_key(l_wallet, 0); +// dap_pkey_t *l_key_cond = dap_pkey_from_enc_key(l_cert_cond->enc_key); +// if (!l_key_cond) { +// dap_chain_wallet_close(l_wallet); +// dap_enc_key_delete(l_key_from); +// printf("Cert '%s' doesn't contain a valid public key\n", l_cert_str); +// return -17; +// } + +// uint256_t l_value_per_unit_max = {}; +// dap_chain_datum_tx_t *l_tx = dap_chain_mempool_tx_create_cond_compose(l_net_name, l_key_from, l_key_cond, l_token_ticker, +// l_value_datoshi, l_value_per_unit_max, l_price_unit, +// l_srv_uid, l_value_fee, NULL, 0, l_url_str, l_port); + +// json_object * l_json_obj_ret = json_object_new_object(); +// dap_chain_net_tx_to_json(l_tx, l_json_obj_ret); +// printf("%s", json_object_to_json_string(l_json_obj_ret)); +// json_object_put(l_json_obj_ret); +// dap_chain_datum_tx_delete(l_tx); +// dap_chain_wallet_close(l_wallet); +// dap_enc_key_delete(l_key_from); +// DAP_DELETE(l_key_cond); +// return 0; +// } + + +// dap_chain_datum_tx_t *dap_chain_mempool_tx_create_cond_compose(const char *a_net_name, +// dap_enc_key_t *a_key_from, dap_pkey_t *a_key_cond, +// const char a_token_ticker[DAP_CHAIN_TICKER_SIZE_MAX], +// uint256_t a_value, uint256_t a_value_per_unit_max, +// dap_chain_net_srv_price_unit_uid_t a_unit, dap_chain_net_srv_uid_t a_srv_uid, +// uint256_t a_value_fee, const void *a_cond, +// size_t a_cond_size, const char *a_url_str, uint16_t a_port) +// { +// // check valid param +// if (!a_net_name || !*a_net_name || !a_key_from || !a_key_cond || +// !a_key_from->priv_key_data || !a_key_from->priv_key_data_size || IS_ZERO_256(a_value) || !a_url_str || !*a_url_str || a_port == 0) +// return NULL; + +// if (dap_strcmp(s_get_native_ticker(a_net_name), a_token_ticker)) { +// printf("Pay for service should be only in native token ticker\n"); +// return NULL; +// } + +// uint256_t l_net_fee = {}; +// dap_chain_addr_t* l_addr_fee = NULL; +// bool l_net_fee_used = dap_get_remote_net_fee_and_address(a_net_name, &l_net_fee, &l_addr_fee, a_url_str, a_port); +// // find the transactions from which to take away coins +// uint256_t l_value_transfer = {}; // how many coins to transfer +// uint256_t l_value_need = {}; +// SUM_256_256(a_value, a_value_fee, &l_value_need); +// if (l_net_fee_used) { +// SUM_256_256(l_value_need, l_net_fee, &l_value_need); +// } +// // where to take coins for service +// dap_chain_addr_t l_addr_from; +// dap_chain_addr_fill_from_key(&l_addr_from, a_key_from, s_get_net_id(a_net_name)); +// // list of transaction with 'out' items +// json_object *l_outs = NULL; +// int l_outputs_count = 0; +// if (!dap_get_remote_wallet_outs_and_count(&l_addr_from, a_token_ticker, a_net_name, &l_outs, &l_outputs_count, a_url_str, a_port)) { +// return NULL; +// } +// dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_outputs_count, +// l_value_need, +// &l_value_transfer); +// json_object_put(l_outs); +// if(!l_list_used_out) { +// printf("Nothing to transfer (not enough funds)\n"); +// return NULL; +// } + +// // create empty transaction +// dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); +// // add 'in' items +// { +// uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); +// assert(EQUAL_256(l_value_to_items, l_value_transfer)); +// dap_list_free_full(l_list_used_out, NULL); +// } +// // add 'out_cond' and 'out' items +// { +// uint256_t l_value_pack = {}; // how much coin add to 'out' items +// if(dap_chain_datum_tx_add_out_cond_item(&l_tx, a_key_cond, a_srv_uid, a_value, a_value_per_unit_max, a_unit, a_cond, +// a_cond_size) == 1) { +// SUM_256_256(l_value_pack, a_value, &l_value_pack); +// } else { +// dap_chain_datum_tx_delete(l_tx); +// printf("Cant add conditional output\n"); +// return NULL; +// } +// // Network fee +// if (l_net_fee_used) { +// if (dap_chain_datum_tx_add_out_item(&l_tx, l_addr_fee, l_net_fee) == 1) +// SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); +// else { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// } +// // Validator's fee +// if (!IS_ZERO_256(a_value_fee)) { +// if (dap_chain_datum_tx_add_fee_item(&l_tx, a_value_fee) == 1) +// SUM_256_256(l_value_pack, a_value_fee, &l_value_pack); +// else { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// } +// // coin back +// uint256_t l_value_back = {}; +// SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); +// if (!IS_ZERO_256(l_value_back)) { +// if(dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_from, l_value_back) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Cant add coin back output\n"); +// return NULL; +// } +// } +// } + +// return l_tx; +// } + +// // stake_lock hold -net <net_name> -w <wallet_name> -time_staking <YYMMDD> -token <ticker> -value <value> -fee <value>[-chain <chain_name>] [-reinvest <percentage>] +// int dap_cli_hold_compose(int a_argc, char **a_argv) +// { +// int arg_index = 1; +// const char *l_net_name = NULL, *l_ticker_str = NULL, *l_coins_str = NULL, +// *l_wallet_str = NULL, *l_cert_str = NULL, *l_chain_id_str = NULL, +// *l_time_staking_str = NULL, *l_reinvest_percent_str = NULL, *l_value_fee_str = NULL; + +// char l_delegated_ticker_str[DAP_CHAIN_TICKER_SIZE_MAX] = {}; +// dap_time_t l_time_staking = 0; +// uint256_t l_reinvest_percent = {}; +// uint256_t l_value_delegated = {}; +// uint256_t l_value_fee = {}; +// uint256_t l_value; +// dap_enc_key_t *l_key_from; +// dap_chain_wallet_t *l_wallet; +// dap_chain_addr_t *l_addr_holder; +// const char *l_url_str = NULL; +// uint16_t l_port = 0; + +// const char *l_wallet_path = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-wallet_path", &l_wallet_path); +// if (!l_wallet_path) { +// l_wallet_path = +// #ifdef DAP_OS_WINDOWS +// dap_strdup_printf("%s/var/lib/wallets", regGetUsrPath()); +// #elif defined DAP_OS_MAC +// dap_strdup_printf("Library/Application Support/CellframeNode/var/lib/wallets"); +// #elif defined DAP_OS_UNIX +// dap_strdup_printf("/opt/CellframeNode/var/lib/wallets"); +// #endif +// } + + +// const char *l_hash_out_type = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-H", &l_hash_out_type); +// if(!l_hash_out_type) +// l_hash_out_type = "hex"; +// if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type, "base58")) { +// printf("Error: Invalid hash type argument\n"); +// return -1; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_name) || NULL == l_net_name) { +// printf("Error: Missing or invalid network argument\n"); +// return -2; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-token", &l_ticker_str) || NULL == l_ticker_str || dap_strlen(l_ticker_str) > 8) { +// printf("Error: Missing or invalid token argument\n"); +// return -3; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-url", &l_url_str)) { +// l_url_str = dap_compose_get_net_url(l_net_name); +// } + +// const char *l_port_str = NULL; +// if (!dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-port", &l_port_str)) { +// l_port = dap_compose_get_net_port(l_net_name); +// } else { +// l_port = atoi(l_port_str); +// } + +// char data[512]; +// snprintf(data, sizeof(data), +// "{\"method\": \"ledger\",\"params\": [\"ledger;list;coins;-net;%s\"],\"id\": \"2\"}", l_net_name); +// json_object *l_json_coins = dap_request_command_to_rpc(data, l_net_name, l_url_str, l_port); +// if (!l_json_coins) { +// return -4; +// } +// if (!check_token_in_ledger(l_json_coins, l_ticker_str)) { +// printf("Error: Invalid token '%s'\n", l_ticker_str); +// return -4; +// } + +// if ((!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-coins", &l_coins_str) || NULL == l_coins_str) && +// (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-value", &l_coins_str) || NULL == l_coins_str)) { +// printf("Error: Missing coins or value argument\n"); +// return -5; +// } + +// if (IS_ZERO_256((l_value = dap_chain_balance_scan(l_coins_str)))) { +// printf("Error: Invalid coins format\n"); +// return -6; +// } + +// dap_chain_datum_token_get_delegated_ticker(l_delegated_ticker_str, l_ticker_str); + +// if (!check_token_in_ledger(l_json_coins, l_delegated_ticker_str)) { +// printf("Error: No delegated token found\n"); +// return -7; +// } +// json_object_put(l_json_coins); + +// uint256_t l_emission_rate = dap_chain_coins_to_balance("0.001"); // TODO 16126 +// // uint256_t l_emission_rate = dap_ledger_token_get_emission_rate(l_ledger, l_delegated_ticker_str); +// // if (IS_ZERO_256(l_emission_rate)) { +// // printf("Error: Invalid token emission rate\n"); +// // return -8; +// // } + +// if (MULT_256_COIN(l_value, l_emission_rate, &l_value_delegated) || IS_ZERO_256(l_value_delegated)) { +// printf("Error: Invalid coins format\n"); +// return -9; +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-chain_id", &l_chain_id_str); +// if (!l_chain_id_str) { +// printf("Error: Missing or invalid chain_id argument\n"); +// return -10; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str) || !l_wallet_str) { +// printf("Error: Missing wallet argument\n"); +// return -11; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_value_fee_str) || !l_value_fee_str) { +// printf("Error: Missing fee argument\n"); +// return -12; +// } + +// if (IS_ZERO_256((l_value_fee = dap_chain_balance_scan(l_value_fee_str)))) { +// printf("Error: Invalid fee format\n"); +// return -13; +// } + +// // Read time staking +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-time_staking", &l_time_staking_str) || !l_time_staking_str) { +// printf("Error: Missing time staking argument\n"); +// return -14; +// } + +// if (dap_strlen(l_time_staking_str) != 6) { +// printf("Error: Invalid time staking format\n"); +// return -15; +// } + +// char l_time_staking_month_str[3] = {l_time_staking_str[2], l_time_staking_str[3], 0}; +// int l_time_staking_month = atoi(l_time_staking_month_str); +// if (l_time_staking_month < 1 || l_time_staking_month > 12) { +// printf("Error: Invalid time staking month\n"); +// return -16; +// } + +// char l_time_staking_day_str[3] = {l_time_staking_str[4], l_time_staking_str[5], 0}; +// int l_time_staking_day = atoi(l_time_staking_day_str); +// if (l_time_staking_day < 1 || l_time_staking_day > 31) { +// printf("Error: Invalid time staking day\n"); +// return -17; +// } + +// l_time_staking = dap_time_from_str_simplified(l_time_staking_str); +// if (0 == l_time_staking) { +// printf("Error: Invalid time staking\n"); +// return -18; +// } +// dap_time_t l_time_now = dap_time_now(); +// if (l_time_staking < l_time_now) { +// printf("Error: Time staking is in the past\n"); +// return -19; +// } +// l_time_staking -= l_time_now; + +// if (dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-reinvest", &l_reinvest_percent_str) && NULL != l_reinvest_percent_str) { +// l_reinvest_percent = dap_chain_coins_to_balance(l_reinvest_percent_str); +// if (compare256(l_reinvest_percent, dap_chain_coins_to_balance("100.0")) == 1) { +// printf("Error: Invalid reinvest percentage\n"); +// return -20; +// } +// if (IS_ZERO_256(l_reinvest_percent)) { +// int l_reinvest_percent_int = atoi(l_reinvest_percent_str); +// if (l_reinvest_percent_int < 0 || l_reinvest_percent_int > 100) { +// printf("Error: Invalid reinvest percentage\n"); +// return -21; +// } +// l_reinvest_percent = dap_chain_uint256_from(l_reinvest_percent_int); +// MULT_256_256(l_reinvest_percent, GET_256_FROM_64(1000000000000000000ULL), &l_reinvest_percent); +// } +// } + +// if(NULL == (l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL))) { +// printf("Error: Unable to open wallet '%s'\n", l_wallet_str); +// return -22; +// } + + +// if (NULL == (l_addr_holder = dap_chain_wallet_get_addr(l_wallet, s_get_net_id(l_net_name)))) { +// dap_chain_wallet_close(l_wallet); +// printf("Error: Unable to get wallet address for '%s'\n", l_wallet_str); +// return -24; +// } + +// snprintf(data, sizeof(data), +// "{\"method\": \"wallet\",\"params\": [\"wallet;info;-addr;%s;-net;%s\"],\"id\": \"2\"}", +// dap_chain_addr_to_str(l_addr_holder), l_net_name); +// DAP_DEL_Z(l_addr_holder); + +// json_object *l_json_outs = dap_request_command_to_rpc(data, l_net_name, l_url_str, l_port); +// uint256_t l_value_balance = get_balance_from_json(l_json_outs, l_ticker_str); +// json_object_put(l_json_outs); +// if (compare256(l_value_balance, l_value) == -1) { +// dap_chain_wallet_close(l_wallet); +// printf("Error: Insufficient funds in wallet\n"); +// return -23; +// } + +// l_key_from = dap_chain_wallet_get_key(l_wallet, 0); + +// // Make transfer transaction +// dap_chain_datum_tx_t *l_tx = dap_stake_lock_datum_create_compose(l_net_name, l_key_from, +// l_ticker_str, l_value, l_value_fee, +// l_time_staking, l_reinvest_percent, +// l_delegated_ticker_str, l_value_delegated, l_chain_id_str, l_url_str, l_port); + +// json_object * l_json_obj_ret = json_object_new_object(); +// dap_chain_net_tx_to_json(l_tx, l_json_obj_ret); +// printf("%s", json_object_to_json_string(l_json_obj_ret)); +// json_object_put(l_json_obj_ret); +// dap_chain_datum_tx_delete(l_tx); + +// dap_chain_wallet_close(l_wallet); +// dap_enc_key_delete(l_key_from); + +// return 0; +// } + + +// dap_chain_datum_tx_t * dap_stake_lock_datum_create_compose(const char *a_net_name, dap_enc_key_t *a_key_from, +// const char *a_main_ticker, +// uint256_t a_value, uint256_t a_value_fee, +// dap_time_t a_time_staking, uint256_t a_reinvest_percent, +// const char *a_delegated_ticker_str, uint256_t a_delegated_value, +// const char * l_chain_id_str, const char *l_url_str, uint16_t l_port) +// { +// dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_STAKE_LOCK_ID }; +// // check valid param +// if (!a_net_name || !a_key_from || +// !a_key_from->priv_key_data || !a_key_from->priv_key_data_size || IS_ZERO_256(a_value)) +// return NULL; + +// const char *l_native_ticker = s_get_native_ticker(a_net_name); +// bool l_main_native = !dap_strcmp(a_main_ticker, l_native_ticker); +// // find the transactions from which to take away coins +// uint256_t l_value_transfer = {}; // how many coins to transfer +// uint256_t l_value_need = a_value, l_net_fee = {}, l_total_fee = {}, l_fee_transfer = {}; +// dap_chain_addr_t * l_addr_fee = NULL; +// dap_chain_addr_t l_addr = {}; + +// dap_chain_addr_fill_from_key(&l_addr, a_key_from, s_get_net_id(a_net_name)); +// bool l_net_fee_used = dap_get_remote_net_fee_and_address(a_net_name, &l_net_fee, &l_addr_fee, l_url_str, l_port); +// SUM_256_256(l_net_fee, a_value_fee, &l_total_fee); + +// json_object *l_outs_native = dap_get_remote_tx_outs(l_native_ticker, a_net_name, &l_addr, l_url_str, l_port); +// if (!l_outs_native) { +// return NULL; +// } + +// json_object *l_outs_main = NULL; +// if (!dap_strcmp(a_main_ticker, l_native_ticker)) { +// l_outs_main = l_outs_native; +// } else { +// l_outs_main = dap_get_remote_tx_outs(a_main_ticker, a_net_name, &l_addr, l_url_str, l_port); +// } +// int l_out_native_count = json_object_array_length(l_outs_native); +// int l_out_main_count = json_object_array_length(l_outs_main); + +// dap_list_t *l_list_fee_out = NULL; +// if (l_main_native) +// SUM_256_256(l_value_need, l_total_fee, &l_value_need); +// else if (!IS_ZERO_256(l_total_fee)) { +// l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, +// l_total_fee, +// &l_fee_transfer); +// if (!l_list_fee_out) { +// printf("Not enough funds to pay fee"); +// json_object_put(l_outs_native); +// json_object_put(l_outs_main); +// return NULL; +// } +// } +// // list of transaction with 'out' items +// dap_list_t * l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs_main, l_out_main_count, +// l_value_need, +// &l_value_transfer); +// if (!l_list_used_out) { +// printf("Not enough funds to transfer"); +// json_object_put(l_outs_native); +// json_object_put(l_outs_main); +// return NULL; +// } + +// // create empty transaction +// dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + +// // add 'in' items +// { +// uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); +// assert(EQUAL_256(l_value_to_items, l_value_transfer)); +// dap_list_free_full(l_list_used_out, NULL); +// if (l_list_fee_out) { +// uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); +// assert(EQUAL_256(l_value_fee_items, l_fee_transfer)); +// dap_list_free_full(l_list_fee_out, NULL); +// } +// } + +// // add 'in_ems' item +// { +// dap_chain_id_t l_chain_id = { }; +// dap_chain_id_parse(l_chain_id_str, &l_chain_id); +// dap_hash_fast_t l_blank_hash = {}; +// dap_chain_tx_in_ems_t *l_in_ems = dap_chain_datum_tx_item_in_ems_create(l_chain_id, &l_blank_hash, a_delegated_ticker_str); +// dap_chain_datum_tx_add_item(&l_tx, (const uint8_t*) l_in_ems); +// } + +// // add 'out_cond' and 'out_ext' items +// { +// uint256_t l_value_pack = {}, l_native_pack = {}; // how much coin add to 'out_ext' items +// dap_chain_tx_out_cond_t* l_tx_out_cond = dap_chain_datum_tx_item_out_cond_create_srv_stake_lock( +// l_uid, a_value, a_time_staking, a_reinvest_percent); +// if (l_tx_out_cond) { +// SUM_256_256(l_value_pack, a_value, &l_value_pack); +// dap_chain_datum_tx_add_item(&l_tx, (const uint8_t *)l_tx_out_cond); +// DAP_DEL_Z(l_tx_out_cond); +// } else { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add conditional output\n"); +// return NULL; +// } + +// uint256_t l_value_back = {}; +// // Network fee +// if (l_net_fee_used) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_fee, l_net_fee, l_native_ticker) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add network fee output\n"); +// return NULL; +// } +// if (l_main_native) +// SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); +// else +// SUM_256_256(l_native_pack, l_net_fee, &l_native_pack); +// } +// // Validator's fee +// if (!IS_ZERO_256(a_value_fee)) { +// if (dap_chain_datum_tx_add_fee_item(&l_tx, a_value_fee) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add validator's fee output\n"); +// return NULL; +// } +// if (l_main_native) +// SUM_256_256(l_value_pack, a_value_fee, &l_value_pack); +// else +// SUM_256_256(l_native_pack, a_value_fee, &l_native_pack); +// } +// // coin back +// SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); +// if (!IS_ZERO_256(l_value_back)) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, l_value_back, a_main_ticker) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add coin back output for main ticker\n"); +// return NULL; +// } +// } +// // fee coin back +// if (!IS_ZERO_256(l_fee_transfer)) { +// SUBTRACT_256_256(l_fee_transfer, l_native_pack, &l_value_back); +// if (!IS_ZERO_256(l_value_back)) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, l_value_back, l_native_ticker) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add coin back output for native ticker\n"); +// return NULL; +// } +// } +// } +// } + +// // add delegated token emission 'out_ext' +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, a_delegated_value, a_delegated_ticker_str) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Cant add delegated token emission output\n"); +// return NULL; +// } + +// return l_tx; +// } + + +// int dap_cli_take_compose(int a_argc, char **a_argv) +// { +// int arg_index = 1; +// const char *l_net_str, *l_ticker_str, *l_wallet_str, *l_tx_str, *l_tx_burning_str, *l_chain_id_str, *l_value_fee_str; +// l_net_str = l_ticker_str = l_wallet_str = l_tx_str = l_tx_burning_str = l_chain_id_str = l_value_fee_str = NULL; +// char l_delegated_ticker_str[DAP_CHAIN_TICKER_SIZE_MAX] = {}; +// int l_prev_cond_idx = 0; +// uint256_t l_value_delegated = {}; +// uint256_t l_value_fee = {}; +// dap_chain_wallet_t *l_wallet; +// dap_hash_fast_t l_tx_hash; +// dap_chain_tx_out_cond_t *l_cond_tx = NULL; +// dap_enc_key_t *l_owner_key; +// const char *l_url_str = NULL; +// uint16_t l_port = 0; + + +// const char *l_wallet_path = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-wallet_path", &l_wallet_path); +// if (!l_wallet_path) { +// l_wallet_path = +// #ifdef DAP_OS_WINDOWS +// dap_strdup_printf("%s/var/lib/wallets", regGetUsrPath()); +// #elif defined DAP_OS_MAC +// dap_strdup_printf("Library/Application Support/CellframeNode/var/lib/wallets"); +// #elif defined DAP_OS_UNIX +// dap_strdup_printf("/opt/CellframeNode/var/lib/wallets"); +// #endif +// } + + +// const char *l_hash_out_type = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-H", &l_hash_out_type); +// if (!l_hash_out_type) +// l_hash_out_type = "hex"; +// if (dap_strcmp(l_hash_out_type, "hex") && dap_strcmp(l_hash_out_type, "base58")) { +// printf("Error: Invalid hash type argument\n"); +// return -1; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str) || NULL == l_net_str) { +// printf("Error: Missing or invalid network argument\n"); +// return -2; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-url", &l_url_str)) { +// l_url_str = dap_compose_get_net_url(l_net_str); +// } + +// const char *l_port_str = NULL; +// if (!dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-port", &l_port_str)) { +// l_port = dap_compose_get_net_port(l_net_str); +// } else { +// l_port = atoi(l_port_str); +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-chain_id", &l_chain_id_str); +// if (!l_chain_id_str) { +// printf("Error: Missing or invalid chain_id argument\n"); +// return -10; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-tx", &l_tx_str) || NULL == l_tx_str) { +// printf("Error: Missing or invalid transaction argument\n"); +// return -5; +// } + +// if (dap_chain_hash_fast_from_str(l_tx_str, &l_tx_hash)) { +// printf("Error: Invalid transaction hash\n"); +// return -6; +// } + +// char data[512]; +// snprintf(data, sizeof(data), +// "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", +// l_tx_str, l_net_str); + +// json_object *response = dap_request_command_to_rpc(data, l_net_str, l_url_str, l_port); +// if (!response) { +// printf("Error: Failed to get response from remote node\n"); +// return -15; +// } + +// json_object *items = NULL; +// json_object *items_array = json_object_array_get_idx(response, 0); +// if (items_array) { +// items = json_object_object_get(items_array, "ITEMS"); +// } +// if (!items) { +// printf("Error: No items found in response\n"); +// return -16; +// } +// int items_count = json_object_array_length(items); +// for (int i = 0; i < items_count; i++) { +// json_object *item = json_object_array_get_idx(items, i); +// const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); +// if (dap_strcmp(item_type, "OUT COND") == 0) { +// const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); +// if (!dap_strcmp(subtype, "DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK")) { +// l_cond_tx = DAP_NEW_Z(dap_chain_tx_out_cond_t); +// l_cond_tx->header.item_type = TX_ITEM_TYPE_OUT_COND; +// l_cond_tx->header.value = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "value"))); +// l_cond_tx->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK; +// l_cond_tx->header.srv_uid.uint64 = strtoull(json_object_get_string(json_object_object_get(item, "uid")), NULL, 16); +// l_cond_tx->subtype.srv_stake_lock.time_unlock = dap_time_from_str_rfc822(json_object_get_string(json_object_object_get(item, "time_unlock"))); +// break; +// } +// } +// } +// if (!l_cond_tx) { +// printf("Error: No transaction output condition found\n"); +// return -7; +// } + + +// json_object *spent_outs = json_object_object_get(response, "all OUTs yet unspent"); +// const char *spent_outs_value = json_object_get_string(spent_outs); +// if (spent_outs_value && dap_strcmp(spent_outs_value, "yes") != 0) { +// printf("Error: Transaction output item already used\n"); +// return -9; +// } + +// json_object *response_header_array = json_object_array_get_idx(response, 0); +// if (!response_header_array) { +// printf("Error: Failed to get items array from response\n"); +// return -10; +// } + +// json_object *token_ticker_obj = json_object_object_get(response_header_array, "token ticker"); +// if (!token_ticker_obj) { +// printf("Error: Token ticker not found in response\n"); +// return -11; +// } +// l_ticker_str = json_object_get_string(token_ticker_obj); + + + +// dap_chain_datum_token_get_delegated_ticker(l_delegated_ticker_str, l_ticker_str); + +// uint256_t l_emission_rate = dap_chain_coins_to_balance("0.001"); + +// if (IS_ZERO_256(l_emission_rate) || +// MULT_256_COIN(l_cond_tx->header.value, l_emission_rate, &l_value_delegated) || +// IS_ZERO_256(l_value_delegated)) { +// printf("Error: Invalid coins format\n"); +// return -12; +// } + + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str) || !l_wallet_str) { +// printf("Error: Missing or invalid wallet argument\n"); +// return -13; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_value_fee_str) || !l_value_fee_str) { +// printf("Error: Missing or invalid fee argument\n"); +// return -14; +// } + +// if (IS_ZERO_256((l_value_fee = dap_chain_balance_scan(l_value_fee_str)))) { +// printf("Error: Invalid fee format\n"); +// return -15; +// } + +// if (NULL == (l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL))) { +// printf("Error: Unable to open wallet\n"); +// return -16; +// } + +// if (NULL == (l_owner_key = dap_chain_wallet_get_key(l_wallet, 0))) { +// dap_chain_wallet_close(l_wallet); +// printf("Error: Owner key not found\n"); +// return -17; +// } + +// if (l_cond_tx->subtype.srv_stake_lock.time_unlock > dap_time_now()) { +// dap_chain_wallet_close(l_wallet); +// dap_enc_key_delete(l_owner_key); +// printf("Error: Not enough time has passed for unlocking\n"); +// return -19; +// } +// dap_chain_datum_tx_t *l_tx = dap_stake_unlock_datum_create_compose(l_net_str, l_owner_key, &l_tx_hash, l_prev_cond_idx, +// l_ticker_str, l_cond_tx->header.value, l_value_fee, +// l_delegated_ticker_str, l_value_delegated, l_url_str, l_port); + +// json_object * l_json_obj_ret = json_object_new_object(); +// dap_chain_net_tx_to_json(l_tx, l_json_obj_ret); +// printf("%s", json_object_to_json_string(l_json_obj_ret)); +// json_object_put(l_json_obj_ret); + + +// dap_chain_datum_tx_delete(l_tx); +// dap_enc_key_delete(l_owner_key); + +// return 0; +// } + +// dap_chain_datum_tx_t *dap_stake_unlock_datum_create_compose(const char *a_net_name, dap_enc_key_t *a_key_from, +// dap_hash_fast_t *a_stake_tx_hash, uint32_t a_prev_cond_idx, +// const char *a_main_ticker, uint256_t a_value, +// uint256_t a_value_fee, +// const char *a_delegated_ticker_str, uint256_t a_delegated_value, +// const char *l_url_str, uint16_t l_port) +// { +// // check valid param +// if (!a_net_name | !a_key_from || !a_key_from->priv_key_data || !a_key_from->priv_key_data_size || dap_hash_fast_is_blank(a_stake_tx_hash)) { +// printf("Error: Invalid parameters\n"); +// return NULL; +// } + +// const char *l_native_ticker = s_get_native_ticker(a_net_name); +// bool l_main_native = !dap_strcmp(a_main_ticker, l_native_ticker); +// // find the transactions from which to take away coins +// uint256_t l_value_transfer = {}; // how many coins to transfer +// uint256_t l_net_fee = {}, l_total_fee = {}, l_fee_transfer = {}; +// dap_chain_addr_t* l_addr_fee = NULL; +// dap_chain_addr_t l_addr = {}; + +// dap_chain_addr_fill_from_key(&l_addr, a_key_from, s_get_net_id(a_net_name)); +// dap_list_t *l_list_fee_out = NULL, *l_list_used_out = NULL; + +// bool l_net_fee_used = dap_get_remote_net_fee_and_address(a_net_name, &l_net_fee, &l_addr_fee, l_url_str, l_port); + +// json_object *l_outs_native = dap_get_remote_tx_outs(l_native_ticker, a_net_name, &l_addr, l_url_str, l_port); +// if (!l_outs_native) { +// return NULL; +// } + +// json_object *l_outs_delegated = dap_get_remote_tx_outs(a_delegated_ticker_str, a_net_name, &l_addr, l_url_str, l_port); +// if (!l_outs_delegated) { +// return NULL; +// } + +// int l_out_native_count = json_object_array_length(l_outs_native); +// int l_out_delegated_count = json_object_array_length(l_outs_delegated); + +// SUM_256_256(l_net_fee, a_value_fee, &l_total_fee); +// if (!IS_ZERO_256(l_total_fee)) { +// if (!l_main_native) { +// l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, +// l_total_fee, +// &l_fee_transfer); +// if (!l_list_fee_out) { +// printf("Not enough funds to pay fee"); +// json_object_put(l_outs_native); +// json_object_put(l_outs_delegated); +// return NULL; +// } +// } else if (compare256(a_value, l_total_fee) == -1) { +// printf("Error: Total fee more than stake\n"); +// return NULL; +// } +// } +// if (!IS_ZERO_256(a_delegated_value)) { +// l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs_delegated, l_out_delegated_count, +// a_delegated_value, +// &l_value_transfer); +// if (!l_list_used_out) { +// printf("Not enough funds to pay fee"); +// json_object_put(l_outs_native); +// json_object_put(l_outs_delegated); +// return NULL; +// } +// } + +// // create empty transaction +// dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + +// // add 'in_cond' & 'in' items +// { +// dap_chain_datum_tx_add_in_cond_item(&l_tx, a_stake_tx_hash, a_prev_cond_idx, 0); +// if (l_list_used_out) { +// uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); +// assert(EQUAL_256(l_value_to_items, l_value_transfer)); +// dap_list_free_full(l_list_used_out, NULL); +// } +// if (l_list_fee_out) { +// uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); +// assert(EQUAL_256(l_value_fee_items, l_fee_transfer)); +// dap_list_free_full(l_list_fee_out, NULL); +// } +// } + +// // add 'out_ext' items +// uint256_t l_value_back; +// { +// uint256_t l_value_pack = {}; // how much datoshi add to 'out' items +// // Network fee +// if(l_net_fee_used){ +// if (!dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_fee, l_net_fee, l_native_ticker)){ +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add network fee output\n"); +// return NULL; +// } +// SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); +// } +// // Validator's fee +// if (!IS_ZERO_256(a_value_fee)) { +// if (dap_chain_datum_tx_add_fee_item(&l_tx, a_value_fee) == 1) +// { +// SUM_256_256(l_value_pack, a_value_fee, &l_value_pack); +// } +// else { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add validator's fee output\n"); +// return NULL; +// } +// } +// // coin back +// //SUBTRACT_256_256(l_fee_transfer, l_value_pack, &l_value_back); +// if(l_main_native){ +// if (SUBTRACT_256_256(a_value, l_value_pack, &l_value_back)) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't subtract value pack from value\n"); +// return NULL; +// } +// if(!IS_ZERO_256(l_value_back)) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, l_value_back, a_main_ticker)!=1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add coin back output for main ticker\n"); +// return NULL; +// } +// } +// } else { +// SUBTRACT_256_256(l_fee_transfer, l_value_pack, &l_value_back); +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, a_value, a_main_ticker)!=1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add coin back output for main ticker\n"); +// return NULL; +// } +// else +// { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, l_value_back, l_native_ticker)!=1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add coin back output for native ticker\n"); +// return NULL; +// } +// } +// } +// } + +// // add burning 'out_ext' +// if (!IS_ZERO_256(a_delegated_value)) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &c_dap_chain_addr_blank, +// a_delegated_value, a_delegated_ticker_str) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add burning output for delegated value\n"); +// return NULL; +// } +// // delegated token coin back +// SUBTRACT_256_256(l_value_transfer, a_delegated_value, &l_value_back); +// if (!IS_ZERO_256(l_value_back)) { +// if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr, l_value_back, a_delegated_ticker_str) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// printf("Error: Can't add coin back output for delegated ticker\n"); +// return NULL; +// } +// } +// } + +// return l_tx; +// } + +uint256_t s_get_key_delegating_min_value(compose_config_t *a_config){ + uint256_t l_key_delegating_min_value = uint256_0; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;list;keys;-net;%s\"],\"id\": \"1\"}", + a_config->net_name); + + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + printf("Error: Failed to get response from remote node\n"); + return l_key_delegating_min_value; + } + + json_object *response_array = json_object_array_get_idx(response, 0); + if (!response_array) { + printf("Error: Invalid response format\n"); + return l_key_delegating_min_value; + } + + json_object *summary_obj = json_object_array_get_idx(response_array, json_object_array_length(response_array) - 1); + if (!summary_obj) { + printf("Error: Summary object not found in response\n"); + return l_key_delegating_min_value; + } + + json_object *key_delegating_min_value_obj = json_object_object_get(summary_obj, "key_delegating_min_value"); + if (!key_delegating_min_value_obj) { + printf("Error: key_delegating_min_value not found in summary\n"); + return l_key_delegating_min_value; + } + + const char *key_delegating_min_value_str = json_object_get_string(key_delegating_min_value_obj); + if (!key_delegating_min_value_str) { + printf("Error: Invalid key_delegating_min_value format\n"); + return l_key_delegating_min_value; + } + + l_key_delegating_min_value = dap_chain_balance_scan(key_delegating_min_value_str); + if (IS_ZERO_256(l_key_delegating_min_value)) { + printf("Error: Unrecognized number in key_delegating_min_value\n"); + return l_key_delegating_min_value; + } + + return l_key_delegating_min_value; +} + + +// int dap_cli_voting_compose(int a_argc, char **a_argv) +// { +// int arg_index = 1; +// const char* l_question_str = NULL; +// const char* l_options_list_str = NULL; +// const char* l_voting_expire_str = NULL; +// const char* l_max_votes_count_str = NULL; +// const char* l_fee_str = NULL; +// const char* l_wallet_str = NULL; +// const char* l_net_str = NULL; +// const char* l_token_str = NULL; +// const char* l_url_str = NULL; +// uint16_t l_port = 0; +// const char *l_wallet_path = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-wallet_path", &l_wallet_path); +// if (!l_wallet_path) { +// l_wallet_path = +// #ifdef DAP_OS_WINDOWS +// dap_strdup_printf("%s/var/lib/wallets", regGetUsrPath()); +// #elif defined DAP_OS_MAC +// dap_strdup_printf("Library/Application Support/CellframeNode/var/lib/wallets"); +// #elif defined DAP_OS_UNIX +// dap_strdup_printf("/opt/CellframeNode/var/lib/wallets"); +// #endif +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str); +// // Select chain network +// if(!l_net_str) { +// printf("Voting requires parameter '-net' to be valid.\n"); +// return -DAP_CHAIN_NET_VOTE_VOTING_NET_PARAM_MISSING; +// } + +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-url", &l_url_str)) { +// l_url_str = dap_compose_get_net_url(l_net_str); +// } + +// const char *l_port_str = NULL; +// if (!dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-port", &l_port_str)) { +// l_port = dap_compose_get_net_port(l_net_str); +// } else { +// l_port = atoi(l_port_str); +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-question", &l_question_str); +// if (!l_question_str){ +// printf("Voting requires a question parameter to be valid.\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_QUESTION_PARAM_MISSING; +// } + +// if (strlen(l_question_str) > DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH){ +// printf("The question must contain no more than %d characters\n", DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH); +// return -DAP_CHAIN_NET_VOTE_CREATE_QUESTION_CONTAIN_MAX_CHARACTERS; +// } + +// dap_list_t *l_options_list = NULL; +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-options", &l_options_list_str); +// if (!l_options_list_str){ +// printf("Voting requires a question parameter to be valid.\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_OPTION_PARAM_MISSING; +// } +// // Parse options list +// l_options_list = dap_get_options_list_from_str(l_options_list_str); +// if(!l_options_list || dap_list_length(l_options_list) < 2){ +// printf("Number of options must be 2 or greater.\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_NUMBER_OPTIONS_ERROR; +// } + +// if(dap_list_length(l_options_list)>DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT){ +// printf("The voting can contain no more than %d options\n", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT); +// return -DAP_CHAIN_NET_VOTE_CREATE_CONTAIN_MAX_OPTIONS; +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-expire", &l_voting_expire_str); +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-max_votes_count", &l_max_votes_count_str); +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str); +// if (!l_fee_str){ +// printf("Voting requires parameter -fee to be valid.\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_FEE_PARAM_NOT_VALID; +// } +// uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str); + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str); +// if (!l_wallet_str){ +// printf("Voting requires parameter -w to be valid.\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_WALLET_PARAM_NOT_VALID; +// } + +// dap_time_t l_time_expire = 0; +// if (l_voting_expire_str) +// l_time_expire = dap_time_from_str_rfc822(l_voting_expire_str); +// if (l_voting_expire_str && !l_time_expire){ +// printf("Wrong time format. -expire parameter must be in format \"Day Month Year HH:MM:SS Timezone\" e.g. \"19 August 2024 22:00:00 +00\"\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_WRONG_TIME_FORMAT; +// } +// uint64_t l_max_count = 0; +// if (l_max_votes_count_str) +// l_max_count = strtoul(l_max_votes_count_str, NULL, 10); + +// bool l_is_delegated_key = dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-delegated_key_required", NULL) ? true : false; +// bool l_is_vote_changing_allowed = dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-vote_changing_allowed", NULL) ? true : false; +// dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL); +// if (!l_wallet_fee) { +// printf("Wallet %s does not exist\n", l_wallet_str); +// return -DAP_CHAIN_NET_VOTE_CREATE_WALLET_DOES_NOT_EXIST; +// } + +// dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-token", &l_token_str); +// if (!l_token_str) { +// printf("Command required -token argument"); +// return -DAP_CHAIN_NET_VOTE_CREATE_WALLET_DOES_NOT_EXIST; +// } + +// char data[512]; +// snprintf(data, sizeof(data), +// "{\"method\": \"ledger\",\"params\": [\"ledger;list;coins;-net;%s\"],\"id\": \"2\"}", l_net_str); +// json_object *l_json_coins = dap_request_command_to_rpc(data, l_net_str, l_url_str, l_port); +// if (!l_json_coins) { +// printf("Error: Can't get ledger coins list\n"); +// return -DAP_CHAIN_NET_VOTE_CREATE_ERROR_CAN_NOT_GET_TX_OUTS; +// } +// if (!check_token_in_ledger(l_json_coins, l_token_str)) { +// json_object_put(l_json_coins); +// printf("Token %s does not exist\n", l_token_str); +// return -DAP_CHAIN_NET_VOTE_CREATE_WRONG_TOKEN; +// } +// json_object_put(l_json_coins); + +// dap_chain_datum_tx_t* l_tx = dap_chain_net_vote_create_compose(l_question_str, l_options_list, l_time_expire, l_max_count, +// l_value_fee, l_is_delegated_key, l_is_vote_changing_allowed, +// l_wallet_fee, l_net_str, l_token_str, l_url_str, l_port); +// dap_list_free(l_options_list); +// dap_chain_wallet_close(l_wallet_fee); +// json_object * l_json_obj_ret = json_object_new_object(); +// dap_chain_net_tx_to_json(l_tx, l_json_obj_ret); +// printf("%s", json_object_to_json_string(l_json_obj_ret)); +// json_object_put(l_json_obj_ret); +// return 0; +// } + + +// dap_chain_datum_tx_t* dap_chain_net_vote_create_compose(const char *a_question, dap_list_t *a_options, dap_time_t a_expire_vote, +// uint64_t a_max_vote, uint256_t a_fee, bool a_delegated_key_required, +// bool a_vote_changing_allowed, dap_chain_wallet_t *a_wallet, +// const char *a_net_str, const char *a_token_ticker, const char *l_url_str, uint16_t l_port) { + +// if (strlen(a_question) > DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH){ +// return NULL; +// } + +// // Parse options list + +// if(dap_list_length(a_options) > DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT){ +// return NULL; +// } + +// if (IS_ZERO_256(a_fee)) { +// return NULL; +// } + +// dap_chain_addr_t *l_addr_from = dap_chain_wallet_get_addr(a_wallet, s_get_net_id(a_net_str)); + +// if(!l_addr_from) { +// return NULL; +// } + +// const char *l_native_ticker = s_get_native_ticker(a_net_str); +// uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer; +// dap_chain_addr_t *l_addr_fee = NULL; +// bool l_net_fee_used = dap_get_remote_net_fee_and_address(a_net_str, &l_net_fee, &l_addr_fee, l_url_str, l_port); +// SUM_256_256(l_net_fee, a_fee, &l_total_fee); + +// json_object *l_outs = NULL; +// int l_outputs_count = 0; +// if (!dap_get_remote_wallet_outs_and_count(l_addr_from, l_native_ticker, a_net_str, &l_outs, &l_outputs_count, l_url_str, l_port)) { +// return NULL; +// } + +// dap_list_t *l_list_used_out = NULL; +// l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_outputs_count, +// l_total_fee, +// &l_value_transfer); + +// json_object_put(l_outs); +// if (!l_list_used_out) { +// printf("Not enough funds to transfer"); +// return NULL; +// } + + +// // create empty transaction +// dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + +// // Add Voting item +// dap_chain_tx_voting_t* l_voting_item = dap_chain_datum_tx_item_voting_create(); + +// dap_chain_datum_tx_add_item(&l_tx, l_voting_item); +// DAP_DELETE(l_voting_item); + +// // Add question to tsd data +// dap_chain_tx_tsd_t* l_question_tsd = dap_chain_datum_voting_question_tsd_create(a_question, strlen(a_question)); +// dap_chain_datum_tx_add_item(&l_tx, l_question_tsd); + +// // Add options to tsd +// dap_list_t *l_temp = a_options; +// while(l_temp){ +// if(strlen((char*)l_temp->data) > DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_tx_tsd_t* l_option = dap_chain_datum_voting_answer_tsd_create((char*)l_temp->data, strlen((char*)l_temp->data)); +// if(!l_option){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_option); +// DAP_DEL_Z(l_option); + +// l_temp = l_temp->next; +// } + +// // add voting expire time if needed +// if(a_expire_vote != 0){ +// dap_time_t l_expired_vote = a_expire_vote; +// if (l_expired_vote < dap_time_now()){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } + +// dap_chain_tx_tsd_t* l_expired_item = dap_chain_datum_voting_expire_tsd_create(l_expired_vote); +// if(!l_expired_item){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_expired_item); +// DAP_DEL_Z(l_expired_item); +// } + +// // Add vote max count if needed +// if (a_max_vote != 0) { +// dap_chain_tx_tsd_t* l_max_votes_item = dap_chain_datum_voting_max_votes_count_tsd_create(a_max_vote); +// if(!l_max_votes_item){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_max_votes_item); +// DAP_DEL_Z(l_max_votes_item); +// } + +// if (a_delegated_key_required) { +// dap_chain_tx_tsd_t* l_delegated_key_req_item = dap_chain_datum_voting_delegated_key_required_tsd_create(true); +// if(!l_delegated_key_req_item){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_delegated_key_req_item); +// DAP_DEL_Z(l_delegated_key_req_item); +// } + +// if(a_vote_changing_allowed){ +// dap_chain_tx_tsd_t* l_vote_changing_item = dap_chain_datum_voting_vote_changing_allowed_tsd_create(true); +// if(!l_vote_changing_item){ +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_vote_changing_item); +// DAP_DEL_Z(l_vote_changing_item); +// } +// if (a_token_ticker) { +// dap_chain_tx_tsd_t *l_voting_token_item = dap_chain_datum_voting_token_tsd_create(a_token_ticker); +// if (!l_voting_token_item) { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// dap_chain_datum_tx_add_item(&l_tx, l_voting_token_item); +// DAP_DEL_Z(l_voting_token_item); +// } + +// // add 'in' items +// uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); +// assert(EQUAL_256(l_value_to_items, l_value_transfer)); +// dap_list_free_full(l_list_used_out, NULL); +// uint256_t l_value_pack = {}; +// // Network fee +// if (l_net_fee_used) { +// if (dap_chain_datum_tx_add_out_item(&l_tx, l_addr_fee, l_net_fee) == 1) +// SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); +// else { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// } +// // Validator's fee +// if (!IS_ZERO_256(a_fee)) { +// if (dap_chain_datum_tx_add_fee_item(&l_tx, a_fee) == 1) +// SUM_256_256(l_value_pack, a_fee, &l_value_pack); +// else { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// } +// // coin back +// uint256_t l_value_back; +// SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); +// if(!IS_ZERO_256(l_value_back)) { +// if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) { +// dap_chain_datum_tx_delete(l_tx); +// return NULL; +// } +// } + + +// return l_tx; +// } + +/** +int dap_cli_vote_compose(int a_argc, char **a_argv){ + const char* l_cert_name = NULL; + const char* l_fee_str = NULL; + const char* l_wallet_str = NULL; + const char* l_hash_str = NULL; + const char* l_option_idx_str = NULL; + const char* l_net_str = NULL; + int arg_index = 1; + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str); + if(!l_net_str) { + printf("command requires parameter '-net'\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_NET_PARAM_MISSING; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str); + if(!l_hash_str){ + printf("Command 'vote' require the parameter -hash\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_HASH_NOT_FOUND; + } + + dap_hash_fast_t l_voting_hash = {}; + if (dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash)) { + printf("Hash string is not recognozed as hex of base58 hash\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_HASH_INVALID; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-cert", &l_cert_name); + dap_cert_t * l_cert = dap_cert_find_by_name(l_cert_name); + if (l_cert_name){ + if (l_cert == NULL) { + printf("Can't find \"%s\" certificate\n", l_cert_name); + return -DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_FIND_CERT; + } + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str); + if (!l_fee_str){ + printf("Command 'vote' requires paramete -fee to be valid.\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_FEE_PARAM_NOT_VALID; + } + uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_value_fee)) { + printf("command requires parameter '-fee' to be valid uint256\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_FEE_PARAM_BAD_TYPE; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str); + if (!l_wallet_str){ + printf("Command 'vote' requires parameter -w to be valid.\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_WALLET_PARAM_NOT_VALID; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-option_idx", &l_option_idx_str); + if (!l_option_idx_str){ + printf("Command 'vote' requires parameter -option_idx to be valid.\n"); + return -DAP_CHAIN_NET_VOTE_VOTING_OPTION_IDX_PARAM_NOT_VALID; + } + + const char *l_wallet_path = dap_chain_wallet_get_path(g_config); + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path,NULL); + if (!l_wallet) { + printf("Wallet %s does not exist\n", l_wallet_str); + return -DAP_CHAIN_NET_VOTE_VOTING_WALLET_DOES_NOT_EXIST; + } + + uint64_t l_option_idx_count = strtoul(l_option_idx_str, NULL, 10); + + char *l_hash_tx; + + int res = dap_chain_net_vote_voting_compose(l_cert, l_value_fee, l_wallet, l_voting_hash, l_option_idx_count, + l_net_str, &l_hash_tx); + dap_chain_wallet_close(l_wallet); + + return res; +} + + +int dap_chain_net_vote_voting_compose(dap_cert_t *a_cert, uint256_t a_fee, dap_chain_wallet_t *a_wallet, dap_hash_fast_t a_hash, + uint64_t a_option_idx, const char *a_net_str, const char *a_hash_out_type, + char **a_hash_tx_out) { + const char * l_hash_str = dap_chain_hash_fast_to_str_static(&a_hash); + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"voting\",\"params\": [\"voting;dump;-hash;%s\"],\"id\": \"2\"}", l_hash_str); + json_object *l_json_voting = dap_request_command_to_rpc(data); + if (!l_json_voting) { + printf("Error: Can't get voting info\n"); + return -DAP_CHAIN_NET_VOTE_CREATE_ERROR_CAN_NOT_GET_TX_OUTS; + } + + + json_object *l_voting_info = json_object_array_get_idx(l_json_voting, 0); + if (!l_voting_info) { + printf("Error: Can't get voting info from JSON\n"); + return -DAP_CHAIN_NET_VOTE_CREATE_ERROR_CAN_NOT_GET_TX_OUTS; + } + + const char *l_voting_tx = json_object_get_string(json_object_object_get(l_voting_info, "voting_tx")); + const char *l_expiration_str = json_object_get_string(json_object_object_get(l_voting_info, "expiration")); + const char *l_status = json_object_get_string(json_object_object_get(l_voting_info, "status")); + int l_votes_max = json_object_get_int(json_object_object_get(l_voting_info, "votes_max")); + int l_votes_available = json_object_get_int(json_object_object_get(l_voting_info, "votes_available")); + bool l_vote_changed = json_object_get_boolean(json_object_object_get(l_voting_info, "can_change_status")); + bool l_delegated_key_required = json_object_get_boolean(json_object_object_get(l_voting_info, "delegated_key_required")); + + json_object *l_results = json_object_object_get(l_voting_info, "results"); + if (!l_results) { + printf("Error: Can't get results from JSON\n"); + return -DAP_CHAIN_NET_VOTE_CREATE_ERROR_CAN_NOT_GET_TX_OUTS; + } + + int l_results_count = json_object_array_length(l_results); + + + if (l_votes_max && l_votes_max <= l_results_count) + return DAP_CHAIN_NET_VOTE_VOTING_THIS_VOTING_HAVE_MAX_VALUE_VOTES; + + if (l_expiration_str) { + struct tm tm; + strptime(l_expiration_str, "%a, %d %b %Y %H:%M:%S %z", &tm); + time_t l_expiration_time = mktime(&tm); + if (l_expiration_time && dap_time_now() > l_expiration_time) + return DAP_CHAIN_NET_VOTE_VOTING_ALREADY_EXPIRED; + } + + dap_chain_addr_t *l_addr_from = dap_chain_wallet_get_addr(a_wallet, s_get_net_id(a_net_str)); + if (!l_addr_from) + return DAP_CHAIN_NET_VOTE_VOTING_SOURCE_ADDRESS_INVALID; + + dap_hash_fast_t l_pkey_hash = {0}; + if (l_delegated_key_required) { + if (!a_cert) + return DAP_CHAIN_NET_VOTE_VOTING_CERT_REQUIRED; + if (dap_cert_get_pkey_hash(a_cert, &l_pkey_hash)) + return DAP_CHAIN_NET_VOTE_VOTING_NO_KEY_FOUND_IN_CERT; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;list;keys;-net;%s\"],\"id\": \"1\"}", a_net_str); + json_object *l_json_coins = dap_request_command_to_rpc(data); + if (!l_json_coins) { + printf("Error: Failed to retrieve coins from ledger\n"); + return -4; + } + const char * l_hash_fast_str[DAP_HASH_FAST_STR_SIZE] = {}; + dap_chain_hash_fast_from_str(l_hash_fast_str, &l_pkey_hash); + if (!l_hash_fast_str) { + printf("Error: Can't covert l_pkey_hash to str"); + return -5; + } + int items_count = json_object_array_length(l_json_coins); + bool found = false; + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(l_json_coins, i); + const char *pkey_hash_str = json_object_get_string(json_object_object_get(item, "pkey_hash")); + if (l_hash_fast_str && !dap_strcmp(l_hash_fast_str, pkey_hash_str)) { + const char *tx_hash_str = json_object_get_string(json_object_object_get(item, "tx_hash")); + if (dap_chain_hash_fast_from_str(tx_hash_str, &l_pkey_hash)) { + printf("Invalid transaction hash format\n"); + return DAP_CHAIN_NET_VOTE_VOTING_KEY_IS_NOT_DELEGATED; + } + found = true; + break; + } + } + if (!found) { + printf("Specified certificate/pkey hash is not delegated nor this delegating is approved. Try to invalidate with tx hash instead\n"); + return -9; + } + + + } else + l_pkey_hash = l_addr_from->data.hash_fast; + + + const char *l_token_ticker = json_object_get_string(json_object_object_get(l_voting_info, "token")); + uint256_t l_net_fee = {}, l_total_fee = a_fee, l_value_transfer, l_fee_transfer; + dap_chain_addr_t* l_addr_fee = NULL; + bool l_net_fee_used = dap_get_remote_net_fee_and_address(a_net_str, &l_net_fee, &l_addr_fee); + if (l_net_fee_used) + SUM_256_256(l_net_fee, a_fee, &l_total_fee); + + bool l_native_tx = !dap_strcmp(l_token_ticker, s_get_native_ticker(a_net_str)); + + json_object *l_outs = NULL; + int l_outputs_count = 0; + if (!dap_get_remote_wallet_outs_and_count(l_addr_from, l_token_ticker, a_net_str, &l_outs, &l_outputs_count)) { + return -11; + } + + dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs, l_outputs_count, + l_total_fee, + &l_value_transfer); + json_object_put(l_outs); + if (!l_list_used_out) { + printf("Not enough funds to transfer"); + return DAP_CHAIN_NET_VOTE_VOTING_NOT_ENOUGH_FUNDS_TO_TRANSFER; + } + + // check outputs UTXOs + uint256_t l_value_transfer_new = {}; + dap_list_t *it, *tmp; + DL_FOREACH_SAFE(l_list_used_out, it, tmp) { + dap_chain_tx_used_out_item_t *l_out = (dap_chain_tx_used_out_item_t *)it->data; + if (s_datum_tx_voting_coin_check_spent(a_net, a_hash, l_out->tx_hash_fast, l_out->num_idx_out, + l_vote_changed ? &l_pkey_hash : NULL)) { + l_list_used_out = dap_list_delete_link(l_list_used_out, it); + continue; + } + if (SUM_256_256(l_value_transfer_new, l_out->value, &l_value_transfer_new)) + return DAP_CHAIN_NET_VOTE_VOTING_INTEGER_OVERFLOW; + } + + if (IS_ZERO_256(l_value_transfer_new) || (l_native_tx && compare256(l_value_transfer_new, l_total_fee) <= 0)) + return DAP_CHAIN_NET_VOTE_VOTING_UNSPENT_UTX0_FOR_PARTICIPATION_THIS_VOTING; + + l_value_transfer = l_value_transfer_new; + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + uint256_t l_value_back = l_value_transfer, l_fee_back = {}; + if (!l_native_tx) { + dap_list_t *l_list_fee_outs = dap_ledger_get_list_tx_outs_with_val(l_ledger, a_net->pub.native_ticker, l_addr_from, l_total_fee, &l_fee_transfer); + if (!l_list_fee_outs) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_NOT_ENOUGH_FUNDS_TO_TRANSFER; + } + uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_outs); + assert(EQUAL_256(l_value_fee_items, l_fee_transfer)); + dap_list_free_full(l_list_fee_outs, NULL); + SUBTRACT_256_256(l_fee_transfer, l_total_fee, &l_fee_back); + } else + SUBTRACT_256_256(l_value_transfer, l_total_fee, &l_value_back); + + // add 'in' items + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + assert(EQUAL_256(l_value_to_items, l_value_transfer)); + dap_list_free_full(l_list_used_out, NULL); + + // Add vote item + if (a_option_idx > dap_list_length(l_voting->voting_params.option_offsets_list)){ + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_INVALID_OPTION_INDEX; + } + dap_chain_tx_vote_t* l_vote_item = dap_chain_datum_tx_item_vote_create(&a_hash, &a_option_idx); + if(!l_vote_item){ + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_CREATE_VOTE_ITEM; + } + dap_chain_datum_tx_add_item(&l_tx, l_vote_item); + DAP_DEL_Z(l_vote_item); + + // add out conds items + dap_list_t *l_outs = dap_ledger_get_list_tx_cond_outs(l_ledger, DAP_CHAIN_TX_OUT_COND_SUBTYPE_ALL, l_token_ticker, l_addr_from); + for (dap_list_t *it = l_outs; it; it = it->next) { + dap_chain_tx_used_out_item_t *l_out_item = (dap_chain_tx_used_out_item_t *)it->data; + if (s_datum_tx_voting_coin_check_cond_out(a_net, a_hash, l_out_item->tx_hash_fast, l_out_item->num_idx_out, + l_vote_changed ? &l_pkey_hash : NULL) != 0) + continue; + dap_chain_tx_tsd_t *l_item = dap_chain_datum_voting_vote_tx_cond_tsd_create(l_out_item->tx_hash_fast, l_out_item->num_idx_out); + if(!l_item){ + dap_chain_datum_tx_delete(l_tx); + + dap_list_free_full(l_outs, NULL); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_CREATE_TSD_TX_COND_ITEM; + } + dap_chain_datum_tx_add_item(&l_tx, l_item); + DAP_DEL_Z(l_item); + } + dap_list_free_full(l_outs, NULL); + + // Network fee + if (l_net_fee_used && dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_addr_fee, l_net_fee, a_net->pub.native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_ADD_NET_FEE_OUT; + } + + // Validator's fee + if (!IS_ZERO_256(a_fee) && dap_chain_datum_tx_add_fee_item(&l_tx, a_fee) != 1) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_ADD_NET_FEE_OUT; + } + + // coin back + if (!IS_ZERO_256(l_value_back) && dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_from, l_value_back, l_token_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_ADD_OUT_WITH_VALUE_BACK; + } + if (!IS_ZERO_256(l_fee_back) && dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_from, l_fee_back, a_net->pub.native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_ADD_OUT_WITH_VALUE_BACK; + } + + dap_enc_key_t *l_priv_key = dap_chain_wallet_get_key(a_wallet, 0); + // add 'sign' items with wallet sign + if (dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_enc_key_delete(l_priv_key); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_SIGN_TX; + } + dap_enc_key_delete(l_priv_key); + + // add 'sign' items with delegated key if needed + if (a_cert && dap_chain_datum_tx_add_sign_item(&l_tx, a_cert->enc_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_SIGN_TX; + } + + size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx); + dap_hash_fast_t l_tx_hash; + dap_hash_fast(l_tx, l_tx_size, &l_tx_hash); + dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size); + DAP_DELETE(l_tx); + dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(a_net, CHAIN_TYPE_TX); + + char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, a_hash_out_type); + DAP_DELETE(l_datum); + if (l_ret) { + *a_hash_tx_out = l_ret; + return DAP_CHAIN_NET_VOTE_VOTING_OK; + } else { + return DAP_CHAIN_NET_VOTE_VOTING_CAN_NOT_POOL_IN_MEMPOOL; + } +} + +*/ + + + +json_object* dap_cli_srv_stake_invalidate_compose(const char *a_net_str, const char *a_tx_hash_str, const char *a_wallet_str, + const char *a_wallet_path, const char *a_cert_str, const char *a_fee_str, const char *a_url_str, uint16_t a_port) +{ + compose_config_t* l_config = s_compose_config_init(a_net_str, a_url_str, a_port); + dap_hash_fast_t l_tx_hash = {}; + + uint256_t l_fee = dap_chain_balance_scan(a_fee_str); + if (IS_ZERO_256(l_fee)) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_FEE_ERROR, "Unrecognized number in '-fee' param"); + return s_compose_config_return_response_handler(l_config); + } + + if (a_tx_hash_str) { + dap_chain_hash_fast_from_str(a_tx_hash_str, &l_tx_hash); + } else { + dap_chain_addr_t l_signing_addr; + if (a_cert_str) { + dap_cert_t *l_cert = dap_cert_find_by_name(a_cert_str); + if (!l_cert) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_CERT_NOT_FOUND, "Specified certificate not found"); + return s_compose_config_return_response_handler(l_config); + } + if (!l_cert->enc_key->priv_key_data || l_cert->enc_key->priv_key_data_size == 0) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_PRIVATE_KEY_MISSING, "Private key missing in certificate"); + return s_compose_config_return_response_handler(l_config); + } + if (dap_chain_addr_fill_from_key(&l_signing_addr, l_cert->enc_key, s_get_net_id(a_net_str))) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_WRONG_CERT, "Wrong certificate"); + return s_compose_config_return_response_handler(l_config); + } + } + const char *l_addr_str = dap_chain_addr_to_str_static(&l_signing_addr); + + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;list;keys;-net;%s\"],\"id\": \"1\"}", l_config->net_name); + json_object *l_json_coins = dap_request_command_to_rpc(data, l_config); + if (!l_json_coins) { + return s_compose_config_return_response_handler(l_config); + } + + int items_count = json_object_array_length(l_json_coins); + bool found = false; + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(l_json_coins, i); + const char *node_addr_str = json_object_get_string(json_object_object_get(item, "node_addr")); + if (node_addr_str && !dap_strcmp(l_addr_str, node_addr_str)) { + const char *tx_hash_str = json_object_get_string(json_object_object_get(item, "tx_hash")); + if (dap_chain_hash_fast_from_str(tx_hash_str, &l_tx_hash)) { + json_object_put(l_json_coins); + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_INVALID_TX_HASH, "Invalid transaction hash format"); + return s_compose_config_return_response_handler(l_config); + } + found = true; + break; + } + } + json_object_put(l_json_coins); + if (!found) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_NOT_DELEGATED, "Specified certificate/pkey hash is not delegated"); + return s_compose_config_return_response_handler(l_config); + } + } + + const char *l_tx_hash_str_tmp = a_tx_hash_str ? a_tx_hash_str : dap_hash_fast_to_str_static(&l_tx_hash); + + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", l_tx_hash_str_tmp, l_config->net_name); + json_object *l_json_response = dap_request_command_to_rpc(data, l_config); + if (!l_json_response) { + return s_compose_config_return_response_handler(l_config); + } + + json_object *l_json_items = json_object_array_get_idx(l_json_response, 0); + l_json_items = json_object_object_get(l_json_items, "ITEMS"); + bool has_delegate_out = false; + if (l_json_items) { + int items_count = json_object_array_length(l_json_items); + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(l_json_items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (item_type && strcmp(item_type, "OUT COND") == 0) { + const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); + if (subtype && strcmp(subtype, "DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_POS_DELEGATE") == 0) { + has_delegate_out = true; + break; + } + } + } + } + + if (!has_delegate_out) { + json_object_put(l_json_response); + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_NO_DELEGATE_OUT, "No delegate output found in transaction"); + return s_compose_config_return_response_handler(l_config); + } + + json_object *l_json_spents = json_object_object_get(l_json_response, "Spent OUTs"); + if (l_json_spents) { + int spents_count = json_object_array_length(l_json_spents); + for (int i = 0; i < spents_count; i++) { + json_object *spent_item = json_object_array_get_idx(l_json_spents, i); + const char *spent_by_tx = json_object_get_string(json_object_object_get(spent_item, "is spent by tx")); + if (spent_by_tx) { + if (dap_chain_hash_fast_from_str(spent_by_tx, &l_tx_hash)) { + json_object_put(l_json_response); + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_INVALID_TX_HASH, "Invalid transaction hash format"); + return s_compose_config_return_response_handler(l_config); + } + l_tx_hash_str_tmp = dap_hash_fast_to_str_static(&l_tx_hash); + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;tx;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", l_tx_hash_str_tmp, l_config->net_name); + json_object *l_json_prev_tx = dap_request_command_to_rpc(data, l_config); + if (!l_json_prev_tx) { + json_object_put(l_json_response); + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_PREV_TX_NOT_FOUND, "Previous transaction not found"); + return s_compose_config_return_response_handler(l_config); + } + json_object_put(l_json_prev_tx); + break; + } + } + } + json_object_put(l_json_response); + + if (a_tx_hash_str) { + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;list;tx;-net;%s\"],\"id\": \"1\"}", l_config->net_name); + json_object *l_json_coins = dap_request_command_to_rpc(data, l_config); + if (!l_json_coins) { + return s_compose_config_return_response_handler(l_config); + } + + bool tx_exists = false; + int tx_count = json_object_array_length(l_json_coins); + for (int i = 0; i < tx_count; i++) { + json_object *tx_item = json_object_array_get_idx(l_json_coins, i); + const char *tx_hash = json_object_get_string(json_object_object_get(tx_item, "tx_hash")); + if (tx_hash && strcmp(tx_hash, l_tx_hash_str_tmp) == 0) { + json_object_put(l_json_coins); + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_TX_EXISTS, "Transaction already exists"); + return s_compose_config_return_response_handler(l_config); + } + } + json_object_put(l_json_coins); + } + + + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(a_wallet_str, a_wallet_path,NULL); + if (!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, DAP_CLI_STAKE_INVALIDATE_WALLET_NOT_FOUND, "Specified wallet not found"); + return s_compose_config_return_response_handler(l_config); + } + dap_enc_key_t *l_enc_key = dap_chain_wallet_get_key(l_wallet, 0); + dap_chain_datum_tx_t *l_tx = dap_stake_tx_invalidate_compose(&l_tx_hash, l_fee, l_enc_key, l_config); + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + DAP_DELETE(l_tx); + } + + dap_chain_wallet_close(l_wallet); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); +} + +dap_chain_datum_tx_t *dap_stake_tx_invalidate_compose(dap_hash_fast_t *a_tx_hash, uint256_t a_fee, dap_enc_key_t *a_key, compose_config_t *a_config) +{ + if(!a_config || !a_config->net_name || !*a_config->net_name || !a_tx_hash || !a_key || !a_config->url_str || !*a_config->url_str || a_config->port == 0) + return NULL; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-need_sign;-hash;%s;-net;%s\"],\"id\": \"1\"}", + dap_hash_fast_to_str_static(a_tx_hash), a_config->net_name); + + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_LEDGER_ERROR, "Failed to get ledger info"); + return NULL; + } + json_object *l_items_array = json_object_array_get_idx(response, 0); + l_items_array = json_object_object_get(l_items_array, "ITEMS"); + if (!l_items_array) { + json_object_put(response); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_ITEMS_NOT_FOUND, "Items not found in ledger response"); + return NULL; + } + + json_object *l_unspent_outs = json_object_object_get(response, "all OUTs yet unspent"); + if (l_unspent_outs) { + const char *all_unspent = json_object_get_string(l_unspent_outs); + if (all_unspent && strcmp(all_unspent, "yes") == 0) { + json_object_put(response); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_OUTPUTS_SPENT, "All outputs are already spent"); + return NULL; + } + } + + dap_chain_tx_out_cond_t *l_tx_out_cond = NULL; + const char * l_tx_prev_hash = NULL; + int l_prev_cond_idx = 0; + + size_t items_count = json_object_array_length(l_items_array); + for (size_t i = 0; i < items_count; i++) { + json_object *l_item = json_object_array_get_idx(l_items_array, i); + const char *item_type = json_object_get_string(json_object_object_get(l_item, "item type")); + + if (item_type && strcmp(item_type, "OUT COND") == 0) { + l_tx_out_cond = DAP_NEW_Z(dap_chain_tx_out_cond_t); + l_tx_out_cond->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_POS_DELEGATE; + l_tx_out_cond->header.value = dap_uint256_scan_uninteger(json_object_get_string(json_object_object_get(l_item, "value"))); + } else if (item_type && strcmp(item_type, "IN COND") == 0) { + l_tx_prev_hash = json_object_get_string(json_object_object_get(l_item, "Tx_prev_hash")); + if (!l_tx_prev_hash) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_HASH_NOT_FOUND, "Previous transaction hash not found"); + return NULL; + } + l_prev_cond_idx = json_object_get_int(json_object_object_get(l_item, "Tx_out_prev_idx")); + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", + l_tx_prev_hash, a_config->net_name); + + json_object *response_cond = dap_request_command_to_rpc(data, a_config); + if (!response_cond) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_COND_TX_ERROR, "Failed to get conditional transaction info"); + return NULL; + } + json_object_put(response_cond); + } + } + + if (!l_tx_out_cond || !l_tx_prev_hash) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_COND_TX_NOT_FOUND, "Conditional transaction not found"); + return NULL; + } + + json_object *l_sig_item = NULL; + for (size_t i = 0; i < items_count; i++) { + json_object *l_item = json_object_array_get_idx(l_items_array, i); + const char *item_type = json_object_get_string(json_object_object_get(l_item, "item type")); + if (item_type && strcmp(item_type, "SIG") == 0) { + l_sig_item = l_item; + break; + } + } + + if (!l_sig_item) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_SIG_NOT_FOUND, "Signature item not found"); + return NULL; + } + + const char *l_sign_b64_str = json_object_get_string(json_object_object_get(l_sig_item, "sig_b64")); + if (!l_sign_b64_str) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_SIG_DECODE_ERROR, "Failed to decode signature"); + return NULL; + } + + int64_t l_sign_b64_strlen = json_object_get_string_len(json_object_object_get(l_sig_item, "sig_b64")); + int64_t l_sign_decoded_size = DAP_ENC_BASE64_DECODE_SIZE(l_sign_b64_strlen); + dap_chain_tx_sig_t *l_tx_sig = DAP_NEW_Z_SIZE(dap_chain_tx_sig_t, sizeof(dap_chain_tx_sig_t) + l_sign_decoded_size); + *l_tx_sig = (dap_chain_tx_sig_t) { + .header = { + .type = TX_ITEM_TYPE_SIG, .version = 1, + .sig_size = dap_enc_base64_decode(l_sign_b64_str, l_sign_b64_strlen, l_tx_sig->sig, DAP_ENC_DATA_TYPE_B64_URLSAFE) + } + }; + + dap_sign_t *l_sign = dap_chain_datum_tx_item_sign_get_sig(l_tx_sig); + dap_chain_addr_t l_owner_addr; + dap_chain_addr_fill_from_sign(&l_owner_addr, l_sign, s_get_net_id(a_config->net_name)); + dap_chain_addr_t l_wallet_addr; + dap_chain_addr_fill_from_key(&l_wallet_addr, a_key, s_get_net_id(a_config->net_name)); + if (!dap_chain_addr_compare(&l_owner_addr, &l_wallet_addr)) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_WRONG_OWNER, "Wrong transaction owner"); + return NULL; + } + + const char *l_native_ticker = s_get_native_ticker(a_config->net_name); + + json_object *l_json_tiker = json_object_array_get_idx(response, 0); + json_object *token_ticker_obj = json_object_object_get(l_json_tiker, "Token_ticker"); + if (!token_ticker_obj) { + token_ticker_obj = json_object_object_get(l_json_tiker, "token ticker"); + if (!token_ticker_obj) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_TOKEN_NOT_FOUND, "Token ticker not found"); + return NULL; + } + } + const char *l_delegated_ticker = json_object_get_string(token_ticker_obj); + + json_object *l_outs_native = dap_get_remote_tx_outs(l_native_ticker, &l_owner_addr, a_config); + if (!l_outs_native) { + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_OUTS_NOT_FOUND, "Transaction outputs not found"); + return NULL; + } + + int l_out_native_count = json_object_array_length(l_outs_native); + uint256_t l_fee_transfer = {}; // how many coins to transfer + // list of transaction with 'out' items to sell + uint256_t l_net_fee, l_fee_total = a_fee; + dap_chain_addr_t*l_net_fee_addr = NULL; + bool l_net_fee_used = dap_get_remote_net_fee_and_address(&l_net_fee, &l_net_fee_addr, a_config); + if (l_net_fee_used) + SUM_256_256(l_fee_total, l_net_fee, &l_fee_total); + dap_list_t *l_list_fee_out = NULL; + l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, + l_fee_total, + &l_fee_transfer); + if (!l_list_fee_out) { + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_NOT_ENOUGH_FUNDS, "Not enough funds to pay fees"); + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + // add 'in' item to buy from conditional transaction + dap_chain_datum_tx_add_in_cond_item(&l_tx, a_tx_hash, l_prev_cond_idx, 0); + + // add 'in' items to pay fee + uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); + dap_list_free_full(l_list_fee_out, NULL); + if (!EQUAL_256(l_value_fee_items, l_fee_transfer)) { + dap_chain_datum_tx_delete(l_tx); + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_IN_ERROR, "Error adding input items"); + return NULL; + } + + // add 'out_ext' item + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_owner_addr, l_tx_out_cond->header.value, l_delegated_ticker) == -1) { + dap_chain_datum_tx_delete(l_tx); + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_OUT_ERROR, "Error adding output items"); + return NULL; + } + // add fee items + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_net_fee_addr, l_net_fee, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_NET_FEE_ERROR, "Error adding network fee"); + return NULL; + } + } + if (!IS_ZERO_256(a_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_fee) != 1) { + dap_chain_datum_tx_delete(l_tx); + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_FEE_ERROR, "Error adding fee"); + return NULL; + } + } + // fee coin back + uint256_t l_fee_back = {}; + SUBTRACT_256_256(l_fee_transfer, l_fee_total, &l_fee_back); + if(!IS_ZERO_256(l_fee_back)) { + if(dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_owner_addr, l_fee_back, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_INVALIDATE_COMPOSE_FEE_BACK_ERROR, "Error adding fee back"); + return NULL; + } + } + json_object_put(l_outs_native); + json_object_put(response); + DAP_DELETE(l_tx_out_cond); + DAP_DELETE(l_tx_sig); + return l_tx; +} + +dap_chain_net_srv_order_direction_t dap_chain_net_srv_order_direction_from_str(const char* str) { + dap_chain_net_srv_order_direction_t direction = SERV_DIR_UNDEFINED; + if (strcmp(str, "BUY") == 0) { + direction = SERV_DIR_BUY; + } else if (strcmp(str, "SELL") == 0) { + direction = SERV_DIR_SELL; + } + return direction; +} + +dap_chain_net_srv_order_t* dap_check_remote_srv_order(const char* l_net_str, const char* l_order_hash_str, uint256_t* a_tax, + uint256_t* a_value_max, dap_chain_addr_t* a_sovereign_addr, uint256_t* a_sovereign_tax, json_object* response){ + dap_chain_net_srv_order_t* l_order = NULL; + json_object *orders_array = json_object_array_get_idx(response, 0); + size_t orders_count = json_object_array_length(orders_array); + for (size_t i = 0; i < orders_count; i++) { + json_object *order_obj = json_object_array_get_idx(orders_array, i); + const char *order_hash_str = json_object_get_string(json_object_object_get(order_obj, "order")); + + if (strcmp(order_hash_str, l_order_hash_str) == 0) { + l_order = DAP_NEW_Z_SIZE(dap_chain_net_srv_order_t, sizeof(dap_chain_net_srv_order_t)); + l_order->version = json_object_get_int(json_object_object_get(order_obj, "version")); + l_order->direction = dap_chain_net_srv_order_direction_from_str(json_object_get_string(json_object_object_get(order_obj, "direction"))); + l_order->ts_created = dap_time_from_str_rfc822(json_object_get_string(json_object_object_get(order_obj, "created"))); + l_order->srv_uid.uint64 = dap_chain_net_srv_uid_from_str(json_object_get_string(json_object_object_get(order_obj, "srv_uid"))).uint64; + l_order->price = dap_uint256_scan_uninteger(json_object_get_string(json_object_object_get(order_obj, "price datoshi"))); + strncpy(l_order->price_ticker, json_object_get_string(json_object_object_get(order_obj, "price token")), DAP_CHAIN_TICKER_SIZE_MAX); + l_order->units = json_object_get_int(json_object_object_get(order_obj, "units")); + l_order->price_unit = dap_chain_net_srv_price_unit_uid_from_str(json_object_get_string(json_object_object_get(order_obj, "price unit"))); + dap_chain_node_addr_from_str(&l_order->node_addr, json_object_get_string(json_object_object_get(order_obj, "node_addr"))); + const char *tx_cond_hash_str = json_object_get_string(json_object_object_get(order_obj, "tx_cond_hash")); + if (tx_cond_hash_str) { + dap_chain_hash_fast_from_str(tx_cond_hash_str, &l_order->tx_cond_hash); + } + l_order->ext_size = json_object_get_int(json_object_object_get(order_obj, "ext_size")); + + if (l_order->ext_size > 0) { + json_object *external_params = json_object_object_get(order_obj, "external_params"); + if (external_params) { + const char *tax_str = json_object_get_string(json_object_object_get(external_params, "tax")); + const char *value_max_str = json_object_get_string(json_object_object_get(external_params, "maximum_value")); + *a_tax = dap_uint256_scan_decimal(tax_str); + *a_value_max = dap_uint256_scan_decimal(value_max_str); + } + } + + json_object *conditional_tx_params = json_object_object_get(order_obj, "conditional_tx_params"); + if (conditional_tx_params && json_object_is_type(conditional_tx_params, json_type_object)) { + const char *sovereign_tax_str = json_object_get_string(json_object_object_get(conditional_tx_params, "sovereign_tax")); + const char *sovereign_addr_str = json_object_get_string(json_object_object_get(conditional_tx_params, "sovereign_addr")); + *a_sovereign_tax = dap_uint256_scan_decimal(sovereign_tax_str); + a_sovereign_addr = dap_chain_addr_from_str(sovereign_addr_str); + } + break; + } + } + return l_order; +} + +dap_chain_net_srv_order_t* dap_get_remote_srv_order(const char* l_order_hash_str, uint256_t* a_tax, + uint256_t* a_value_max, dap_chain_addr_t* a_sovereign_addr, uint256_t* a_sovereign_tax, + compose_config_t *a_config){ + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;order;list;staker;-net;%s\"],\"id\": \"1\"}", + a_config->net_name); + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + printf("Error: Failed to get response from remote node\n"); + return NULL; + } + + dap_chain_net_srv_order_t *l_order = dap_check_remote_srv_order(a_config->net_name, l_order_hash_str, a_tax, a_value_max, a_sovereign_addr, a_sovereign_tax, response); + json_object_put(response); + + if (!l_order) { + snprintf(data, sizeof(data), + "{\"method\": \"srv_stake\",\"params\": [\"srv_stake;order;list;validator;-net;%s\"],\"id\": \"1\"}", + a_config->net_name); + response = dap_request_command_to_rpc(data, a_config); + if (!response) { + printf("Error: Failed to get response from remote node\n"); + return NULL; + } + l_order = dap_check_remote_srv_order(a_config->net_name, l_order_hash_str, a_tax, a_value_max, a_sovereign_addr, a_sovereign_tax, response); + json_object_put(response); + } + return l_order; +} + +dap_sign_t* dap_get_remote_srv_order_sign(const char* l_order_hash_str, compose_config_t *a_config){ + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"net_srv\",\"params\": [\"net_srv;-net;%s;order;dump;-hash;%s;-need_sign\"],\"id\": \"1\"}", + a_config->net_name, l_order_hash_str); + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + printf("Error: Failed to get response from remote node\n"); + return NULL; + } + json_object *l_response_array = json_object_array_get_idx(response, 0); + if (!l_response_array) { + printf("Error: Can't get the first element from the response array\n"); + json_object_put(response); + return NULL; + } + + const char *l_sign_b64_str = json_object_get_string(json_object_object_get(l_response_array, "sig_b64")); + if (!l_sign_b64_str) { + printf("Error: Can't get base64-encoded sign from SIG item\n"); + json_object_put(response); + return NULL; + } + + // *a_sign_size = json_object_get_int(json_object_object_get(l_response_array, "sig_b64_size")); + int64_t l_sign_b64_strlen = json_object_get_string_len(json_object_object_get(l_response_array, "sig_b64")); + int64_t l_sign_decoded_size = DAP_ENC_BASE64_DECODE_SIZE(l_sign_b64_strlen); + dap_chain_tx_sig_t *l_tx_sig = DAP_NEW_Z_SIZE(dap_chain_tx_sig_t, sizeof(dap_chain_tx_sig_t) + l_sign_decoded_size); + *l_tx_sig = (dap_chain_tx_sig_t) { + .header = { + .type = TX_ITEM_TYPE_SIG, .version = 1, + .sig_size = dap_enc_base64_decode(l_sign_b64_str, l_sign_b64_strlen, l_tx_sig->sig, DAP_ENC_DATA_TYPE_B64_URLSAFE) + } + }; + + dap_sign_t *l_sign = dap_chain_datum_tx_item_sign_get_sig(l_tx_sig); + DAP_DELETE(l_tx_sig); + json_object_put(response); + return l_sign; +} + + + + +json_object* dap_cli_srv_stake_delegate_compose(const char* a_net_str, const char* a_wallet_str, const char* a_cert_str, + const char* a_pkey_full_str, const char* a_sign_type_str, const char* a_value_str, const char* a_node_addr_str, + const char* a_order_hash_str, const char* a_url_str, uint16_t a_port, const char* a_sovereign_addr_str, const char* a_fee_str, const char* a_wallets_path) { + compose_config_t *l_config = s_compose_config_init(a_net_str, a_url_str, a_port); + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(a_wallet_str, a_wallets_path, NULL); + if (!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_WALLET_NOT_FOUND, "Specified wallet not found"); + return l_config->response_handler; + } + dap_enc_key_t *l_enc_key = dap_chain_wallet_get_key(l_wallet, 0); + dap_chain_wallet_close(l_wallet); + dap_chain_addr_t l_signing_addr, l_sovereign_addr = {}; + uint256_t l_sovereign_tax = uint256_0; + uint256_t l_value = uint256_0; + if (a_value_str) { + l_value = dap_chain_balance_scan(a_value_str); + if (IS_ZERO_256(l_value)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_VALUE, "Unrecognized number in '-value' param"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + } + dap_pkey_t *l_pkey = NULL; + dap_chain_datum_tx_t *l_prev_tx = NULL; + if (a_cert_str) { + dap_cert_t *l_signing_cert = dap_cert_find_by_name(a_cert_str); + if (!l_signing_cert) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_CERT_NOT_FOUND, "Specified certificate not found"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + if (dap_chain_addr_fill_from_key(&l_signing_addr, l_signing_cert->enc_key, s_get_net_id(a_net_str))) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_CERT_WRONG, "Specified certificate is wrong"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + l_pkey = dap_pkey_from_enc_key(l_signing_cert->enc_key); + } else if (a_pkey_full_str) { + dap_sign_type_t l_type = dap_sign_type_from_str(a_sign_type_str); + if (l_type.type == SIG_TYPE_NULL) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_WRONG_SIGN_TYPE, "Wrong sign type"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + l_pkey = dap_pkey_get_from_str(a_pkey_full_str); + if (!l_pkey) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_PKEY, "Invalid pkey string format, can't get pkey_full"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + if (l_pkey->header.type.type != dap_pkey_type_from_sign_type(l_type).type) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_PKEY, "pkey and sign types is different"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + dap_chain_hash_fast_t l_hash_public_key = {0}; + if (!dap_pkey_get_hash(l_pkey, &l_hash_public_key)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_PKEY, "Invalid pkey hash format"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + dap_chain_addr_fill(&l_signing_addr, l_type, &l_hash_public_key, s_get_net_id(a_net_str)); + } + + dap_chain_node_addr_t l_node_addr = g_node_addr; + if (a_node_addr_str) { + if (dap_chain_node_addr_from_str(&l_node_addr, a_node_addr_str)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_NODE_ADDR, "Unrecognized node addr %s", a_node_addr_str); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + } + if (a_order_hash_str) { + uint256_t l_tax; + uint256_t l_value_max; + int l_prev_tx_count = 0; + dap_chain_net_srv_order_t* l_order = dap_get_remote_srv_order(a_order_hash_str, &l_tax, &l_value_max, &l_sovereign_addr, &l_sovereign_tax, l_config); + if (!l_order) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_ORDER_NOT_FOUND, "Error: Failed to get order from remote node"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + l_sovereign_tax = l_tax; + + if (l_order->direction == SERV_DIR_BUY) { // Staker order + if (!a_cert_str) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_CERT_REQUIRED, "Command 'delegate' requires parameter -cert with this order type"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + if (l_order->ext_size != 0) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_ORDER_SIZE, "Specified order has invalid size"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + + dap_chain_tx_out_cond_t *l_cond_tx = NULL; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_hash_fast_to_str_static(&l_order->tx_cond_hash), a_net_str); + + json_object *response = dap_request_command_to_rpc(data, l_config); + if (!response) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_RPC_RESPONSE, "Error: Failed to get response from remote node"); + return s_compose_config_return_response_handler(l_config); + } + + json_object *items = json_object_object_get(response, "ITEMS"); + if (!items) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_NO_ITEMS, "Error: No items found in response"); + return s_compose_config_return_response_handler(l_config); + } + int items_count = json_object_array_length(items); + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (dap_strcmp(item_type, "OUT COND") == 0) { + const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); + if (!dap_strcmp(subtype, "DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_POS_DELEGATE")) { + l_cond_tx = DAP_NEW_Z(dap_chain_tx_out_cond_t); + l_cond_tx->header.item_type = TX_ITEM_TYPE_OUT_COND; + l_cond_tx->header.value = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "value"))); + l_cond_tx->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_POS_DELEGATE; + l_cond_tx->header.srv_uid.uint64 = strtoull(json_object_get_string(json_object_object_get(item, "uid")), NULL, 16); + l_cond_tx->header.ts_expires = dap_time_from_str_rfc822(json_object_get_string(json_object_object_get(item, "ts_expires"))); + l_cond_tx->subtype.srv_stake_pos_delegate.signing_addr = *dap_chain_addr_from_str(json_object_get_string(json_object_object_get(item, "signing_addr"))); + if (dap_chain_node_addr_from_str(&l_cond_tx->subtype.srv_stake_pos_delegate.signer_node_addr, json_object_get_string(json_object_object_get(item, "signer_node_addr"))) != 0) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_SIGNER_ADDR, "Error: Failed to parse signer node address"); + return s_compose_config_return_response_handler(l_config); + } + l_cond_tx->tsd_size = json_object_get_int(json_object_object_get(item, "tsd_size")); + l_prev_tx_count++; + break; + } + } else if (dap_strcmp(item_type, "OUT") == 0 || dap_strcmp(item_type, "OUT COND") == 0 || dap_strcmp(item_type, "OUT OLD") == 0) { + l_prev_tx_count++; + } + } + if (!l_cond_tx) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_NODE_ADDR, "Error: No transaction output condition found"); + return s_compose_config_return_response_handler(l_config); + } + + json_object *spent_outs = json_object_object_get(response, "all OUTs yet unspent"); + const char *spent_outs_value = json_object_get_string(spent_outs); + if (spent_outs_value && dap_strcmp(spent_outs_value, "yes") != 0) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_ORDER_SIZE, "Error: Transaction output item already used"); + return s_compose_config_return_response_handler(l_config); + } + + char l_delegated_ticker[DAP_CHAIN_TICKER_SIZE_MAX]; + dap_chain_datum_token_get_delegated_ticker(l_delegated_ticker, s_get_native_ticker(a_net_str)); + const char *l_token_ticker = json_object_get_string(json_object_object_get(response, "token_ticker")); + if (!l_token_ticker) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_NO_TOKEN_TICKER, "Error: Token ticker not found in response"); + return s_compose_config_return_response_handler(l_config); + } + json_object_put(response); + if (dap_strcmp(l_token_ticker, l_delegated_ticker)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_WRONG_TICKER, "Requested conditional transaction have another ticker (not %s)", l_delegated_ticker); + return s_compose_config_return_response_handler(l_config); + } + if (l_cond_tx->tsd_size != dap_chain_datum_tx_item_out_cond_create_srv_stake_get_tsd_size(true, 0)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_FORMAT, "The order's conditional transaction has invalid format"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + if (compare256(l_cond_tx->header.value, l_order->price)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_VALUE, "The order's conditional transaction has different value"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + if (!dap_chain_addr_is_blank(&l_cond_tx->subtype.srv_stake_pos_delegate.signing_addr) || + l_cond_tx->subtype.srv_stake_pos_delegate.signer_node_addr.uint64) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_ADDR, "The order's conditional transaction gas not blank address or key"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + l_value = l_order->price; + } else { + if (!a_value_str) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_VALUE_REQUIRED, "Command 'delegate' requires parameter -value with this order type"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + if (a_sovereign_addr_str) { + dap_chain_addr_t *l_spec_addr = dap_chain_addr_from_str(a_sovereign_addr_str); + if (!l_spec_addr) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_SOVEREIGN_ADDR, "Specified address is invalid"); + return s_compose_config_return_response_handler(l_config); + } + l_sovereign_addr = *l_spec_addr; + DAP_DELETE(l_spec_addr); + } else + dap_chain_addr_fill_from_key(&l_sovereign_addr, l_enc_key, s_get_net_id(a_net_str)); + + if (a_order_hash_str && compare256(l_value, l_order->price) == -1) { + const char *l_coin_min_str, *l_value_min_str = + dap_uint256_to_char(l_order->price, &l_coin_min_str); + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_VALUE_TOO_LOW, "Number in '-value' param %s is lower than order minimum allowed value %s(%s)", + a_value_str, l_coin_min_str, l_value_min_str); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + if (a_order_hash_str && compare256(l_value, l_value_max) == 1) { + const char *l_coin_max_str, *l_value_max_str = + dap_uint256_to_char(l_value_max, &l_coin_max_str); + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_VALUE_TOO_HIGH, "Number in '-value' param %s is higher than order minimum allowed value %s(%s)", + a_value_str, l_coin_max_str, l_value_max_str); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + size_t l_sign_size = 0; + dap_sign_t *l_sign = dap_get_remote_srv_order_sign(a_order_hash_str, l_config); + if (!l_sign) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_UNSIGNED_ORDER, "Specified order is unsigned"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + dap_chain_addr_fill_from_sign(&l_signing_addr, l_sign, s_get_net_id(a_net_str)); + l_pkey = dap_pkey_get_from_sign(l_sign); + char l_delegated_ticker_str[DAP_CHAIN_TICKER_SIZE_MAX]; + dap_chain_datum_token_get_delegated_ticker(l_delegated_ticker_str, s_get_native_ticker(a_net_str)); + if (dap_strcmp(l_order->price_ticker, l_delegated_ticker_str)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_ORDER, "Specified order is invalid"); + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_order); + return s_compose_config_return_response_handler(l_config); + } + l_node_addr = l_order->node_addr; + } + DAP_DELETE(l_order); + if (compare256(l_sovereign_tax, dap_chain_coins_to_balance("100.0")) == 1 || + compare256(l_sovereign_tax, GET_256_FROM_64(100)) == -1) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_TAX, "Tax must be lower or equal than 100%% and higher or equal than 1.0e-16%%"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + DIV_256(l_sovereign_tax, GET_256_FROM_64(100), &l_sovereign_tax); + } + if (!l_pkey) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_PKEY_UNDEFINED, "pkey not defined"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + + // TODO: need to make sure that the key and node are required verification + // int l_check_result = dap_chain_net_srv_stake_verify_key_and_node(&l_signing_addr, &l_node_addr); + // if (l_check_result) { + // dap_json_compose_error_add(a_json_obj_ret, l_check_result, "Key and node verification error"); + // dap_enc_key_delete(l_enc_key); + // return l_check_result; + // } + + + uint256_t l_allowed_min = s_get_key_delegating_min_value(l_config); + if (compare256(l_value, l_allowed_min) == -1) { + const char *l_coin_min_str, *l_value_min_str = dap_uint256_to_char(l_allowed_min, &l_coin_min_str); + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_VALUE_BELOW_MIN, "Number in '-value' param %s is lower than minimum allowed value %s(%s)", + a_value_str, l_coin_min_str, l_value_min_str); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + + uint256_t l_fee = dap_chain_balance_scan(a_fee_str); + if (IS_ZERO_256(l_fee)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_DELEGATE_COMPOSE_ERR_INVALID_VALUE, "Unrecognized number in '-fee' param"); + dap_enc_key_delete(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + dap_chain_datum_tx_t *l_tx = dap_stake_tx_create_compose(l_enc_key, l_value, l_fee, &l_signing_addr, &l_node_addr, + a_order_hash_str ? &l_sovereign_addr : NULL, l_sovereign_tax, l_prev_tx, l_pkey, l_config); + + dap_enc_key_delete(l_enc_key); + DAP_DELETE(l_pkey); + + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + DAP_DELETE(l_tx); + } + + return s_compose_config_return_response_handler(l_config); + +} + +dap_chain_datum_tx_t *dap_stake_tx_create_compose(dap_enc_key_t *a_key, + uint256_t a_value, uint256_t a_fee, + dap_chain_addr_t *a_signing_addr, dap_chain_node_addr_t *a_node_addr, + dap_chain_addr_t *a_sovereign_addr, uint256_t a_sovereign_tax, + dap_chain_datum_tx_t *a_prev_tx, dap_pkey_t *a_pkey, compose_config_t *a_config) +{ + if (!a_key || IS_ZERO_256(a_value) || !a_signing_addr || !a_node_addr) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_INVALID_PARAMS, "Invalid parameters for transaction creation"); + return NULL; + } + const char *l_native_ticker = s_get_native_ticker(a_config->net_name); + char l_delegated_ticker[DAP_CHAIN_TICKER_SIZE_MAX]; + dap_chain_datum_token_get_delegated_ticker(l_delegated_ticker, l_native_ticker); + uint256_t l_value_transfer = {}, l_fee_transfer = {}; + // list of transaction with 'out' items to sell + dap_chain_addr_t l_owner_addr; + dap_chain_addr_fill_from_key(&l_owner_addr, a_key, s_get_net_id(a_config->net_name)); + uint256_t l_net_fee, l_fee_total = a_fee; + dap_chain_addr_t * l_net_fee_addr = NULL; + bool l_net_fee_used = dap_get_remote_net_fee_and_address(&l_net_fee, &l_net_fee_addr, a_config); + if (l_net_fee_used) + SUM_256_256(l_fee_total, l_net_fee, &l_fee_total); + + dap_list_t *l_list_fee_out = NULL; + + json_object *l_outs_native = dap_get_remote_tx_outs(l_native_ticker, &l_owner_addr, a_config); + if (!l_outs_native) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_FEE, "Not enough funds to pay fee"); + return NULL; + } + + json_object *l_outs_delegated = dap_get_remote_tx_outs(l_delegated_ticker, &l_owner_addr, a_config); + if (!l_outs_delegated) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_VALUE, "Not enough funds for value"); + return NULL; + } + + int l_out_native_count = json_object_array_length(l_outs_native); + int l_out_delegated_count = json_object_array_length(l_outs_delegated); + + l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, + l_fee_total, + &l_fee_transfer); + if (!l_list_fee_out) { + json_object_put(l_outs_native); + json_object_put(l_outs_delegated); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_FEE, "Not enough funds to pay fee"); + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + if (!a_prev_tx) { + dap_list_t * l_list_used_out = dap_ledger_get_list_tx_outs_from_json(l_outs_delegated, l_out_delegated_count, + a_value, + &l_value_transfer); + if (!l_list_used_out) { + json_object_put(l_outs_native); + json_object_put(l_outs_delegated); + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_VALUE, "Not enough funds for value"); + return NULL; + } + // add 'in' items to pay for delegate + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + dap_list_free_full(l_list_used_out, NULL); + if (!EQUAL_256(l_value_to_items, l_value_transfer)) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_TX_IN_ERROR, "Error creating transaction input"); + goto tx_fail; + } + } else { + dap_hash_fast_t l_prev_tx_hash; + dap_hash_fast(a_prev_tx, dap_chain_datum_tx_get_size(a_prev_tx), &l_prev_tx_hash); + int l_out_num = 0; + dap_chain_datum_tx_out_cond_get(a_prev_tx, DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_POS_DELEGATE, &l_out_num); + // add 'in' item to buy from conditional transaction + if (1 != dap_chain_datum_tx_add_in_cond_item(&l_tx, &l_prev_tx_hash, l_out_num, -1)) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_TX_IN_ERROR, "Error creating transaction input"); + goto tx_fail; + } + } + // add 'in' items to pay fee + uint256_t l_value_fee_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); + dap_list_free_full(l_list_fee_out, NULL); + if (!EQUAL_256(l_value_fee_items, l_fee_transfer)) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_TX_IN_ERROR, "Error creating transaction input"); + goto tx_fail; + } + + // add 'out_cond' & 'out_ext' items + dap_chain_net_srv_uid_t l_uid = { .uint64 = DAP_CHAIN_NET_SRV_STAKE_POS_DELEGATE_ID }; + dap_chain_tx_out_cond_t *l_tx_out = dap_chain_datum_tx_item_out_cond_create_srv_stake(l_uid, a_value, a_signing_addr, a_node_addr, + a_sovereign_addr, a_sovereign_tax, a_pkey); + + if (!l_tx_out) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_TX_COND_OUT_ERROR, "Error creating conditional transaction output"); + goto tx_fail; + } + dap_chain_datum_tx_add_item(&l_tx, (const uint8_t *)l_tx_out); + DAP_DELETE(l_tx_out); + if (!a_prev_tx) { + // coin back + uint256_t l_value_back = {}; + SUBTRACT_256_256(l_value_transfer, a_value, &l_value_back); + if (!IS_ZERO_256(l_value_back)) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_owner_addr, l_value_back, l_delegated_ticker) != 1) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_TX_OUT_ERROR, "Error creating transaction output"); + goto tx_fail; + } + } + } + + // add fee items + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, l_net_fee_addr, l_net_fee, l_native_ticker) != 1) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_NET_FEE_ERROR, "Error with network fee"); + goto tx_fail; + } + } + if (!IS_ZERO_256(a_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_fee) != 1) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_VALIDATOR_FEE_ERROR, "Error with validator fee"); + goto tx_fail; + } + } + uint256_t l_fee_back = {}; + // fee coin back + SUBTRACT_256_256(l_fee_transfer, l_fee_total, &l_fee_back); + if (!IS_ZERO_256(l_fee_back)) { + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_owner_addr, l_fee_back, l_native_ticker) != 1) { + dap_json_compose_error_add(a_config->response_handler, DAP_STAKE_TX_CREATE_COMPOSE_FEE_BACK_ERROR, "Error with fee back"); + goto tx_fail; + } + } + + return l_tx; + +tx_fail: + dap_chain_datum_tx_delete(l_tx); + return NULL; +} + +static dap_chain_datum_tx_t *dap_order_tx_create_compose(dap_enc_key_t *a_key, + uint256_t a_value, uint256_t a_fee, + uint256_t a_sovereign_tax, dap_chain_addr_t *a_sovereign_addr, + compose_config_t *a_config) +{ + dap_chain_node_addr_t l_node_addr = {}; + return dap_stake_tx_create_compose(a_key, a_value, a_fee, + (dap_chain_addr_t *)&c_dap_chain_addr_blank, &l_node_addr, + a_sovereign_addr, a_sovereign_tax, NULL, NULL, a_config); +} + + +json_object* dap_cli_srv_stake_order_create_staker_compose(const char *l_net_str, const char *l_value_str, const char *l_fee_str, const char *l_tax_str, const char *l_addr_str, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port) { + compose_config_t *l_config = s_compose_config_init(l_net_str, l_url_str, l_port); + if (!l_config) { + json_object *l_json_obj_ret = json_object_new_object(); + dap_json_compose_error_add(l_json_obj_ret, STAKE_ORDER_CREATE_STAKER_ERR_INVALID_PARAMS, "Invalid arguments"); + return l_json_obj_ret; + } + uint256_t l_value = dap_chain_balance_scan(l_value_str); + if (IS_ZERO_256(l_value)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_INVALID_VALUE, "Format -value <256 bit integer>"); + return s_compose_config_return_response_handler(l_config); + } + uint256_t l_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_fee)) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_INVALID_FEE, "Format -fee <256 bit integer>"); + return s_compose_config_return_response_handler(l_config); + } + uint256_t l_tax = dap_chain_coins_to_balance(l_tax_str); + if (compare256(l_tax, dap_chain_coins_to_balance("100.0")) == 1 || + compare256(l_tax, GET_256_FROM_64(100)) == -1) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_INVALID_TAX, "Tax must be lower or equal than 100%% and higher or equal than 1.0e-16%%"); + return s_compose_config_return_response_handler(l_config); + } + + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL); + if (!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_WALLET_NOT_FOUND, "Specified wallet not found"); + return s_compose_config_return_response_handler(l_config); + } + + const char *l_sign_str = dap_chain_wallet_check_sign(l_wallet); + dap_enc_key_t *l_enc_key = dap_chain_wallet_get_key(l_wallet, 0); + dap_chain_wallet_close(l_wallet); + + if (!l_enc_key) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_KEY_NOT_FOUND, "Failed to retrieve encryption key"); + return s_compose_config_return_response_handler(l_config); + } + + dap_chain_addr_t l_addr = {}; + if (l_addr_str) { + dap_chain_addr_t *l_spec_addr = dap_chain_addr_from_str(l_addr_str); + if (!l_spec_addr) { + dap_json_compose_error_add(l_config->response_handler, STAKE_ORDER_CREATE_STAKER_ERR_INVALID_ADDR, "Specified address is invalid"); + DAP_DELETE(l_enc_key); + return s_compose_config_return_response_handler(l_config); + } + l_addr = *l_spec_addr; + DAP_DELETE(l_spec_addr); + } else + dap_chain_addr_fill_from_key(&l_addr, l_enc_key, s_get_net_id(l_net_str)); + DIV_256(l_tax, GET_256_FROM_64(100), &l_tax); + dap_chain_datum_tx_t *l_tx = dap_order_tx_create_compose(l_enc_key, l_value, l_fee, l_tax, &l_addr, l_config); + DAP_DEL_Z(l_enc_key); + + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + DAP_DELETE(l_tx); + } + + return s_compose_config_return_response_handler(l_config); +} + +json_object * dap_cli_srv_stake_order_remove_compose(const char *l_net_str, const char *l_order_hash_str, const char *l_fee_str, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port) { + + compose_config_t *l_config = s_compose_config_init(l_net_str, l_url_str, l_port); + if (!l_config) { + json_object *l_json_obj_ret = json_object_new_object(); + dap_json_compose_error_add(l_json_obj_ret, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_PARAMS, "Invalid arguments"); + return l_json_obj_ret; + } + + dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_str, l_wallet_path, NULL); + if (!l_wallet) { + dap_json_compose_error_add(l_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_WALLET_NOT_FOUND, "Specified wallet not found"); + return s_compose_config_return_response_handler(l_config); + } + const char* l_sign_str = dap_chain_wallet_check_sign(l_wallet); + if (!l_sign_str) { + dap_json_compose_error_add(l_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_KEY_NOT_FOUND, "Failed to retrieve encryption key"); + return s_compose_config_return_response_handler(l_config); + } + uint256_t l_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_fee)) { + dap_json_compose_error_add(l_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_FEE, "Format -fee <256 bit integer>"); + return s_compose_config_return_response_handler(l_config); + } + dap_hash_fast_t l_tx_hash = {}; + dap_chain_hash_fast_from_str(l_order_hash_str, &l_tx_hash); + if (dap_hash_fast_is_blank(&l_tx_hash)) { + dap_json_compose_error_add(l_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_ORDER_HASH, "Invalid order hash"); + return s_compose_config_return_response_handler(l_config); + } + char *l_tx_hash_ret = NULL; + dap_chain_datum_tx_t *l_tx = dap_chain_net_srv_xchange_remove_compose(&l_tx_hash, l_fee, l_wallet, l_config); + dap_chain_wallet_close(l_wallet); + if (l_tx) { + dap_chain_net_tx_to_json(l_tx, l_config->response_handler); + DAP_DELETE(l_tx); + } + + return s_compose_config_return_response_handler(l_config); +} + +static bool s_process_ledger_response(dap_chain_tx_out_cond_subtype_t a_cond_type, + dap_chain_hash_fast_t *a_tx_hash, dap_chain_hash_fast_t *a_out_hash, compose_config_t *a_config) { + *a_out_hash = *a_tx_hash; + int l_prev_tx_count = 0; + dap_chain_hash_fast_t l_hash = {}; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_hash_fast_to_str_static(a_tx_hash), a_config->net_name); + + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + printf("Error: Failed to get response from remote node\n"); + return false; + } + + json_object *l_response_array = json_object_array_get_idx(response, 0); + if (!l_response_array) { + printf("Error: Can't get the first element from the response array\n"); + json_object_put(response); + return false; + } + + json_object *items = json_object_object_get(l_response_array, "ITEMS"); + if (!items) { + printf("Error: No items found in response\n"); + return false; + } + bool l_found = false; + int items_count = json_object_array_length(items); + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (dap_strcmp(item_type, "OUT COND") == 0) { + const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); + if (!dap_strcmp(subtype, dap_chain_tx_out_cond_subtype_to_str(a_cond_type))) { + dap_chain_hash_fast_from_str(json_object_get_string(json_object_object_get(item, "hash")), &l_hash); + l_prev_tx_count++; + l_found = true; + break; + } + } else if (dap_strcmp(item_type, "OUT") == 0 || dap_strcmp(item_type, "OUT COND") == 0 || dap_strcmp(item_type, "OUT OLD") == 0) { + l_prev_tx_count++; + } + } + if (!l_found) { + return false; + } + bool l_another_tx = false; + json_object *spent_outs = json_object_object_get(l_response_array, "Spent OUTs"); + if (spent_outs) { + int spent_outs_count = json_object_array_length(spent_outs); + for (int i = 0; i < spent_outs_count; i++) { + json_object *spent_out = json_object_array_get_idx(spent_outs, i); + int out_index = json_object_get_int(json_object_object_get(spent_out, "OUT - ")); + if (out_index == l_prev_tx_count) { + dap_chain_hash_fast_from_str(json_object_get_string(json_object_object_get(spent_out, "is spent by tx")), &l_hash); + l_another_tx = true; + break; + } + } + } + if (l_another_tx) { + *a_out_hash = l_hash; + return true; + } + return false; +} + +dap_chain_hash_fast_t dap_ledger_get_final_chain_tx_hash_compose(dap_chain_tx_out_cond_subtype_t a_cond_type, dap_chain_hash_fast_t *a_tx_hash, bool a_unspent_only, compose_config_t *a_config) +{ + dap_chain_hash_fast_t l_hash = { }; + if(!a_tx_hash || dap_hash_fast_is_blank(a_tx_hash)) + return l_hash; + l_hash = *a_tx_hash; + + while(s_process_ledger_response( a_cond_type, a_tx_hash, &l_hash, a_config)); + + return l_hash; +} + +dap_chain_net_srv_xchange_price_t *dap_chain_net_srv_xchange_price_from_order_compose(dap_chain_tx_out_cond_t *a_cond_tx, + dap_time_t a_ts_created, const char *a_token_ticker, dap_hash_fast_t *a_order_hash, + uint256_t *a_fee, bool a_ret_is_invalid, compose_config_t *a_config) +{ + if (!a_cond_tx || !a_token_ticker || !a_order_hash || !a_config) + return NULL; + dap_chain_net_srv_xchange_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_xchange_price_t); + if (!l_price) + return NULL; + l_price->creation_date = a_ts_created; + dap_strncpy(l_price->token_buy, a_cond_tx->subtype.srv_xchange.buy_token, sizeof(l_price->token_buy) - 1); + + l_price->order_hash = *a_order_hash; + strncpy(l_price->token_sell, a_token_ticker, sizeof(l_price->token_sell) - 1); + + if (a_fee) + l_price->fee = *a_fee; + + l_price->datoshi_sell = a_cond_tx->header.value; + l_price->creator_addr = a_cond_tx->subtype.srv_xchange.seller_addr; + l_price->rate = a_cond_tx->subtype.srv_xchange.rate; + dap_chain_hash_fast_t l_final_hash = dap_ledger_get_final_chain_tx_hash_compose(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE, &l_price->order_hash, false, a_config); + if ( !dap_hash_fast_is_blank(&l_final_hash) ) { + l_price->tx_hash = l_final_hash; + return l_price; + } else { + printf( "This order have no active conditional transaction"); + if (a_ret_is_invalid) { + dap_hash_fast_t l_tx_hash_zero = {0}; + l_price->tx_hash = l_tx_hash_zero; + return l_price; + } + } + + return NULL; +} + + +dap_chain_datum_tx_t* dap_xchange_tx_invalidate_compose( dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet, compose_config_t *a_config) +{ + if (!a_config) { + return NULL; + } + + if (!a_price) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_PARAMS, "An a_price NULL argument was passed to the s_xchange_tx_invalidate() function."); + return NULL; + } + if (!a_wallet) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_PARAMS, "An a_wallet NULL argument was passed to the s_xchange_tx_invalidate() function."); + return NULL; + } + const char *l_native_ticker = s_get_native_ticker(a_config->net_name); + + dap_chain_addr_t *l_wallet_addr = dap_chain_wallet_get_addr(a_wallet, s_get_net_id(a_config->net_name)); + dap_chain_addr_t l_seller_addr = *l_wallet_addr; + DAP_DELETE(l_wallet_addr); + + + dap_chain_tx_out_cond_t *l_cond_tx = NULL; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_hash_fast_to_str_static(&a_price->tx_hash), a_config->net_name); + + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_REMOTE_NODE_UNREACHABLE, "Failed to get response from remote node"); + return NULL; + } + + json_object *l_response_array = json_object_array_get_idx(response, 0); + if (!l_response_array) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_RESPONSE_FORMAT, "Can't get the first element from the response array"); + json_object_put(response); + return NULL; + } + + json_object *items = json_object_object_get(l_response_array, "ITEMS"); + if (!items) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_ITEMS_FOUND, "No items found in response"); + return NULL; + } + int l_prev_cond_idx = 0; + int items_count = json_object_array_length(items); + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (dap_strcmp(item_type, "OUT COND") == 0) { + const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); + if (!dap_strcmp(subtype, "DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE")) { + l_cond_tx = DAP_NEW_Z(dap_chain_tx_out_cond_t); + l_cond_tx->header.item_type = TX_ITEM_TYPE_OUT_COND; + l_cond_tx->header.value = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "value"))); + l_cond_tx->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE; + l_cond_tx->header.srv_uid.uint64 = strtoull(json_object_get_string(json_object_object_get(item, "uid")), NULL, 16); + l_cond_tx->header.ts_expires = dap_time_from_str_rfc822(json_object_get_string(json_object_object_get(item, "ts_expires"))); + strncpy(l_cond_tx->subtype.srv_xchange.buy_token, json_object_get_string(json_object_object_get(item, "buy_token")), sizeof(l_cond_tx->subtype.srv_xchange.buy_token) - 1); + l_cond_tx->subtype.srv_xchange.buy_token[sizeof(l_cond_tx->subtype.srv_xchange.buy_token) - 1] = '\0'; + l_cond_tx->subtype.srv_xchange.rate = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "rate"))); + l_cond_tx->tsd_size = json_object_get_int(json_object_object_get(item, "tsd_size")); + break; + } else if (dap_strcmp(subtype, "OUT") == 0 || dap_strcmp(subtype, "OUT COND") == 0 || dap_strcmp(subtype, "OUT OLD") == 0) { + l_prev_cond_idx++; + } + } + } + if (!l_cond_tx) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_COND_TX, "No transaction output condition found"); + return NULL; + } + + const char *l_tx_ticker = json_object_get_string(json_object_object_get(l_response_array, "Token_ticker")); + if (!l_tx_ticker) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_TOKEN_TICKER, "Token_ticker not found in response"); + return NULL; + } + + bool l_single_channel = !dap_strcmp(l_tx_ticker, l_native_ticker); + + json_object *spent_outs = json_object_object_get(l_response_array, "all OUTs yet unspent"); + const char *spent_outs_value = json_object_get_string(spent_outs); + if (spent_outs_value && !dap_strcmp(spent_outs_value, "yes")) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_ALREADY_USED, "Transaction output item already used"); + return NULL; + } + + if (!dap_chain_addr_compare(&l_seller_addr, &l_cond_tx->subtype.srv_xchange.seller_addr)) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NOT_OWNER, "Only owner can invalidate exchange transaction"); + return NULL; + } + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + // add 'in' item to buy from conditional transaction + dap_chain_datum_tx_add_in_cond_item(&l_tx, &a_price->tx_hash, l_prev_cond_idx, 0); + uint256_t l_net_fee = {}; + dap_chain_addr_t* l_addr_fee = NULL; + bool l_net_fee_used = dap_get_remote_net_fee_and_address(&l_net_fee, &l_addr_fee, a_config); + uint256_t l_total_fee = a_price->fee; + if (l_net_fee_used) + SUM_256_256(l_total_fee, l_net_fee, &l_total_fee); + + if (!l_single_channel) { + json_object *l_outs_native = dap_get_remote_tx_outs(l_native_ticker, &l_seller_addr, a_config); + if (!l_outs_native) { + return NULL; + } + int l_out_native_count = json_object_array_length(l_outs_native); + uint256_t l_transfer_fee = {}, l_fee_back = {}; + // list of transaction with 'out' items to get net fee + dap_list_t *l_list_fee_out = dap_ledger_get_list_tx_outs_from_json(l_outs_native, l_out_native_count, + l_total_fee, + &l_transfer_fee); + if (!l_list_fee_out) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INSUFFICIENT_FUNDS, "Not enough funds to pay fee"); + json_object_put(l_outs_native); + return NULL; + } + + + // add 'in' items to net fee + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_fee_out); + dap_list_free_full(l_list_fee_out, NULL); + if (!EQUAL_256(l_value_to_items, l_transfer_fee)) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_COMPOSE_FAILED, "Can't compose the transaction input"); + return NULL; + } + // return coins to owner + if (dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_seller_addr, l_cond_tx->header.value, l_tx_ticker) == -1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_COIN_RETURN_FAILED, "Cant add returning coins output"); + return NULL; + } + // Network fee + if (l_net_fee_used && + dap_chain_datum_tx_add_out_ext_item(&l_tx, l_addr_fee, l_net_fee, l_native_ticker) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NET_FEE_FAILED, "Cant add network fee output"); + return NULL; + } + // put fee coinback + SUBTRACT_256_256(l_transfer_fee, l_total_fee, &l_fee_back); + if (!IS_ZERO_256(l_fee_back) && + dap_chain_datum_tx_add_out_ext_item(&l_tx, &l_seller_addr, l_fee_back, l_native_ticker) == -1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_FEE_COINBACK_FAILED, "Cant add fee cachback output"); + return NULL; + } + } else { + uint256_t l_coin_back = {}; + if (compare256(l_total_fee, l_cond_tx->header.value) >= 0) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_FEE_TOO_HIGH, "Total fee is greater or equal than order liquidity"); + return NULL; + } + SUBTRACT_256_256(l_cond_tx->header.value, l_total_fee, &l_coin_back); + // return coins to owner + if (dap_chain_datum_tx_add_out_item(&l_tx, &l_seller_addr, l_coin_back) == -1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_COIN_RETURN_FAILED, "Cant add returning coins output"); + return NULL; + } + // Network fee + if (l_net_fee_used && + dap_chain_datum_tx_add_out_item(&l_tx, l_addr_fee, l_net_fee) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NET_FEE_FAILED, "Cant add network fee output"); + return NULL; + } + } + // Validator's fee + if (!IS_ZERO_256(a_price->fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, a_price->fee) == -1) { + dap_chain_datum_tx_delete(l_tx); + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_VALIDATOR_FEE_FAILED, "Cant add validator's fee output"); + return NULL; + } + } + // // add 'sign' items + // dap_enc_key_t *l_seller_key = dap_chain_wallet_get_key(a_wallet, 0); + // if(dap_chain_datum_tx_add_sign_item(&l_tx, l_seller_key) != 1) { + // dap_chain_datum_tx_delete(l_tx); + // dap_enc_key_delete(l_seller_key); + // log_it( L_ERROR, "Can't add sign output"); + // return false; + // } + // dap_enc_key_delete(l_seller_key); + // l_ret = s_xchange_tx_put(l_tx, a_price->net); + + return l_tx; +} + + +dap_chain_datum_tx_t* dap_chain_net_srv_xchange_remove_compose(dap_hash_fast_t *a_hash_tx, uint256_t a_fee, + dap_chain_wallet_t *a_wallet, compose_config_t *a_config) { + if (!a_hash_tx || !a_wallet || !a_config) { + return NULL; + } + if(IS_ZERO_256(a_fee)){ + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_FEE, "Fee must be greater than 0"); + return NULL; + } + + dap_time_t ts_created = 0; + + dap_chain_tx_out_cond_t *l_cond_tx = NULL; + char data[512]; + snprintf(data, sizeof(data), + "{\"method\": \"ledger\",\"params\": [\"ledger;info;-hash;%s;-net;%s\"],\"id\": \"1\"}", + dap_chain_hash_fast_to_str_static(a_hash_tx), a_config->net_name); + + json_object *response = dap_request_command_to_rpc(data, a_config); + if (!response) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_REMOTE_NODE_UNREACHABLE, "Failed to get response from remote node"); + return NULL; + } + + const char *items_str = json_object_get_string(response); + json_object *l_response_array = json_object_array_get_idx(response, 0); + if (!l_response_array) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_RESPONSE_FORMAT, "Can't get the first element from the response array"); + json_object_put(response); + return NULL; + } + json_object *items = json_object_object_get(l_response_array, "ITEMS"); + if (!items) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_ITEMS_FOUND, "No items found in response"); + return NULL; + } + int items_count = json_object_array_length(items); + for (int i = 0; i < items_count; i++) { + json_object *item = json_object_array_get_idx(items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (dap_strcmp(item_type, "OUT COND") == 0) { + const char *subtype = json_object_get_string(json_object_object_get(item, "subtype")); + if (!dap_strcmp(subtype, "DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE")) { + l_cond_tx = DAP_NEW_Z(dap_chain_tx_out_cond_t); + l_cond_tx->header.item_type = TX_ITEM_TYPE_OUT_COND; + l_cond_tx->header.value = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "value"))); + l_cond_tx->header.subtype = DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE; + l_cond_tx->header.srv_uid.uint64 = strtoull(json_object_get_string(json_object_object_get(item, "uid")), NULL, 16); + l_cond_tx->header.ts_expires = dap_time_from_str_rfc822(json_object_get_string(json_object_object_get(item, "ts_expires"))); + strncpy(l_cond_tx->subtype.srv_xchange.buy_token, json_object_get_string(json_object_object_get(item, "buy_token")), sizeof(l_cond_tx->subtype.srv_xchange.buy_token) - 1); + l_cond_tx->subtype.srv_xchange.buy_token[sizeof(l_cond_tx->subtype.srv_xchange.buy_token) - 1] = '\0'; + l_cond_tx->subtype.srv_xchange.rate = dap_chain_balance_scan(json_object_get_string(json_object_object_get(item, "rate"))); + l_cond_tx->tsd_size = json_object_get_int(json_object_object_get(item, "tsd_size")); + break; + } + } + } + if (!l_cond_tx) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_COND_TX, "No transaction output condition found"); + return NULL; + } + + const char *token_ticker = json_object_get_string(json_object_object_get(l_response_array, "Token_ticker")); + if (!token_ticker) { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_TOKEN_TICKER, "Token_ticker not found in response"); + return NULL; + } + const char *ts_created_str = json_object_get_string(json_object_object_get(l_response_array, "TS_Created")); + if (ts_created_str) { + ts_created = dap_time_from_str_rfc822(ts_created_str); + } else { + dap_json_compose_error_add(a_config->response_handler, SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_TIMESTAMP, "TS_Created not found in response"); + return NULL; + } + + dap_chain_net_srv_xchange_price_t *l_price = dap_chain_net_srv_xchange_price_from_order_compose(l_cond_tx, ts_created, token_ticker, a_hash_tx, &a_fee, false, a_config); + if (!l_price) { + return NULL; + } + dap_chain_datum_tx_t *l_tx = dap_xchange_tx_invalidate_compose(a_hash_tx, a_wallet, a_config); + json_object * l_json_obj_ret = json_object_new_object(); + dap_chain_net_tx_to_json(l_tx, l_json_obj_ret); + printf("%s", json_object_to_json_string(l_json_obj_ret)); + json_object_put(l_json_obj_ret); + + DAP_DELETE(l_price); + return l_tx; +} + + +// srv_xchange purchase -order <order hash> -net <net_name> -w <wallet_name> -value <value> -fee <value> +// int dap_tx_create_xchange_purchase_compose(int argc, char ** argv) { +// int arg_index = 1; +// const char * l_net_name = NULL; +// const char * l_wallet_name = NULL; +// const char * l_order_hash = NULL; +// const char * l_value = NULL; +// const char * l_fee = NULL; + +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-net", &l_net_name); +// if (!l_net_name) { +// printf("tx_create requires parameter '-net'"); +// return -1; +// } + +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-w", &l_wallet_name); +// if (!l_wallet_name) { +// printf("Error: Command 'purchase' requires parameter -w\n"); +// return -1; +// } +// dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_name, l_wallet_path, NULL); +// if (!l_wallet) { +// printf("Error: Specified wallet not found\n"); +// return -2; +// } +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-order", &l_order_hash); +// if (!l_order_hash) { +// printf("Error: Command 'purchase' requires parameter -order\n"); +// return -3; +// } +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-value", &l_value); +// if (!l_value) { +// printf("Error: Command 'purchase' requires parameter -value\n"); +// return -4; +// } +// uint256_t l_datoshi_buy = dap_chain_balance_scan(l_value); +// dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-fee", &l_fee); +// if (!l_fee) { +// printf("Error: Command 'purchase' requires parameter -fee\n"); +// return -5; +// } +// uint256_t l_datoshi_fee = dap_chain_balance_scan(l_fee); +// dap_hash_fast_t l_tx_hash = {}; +// dap_chain_hash_fast_from_str(l_order_hash, &l_tx_hash); +// char *l_str_ret_hash = NULL; +// int l_ret_code = dap_chain_net_srv_xchange_purchase_compose(l_net_name, &l_tx_hash, l_datoshi_buy, l_datoshi_fee, +// l_wallet, &l_str_ret_hash); +// switch (l_ret_code) { +// case XCHANGE_PURCHASE_ERROR_OK: { +// printf("Exchange transaction has done\n"); +// printf("hash: %s\n", l_str_ret_hash); +// DAP_DELETE(l_str_ret_hash); +// return 0; +// } +// case XCHANGE_PURCHASE_ERROR_SPECIFIED_ORDER_NOT_FOUND: { +// printf("Error: Specified order not found\n"); +// return -6; +// } +// case XCHANGE_PURCHASE_ERROR_CAN_NOT_CREATE_PRICE: { +// printf("Error: Can't create price from order\n"); +// return -7; +// } +// case XCHANGE_PURCHASE_ERROR_CAN_NOT_CREATE_EXCHANGE_TX: { +// printf("Error: Exchange transaction error\n"); +// return -8; +// } +// default: { +// printf("Error: An error occurred with an unknown code: %d.\n", l_ret_code); +// return -9; +// } +// } +// return 0; +// } + + +// dap_chain_net_srv_xchange_purchase_error_t dap_chain_net_srv_xchange_purchase_compose(const char *a_net_name, dap_hash_fast_t *a_order_hash, uint256_t a_value, +// uint256_t a_fee, dap_chain_wallet_t *a_wallet, char **a_hash_out){ +// if (!a_net_name || !a_order_hash || !a_wallet || !a_hash_out) { +// return XCHANGE_PURCHASE_ERROR_INVALID_ARGUMENT; +// } +// dap_chain_datum_tx_t *l_cond_tx = dap_ledger_tx_find_by_hash(a_net->pub.ledger, a_order_hash); +// if (l_cond_tx) { +// dap_chain_net_srv_xchange_price_t *l_price = s_xchange_price_from_order(a_net, l_cond_tx, a_order_hash, &a_fee, false); +// if(!l_price){ +// return XCHANGE_PURCHASE_ERROR_CAN_NOT_CREATE_PRICE; +// } +// // Create conditional transaction +// char *l_ret = NULL; +// dap_chain_datum_tx_t *l_tx = s_xchange_tx_create_exchange(l_price, a_wallet, a_value, a_fee); +// if (l_tx ) { +// l_ret = s_xchange_tx_put(l_tx, a_net); +// } +// DAP_DELETE(l_price); +// if (l_tx && l_ret){ +// *a_hash_out = l_ret; +// return XCHANGE_PURCHASE_ERROR_OK; +// } else +// return XCHANGE_PURCHASE_ERROR_CAN_NOT_CREATE_EXCHANGE_TX; +// } else { +// return XCHANGE_PURCHASE_ERROR_SPECIFIED_ORDER_NOT_FOUND; +// } +// } + + diff --git a/modules/compose/include/dap_chain_tx_compose.h b/modules/compose/include/dap_chain_tx_compose.h new file mode 100644 index 0000000000000000000000000000000000000000..fcca731a130bab8fea457c9e31f1b95227cc86cf --- /dev/null +++ b/modules/compose/include/dap_chain_tx_compose.h @@ -0,0 +1,263 @@ +/* + * Authors: + * Roman Padenkov <roman.padenkov@demlabs.net> + * Olzhas Zharasbaev <oljas.jarasbaev@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2025-2026 + * All rights reserved. + + This file is part of DAP (Distributed Applications Platform) the open source project + + DAP (Distributed Applications Platform) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ + + #pragma once + #include "dap_chain_common.h" + #include "dap_list.h" + #include "dap_math_ops.h" + #include "dap_chain_datum_tx.h" + #include "dap_chain_wallet.h" + #include "dap_chain_net_srv_xchange.h" + + #include <json-c/json.h> + +#define NET_COUNT 6 + +typedef struct { + char name[20]; + char native_ticker[DAP_CHAIN_TICKER_SIZE_MAX]; + dap_chain_net_id_t net_id; + char url[128]; + uint16_t port; +} NetInfo; + +typedef struct { + const char *net_name; + const char *url_str; + uint16_t port; + json_object *response_handler; +} compose_config_t; + +static NetInfo netinfo[NET_COUNT] = { + {"riemann", "tKEL", {.uint64 = 0x000000000000dddd}, "45.76.140.191", 8081}, + {"raiden", "tCELL", {.uint64 = 0x000000000000bbbb}, "http://rpc.cellframe.net", 8081}, + {"KelVPN", "KEL", {.uint64 = 0x1807202300000000}, "http://rpc.cellframe.net", 8081}, + {"Backbone", "CELL", {.uint64 = 0x0404202200000000}, "http://rpc.cellframe.net", 8081}, + {"mileena", "tMIL", {.uint64 = 0x000000000000cccc}, "http://rpc.cellframe.net", 8081}, + {"subzero", "tCELL", {.uint64 = 0x000000000000acca}, "http://rpc.cellframe.net", 8081} +}; + + + + +#ifdef __cplusplus +extern "C" { +#endif +const char* dap_compose_get_net_url(const char* name); +uint16_t dap_compose_get_net_port(const char* name); + +json_object* dap_tx_create_compose(const char *l_net_str, const char *l_token_ticker, const char *l_value_str, const char *l_fee_str, const char *addr_base58_to, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port); +json_object* dap_tx_create_xchange_compose(const char *l_net_str, const char *l_token_sell, const char *l_token_buy, const char *l_wallet_str, const char *l_wallet_path, const char *l_value_str, const char *l_rate_str, const char *l_fee_str, const char *l_url_str, uint16_t l_port); +int dap_tx_cond_create_compose(int argc, char ** argv); +int dap_cli_hold_compose(int a_argc, char **a_argv); +int dap_cli_take_compose(int a_argc, char **a_argv); +int dap_cli_voting_compose(int a_argc, char **a_argv); +typedef enum { + STAKE_ORDER_CREATE_STAKER_OK = 0, + STAKE_ORDER_CREATE_STAKER_ERR_INVALID_PARAMS = -1, + STAKE_ORDER_CREATE_STAKER_ERR_INVALID_VALUE = -2, + STAKE_ORDER_CREATE_STAKER_ERR_INVALID_FEE = -3, + STAKE_ORDER_CREATE_STAKER_ERR_INVALID_TAX = -4, + STAKE_ORDER_CREATE_STAKER_ERR_WALLET_NOT_FOUND = -5, + STAKE_ORDER_CREATE_STAKER_ERR_KEY_NOT_FOUND = -6, + STAKE_ORDER_CREATE_STAKER_ERR_INVALID_ADDR = -7, + STAKE_ORDER_CREATE_STAKER_ERR_TX_CREATE_FAILED = -8, + STAKE_ORDER_CREATE_STAKER_ERR_JSON_FAILED = -9 +} dap_cli_srv_stake_order_create_staker_error_t; +json_object* dap_cli_srv_stake_order_create_staker_compose(const char *l_net_str, const char *l_value_str, const char *l_fee_str, const char *l_tax_str, const char *l_addr_str, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port); + +typedef enum { + SRV_STAKE_ORDER_REMOVE_COMPOSE_OK = 0, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_PARAMS, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_WALLET_NOT_FOUND, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_KEY_NOT_FOUND, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_FEE, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_ORDER_HASH, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_RPC_RESPONSE, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_ADDR, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_TAX, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_COND_TX, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_TOKEN_TICKER, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_TS_CREATED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_PRICE, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_TX, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NOT_ENOUGH_FUNDS, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_COMPOSE, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_SIGN, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_REMOTE_NODE_UNREACHABLE, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INVALID_RESPONSE_FORMAT, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_ITEMS_FOUND, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_COND_TX, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_TOKEN_TICKER, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NO_TIMESTAMP, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_INSUFFICIENT_FUNDS, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_COMPOSE_FAILED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_COIN_RETURN_FAILED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NET_FEE_FAILED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_FEE_COINBACK_FAILED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_FEE_TOO_HIGH, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_VALIDATOR_FEE_FAILED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_TX_ALREADY_USED, + SRV_STAKE_ORDER_REMOVE_COMPOSE_ERR_NOT_OWNER +} srv_stake_order_remove_compose_error_t; +dap_chain_datum_tx_t* dap_chain_net_srv_xchange_remove_compose(dap_hash_fast_t *a_hash_tx, uint256_t a_fee, + dap_chain_wallet_t *a_wallet, compose_config_t *a_config); +json_object* dap_cli_srv_stake_order_remove_compose(const char *l_net_str, const char *l_order_hash_str, const char *l_fee_str, const char *l_wallet_str, const char *l_wallet_path, const char *l_url_str, uint16_t l_port); + +typedef enum { + STAKE_DELEGATE_COMPOSE_OK = 0, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_VALUE = -1, + STAKE_DELEGATE_COMPOSE_ERR_WALLET_NOT_FOUND = -2, + STAKE_DELEGATE_COMPOSE_ERR_CERT_NOT_FOUND = -3, + STAKE_DELEGATE_COMPOSE_ERR_CERT_WRONG = -4, + STAKE_DELEGATE_COMPOSE_ERR_WRONG_SIGN_TYPE = -5, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_PKEY = -6, + STAKE_DELEGATE_COMPOSE_ERR_PKEY_UNDEFINED = -7, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_NODE_ADDR = -8, + STAKE_DELEGATE_COMPOSE_ERR_ORDER_NOT_FOUND = -9, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_ORDER_SIZE = -10, + STAKE_DELEGATE_COMPOSE_ERR_CERT_REQUIRED = -11, + STAKE_DELEGATE_COMPOSE_ERR_VALUE_REQUIRED = -12, + STAKE_DELEGATE_COMPOSE_ERR_WRONG_TICKER = -13, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_FORMAT = -14, + STAKE_DELEGATE_COMPOSE_ERR_RPC_RESPONSE = -15, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_VALUE = -16, + STAKE_DELEGATE_COMPOSE_ERR_NO_ITEMS = -17, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_COND_TX_ADDR = -18, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_SIGNER_ADDR = -19, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_SOVEREIGN_ADDR = -20, + STAKE_DELEGATE_COMPOSE_ERR_NO_TOKEN_TICKER = -21, + STAKE_DELEGATE_COMPOSE_ERR_VALUE_TOO_LOW = -22, + STAKE_DELEGATE_COMPOSE_ERR_VALUE_TOO_HIGH = -23, + STAKE_DELEGATE_COMPOSE_ERR_UNSIGNED_ORDER = -24, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_ORDER = -25, + STAKE_DELEGATE_COMPOSE_ERR_INVALID_TAX = -26, + STAKE_DELEGATE_COMPOSE_ERR_VALUE_BELOW_MIN = -27, + DAP_STAKE_TX_CREATE_COMPOSE_INVALID_PARAMS = -28, + DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_FEE = -29, + DAP_STAKE_TX_CREATE_COMPOSE_NOT_ENOUGH_FUNDS_VALUE = -30, + DAP_STAKE_TX_CREATE_COMPOSE_TX_IN_ERROR = -31, + DAP_STAKE_TX_CREATE_COMPOSE_TX_COND_OUT_ERROR = -32, + DAP_STAKE_TX_CREATE_COMPOSE_TX_OUT_ERROR = -33, + DAP_STAKE_TX_CREATE_COMPOSE_NET_FEE_ERROR = -34, + DAP_STAKE_TX_CREATE_COMPOSE_VALIDATOR_FEE_ERROR = -35, + DAP_STAKE_TX_CREATE_COMPOSE_FEE_BACK_ERROR = -36 +} stake_delegate_error_t; +json_object* dap_cli_srv_stake_delegate_compose(const char* a_net_str, const char* a_wallet_str, const char* a_cert_str, + const char* a_pkey_full_str, const char* a_sign_type_str, const char* a_value_str, const char* a_node_addr_str, + const char* a_order_hash_str, const char* a_url_str, uint16_t a_port, const char* a_sovereign_addr_str, const char* a_fee_str, const char* a_wallets_path); +typedef enum { + DAP_CLI_STAKE_INVALIDATE_OK = 0, + DAP_CLI_STAKE_INVALIDATE_CERT_NOT_FOUND = -1, + DAP_CLI_STAKE_INVALIDATE_PRIVATE_KEY_MISSING = -2, + DAP_CLI_STAKE_INVALIDATE_WRONG_CERT = -3, + DAP_CLI_STAKE_INVALIDATE_LEDGER_ERROR = -4, + DAP_CLI_STAKE_INVALIDATE_INVALID_TX_HASH = -5, + DAP_CLI_STAKE_INVALIDATE_NOT_DELEGATED = -6, + DAP_CLI_STAKE_INVALIDATE_NO_DELEGATE_OUT = -7, + DAP_CLI_STAKE_INVALIDATE_PREV_TX_NOT_FOUND = -8, + DAP_CLI_STAKE_INVALIDATE_TX_EXISTS = -9, + DAP_CLI_STAKE_INVALIDATE_WALLET_NOT_FOUND = -10, + DAP_CLI_STAKE_INVALIDATE_COMPOSE_ERROR = -11, + DAP_STAKE_TX_INVALIDATE_COMPOSE_LEDGER_ERROR = -12, + DAP_STAKE_TX_INVALIDATE_COMPOSE_ITEMS_NOT_FOUND = -13, + DAP_STAKE_TX_INVALIDATE_COMPOSE_OUTPUTS_SPENT = -14, + DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_HASH_NOT_FOUND = -15, + DAP_STAKE_TX_INVALIDATE_COMPOSE_COND_TX_ERROR = -16, + DAP_STAKE_TX_INVALIDATE_COMPOSE_COND_TX_NOT_FOUND = -17, + DAP_STAKE_TX_INVALIDATE_COMPOSE_SIG_NOT_FOUND = -18, + DAP_STAKE_TX_INVALIDATE_COMPOSE_SIG_DECODE_ERROR = -19, + DAP_STAKE_TX_INVALIDATE_COMPOSE_WRONG_OWNER = -20, + DAP_STAKE_TX_INVALIDATE_COMPOSE_TOKEN_NOT_FOUND = -21, + DAP_STAKE_TX_INVALIDATE_COMPOSE_OUTS_NOT_FOUND = -22, + DAP_STAKE_TX_INVALIDATE_COMPOSE_NOT_ENOUGH_FUNDS = -23, + DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_IN_ERROR = -24, + DAP_STAKE_TX_INVALIDATE_COMPOSE_TX_OUT_ERROR = -25, + DAP_STAKE_TX_INVALIDATE_COMPOSE_NET_FEE_ERROR = -26, + DAP_STAKE_TX_INVALIDATE_COMPOSE_FEE_ERROR = -27, + DAP_STAKE_TX_INVALIDATE_COMPOSE_FEE_BACK_ERROR = -28, + DAP_CLI_STAKE_INVALIDATE_FEE_ERROR = -29 +} dap_cli_stake_invalidate_error_t; +dap_chain_datum_tx_t *dap_stake_tx_invalidate_compose(dap_hash_fast_t *a_tx_hash, uint256_t a_fee, dap_enc_key_t *a_key, compose_config_t *a_config); +json_object* dap_cli_srv_stake_invalidate_compose(const char *a_net_str, const char *a_tx_hash_str, const char *a_wallet_str, + const char *a_wallet_path, const char *a_cert_str, const char *a_fee_str, const char *a_url_str, uint16_t a_port); + +typedef enum { + DAP_COMPOSE_ERROR_NONE = 0, + DAP_COMPOSE_ERROR_RESPONSE_NULL = -1, + DAP_COMPOSE_ERROR_RESULT_NOT_FOUND = -2, + DAP_COMPOSE_ERROR_REQUEST_INIT_FAILED = -3, + DAP_COMPOSE_ERROR_REQUEST_TIMEOUT = -4, + DAP_COMPOSE_ERROR_REQUEST_FAILED = -5 +} dap_compose_error_t; +json_object* dap_request_command_to_rpc(const char *request, compose_config_t *a_config); +int dap_tx_json_tsd_add(json_object * json_tx, json_object * json_add); + +dap_list_t *dap_ledger_get_list_tx_outs_from_json(json_object * a_outputs_array, int a_outputs_count, uint256_t a_value_need, uint256_t *a_value_transfer); +dap_chain_datum_tx_t *dap_chain_datum_tx_create_compose(dap_chain_addr_t* a_addr_from, dap_chain_addr_t** a_addr_to, + const char* a_token_ticker, uint256_t *a_value, uint256_t a_value_fee, size_t a_tx_num, compose_config_t *a_config); +dap_chain_datum_tx_t* dap_chain_net_srv_xchange_create_compose(const char *a_token_buy, const char *a_token_sell, uint256_t a_datoshi_sell, + uint256_t a_rate, uint256_t a_fee, dap_chain_wallet_t *a_wallet, compose_config_t *a_config); +dap_chain_datum_tx_t *dap_xchange_tx_create_request_compose(dap_chain_net_srv_xchange_price_t *a_price, dap_chain_wallet_t *a_wallet, + const char *a_native_ticker, compose_config_t *a_config); +dap_chain_datum_tx_t *dap_chain_mempool_tx_create_cond_compose(const char *a_net_name, + dap_enc_key_t *a_key_from, dap_pkey_t *a_key_cond, + const char a_token_ticker[DAP_CHAIN_TICKER_SIZE_MAX], + uint256_t a_value, uint256_t a_value_per_unit_max, + dap_chain_net_srv_price_unit_uid_t a_unit, dap_chain_net_srv_uid_t a_srv_uid, + uint256_t a_value_fee, const void *a_cond, + size_t a_cond_size, const char *a_url_str, uint16_t a_port); +bool dap_get_remote_net_fee_and_address(uint256_t *a_net_fee, dap_chain_addr_t **l_addr_fee, compose_config_t *a_config); +bool dap_get_remote_wallet_outs_and_count(dap_chain_addr_t *a_addr_from, const char *a_token_ticker, + json_object **l_outs, int *l_outputs_count, compose_config_t *a_config); +dap_chain_datum_tx_t * dap_stake_lock_datum_create_compose(const char *a_net_name, dap_enc_key_t *a_key_from, + const char *a_main_ticker, + uint256_t a_value, uint256_t a_value_fee, + dap_time_t a_time_staking, uint256_t a_reinvest_percent, + const char *a_delegated_ticker_str, uint256_t a_delegated_value, + const char * l_chain_id_str, const char *l_url_str, uint16_t l_port); +bool check_token_in_ledger(json_object *l_json_coins, const char *a_token); +dap_chain_datum_tx_t *dap_stake_unlock_datum_create_compose(const char *a_net_name, dap_enc_key_t *a_key_from, + dap_hash_fast_t *a_stake_tx_hash, uint32_t a_prev_cond_idx, + const char *a_main_ticker, uint256_t a_value, + uint256_t a_value_fee, + const char *a_delegated_ticker_str, uint256_t a_delegated_value, + const char *l_url_str, uint16_t l_port); +dap_chain_datum_tx_t* dap_chain_net_vote_create_compose(const char *a_question, dap_list_t *a_options, dap_time_t a_expire_vote, + uint64_t a_max_vote, uint256_t a_fee, bool a_delegated_key_required, + bool a_vote_changing_allowed, dap_chain_wallet_t *a_wallet, + const char *a_net_str, const char *a_token_ticker, const char *l_url_str, uint16_t l_port); +dap_chain_datum_tx_t *dap_stake_tx_create_compose(dap_enc_key_t *a_key, + uint256_t a_value, uint256_t a_fee, + dap_chain_addr_t *a_signing_addr, dap_chain_node_addr_t *a_node_addr, + dap_chain_addr_t *a_sovereign_addr, uint256_t a_sovereign_tax, + dap_chain_datum_tx_t *a_prev_tx, dap_pkey_t *a_pkey, compose_config_t *a_config); + + + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/modules/net/dap_chain_net_tx.c b/modules/net/dap_chain_net_tx.c index cdf7a3d9b8d0ec1d0e0fc5fc7891bc1d23ae8f6b..04ae89062d966d3372f03b4843c008f3eca8d182 100644 --- a/modules/net/dap_chain_net_tx.c +++ b/modules/net/dap_chain_net_tx.c @@ -1563,9 +1563,13 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json json_object_object_add(json_obj_item, "sig_b64", json_object_new_string(l_sign_b64)); } break; case TX_ITEM_TYPE_TSD: { - json_object_object_add(json_obj_item,"type", json_object_new_string("data")); - json_object_object_add(json_obj_item,"type", json_object_new_uint64(((dap_chain_tx_tsd_t*)item)->header.type)); - json_object_object_add(json_obj_item,"size", json_object_new_uint64(((dap_chain_tx_tsd_t*)item)->header.size)); + json_object_object_add(json_obj_item,"type", json_object_new_string("TX_ITEM_TYPE_TSD")); + json_object_object_add(json_obj_item,"type_tsd", json_object_new_string("data")); + json_object_object_add(json_obj_item,"size", json_object_new_uint64(((dap_chain_tx_tsd_t*)item)->header.size)); + char *l_tsd_str = DAP_NEW_Z_SIZE(char, ((dap_chain_tx_tsd_t*)item)->header.size + 1); + memcpy(l_tsd_str, ((dap_chain_tx_tsd_t*)item)->tsd, ((dap_chain_tx_tsd_t*)item)->header.size); + json_object_object_add(json_obj_item, "data", json_object_new_string(l_tsd_str)); + DAP_DEL_Z(l_tsd_str); } break; case TX_ITEM_TYPE_IN_COND: json_object_object_add(json_obj_item,"type", json_object_new_string("in_cond")); @@ -1580,7 +1584,8 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json json_object_object_add(json_obj_item,"type", json_object_new_string("out_cond")); const char *l_coins_str, *l_value_str = dap_uint256_to_char(((dap_chain_tx_out_cond_t*)item)->header.value, &l_coins_str); dap_time_t l_ts_exp = ((dap_chain_tx_out_cond_t*)item)->header.ts_expires; - dap_time_to_str_rfc822(l_tmp_buf, DAP_TIME_STR_SIZE, l_ts_exp); + if (l_ts_exp > 0) + dap_time_to_str_rfc822(l_tmp_buf, DAP_TIME_STR_SIZE, l_ts_exp); json_object_object_add(json_obj_item,"ts_expires", l_ts_exp ? json_object_new_string(l_tmp_buf) : json_object_new_string("never")); json_object_object_add(json_obj_item,"value", json_object_new_string(l_value_str)); sprintf(l_tmp_buff,"0x%016"DAP_UINT64_FORMAT_x"",((dap_chain_tx_out_cond_t*)item)->header.srv_uid.uint64); @@ -1614,15 +1619,15 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json const char *l_rate_str, *l_tmp_str = dap_uint256_to_char( (((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.rate), &l_rate_str ); sprintf(l_tmp_buff,"0x%016"DAP_UINT64_FORMAT_x"",((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_net_id.uint64); - json_object_object_add(json_obj_item,"net_id", json_object_new_string(l_tmp_buff)); + json_object_object_add(json_obj_item,"net", json_object_new_string(l_tmp_buff)); json_object_object_add(json_obj_item,"token", json_object_new_string(((dap_chain_tx_out_cond_t*)item)->subtype.srv_xchange.buy_token)); json_object_object_add(json_obj_item,"rate", json_object_new_string(l_rate_str)); json_object_object_add(json_obj_item,"subtype", json_object_new_string("srv_xchange")); } break; case DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK: { dap_time_t l_ts_unlock = ((dap_chain_tx_out_cond_t*)item)->subtype.srv_stake_lock.time_unlock; - dap_time_to_str_rfc822(l_tmp_buf, DAP_TIME_STR_SIZE, l_ts_unlock); - json_object_object_add(json_obj_item,"time_unlock", json_object_new_string(l_tmp_buf)); + const char * l_date_str = dap_time_to_str_simplified(l_ts_unlock); + json_object_object_add(json_obj_item,"time_staking", json_object_new_string(l_date_str)); json_object_object_add(json_obj_item,"subtype", json_object_new_string("srv_stake_lock")); } break; default: break; @@ -1630,11 +1635,18 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json } break; case TX_ITEM_TYPE_OUT_EXT: { const char *l_coins_str, *l_value_str = dap_uint256_to_char( ((dap_chain_tx_out_ext_t*)item)->header.value, &l_coins_str ); - json_object_object_add(json_obj_item,"type", json_object_new_string("out_ext")); + json_object_object_add(json_obj_item,"type", json_object_new_string("out")); json_object_object_add(json_obj_item,"addr", json_object_new_string(dap_chain_addr_to_str_static(&((dap_chain_tx_out_ext_t*)item)->addr))); json_object_object_add(json_obj_item,"token", json_object_new_string(((dap_chain_tx_out_ext_t*)item)->token)); json_object_object_add(json_obj_item,"value", json_object_new_string(l_value_str)); + } break; + case TX_ITEM_TYPE_IN_EMS: { + json_object_object_add(json_obj_item,"type", json_object_new_string("in_ems")); + json_object_object_add(json_obj_item,"chain_id", json_object_new_uint64(((dap_chain_tx_in_ems_t*)item)->header.token_emission_chain_id.uint64)); + json_object_object_add(json_obj_item,"token", json_object_new_string(((dap_chain_tx_in_ems_t*)item)->header.ticker)); + json_object_object_add(json_obj_item,"token_ems_hash", json_object_new_string( dap_hash_fast_to_str_static(&((dap_chain_tx_in_ems_t*)item)->header.token_emission_hash))); + } break; case TX_ITEM_TYPE_OUT_STD: { @@ -1657,15 +1669,16 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json dap_chain_datum_tx_voting_params_t *l_voting_params = dap_chain_voting_parse_tsd(a_tx); json_object_object_add(json_obj_item,"type", json_object_new_string("voting")); json_object_object_add(json_obj_item,"voting_question", json_object_new_string(l_voting_params->voting_question)); - json_object_object_add(json_obj_item,"answer_options", json_object_new_string("")); + json_object *l_json_array = json_object_new_array(); json_object_object_add(json_obj_item, "token", json_object_new_string(l_voting_params->token_ticker)); dap_list_t *l_temp = l_voting_params->answers_list; uint8_t l_index = 0; while (l_temp) { - json_object_object_add(json_obj_item, dap_itoa(l_index), json_object_new_string((char *)l_temp->data)); + json_object_array_add(l_json_array, json_object_new_string((char *)l_temp->data)); l_index++; l_temp = l_temp->next; } + json_object_object_add(json_obj_item, "answer_options", l_json_array); if (l_voting_params->voting_expire) { dap_time_to_str_rfc822(l_tmp_buf, DAP_TIME_STR_SIZE, l_voting_params->voting_expire); json_object_object_add(json_obj_item, "Voting expire", json_object_new_string(l_tmp_buf)); diff --git a/modules/net/dap_chain_node_cli_cmd_tx.c b/modules/net/dap_chain_node_cli_cmd_tx.c index e1b3e7281943bd554229ab281035a44378cc9022..60ae967985062b2769079208397a272e61ba58ff 100644 --- a/modules/net/dap_chain_node_cli_cmd_tx.c +++ b/modules/net/dap_chain_node_cli_cmd_tx.c @@ -46,6 +46,7 @@ #include "dap_chain_mempool.h" #include "dap_math_convert.h" #include "dap_json_rpc_errors.h" +#include "dap_enc_base64.h" #include "dap_chain_wallet_cache.h" @@ -1118,11 +1119,9 @@ int com_ledger(int a_argc, char ** a_argv, void **reply) } return 0; } else if (l_cmd == CMD_TX_INFO){ - //GET hash dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_tx_hash_str); - //get net dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str); - //get search type + bool l_need_sign = dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-need_sign", NULL); bool l_unspent_flag = dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-unspent", NULL); //check input if (l_tx_hash_str == NULL){ @@ -1158,6 +1157,30 @@ int com_ledger(int a_argc, char ** a_argv, void **reply) DAP_DEL_Z(l_tx_hash); return DAP_CHAIN_NODE_CLI_COM_LEDGER_TX_HASH_ERR; } + if (l_need_sign) { + byte_t *item; size_t l_size; + TX_ITEM_ITER_TX(item, l_size, l_datum_tx) { + if (*item == TX_ITEM_TYPE_SIG) { + dap_sign_t *l_sign = dap_chain_datum_tx_item_sign_get_sig((dap_chain_tx_sig_t*)item); + char *l_sign_b64 = DAP_NEW_Z_SIZE(char, DAP_ENC_BASE64_ENCODE_SIZE(dap_sign_get_size(l_sign)) + 1); + size_t l_sign_size = dap_sign_get_size(l_sign); + dap_enc_base64_encode(l_sign, l_sign_size, l_sign_b64, DAP_ENC_DATA_TYPE_B64_URLSAFE); + + json_object *json_items = json_object_object_get(json_datum, "ITEMS"); + if (json_items && json_object_is_type(json_items, json_type_array)) { + int array_len = json_object_array_length(json_items); + for (int i = 0; i < array_len; i++) { + json_object *item = json_object_array_get_idx(json_items, i); + const char *item_type = json_object_get_string(json_object_object_get(item, "item type")); + if (item_type && strcmp(item_type, "SIG") == 0) { + json_object_object_add(item, "sig_b64", json_object_new_string(l_sign_b64)); + json_object_object_add(item, "sig_b64_size", json_object_new_uint64(l_sign_size)); + } + } + } + } + } + } DAP_DEL_Z(l_tx_hash); if (json_datum){ json_object_array_add(*a_json_arr_reply, json_datum); diff --git a/modules/net/include/dap_chain_net_tx.h b/modules/net/include/dap_chain_net_tx.h index 0447fcb16541ddf53674e87ed6a2732131353b46..fe57215d189434b1fb5a3fc03f058de48828780b 100644 --- a/modules/net/include/dap_chain_net_tx.h +++ b/modules/net/include/dap_chain_net_tx.h @@ -98,10 +98,6 @@ dap_list_t * dap_chain_net_get_tx_cond_all_by_srv_uid(dap_chain_net_t * a_net, c dap_list_t * dap_chain_net_get_tx_cond_all_for_addr(dap_chain_net_t * a_net, dap_chain_addr_t * a_addr, dap_chain_net_srv_uid_t a_srv_uid); dap_list_t * dap_chain_net_get_tx_all_from_tx(dap_chain_net_t * a_net, dap_hash_fast_t * l_tx_hash); - - - - dap_chain_datum_tx_spends_items_t * dap_chain_net_get_tx_cond_all_with_spends_by_srv_uid(dap_chain_net_t * a_net, const dap_chain_net_srv_uid_t a_srv_uid, const dap_time_t a_time_from, const dap_time_t a_time_to, const dap_chain_net_tx_search_type_t a_search_type); @@ -137,4 +133,4 @@ int dap_chain_net_tx_to_json(dap_chain_datum_tx_t *a_tx, json_object *a_out_json #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/modules/net/srv/CMakeLists.txt b/modules/net/srv/CMakeLists.txt index d48b0f9a3b35480ed37d74d3c8509567ca7876e1..818863316ac7a055839b56e634f28bd657408df3 100644 --- a/modules/net/srv/CMakeLists.txt +++ b/modules/net/srv/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13) project (dap_chain_net_srv) file(GLOB DAP_CHAIN_NET_SRV_SRCS *.c) -file(GLOB DAP_CHAIN_NET_SRV_HEADERS *.h) +file(GLOB DAP_CHAIN_NET_SRV_HEADERS include/*.h) add_library(${PROJECT_NAME} STATIC ${DAP_CHAIN_NET_SRV_SRCS} ${DAP_CHAIN_NET_SRV_HEADERS}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/libmaxminddb ${CMAKE_CURRENT_BINARY_DIR}/../../../3rdparty/libmaxminddb) diff --git a/modules/net/srv/dap_chain_net_srv.c b/modules/net/srv/dap_chain_net_srv.c index f0a757bc49de3fce35bb0f253cdcb47a4a250348..c2ae7b50ce2d340d68fe136a3ea56f733a1222b1 100644 --- a/modules/net/srv/dap_chain_net_srv.c +++ b/modules/net/srv/dap_chain_net_srv.c @@ -404,7 +404,7 @@ static int s_cli_net_srv( int argc, char **argv, void **a_str_reply) for (dap_list_t *l_temp = l_orders; l_temp; l_temp = l_temp->next){ json_object* json_obj_order = json_object_new_object(); dap_chain_net_srv_order_t *l_order = (dap_chain_net_srv_order_t*)l_temp->data; - dap_chain_net_srv_order_dump_to_json(l_order, json_obj_order, l_hash_out_type, l_net->pub.native_ticker); + dap_chain_net_srv_order_dump_to_json(l_order, json_obj_order, l_hash_out_type, l_net->pub.native_ticker, false); json_object_array_add(json_arr_out, json_obj_order); } json_object_object_add(json_obj_net_srv, "orders", json_arr_out); @@ -416,11 +416,12 @@ static int s_cli_net_srv( int argc, char **argv, void **a_str_reply) } } else if(!dap_strcmp( l_order_str, "dump" )) { // Select with specified service uid + bool l_need_sign = dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-need_sign", NULL); if ( l_order_hash_str ){ dap_chain_net_srv_order_t * l_order = dap_chain_net_srv_order_find_by_hash_str( l_net, l_order_hash_hex_str ); json_obj_net_srv = json_object_new_object(); if (l_order) { - dap_chain_net_srv_order_dump_to_json(l_order, json_obj_net_srv, l_hash_out_type, l_net->pub.native_ticker); + dap_chain_net_srv_order_dump_to_json(l_order, json_obj_net_srv, l_hash_out_type, l_net->pub.native_ticker, l_need_sign); l_ret = 0; }else{ if(!dap_strcmp(l_hash_out_type,"hex")) @@ -447,7 +448,7 @@ static int s_cli_net_srv( int argc, char **argv, void **a_str_reply) for(dap_list_t *l_temp = l_orders;l_temp; l_temp = l_orders->next) { json_object* json_obj_order = json_object_new_object(); dap_chain_net_srv_order_t *l_order =(dap_chain_net_srv_order_t *) l_temp->data; - dap_chain_net_srv_order_dump_to_json(l_order, json_obj_order, l_hash_out_type, l_net->pub.native_ticker); + dap_chain_net_srv_order_dump_to_json(l_order, json_obj_order, l_hash_out_type, l_net->pub.native_ticker, l_need_sign); json_object_array_add(json_arr_out, json_obj_order); } json_object_object_add(json_obj_net_srv, "orders", json_arr_out); diff --git a/modules/net/srv/dap_chain_net_srv_order.c b/modules/net/srv/dap_chain_net_srv_order.c index ff01b58e6949e759a14e5eaa96763f0760d5bb26..14962f2300885187bd5977a5134ab3b5050ee6ad 100644 --- a/modules/net/srv/dap_chain_net_srv_order.c +++ b/modules/net/srv/dap_chain_net_srv_order.c @@ -28,6 +28,7 @@ #include "dap_chain_net_srv_order.h" #include "dap_hash.h" #include "dap_enc_base58.h" +#include "dap_enc_base64.h" #include "dap_global_db.h" #include "dap_chain_net_srv_countries.h" #include "dap_chain_net_srv_stake_pos_delegate.h" @@ -592,7 +593,7 @@ void dap_chain_net_srv_order_dump_to_string(const dap_chain_net_srv_order_t *a_o * @param a_str_out */ void dap_chain_net_srv_order_dump_to_json(const dap_chain_net_srv_order_t *a_order, json_object *a_json_obj_out, - const char *a_hash_out_type, const char *a_native_ticker) + const char *a_hash_out_type, const char *a_native_ticker, bool a_need_sign) { if (a_order && a_json_obj_out ){ dap_chain_hash_fast_t l_hash; @@ -663,6 +664,14 @@ void dap_chain_net_srv_order_dump_to_json(const dap_chain_net_srv_order_t *a_ord // DAP_DELETE(l_ext_out); // } dap_sign_t *l_sign = (dap_sign_t*)((byte_t*)a_order->ext_n_sign + a_order->ext_size); + if (a_need_sign) { + char *l_sign_b64 = DAP_NEW_Z_SIZE(char, DAP_ENC_BASE64_ENCODE_SIZE(dap_sign_get_size(l_sign)) + 1); + size_t l_sign_size = dap_sign_get_size(l_sign); + dap_enc_base64_encode(l_sign, l_sign_size, l_sign_b64, DAP_ENC_DATA_TYPE_B64_URLSAFE); + json_object_object_add(a_json_obj_out, "sig_b64", json_object_new_string(l_sign_b64)); + json_object_object_add(a_json_obj_out, "sig_b64_size", json_object_new_uint64(l_sign_size)); + } + dap_hash_fast_t l_sign_pkey = {0}; dap_sign_get_pkey_hash(l_sign, &l_sign_pkey); const char *l_sign_pkey_hash_str = dap_hash_fast_to_str_static(&l_sign_pkey); diff --git a/modules/net/srv/include/dap_chain_net_srv.h b/modules/net/srv/include/dap_chain_net_srv.h index c8b252b9afab1270c9d3b76168abd1b9f7aa1baa..cc697439b7d783495a6635bc8837fa0e3f7b6f1e 100755 --- a/modules/net/srv/include/dap_chain_net_srv.h +++ b/modules/net/srv/include/dap_chain_net_srv.h @@ -50,7 +50,7 @@ typedef struct { typedef struct dap_chain_net_srv_abstract { - uint8_t class; //Class of service (once or permanent) + uint8_t c; //Class of service (once or permanent) dap_chain_net_srv_uid_t type_id; //Type of service union { struct { diff --git a/modules/net/srv/dap_chain_net_srv_countries.h b/modules/net/srv/include/dap_chain_net_srv_countries.h similarity index 100% rename from modules/net/srv/dap_chain_net_srv_countries.h rename to modules/net/srv/include/dap_chain_net_srv_countries.h diff --git a/modules/net/srv/dap_chain_net_srv_geoip.h b/modules/net/srv/include/dap_chain_net_srv_geoip.h similarity index 100% rename from modules/net/srv/dap_chain_net_srv_geoip.h rename to modules/net/srv/include/dap_chain_net_srv_geoip.h diff --git a/modules/net/srv/include/dap_chain_net_srv_order.h b/modules/net/srv/include/dap_chain_net_srv_order.h index 68f7ede1bb4e801ac97b281dcf1ddfe959cba93f..a527f50df8adc13dadf55022c668f411452e4686 100644 --- a/modules/net/srv/include/dap_chain_net_srv_order.h +++ b/modules/net/srv/include/dap_chain_net_srv_order.h @@ -143,7 +143,7 @@ char *dap_chain_net_srv_order_save(dap_chain_net_t *a_net, dap_chain_net_srv_ord void dap_chain_net_srv_order_dump_to_string(const dap_chain_net_srv_order_t *a_order, dap_string_t *a_str_out, const char *a_hash_out_type, const char *a_native_ticker); void dap_chain_net_srv_order_dump_to_json(const dap_chain_net_srv_order_t *a_order, json_object *a_json_obj_out, - const char *a_hash_out_type, const char *a_native_ticker); + const char *a_hash_out_type, const char *a_native_ticker, bool a_need_sign); void dap_chain_net_srv_order_add_notify_callback(dap_chain_net_t *a_net, dap_store_obj_callback_notify_t a_callback, void *a_cb_arg); /** diff --git a/modules/service/stake/dap_chain_net_srv_stake_pos_delegate.c b/modules/service/stake/dap_chain_net_srv_stake_pos_delegate.c index 73fe463d109981f771cd73ad912c6d9687ff4f20..8b0e976581364eb68a1228af217aaeec55cd5884 100644 --- a/modules/service/stake/dap_chain_net_srv_stake_pos_delegate.c +++ b/modules/service/stake/dap_chain_net_srv_stake_pos_delegate.c @@ -1987,7 +1987,7 @@ static int s_cli_srv_stake_order(int a_argc, char **a_argv, int a_arg_index, voi } // TODO add filters to list (token, address, etc.) json_object* l_json_obj_order = json_object_new_object(); - dap_chain_net_srv_order_dump_to_json(l_order, l_json_obj_order, a_hash_out_type, l_net->pub.native_ticker); + dap_chain_net_srv_order_dump_to_json(l_order, l_json_obj_order, a_hash_out_type, l_net->pub.native_ticker, false); if (l_order->srv_uid.uint64 == DAP_CHAIN_NET_SRV_STAKE_POS_DELEGATE_ORDERS) { if (l_order->direction == SERV_DIR_SELL) { json_object_object_add(l_json_obj_order, "message", diff --git a/modules/service/voting/dap_chain_net_srv_voting.c b/modules/service/voting/dap_chain_net_srv_voting.c index 82f420b3690c682b83205c19a82f61e8a8847cc8..60f5e6f2449c054dd03e681dfb9a560426c27bad 100644 --- a/modules/service/voting/dap_chain_net_srv_voting.c +++ b/modules/service/voting/dap_chain_net_srv_voting.c @@ -291,6 +291,8 @@ static int s_voting_verificator(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t } dap_list_free(l_tsd_list); + if (!*l_item->voting_params.token_ticker) + strcpy(l_item->voting_params.token_ticker, a_ledger->net->pub.native_ticker); if (!*l_item->voting_params.token_ticker) strcpy(l_item->voting_params.token_ticker, a_ledger->net->pub.native_ticker); pthread_rwlock_wrlock(&s_votings_rwlock); @@ -468,6 +470,30 @@ static int s_vote_verificator(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a } pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock); + // Mark conditional outs + pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock); + if (l_old_vote) { + dap_hash_fast_t *l_vote_hash = &((dap_chain_net_vote_t *)l_old_vote->data)->vote_hash; + dap_chain_net_voting_cond_outs_t *it = NULL, *tmp; + HASH_ITER(hh, l_voting->voting_spent_cond_outs, it, tmp) { + if (!dap_hash_fast_compare(l_vote_hash, &it->pkey_hash)) + continue; + HASH_DEL(l_voting->voting_spent_cond_outs, it); + DAP_DELETE(it); + } + } + for (dap_list_t *it = l_tsd_list; it; it = it->next) { + dap_tsd_t *l_tsd = (dap_tsd_t *)((dap_chain_tx_tsd_t *)it->data)->tsd; + if (l_tsd->type != VOTING_TSD_TYPE_VOTE_TX_COND) + continue; + dap_chain_net_voting_cond_outs_t *l_tx_out = DAP_NEW_Z_RET_VAL_IF_FAIL(dap_chain_net_voting_cond_outs_t, -DAP_LEDGER_CHECK_NOT_ENOUGH_MEMORY); + l_tx_out->tx_hash = ((dap_chain_tx_voting_tx_cond_t *)l_tsd->data)->tx_hash; + l_tx_out->out_idx = ((dap_chain_tx_voting_tx_cond_t *)l_tsd->data)->out_idx; + l_tx_out->pkey_hash = l_pkey_hash; + HASH_ADD(hh, l_voting->voting_spent_cond_outs, tx_hash, sizeof(dap_hash_fast_t), l_tx_out); + } + pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock); + dap_chain_net_vote_t *l_vote_item = DAP_NEW_Z_RET_VAL_IF_FAIL(dap_chain_net_vote_t, -DAP_LEDGER_CHECK_NOT_ENOUGH_MEMORY); l_vote_item->vote_hash = *a_tx_hash; l_vote_item->pkey_hash = l_pkey_hash; @@ -571,7 +597,7 @@ static bool s_datum_tx_voting_verification_delete_callback(dap_ledger_t *a_ledge return true; } -static dap_list_t* s_get_options_list_from_str(const char* a_str) +dap_list_t* dap_get_options_list_from_str(const char* a_str) { dap_list_t* l_ret = NULL; char * l_options_str_dup = strdup(a_str); @@ -680,7 +706,7 @@ static int s_cli_voting(int a_argc, char **a_argv, void **a_str_reply) return -DAP_CHAIN_NET_VOTE_CREATE_OPTION_PARAM_MISSING; } // Parse options list - l_options_list = s_get_options_list_from_str(l_options_list_str); + l_options_list = dap_get_options_list_from_str(l_options_list_str); if(!l_options_list || dap_list_length(l_options_list) < 2){ dap_json_rpc_error_add(*json_arr_reply, DAP_CHAIN_NET_VOTE_CREATE_NUMBER_OPTIONS_ERROR, "Number of options must be 2 or greater."); return -DAP_CHAIN_NET_VOTE_CREATE_NUMBER_OPTIONS_ERROR; diff --git a/modules/service/voting/include/dap_chain_net_srv_voting.h b/modules/service/voting/include/dap_chain_net_srv_voting.h index 70509562bd780f59492b249905f881a3866799a3..9299ac5022e63cebbda5f03c7fa03c313709b187 100644 --- a/modules/service/voting/include/dap_chain_net_srv_voting.h +++ b/modules/service/voting/include/dap_chain_net_srv_voting.h @@ -96,7 +96,8 @@ enum DAP_CHAIN_NET_VOTE_CREATE_ERROR { DAP_CHAIN_NET_VOTE_CREATE_WRONG_TIME_FORMAT, DAP_CHAIN_NET_VOTE_CREATE_WRONG_TOKEN, DAP_CHAIN_NET_VOTE_CREATE_CAN_NOT_CREATE_TSD_TOKEN, - DAP_CHAIN_NET_VOTE_CREATE_UNKNOWN_ERR + DAP_CHAIN_NET_VOTE_CREATE_UNKNOWN_ERR, + DAP_CHAIN_NET_VOTE_CREATE_ERROR_CAN_NOT_GET_TX_OUTS }; int dap_chain_net_vote_create(const char *a_question, dap_list_t *a_options, dap_time_t a_expire_vote, uint64_t a_max_vote, uint256_t a_fee, bool a_delegated_key_required, @@ -146,11 +147,20 @@ enum DAP_CHAIN_NET_VOTE_DUMP_ERROR{ DAP_CHAIN_NET_VOTE_DUMP_MEMORY_ERR }; +#if defined(__cplusplus) +extern "C" { +#endif + int dap_chain_net_vote_voting(dap_cert_t *a_cert, uint256_t a_fee, dap_chain_wallet_t *a_wallet, dap_hash_fast_t a_hash, uint64_t a_option_idx, dap_chain_net_t *a_net, const char *a_hash_out_type, char **a_hash_tx_out); dap_list_t *dap_chain_net_vote_list(dap_chain_net_t *a_net); +dap_list_t* dap_get_options_list_from_str(const char* a_str); dap_chain_net_vote_info_t *dap_chain_net_vote_extract_info(dap_chain_net_t *a_net, dap_hash_fast_t *a_vote_hash); void dap_chain_net_vote_info_free(dap_chain_net_vote_info_t *a_info); +dap_list_t* dap_get_options_list_from_str(const char* a_str); +#if defined(__cplusplus) +} +#endif