diff --git a/app/src/formatting.c b/app/src/formatting.c index c6c362b2..b324e89e 100644 --- a/app/src/formatting.c +++ b/app/src/formatting.c @@ -71,11 +71,17 @@ zxerr_t number_inplace_thousands(char *s, uint16_t sMaxLen, char separator) { } zxerr_t formatICP(char *out, uint16_t outLen, uint64_t value) { + return formatValue(out, outLen, value, COIN_AMOUNT_DECIMAL_PLACES); +} + +zxerr_t formatValue(char *out, uint16_t outLen, uint64_t value, uint8_t decimals) { MEMZERO(out, outLen); - fpuint64_to_str(out, outLen, value, COIN_AMOUNT_DECIMAL_PLACES); - number_inplace_trimming(out, COIN_AMOUNT_DECIMAL_NON_TRIMMED_PLACES); - CHECK_ZXERR(number_inplace_thousands(out, outLen, COIN_AMOUNT_THOUSAND_SEPARATOR)); + fpuint64_to_str(out, outLen, value, decimals); + if (decimals != 0) { + number_inplace_trimming(out, COIN_AMOUNT_DECIMAL_NON_TRIMMED_PLACES); + CHECK_ZXERR(number_inplace_thousands(out, outLen, COIN_AMOUNT_THOUSAND_SEPARATOR)); + } return zxerr_ok; } diff --git a/app/src/formatting.h b/app/src/formatting.h index 7f97fab4..5edec4dc 100644 --- a/app/src/formatting.h +++ b/app/src/formatting.h @@ -27,7 +27,8 @@ zxerr_t inplace_insert_char(char *s, uint16_t sMaxLen, uint16_t pos, char separa zxerr_t number_inplace_thousands(char *s, uint16_t sMaxLen, char separator); zxerr_t formatICP(char *out, uint16_t outLen, uint64_t value); +zxerr_t formatValue(char *out, uint16_t outLen, uint64_t value, uint8_t decimals); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/app/src/parser_print_candid.c b/app/src/parser_print_candid.c index 5d3ab1c8..a4b35af9 100644 --- a/app/src/parser_print_candid.c +++ b/app/src/parser_print_candid.c @@ -20,9 +20,12 @@ #include "candid_parser.h" #include "parser_txdef.h" #include "timeutils.h" +#include "token_info.h" #include #define DEFAULT_MAXIMUM_FEES 10000 +#define AMOUNT_TITLE "Amount " +#define AMOUNT_TITTLE_LEN sizeof(AMOUNT_TITLE) __Z_INLINE parser_error_t print_permission(int32_t permission, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { switch (permission) @@ -249,6 +252,17 @@ static parser_error_t parser_getItemDisburseCandid(uint8_t displayIdx, *pageCount = 1; const candid_ManageNeuron_t *fields = &parser_tx_obj.tx_fields.call.data.candid_manageNeuron; + uint8_t *canister_id = parser_tx_obj.tx_fields.call.canister_id.data; + uint8_t canister_id_len = sizeof(parser_tx_obj.tx_fields.call.canister_id); + const token_info_t *token = get_token(canister_id, canister_id_len); + + const char *tokenSymbol = NULL; + uint8_t decimals = 0; + + if (token != NULL) { + tokenSymbol = token->token_symbol; + decimals = token->decimals; + } if (displayIdx == 0) { snprintf(outKey, outKeyLen, "Transaction type"); @@ -283,9 +297,12 @@ static parser_error_t parser_getItemDisburseCandid(uint8_t displayIdx, } if (displayIdx == 3) { - snprintf(outKey, outKeyLen, "Amount (ICP)"); + snprintf(outKey, outKeyLen, AMOUNT_TITLE); + if (tokenSymbol != NULL) { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(%s)", tokenSymbol); + } if (fields->command.disburse.has_amount) { - return print_ICP(fields->command.disburse.amount, outVal, outValLen, pageIdx, pageCount); + return print_Amount(fields->command.disburse.amount, outVal, outValLen, pageIdx, pageCount, decimals); } else { snprintf(outVal, outValLen, "All"); return parser_ok; @@ -604,6 +621,19 @@ static parser_error_t parser_getItemSplit(uint8_t displayIdx, const candid_ManageNeuron_t *fields = &parser_tx_obj.tx_fields.call.data.candid_manageNeuron; PARSER_ASSERT_OR_ERROR(fields->command.hash == hash_command_Split, parser_unexpected_value) + uint8_t *canister_id = parser_tx_obj.tx_fields.call.canister_id.data; + uint8_t canister_id_len = sizeof(parser_tx_obj.tx_fields.call.canister_id); + const token_info_t *token = get_token(canister_id, canister_id_len); + + const char *tokenSymbol = NULL; + uint8_t decimals = 0; + + if (token != NULL) { + tokenSymbol = token->token_symbol; + decimals = token->decimals; + } + + if (displayIdx == 0) { snprintf(outKey, outKeyLen, "Transaction type"); snprintf(outVal, outValLen, "Split Neuron"); @@ -626,8 +656,11 @@ static parser_error_t parser_getItemSplit(uint8_t displayIdx, } if (displayIdx == 2) { - snprintf(outKey, outKeyLen, "Amount (ICP)"); - return print_ICP(fields->command.split.amount_e8s, outVal, outValLen, pageIdx, pageCount); + snprintf(outKey, outKeyLen, AMOUNT_TITLE); + if (tokenSymbol != NULL) { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(%s)", tokenSymbol); + } + return print_Amount(fields->command.disburse.amount, outVal, outValLen, pageIdx, pageCount, decimals); } return parser_no_data; @@ -881,6 +914,18 @@ static parser_error_t parser_getItemCandidTransfer(uint8_t displayIdx, const bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; + uint8_t *canister_id = parser_tx_obj.tx_fields.call.canister_id.data; + uint8_t canister_id_len = sizeof(parser_tx_obj.tx_fields.call.canister_id); + const token_info_t *token = get_token(canister_id, canister_id_len); + + const char *tokenSymbol = NULL; + uint8_t decimals = 0; + + if (token != NULL) { + tokenSymbol = token->token_symbol; + decimals = token->decimals; + } + if (displayIdx == 0) { snprintf(outKey, outKeyLen, "Transaction type"); snprintf(outVal, outValLen, is_stake_tx ? "Stake Neuron" : "Send ICP"); @@ -923,10 +968,13 @@ static parser_error_t parser_getItemCandidTransfer(uint8_t displayIdx, } if (displayIdx == 3) { - snprintf(outKey, outKeyLen, "Amount (ICP)"); - return print_ICP(fields->data.candid_transfer.amount, + snprintf(outKey, outKeyLen, AMOUNT_TITLE); + if (tokenSymbol != NULL) { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(%s)", tokenSymbol); + } + return print_Amount(fields->data.candid_transfer.amount, outVal, outValLen, - pageIdx, pageCount); + pageIdx, pageCount, decimals); } if (displayIdx == 4) { @@ -953,6 +1001,18 @@ static parser_error_t parser_getItemICRCTransfer(uint8_t displayIdx, const bool icp_canisterId = call->data.icrcTransfer.icp_canister; const bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; + uint8_t *canister_id = call->canister_id.data; + uint8_t canister_id_len = sizeof(parser_tx_obj.tx_fields.call.canister_id); + const token_info_t *token = get_token(canister_id, canister_id_len); + + const char *tokenSymbol = NULL; + uint8_t decimals = 0; + + if (token != NULL) { + tokenSymbol = token->token_symbol; + decimals = token->decimals; + } + if (displayIdx == 0) { if (icp_canisterId) { snprintf(outKey, outKeyLen, "Transaction type"); @@ -1000,10 +1060,14 @@ static parser_error_t parser_getItemICRCTransfer(uint8_t displayIdx, } if (displayIdx == 4) { - const char *title = icp_canisterId ? "Amount (ICP)" : "Amount (Tokens)"; - snprintf(outKey, outKeyLen, "%s", title); + snprintf(outKey, outKeyLen, AMOUNT_TITLE); + if (tokenSymbol != NULL && icp_canisterId ) { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(%s)", tokenSymbol); + } else { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(Tokens)"); + } - return print_ICP(call->data.icrcTransfer.amount, outVal, outValLen, pageIdx, pageCount); + return print_Amount(call->data.icrcTransfer.amount, outVal, outValLen, pageIdx, pageCount, decimals); } // Skip fee if not present and not icp canister id @@ -1041,6 +1105,18 @@ static parser_error_t parser_getItemDisburseSNS(uint8_t displayIdx, *pageCount = 1; const sns_Disburse_t *fields = &parser_tx_obj.tx_fields.call.data.sns_manageNeuron.command.sns_disburse; + uint8_t *canister_id = parser_tx_obj.tx_fields.call.canister_id.data; + uint8_t canister_id_len = sizeof(parser_tx_obj.tx_fields.call.canister_id); + const token_info_t *token = get_token(canister_id, canister_id_len); + + const char *tokenSymbol = NULL; + uint8_t decimals = 0; + + if (token != NULL) { + tokenSymbol = token->token_symbol; + decimals = token->decimals; + } + if (displayIdx == 0) { snprintf(outKey, outKeyLen, "Transaction type"); snprintf(outVal, outValLen, "Disburse Neuron"); @@ -1084,9 +1160,10 @@ static parser_error_t parser_getItemDisburseSNS(uint8_t displayIdx, } } if (displayIdx == 4) { - snprintf(outKey, outKeyLen, "Amount"); - if (fields->has_amount) { - return print_ICP(fields->amount, outVal, outValLen, pageIdx, pageCount); + snprintf(outKey, outKeyLen, AMOUNT_TITLE); + if (tokenSymbol != NULL && fields->has_amount) { + snprintf(outKey + AMOUNT_TITTLE_LEN, outKeyLen - AMOUNT_TITTLE_LEN, "(%s)", tokenSymbol); + return print_Amount(fields->amount, outVal, outValLen, pageIdx, pageCount, decimals); } else { snprintf(outVal, outValLen, "All"); return parser_ok; diff --git a/app/src/parser_print_helper.c b/app/src/parser_print_helper.c index 7d91b077..89262a5b 100644 --- a/app/src/parser_print_helper.c +++ b/app/src/parser_print_helper.c @@ -36,6 +36,41 @@ static const char SEPARATOR = 0x20; // space #endif +parser_error_t format_principal_with_delimiters(const char *input, const uint16_t inputLen, + char *output, const uint16_t outputLen) { + const uint8_t CHARS_PER_CHUNK = 5; + + if (outputLen < 35) { + return parser_unexpected_buffer_end; + } + + const size_t inputStrLen = strnlen(input, inputLen); + size_t outPos = 0; // Track position in output buffer explicitly + + for (size_t idx = 0; idx * CHARS_PER_CHUNK < inputStrLen; idx++) { + // Add separator between groups of 3 chunks + if (idx % 3 == 0 && idx != 0) { + output[outPos++] = '-'; // Using SEPARATOR or '-' directly + } + + // Copy the chunk + size_t inPos = idx * CHARS_PER_CHUNK; + for (int i = 0; i < CHARS_PER_CHUNK && inPos + i < inputStrLen; i++) { + output[outPos++] = input[inPos + i]; + } + + // Add dash unless it's the last chunk or after every 3rd chunk + const bool isLastChunk = ((idx + 1) * CHARS_PER_CHUNK >= inputStrLen); + const bool skipDash = (idx % 3 == 2); + if (!skipDash && !isLastChunk) { + output[outPos++] = '-'; + } + } + + output[outPos] = '\0'; + return parser_ok; +} + parser_error_t page_textual_with_delimiters(const char *input, const uint16_t inputLen, char *output, const uint16_t outputLen, const uint8_t pageIdx, uint8_t *pageCount) { const uint8_t CHARS_PER_CHUNK = 5; const uint8_t CHARS_PER_PAGE = 15 * LINES_PER_PAGE; @@ -273,6 +308,19 @@ parser_error_t print_ICP(uint64_t value, return parser_ok; } +parser_error_t print_Amount(uint64_t value, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount, uint8_t decimals) { + char buffer[200] = {0}; + zxerr_t err = formatValue(buffer, sizeof(buffer), value, decimals); + if (err != zxerr_ok) { + return parser_unexpected_error; + } + + pageString(outVal, outValLen, buffer, pageIdx, pageCount); + return parser_ok; +} + parser_error_t print_principal(const uint8_t *data, uint16_t len, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { @@ -357,3 +405,28 @@ parser_error_t parser_printDelay(uint64_t value, char *buffer, uint16_t bufferSi buffer[index] = 0; return parser_ok; } + +parser_error_t format_principal(const uint8_t *data, uint16_t len, + char *outVal, uint16_t outValLen) { + if (outValLen < 35) { + return parser_unexpected_buffer_end; + } + + // First convert to textual representation + char tmpBuffer[100] = {'\0'}; + uint16_t outLen = sizeof(tmpBuffer); + zxerr_t err = crypto_principalToTextual(data, len, tmpBuffer, &outLen); + if (err != zxerr_ok) { + return parser_unexpected_error; + } + + // Remove any newlines that might have been added + char *newline = strchr(tmpBuffer, '\n'); + if (newline != NULL) { + *newline = '\0'; + outLen = (uint16_t)(newline - tmpBuffer); + } + + // Now format with delimiters + return format_principal_with_delimiters(tmpBuffer, outLen, outVal, outValLen); +} diff --git a/app/src/parser_print_helper.h b/app/src/parser_print_helper.h index ecf9931b..226241bf 100644 --- a/app/src/parser_print_helper.h +++ b/app/src/parser_print_helper.h @@ -27,6 +27,7 @@ extern "C" { parser_error_t print_u64(uint64_t value, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount); parser_error_t print_ICP(uint64_t value, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount); +parser_error_t print_Amount(uint64_t value, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount, uint8_t decimals); parser_error_t print_principal(const uint8_t *data, uint16_t len, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount); parser_error_t parser_printDelay(uint64_t value, char *buffer, uint16_t bufferSize); @@ -40,6 +41,12 @@ parser_error_t page_principal_with_subaccount(const uint8_t *sender, uint16_t se parser_error_t page_textual_with_delimiters(const char *input, const uint16_t inputLen, char *output, const uint16_t outputLen, const uint8_t pageIdx, uint8_t *pageCount); +parser_error_t format_principal_with_delimiters(const char *input, const uint16_t inputLen, + char *output, const uint16_t outputLen); + +parser_error_t format_principal(const uint8_t *data, uint16_t len, + char *outVal, uint16_t outValLen); + #ifdef __cplusplus } #endif diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 312301c5..55e5203d 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -44,6 +44,9 @@ extern "C" { #define PATH_MAX_LEN 40 #define PATH_MAX_ARRAY 2 +#define TOKEN_SYMBOL_MAX_LEN 16 +#define CANISTER_ID_STR_MAX_LEN 32 + typedef enum { unknown = 0x00, // default is not accepted call = 0x01, @@ -199,6 +202,12 @@ typedef struct { uint64_t candid_rootType; } parser_tx_t; +typedef struct { + char canister_id[CANISTER_ID_STR_MAX_LEN]; // Keeping as string for now + char token_symbol[TOKEN_SYMBOL_MAX_LEN]; + uint8_t decimals; +} token_info_t; + #ifdef __cplusplus } #endif