From 598d626ec6d99db09b4776d6136e04e75a13115d Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Tue, 1 Oct 2024 21:32:19 +0800 Subject: [PATCH] fixup! feat: add provider interface --- examples/provider_example.c | 104 ++++- .../providers/blockfrost/blockfrost_parsers.h | 85 ++++ .../blockfrost/blockfrost_provider.c | 432 +++++++++++++++++- .../blockfrost/blockfrost_utxos_parser.c | 322 ++++++++++++- 4 files changed, 914 insertions(+), 29 deletions(-) diff --git a/examples/provider_example.c b/examples/provider_example.c index 3d6fae6..08ef4d7 100644 --- a/examples/provider_example.c +++ b/examples/provider_example.c @@ -30,6 +30,51 @@ /* MAIN **********************************************************************/ +static cardano_transaction_input_set_t* +get_inputs() +{ + cardano_transaction_input_t* input1 = NULL; + cardano_transaction_input_t* input2 = NULL; + cardano_transaction_input_t* input3 = NULL; + + cardano_cbor_reader_t* reader1 = cardano_cbor_reader_from_hex("825820f28133700ebd27592a9f6e680a6711831f06b50996165a14573bb7276015566d08", strlen("825820f28133700ebd27592a9f6e680a6711831f06b50996165a14573bb7276015566d08")); + cardano_cbor_reader_t* reader2 = cardano_cbor_reader_from_hex("825820052b9375e94317f05463d0d9b211921a2431cab49b3ce21ad37953e2b135987c1847", strlen("825820052b9375e94317f05463d0d9b211921a2431cab49b3ce21ad37953e2b135987c1847")); + cardano_cbor_reader_t* reader3 = cardano_cbor_reader_from_hex("825820141cbe81494e2dc55b7f0e79b3ffd68a034f8a7e758d4bcf4cdec61e40d11dcf1854", strlen("825820141cbe81494e2dc55b7f0e79b3ffd68a034f8a7e758d4bcf4cdec61e40d11dcf1854")); + + cardano_error_t result = cardano_transaction_input_from_cbor(reader1, &input1); + CARDANO_UNUSED(result); + + result = cardano_transaction_input_from_cbor(reader2, &input2); + CARDANO_UNUSED(result); + + result = cardano_transaction_input_from_cbor(reader3, &input3); + CARDANO_UNUSED(result); + + cardano_transaction_input_set_t* inputs = NULL; + + result = cardano_transaction_input_set_new(&inputs); + CARDANO_UNUSED(result); + + result = cardano_transaction_input_set_add(inputs, input1); + CARDANO_UNUSED(result); + + result = cardano_transaction_input_set_add(inputs, input2); + CARDANO_UNUSED(result); + + result = cardano_transaction_input_set_add(inputs, input3); + CARDANO_UNUSED(result); + + cardano_transaction_input_unref(&input1); + cardano_transaction_input_unref(&input2); + cardano_transaction_input_unref(&input3); + + cardano_cbor_reader_unref(&reader1); + cardano_cbor_reader_unref(&reader2); + cardano_cbor_reader_unref(&reader3); + + return inputs; +} + /** * \brief Entry point of the program. * @@ -62,12 +107,16 @@ main() printf("Provider name: %s\n", cardano_provider_get_name(provider)); - cardano_utxo_list_t* utxo_list = NULL; - cardano_address_t* address = NULL; + cardano_utxo_list_t* utxo_list = NULL; + cardano_utxo_t* utxo = NULL; + cardano_address_t* address = NULL; + cardano_protocol_parameters_t* parameters = NULL; + cardano_asset_id_t* asset_id = NULL; + cardano_transaction_input_set_t* inputs = get_inputs(); // addr_test1wppg9l6relcpls4u667twqyggkrpfrs5cdge9hhl9cv2upchtch0h - //addr_test1wrazf7es2yngqh8jzexpv8v99g88xvx0nz83le2cea755eqf68ll6 - result = cardano_address_from_string("addr_test1wrazf7es2yngqh8jzexpv8v99g88xvx0nz83le2cea755eqf68ll6", strlen("addr_test1wrazf7es2yngqh8jzexpv8v99g88xvx0nz83le2cea755eqf68ll6"), &address); + // addr_test1wrazf7es2yngqh8jzexpv8v99g88xvx0nz83le2cea755eqf68ll6 + result = cardano_address_from_string("addr_test1wppg9l6relcpls4u667twqyggkrpfrs5cdge9hhl9cv2upchtch0h", strlen("addr_test1wppg9l6relcpls4u667twqyggkrpfrs5cdge9hhl9cv2upchtch0h"), &address); if (result != CARDANO_SUCCESS) { @@ -77,21 +126,66 @@ main() return 1; } - result = cardano_provider_get_unspent_outputs(provider, address, &utxo_list); + result = cardano_asset_id_from_hex("f8b0701b14c4e588a0f68e45d91d501c62580887dc1cc863d6c0d8b4457175696e6550696f6e656572486f7273653030313237", strlen("f8b0701b14c4e588a0f68e45d91d501c62580887dc1cc863d6c0d8b4457175696e6550696f6e656572486f7273653030313237"), &asset_id); + + if (result != CARDANO_SUCCESS) + { + cardano_provider_unref(&provider); + cardano_address_unref(&address); + + printf("Failed to create asset id: %s\n", cardano_provider_get_last_error(provider)); + return 1; + } + + result = cardano_provider_resolve_unspent_outputs(provider, inputs, &utxo_list); cardano_address_unref(&address); + cardano_asset_id_unref(&asset_id); + cardano_transaction_input_set_unref(&inputs); if (result != CARDANO_SUCCESS) { cardano_utxo_list_unref(&utxo_list); cardano_provider_unref(&provider); + cardano_protocol_parameters_unref(¶meters); + cardano_utxo_unref(&utxo); printf("Failed to get protocol parameters:\n%s: %s\n", cardano_error_to_string(result), cardano_provider_get_last_error(provider)); return 1; } + size_t utxo_count = cardano_utxo_list_get_length(utxo_list); + + printf("UTXO count: %zu\n", utxo_count); + + for (size_t i = 0; i < utxo_count; i++) + { + cardano_utxo_t* utxo = NULL; + result = cardano_utxo_list_get(utxo_list, i, &utxo); + CARDANO_UNUSED(result); + + cardano_cbor_writer_t* writer = cardano_cbor_writer_new(); + + result = cardano_utxo_to_cbor(utxo, writer); + CARDANO_UNUSED(result); + + size_t cbor_size = cardano_cbor_writer_get_hex_size(writer); + char* cbor_hex = malloc(cbor_size); + + result = cardano_cbor_writer_encode_hex(writer, cbor_hex, cbor_size); + CARDANO_UNUSED(result); + + printf("UTXO %zu: %s\n", i, cbor_hex); + + free(cbor_hex); + cardano_cbor_writer_unref(&writer); + cardano_utxo_unref(&utxo); + } + cardano_utxo_list_unref(&utxo_list); cardano_provider_unref(&provider); + cardano_protocol_parameters_unref(¶meters); + cardano_utxo_unref(&utxo); return 0; } diff --git a/examples/providers/blockfrost/blockfrost_parsers.h b/examples/providers/blockfrost/blockfrost_parsers.h index 1385d1c..0ede676 100644 --- a/examples/providers/blockfrost/blockfrost_parsers.h +++ b/examples/providers/blockfrost/blockfrost_parsers.h @@ -81,6 +81,33 @@ parse_blockfrost_unspent_outputs( size_t size, cardano_utxo_list_t** utxo_list); +/** + * \brief Parses unspent transaction outputs (UTXOs) from a Blockfrost API response for a given transaction hash. + * + * This function parses the JSON response from the Blockfrost API to extract the UTXOs associated with the specified transaction hash. The results + * are stored in a UTXO list. + * + * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for parsing the UTXOs. + * This parameter must not be NULL. + * \param[in] json A pointer to a JSON string representing the Blockfrost API response containing the UTXOs. This parameter must not be NULL. + * \param[in] size The size of the JSON string in bytes. + * \param[in] tx_hash A pointer to a null-terminated string representing the transaction hash for which to retrieve UTXOs. This parameter must not be NULL. + * \param[in] tx_hash_len The length of the \p tx_hash string. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the UTXOs associated with the given transaction hash. + * The caller must manage the lifecycle of this list and free it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully parsed, + * or an appropriate error code if an error occurred (e.g., invalid JSON format, failure to parse the UTXOs, or invalid transaction hash). + */ +cardano_error_t +parse_blockfrost_tx_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + size_t size, + const char* tx_hash, + size_t tx_hash_len, + cardano_utxo_list_t** utxo_list); + /** * \brief Retrieves a script from the Blockfrost API using a script hash. * @@ -163,6 +190,64 @@ blockfrost_construct_rewards_url( cardano_provider_impl_t* provider_impl, const char* bech32); +/** + * \brief Constructs the URL for retrieving UTXOs associated with a given Bech32 address and asset ID from the Blockfrost API. + * + * This function constructs a URL used to query the Blockfrost API for the UTXOs that are associated with the specified Bech32 address and asset ID. + * The URL will also include pagination parameters, allowing the user to specify the page and the maximum number of results per page. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context (e.g., API base URL). + * This parameter must not be NULL. + * \param[in] bech32 A pointer to a null-terminated string containing the Bech32 address for which to retrieve UTXOs. This parameter must not be NULL. + * \param[in] asset_id A pointer to a null-terminated string containing the asset ID to filter the UTXOs by. This parameter must not be NULL. + * \param[in] page The page number to retrieve. Used for pagination. + * \param[in] max_results The maximum number of results to retrieve per page. + * + * \return A dynamically allocated string containing the full URL for querying UTXOs with the specified asset. The caller is responsible for freeing the + * returned string using \c free when it is no longer needed. Returns NULL if the URL could not be constructed (e.g., due to invalid input). + */ +char* +blockfrost_construct_utxo_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + const char* asset_id, + size_t page, + size_t max_results); + +/** + * \brief Constructs the URL for retrieving addresses associated with a given asset ID from the Blockfrost API. + * + * This function constructs a URL used to query the Blockfrost API for the addresses that hold the specified asset. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context (e.g., API base URL). + * This parameter must not be NULL. + * \param[in] asset_id A pointer to a null-terminated string containing the asset ID to query for addresses. This parameter must not be NULL. + * + * \return A dynamically allocated string containing the full URL for querying addresses holding the specified asset. The caller is responsible for freeing the + * returned string using \c free when it is no longer needed. Returns NULL if the URL could not be constructed (e.g., due to invalid input). + */ +char* +blockfrost_construct_addresses_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* asset_id); + +/** + * \brief Constructs a URL for retrieving UTXOs from a transaction. + * + * This function constructs the URL required to fetch the UTXOs associated with a given transaction ID. + * The URL is built based on the Blockfrost API and the network details provided in the `provider_impl`. + * + * \param[in] provider_impl A pointer to the provider implementation that contains network information. + * \param[in] tx_id A string representing the transaction ID. + * + * \return A dynamically allocated string containing the constructed URL, or NULL if memory allocation fails. + * The caller is responsible for freeing the memory returned by this function. + */ +char* +blockfrost_construct_transaction_utxos_url( + cardano_provider_impl_t* provider_impl, + const char* tx_id); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/examples/providers/blockfrost/blockfrost_provider.c b/examples/providers/blockfrost/blockfrost_provider.c index 5a204c1..73310cc 100644 --- a/examples/providers/blockfrost/blockfrost_provider.c +++ b/examples/providers/blockfrost/blockfrost_provider.c @@ -27,6 +27,7 @@ #include "./blockfrost_common.h" #include "blockfrost_parsers.h" +#include #include #include #include @@ -185,6 +186,21 @@ get_unspent_outputs( return result; } +/** + * \brief Retrieves the staking rewards balance for a given reward address. + * + * This function queries the staking rewards associated with a given reward address using the provided \ref cardano_provider_impl_t. + * The rewards balance is returned as a 64-bit unsigned integer. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the rewards. + * This parameter must not be NULL. + * \param[in] address A pointer to an initialized \ref cardano_reward_address_t object representing the reward address for which to retrieve the rewards. + * This parameter must not be NULL. + * \param[out] rewards On successful execution, this will hold the total rewards balance as a 64-bit unsigned integer. The caller must ensure this pointer is valid. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the rewards were successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the query failed or if the address is invalid). + */ static cardano_error_t get_rewards_balance(cardano_provider_impl_t* provider_impl, cardano_reward_address_t* address, uint64_t* rewards) { @@ -211,6 +227,416 @@ get_rewards_balance(cardano_provider_impl_t* provider_impl, cardano_reward_addre return result; } +/** + * \brief Retrieves the unspent transaction outputs (UTXOs) associated with a given address and asset ID. + * + * This function queries the UTXOs associated with a specified address and filters them by the provided asset ID. The results are stored in a UTXO list. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for querying the UTXOs. + * This parameter must not be NULL. + * \param[in] address A pointer to an initialized \ref cardano_address_t object representing the address to query for UTXOs. This parameter must not be NULL. + * \param[in] asset_id A pointer to an initialized \ref cardano_asset_id_t object representing the asset ID used to filter the UTXOs. This parameter must not be NULL. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the filtered UTXOs. + * The caller must manage the lifecycle of this list and free it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully retrieved, + * or an appropriate error code if an error occurred (e.g., if the query failed or the address/asset ID is invalid). + */ +static cardano_error_t +get_unspent_outputs_with_asset( + cardano_provider_impl_t* provider_impl, + cardano_address_t* address, + cardano_asset_id_t* asset_id, + cardano_utxo_list_t** utxo_list) +{ + size_t page = 1; + size_t max_results = 100; + size_t last_results = 0; + + cardano_error_t result = CARDANO_SUCCESS; + + do + { + char* url = blockfrost_construct_utxo_with_asset_url(provider_impl, cardano_address_get_string(address), cardano_asset_id_get_hex(asset_id), page, max_results); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if (response_code != 200 || result != CARDANO_SUCCESS) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + + cardano_buffer_unref(&response_buffer); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + cardano_utxo_list_t* current_list = NULL; + result = parse_blockfrost_unspent_outputs(provider_impl, (char*)cardano_buffer_get_data(response_buffer), cardano_buffer_get_size(response_buffer), ¤t_list); + + cardano_buffer_unref(&response_buffer); + + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + } + + last_results = cardano_utxo_list_get_length(current_list); + + if (*utxo_list == NULL) + { + *utxo_list = current_list; + } + else + { + cardano_utxo_list_t* tmp_list = *utxo_list; + + *utxo_list = cardano_utxo_list_concat(tmp_list, current_list); + + cardano_utxo_list_unref(&tmp_list); + cardano_utxo_list_unref(¤t_list); + } + + ++page; + } + while (last_results == max_results); + + return result; +} + +static cardano_error_t +get_unspent_output_by_nft( + cardano_provider_impl_t* provider_impl, + cardano_asset_id_t* asset_id, + cardano_utxo_t** utxo) +{ + char* url = blockfrost_construct_addresses_with_asset_url(provider_impl, cardano_asset_id_get_hex(asset_id)); + cardano_buffer_t* response_buffer = NULL; + uint64_t response_code = 0; + + cardano_error_t result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + free(url); + + if (response_code != 200 || result != CARDANO_SUCCESS) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + cardano_buffer_unref(&response_buffer); + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, (const char*)cardano_buffer_get_data(response_buffer), (int32_t)cardano_buffer_get_size(response_buffer)); + + if (parsed_json == NULL) + { + cardano_buffer_unref(&response_buffer); + json_tokener_free(tok); + return CARDANO_ERROR_INVALID_JSON; + } + + size_t array_len = json_object_array_length(parsed_json); + + if (array_len == 0) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + cardano_utils_set_error_message(provider_impl, "No asset found for the specified asset ID"); + + return CARDANO_ERROR_ELEMENT_NOT_FOUND; + } + + if (array_len != 1) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + cardano_utils_set_error_message(provider_impl, "Asset is not an NFT. Multiple assets found for the specified asset ID"); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + struct json_object* address_with_count_obj = json_object_array_get_idx(parsed_json, 0); + + if (address_with_count_obj == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* address_obj = NULL; + cardano_address_t* address = NULL; + + if (json_object_object_get_ex(address_with_count_obj, "address", &address_obj)) + { + result = cardano_address_from_string(json_object_get_string(address_obj), json_object_get_string_len(address_obj), &address); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + return result; + } + } + + if (address == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_utxo_list_t* utxo_list = NULL; + result = get_unspent_outputs_with_asset(provider_impl, address, asset_id, &utxo_list); + + json_object_put(parsed_json); + json_tokener_free(tok); + + if (result != CARDANO_SUCCESS) + { + cardano_address_unref(&address); + cardano_buffer_unref(&response_buffer); + return result; + } + + if (cardano_utxo_list_get_length(utxo_list) == 0) + { + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "No unspent outputs found for the specified asset ID"); + + return CARDANO_ERROR_ELEMENT_NOT_FOUND; + } + + if (cardano_utxo_list_get_length(utxo_list) > 1) + { + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + cardano_utils_set_error_message(provider_impl, "Asset is not an NFT. Multiple unspent outputs found for the specified asset ID"); + + return CARDANO_ERROR_INVALID_ARGUMENT; + } + + result = cardano_utxo_list_get(utxo_list, 0, utxo); + + cardano_address_unref(&address); + cardano_utxo_list_unref(&utxo_list); + cardano_buffer_unref(&response_buffer); + + return result; +} + +/** + * \brief Resolves the unspent transaction outputs (UTXOs) for a given set of transaction inputs. + * + * This function retrieves the UTXOs corresponding to the provided set of transaction inputs by querying the underlying provider implementation. + * The results are stored in a UTXO list. + * + * \param[in] provider_impl A pointer to an initialized \ref cardano_provider_impl_t object that provides the necessary context for resolving UTXOs. + * This parameter must not be NULL. + * \param[in] tx_ins A pointer to an initialized \ref cardano_transaction_input_set_t object representing the set of transaction inputs to resolve. + * This parameter must not be NULL. + * \param[out] utxo_list A pointer to a pointer of \ref cardano_utxo_list_t that will be populated with the resolved UTXOs. + * The caller is responsible for managing the lifecycle of this list and freeing it when no longer needed. + * + * \return \ref cardano_error_t indicating the outcome of the operation. Returns \ref CARDANO_SUCCESS if the UTXOs were successfully resolved, + * or an appropriate error code if an error occurred (e.g., failure to resolve the UTXOs or invalid input data). + */ +static cardano_error_t +resolve_unspent_outputs( + cardano_provider_impl_t* provider_impl, + cardano_transaction_input_set_t* tx_ins, + cardano_utxo_list_t** utxo_list) +{ + cardano_error_t result = cardano_utxo_list_new(utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to create UTXO list"); + return result; + } + + for (size_t i = 0; i < cardano_transaction_input_set_get_length(tx_ins); ++i) + { + cardano_transaction_input_t* tx_in = NULL; + result = cardano_transaction_input_set_get(tx_ins, i, &tx_in); + cardano_transaction_input_unref(&tx_in); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to get transaction input"); + cardano_utxo_list_unref(utxo_list); + return result; + } + + const uint64_t index = cardano_transaction_input_get_index(tx_in); + cardano_blake2b_hash_t* tx_hash = cardano_transaction_input_get_id(tx_in); + cardano_blake2b_hash_unref(&tx_hash); + + const size_t tx_id_hex_size = cardano_blake2b_hash_get_hex_size(tx_hash); + char* tx_id_hex = malloc(tx_id_hex_size); + + if (tx_id_hex == NULL) + { + cardano_utils_set_error_message(provider_impl, "Failed to allocate memory for transaction ID"); + cardano_utxo_list_unref(utxo_list); + + return CARDANO_ERROR_MEMORY_ALLOCATION_FAILED; + } + + result = cardano_blake2b_hash_to_hex(tx_hash, tx_id_hex, tx_id_hex_size); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider_impl, "Failed to convert transaction ID to hex"); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return result; + } + + char* url = blockfrost_construct_transaction_utxos_url(provider_impl, tx_id_hex); + + cardano_buffer_t* response_buffer = NULL; + + uint64_t response_code = 0; + + result = cardano_blockfrost_http_get(provider_impl, url, strlen(url), &response_code, &response_buffer); + + free(url); + + if (response_code != 200 || result != CARDANO_SUCCESS) + { + cardano_blockfrost_parse_error(provider_impl, response_buffer); + cardano_buffer_unref(&response_buffer); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_HTTP_REQUEST; + } + + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, (const char*)cardano_buffer_get_data(response_buffer), (int32_t)cardano_buffer_get_size(response_buffer)); + + if (parsed_json == NULL) + { + cardano_buffer_unref(&response_buffer); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + struct json_object* tx_outputs = NULL; + + if (!json_object_object_get_ex(parsed_json, "outputs", &tx_outputs)) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + size_t json_len = 0; + const char* json_string = json_object_to_json_string_length(tx_outputs, JSON_C_TO_STRING_PLAIN, &json_len); + + cardano_utxo_list_t* tmp_utxo_list = NULL; + result = parse_blockfrost_tx_unspent_outputs(provider_impl, json_string, json_len, tx_id_hex, tx_id_hex_size - 1, &tmp_utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + for (size_t j = 0; j < cardano_utxo_list_get_length(tmp_utxo_list); ++j) + { + cardano_utxo_t* utxo = NULL; + + result = cardano_utxo_list_get(tmp_utxo_list, j, &utxo); + cardano_utxo_unref(&utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + cardano_transaction_input_t* tx_in = cardano_utxo_get_input(utxo); + cardano_transaction_input_unref(&tx_in); + + if (tx_in == NULL) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return CARDANO_ERROR_INVALID_JSON; + } + + if (index == cardano_transaction_input_get_index(tx_in)) + { + result = cardano_utxo_list_add(*utxo_list, utxo); + + if (result != CARDANO_SUCCESS) + { + cardano_buffer_unref(&response_buffer); + json_object_put(parsed_json); + json_tokener_free(tok); + cardano_utxo_list_unref(utxo_list); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + + return result; + } + + break; + } + } + + cardano_buffer_unref(&response_buffer); + cardano_utxo_list_unref(&tmp_utxo_list); + free(tx_id_hex); + json_object_put(parsed_json); + json_tokener_free(tok); + } + + return result; +} + /* DEFINITIONS ****************************************************************/ cardano_error_t @@ -249,9 +675,9 @@ create_blockfrost_provider( impl.get_parameters = get_parameters; impl.get_unspent_outputs = get_unspent_outputs; impl.get_rewards_balance = get_rewards_balance; - impl.get_unspent_outputs_with_asset = NULL; - impl.get_unspent_output_by_nft = NULL; - impl.resolve_unspent_outputs = NULL; + impl.get_unspent_outputs_with_asset = get_unspent_outputs_with_asset; + impl.get_unspent_output_by_nft = get_unspent_output_by_nft; + impl.resolve_unspent_outputs = resolve_unspent_outputs; impl.resolve_datum = NULL; impl.await_transaction_confirmation = NULL; impl.post_transaction_to_chain = NULL; diff --git a/examples/providers/blockfrost/blockfrost_utxos_parser.c b/examples/providers/blockfrost/blockfrost_utxos_parser.c index 1d89d50..8bd44d1 100644 --- a/examples/providers/blockfrost/blockfrost_utxos_parser.c +++ b/examples/providers/blockfrost/blockfrost_utxos_parser.c @@ -339,21 +339,6 @@ parse_reference_script( /* IMPLEMENTATION *************************************************************/ -/** - * \brief Parses a JSON string of unspent outputs from Blockfrost and returns a list of UTXOs. - * - * This function parses a JSON string of unspent transaction outputs (UTXOs) returned by the Blockfrost API - * and creates a \ref cardano_utxo_list_t object. The UTXOs represent the unspent outputs that belong to a specific address. - * - * \param[in] provider A pointer to an initialized \ref cardano_provider_impl_t object that interacts with the Blockfrost API. - * This parameter must not be NULL. - * \param[in] json A pointer to a JSON string containing the unspent outputs data. This JSON string is typically - * retrieved from a Blockfrost API call. - * \param[in] size The size of the JSON string in bytes. - * \param[out] utxo_list On successful parsing, this will point to a newly created \ref cardano_utxo_list_t object containing the parsed UTXOs. - * The caller is responsible for managing the lifecycle of the object and must call \ref cardano_utxo_list_unref - * when it is no longer needed. - */ cardano_error_t parse_blockfrost_unspent_outputs( cardano_provider_impl_t* provider, @@ -558,21 +543,223 @@ parse_blockfrost_unspent_outputs( return result; } +cardano_error_t +parse_blockfrost_tx_unspent_outputs( + cardano_provider_impl_t* provider, + const char* json, + const size_t size, + const char* tx_hash, + const size_t tx_hash_len, + cardano_utxo_list_t** utxo_list) +{ + struct json_tokener* tok = json_tokener_new(); + struct json_object* parsed_json = json_tokener_parse_ex(tok, json, (int32_t)size); + + if (parsed_json == NULL) + { + cardano_utils_set_error_message(provider, "Failed to parse JSON response"); + json_tokener_free(tok); + return CARDANO_ERROR_INVALID_JSON; + } + + cardano_error_t result = cardano_utxo_list_new(utxo_list); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to allocate memory for UTXO list"); + json_object_put(parsed_json); + json_tokener_free(tok); + return result; + } + + size_t array_len = json_object_array_length(parsed_json); + + cardano_blake2b_hash_t* tx_id = NULL; + uint64_t tx_index = 0; + cardano_address_t* address = NULL; + cardano_value_t* value = NULL; + cardano_blake2b_hash_t* plutus_data_hash = NULL; + cardano_plutus_data_t* plutus_data = NULL; + cardano_script_t* reference_script = NULL; + cardano_transaction_input_t* input = NULL; + cardano_transaction_output_t* output = NULL; + cardano_utxo_t* utxo = NULL; + + for (size_t i = 0U; i < array_len; ++i) + { + // These are always freed at the end of the loop iteration, + // so we can safely set them to NULL here. + tx_id = NULL; + tx_index = 0; + address = NULL; + value = NULL; + plutus_data_hash = NULL; + plutus_data = NULL; + reference_script = NULL; + input = NULL; + output = NULL; + utxo = NULL; + + result = cardano_blake2b_hash_from_hex(tx_hash, tx_hash_len, &tx_id); + + if (result != CARDANO_SUCCESS) + { + cardano_utils_set_error_message(provider, "Failed to parse tx_hash from JSON response"); + goto cleanup; + } + + struct json_object* tx_output = json_object_array_get_idx(parsed_json, i); + + struct json_object* address_obj = NULL; + + if (json_object_object_get_ex(tx_output, "address", &address_obj)) + { + result = parse_address(provider, address_obj, &address); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* output_index_obj = NULL; + + if (json_object_object_get_ex(tx_output, "output_index", &output_index_obj)) + { + tx_index = json_object_get_int64(output_index_obj); + } + + struct json_object* amount_array = NULL; + + if (json_object_object_get_ex(tx_output, "amount", &amount_array)) + { + result = parse_amount(provider, amount_array, &value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* data_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "data_hash", &data_hash_obj)) + { + result = parse_data_hash(provider, data_hash_obj, &plutus_data_hash); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* inline_datum_obj = NULL; + + if (json_object_object_get_ex(tx_output, "inline_datum", &inline_datum_obj)) + { + result = parse_inline_datum(provider, inline_datum_obj, &plutus_data); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + struct json_object* reference_script_hash_obj = NULL; + + if (json_object_object_get_ex(tx_output, "reference_script_hash", &reference_script_hash_obj)) + { + result = parse_reference_script(provider, reference_script_hash_obj, &reference_script); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + } + + result = cardano_transaction_input_new(tx_id, tx_index, &input); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_new(address, 0, &output); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_transaction_output_set_value(output, value); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_new(input, output, &utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + result = cardano_utxo_list_add(*utxo_list, utxo); + + if (result != CARDANO_SUCCESS) + { + goto cleanup; + } + + // Clean up allocated resources for this loop iteration + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + +cleanup: + if (result != CARDANO_SUCCESS) + { + cardano_utxo_list_unref(utxo_list); + cardano_transaction_input_unref(&input); + cardano_transaction_output_unref(&output); + cardano_utxo_unref(&utxo); + cardano_blake2b_hash_unref(&tx_id); + cardano_address_unref(&address); + cardano_value_unref(&value); + cardano_blake2b_hash_unref(&plutus_data_hash); + cardano_plutus_data_unref(&plutus_data); + cardano_script_unref(&reference_script); + } + + json_object_put(parsed_json); + json_tokener_free(tok); + + return result; +} + char* blockfrost_construct_utxo_url( cardano_provider_impl_t* provider_impl, const char* bech32, - size_t page, - size_t max_results) + const size_t page, + const size_t max_results) { blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "addresses/"); - size_t base_path_len = strlen(base_path); - size_t bech32_len = strlen(bech32); - size_t pagination_len = 50; - size_t url_len = base_path_len + bech32_len + pagination_len + 20; + const size_t base_path_len = strlen(base_path); + const size_t bech32_len = strlen(bech32); + const size_t pagination_len = 50; + const size_t url_len = base_path_len + bech32_len + pagination_len + 20; char* url = malloc(url_len); @@ -589,3 +776,96 @@ blockfrost_construct_utxo_url( return url; } + +char* +blockfrost_construct_utxo_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* bech32, + const char* asset_id, + const size_t page, + const size_t max_results) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "addresses/"); + + const size_t base_path_len = strlen(base_path); + const size_t bech32_len = strlen(bech32); + const size_t asset_id_len = strlen(asset_id); + const size_t pagination_len = 50; + const size_t url_len = base_path_len + bech32_len + pagination_len + asset_id_len + 20; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/utxos/%s?count=%zu&page=%zu", base_path, bech32, asset_id, max_results, page)); + + free(base_path); + + return url; +} + +char* +blockfrost_construct_addresses_with_asset_url( + cardano_provider_impl_t* provider_impl, + const char* asset_id) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "assets/"); + + const size_t base_path_len = strlen(base_path); + const size_t asset_id_len = strlen(asset_id); + const size_t pagination_len = 50; + const size_t url_len = base_path_len + pagination_len + asset_id_len + 1; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/addresses", base_path, asset_id)); + + free(base_path); + + return url; +} + +char* +blockfrost_construct_transaction_utxos_url( + cardano_provider_impl_t* provider_impl, + const char* tx_id) +{ + blockfrost_context_t* context = (blockfrost_context_t*)provider_impl->context; + + char* base_path = cardano_blockfrost_get_endpoint_url(context->network, "txs/"); + + const size_t base_path_len = strlen(base_path); + const size_t tx_id_len = strlen(tx_id); + const size_t url_len = base_path_len + tx_id_len + 10; + + char* url = malloc(url_len); + + if (!url) + { + free(base_path); + + return NULL; + } + + CARDANO_UNUSED(snprintf(url, url_len, "%s%s/utxos", base_path, tx_id)); + + free(base_path); + + return url; +}