diff --git a/src/handler/sign_message.c b/src/handler/sign_message.c index b5b0fe262..fe6e902e0 100644 --- a/src/handler/sign_message.c +++ b/src/handler/sign_message.c @@ -83,10 +83,7 @@ static bool display_message_content_and_confirm(dispatcher_context_t* dc, message_chunk[total_chunk_len] = '\0'; } - if (!ui_display_path_and_message_content(dc, - (char*) path_str, - (char*) message_chunk, - (n_chunks - 1) / MESSAGE_CHUNK_PER_DISPLAY)) { + if (!ui_display_path_and_message_content(dc, (char*) path_str, (char*) message_chunk)) { return false; } } @@ -184,13 +181,11 @@ void handler_sign_message(dispatcher_context_t* dc, uint8_t protocol_version) { n_chunks, (uint8_t*) path_str)) { SEND_SW(dc, SW_DENY); - ui_post_processing_confirm_message(dc, false); return; } } else { if (!ui_display_message_path_hash_and_confirm(dc, path_str, message_hash_str)) { SEND_SW(dc, SW_DENY); - ui_post_processing_confirm_message(dc, false); return; } } diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c index 25839210b..7488fcf34 100644 --- a/src/handler/sign_psbt.c +++ b/src/handler/sign_psbt.c @@ -690,15 +690,10 @@ init_global_state(dispatcher_context_t *dc, sign_psbt_state_t *st) { // If it's not a default wallet policy, ask the user for confirmation, and abort if they deny if (!st->is_wallet_default && !ui_authorize_wallet_spend(dc, wallet_header.name)) { SEND_SW(dc, SW_DENY); - ui_post_processing_confirm_wallet_spend(dc, false); return false; } st->master_key_fingerprint = crypto_get_master_key_fingerprint(); - - if (!st->is_wallet_default) { - ui_post_processing_confirm_wallet_spend(dc, true); - } return true; } @@ -1312,7 +1307,7 @@ process_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st) { if (!read_outputs(dc, st, &placeholder_info, true)) return false; - if (!G_swap_state.called_from_swap && !ui_transaction_prompt(dc, st->outputs.n_external)) { + if (!G_swap_state.called_from_swap && !ui_transaction_prompt(dc)) { SEND_SW(dc, SW_DENY); return false; } diff --git a/src/ui/display.c b/src/ui/display.c index 86a99776e..8c045d280 100644 --- a/src/ui/display.c +++ b/src/ui/display.c @@ -103,13 +103,12 @@ bool ui_display_pubkey(dispatcher_context_t *context, bool ui_display_path_and_message_content(dispatcher_context_t *context, const char *path_str, - const char *message_content, - uint8_t pageCount) { + const char *message_content) { ui_path_and_message_state_t *state = (ui_path_and_message_state_t *) &g_ui_state; strncpy(state->bip32_path_str, path_str, sizeof(state->bip32_path_str)); strncpy(state->message, message_content, sizeof(state->message)); - ui_sign_message_content_flow(pageCount); + ui_sign_message_content_flow(); return io_ui_process(context, true); } @@ -221,8 +220,8 @@ bool ui_warn_nondefault_sighash(dispatcher_context_t *context) { return io_ui_process(context, true); } -bool ui_transaction_prompt(dispatcher_context_t *context, const int external_outputs_total_count) { - ui_display_transaction_prompt(external_outputs_total_count); +bool ui_transaction_prompt(dispatcher_context_t *context) { + ui_display_transaction_prompt(); return io_ui_process(context, true); } @@ -274,12 +273,6 @@ bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *contex return true; } -bool ui_post_processing_confirm_wallet_spend(dispatcher_context_t *context, bool success) { - (void) context; - (void) success; - return true; -} - bool ui_post_processing_confirm_transaction(dispatcher_context_t *context, bool success) { (void) context; (void) success; @@ -300,26 +293,20 @@ void ui_pre_processing_message(void) { #ifdef HAVE_NBGL bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *context, bool success) { (void) context; - ui_display_post_processing_confirm_wallet_registation(success); + ui_display_post_processing(success); return true; } -bool ui_post_processing_confirm_wallet_spend(dispatcher_context_t *context, bool success) { - ui_display_post_processing_confirm_wallet_spend(success); - - return io_ui_process(context, success); -} - bool ui_post_processing_confirm_transaction(dispatcher_context_t *context, bool success) { - ui_display_post_processing_confirm_transaction(success); + ui_display_post_processing(success); return io_ui_process(context, success); } bool ui_post_processing_confirm_message(dispatcher_context_t *context, bool success) { (void) context; - ui_display_post_processing_confirm_message(success); + ui_display_post_processing(success); return true; } diff --git a/src/ui/display.h b/src/ui/display.h index 7cb2176c7..c6bbc852f 100644 --- a/src/ui/display.h +++ b/src/ui/display.h @@ -98,8 +98,7 @@ bool ui_display_pubkey(dispatcher_context_t *context, bool ui_display_path_and_message_content(dispatcher_context_t *context, const char *path_str, - const char *message_content, - uint8_t pageCount); + const char *message_content); bool ui_display_message_path_hash_and_confirm(dispatcher_context_t *context, const char *path_str, @@ -164,7 +163,7 @@ void ui_display_pubkey_suspicious_flow(void); void ui_sign_message_path_hash_and_confirm_flow(void); -void ui_sign_message_content_flow(uint8_t pageCount); +void ui_sign_message_content_flow(void); void ui_sign_message_confirm_flow(void); @@ -192,7 +191,7 @@ void ui_warn_high_fee_flow(void); void ui_accept_transaction_flow(bool is_self_transfer); -void ui_display_transaction_prompt(const int external_outputs_total_count); +void ui_display_transaction_prompt(void); bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *context, bool success); @@ -205,11 +204,8 @@ bool ui_post_processing_confirm_message(dispatcher_context_t *context, bool succ void ui_pre_processing_message(void); #ifdef HAVE_NBGL -bool ui_transaction_prompt(dispatcher_context_t *context, const int external_outputs_total_count); -void ui_display_post_processing_confirm_message(bool success); -void ui_display_post_processing_confirm_wallet_registation(bool success); -void ui_display_post_processing_confirm_transaction(bool success); -void ui_display_post_processing_confirm_wallet_spend(bool success); +bool ui_transaction_prompt(dispatcher_context_t *context); +void ui_display_post_processing(bool success); void ui_set_display_prompt(void); #else #define ux_layout_custom_params_t ux_layout_paging_params_t diff --git a/src/ui/display_bagl.c b/src/ui/display_bagl.c index f6f03a087..de2972391 100644 --- a/src/ui/display_bagl.c +++ b/src/ui/display_bagl.c @@ -475,8 +475,7 @@ void ui_sign_message_path_hash_and_confirm_flow(void) { ux_flow_init(0, ux_sign_message_path_hash_and_confirm_flow, NULL); } -void ui_sign_message_content_flow(uint8_t pageCount) { - (void) pageCount; +void ui_sign_message_content_flow(void) { if (get_streaming_index() == 0) { ux_flow_init(0, ux_sign_message_path_and_content_flow, NULL); } else { diff --git a/src/ui/display_nbgl.c b/src/ui/display_nbgl.c index a8bc5ae21..ef72908e8 100644 --- a/src/ui/display_nbgl.c +++ b/src/ui/display_nbgl.c @@ -7,33 +7,19 @@ #include "./menu.h" #include "io.h" -typedef struct { - const char *confirm; // text displayed in last transaction page - const char *confirmed_status; // text displayed in confirmation page (after long press) - const char *rejected_status; // text displayed in rejection page (after reject confirmed) - nbgl_layoutTagValue_t tagValuePair[3]; - nbgl_layoutTagValueList_t tagValueList; - nbgl_pageInfoLongPress_t infoLongPress; - int extOutputCount; - int currentOutput; - bool displayPrompt; -} TransactionContext_t; - -enum { - CANCEL_TOKEN = 0, - CONFIRM_TOKEN, - SILENT_CONFIRM_TOKEN, - BACK_TOKEN_TRANSACTION, // for most transactions - BACK_TOKEN_SELFTRANSFER, // special case when it's a self-transfer (no external outputs) - BACK_TOKEN_MESSAGE, - MESSAGE_DISPLAYABLE_TOKEN, - MESSAGE_NON_DISPLAYABLE_TOKEN, - MESSAGE_CANCEL_TOKEN, - -}; +#define REVIEW_CONFIRM FIRST_USER_TOKEN + 1 + +static const char *confirmed_status; // text displayed in confirmation page (after long press) +static const char *rejected_status; // text displayed in rejection page (after reject confirmed) +static bool show_message_start_page; + +static nbgl_layoutTagValue_t pairs[3]; +static nbgl_layoutTagValueList_t pairList; + +static nbgl_genericContents_t genericContent; +static nbgl_content_t contentList[4]; extern bool G_was_processing_screen_shown; -static TransactionContext_t transactionContext; // ux_flow_response static void ux_flow_response_false(void) { @@ -44,276 +30,95 @@ static void ux_flow_response_true(void) { set_ux_flow_response(true); } -static void ux_flow_response(bool confirm) { - if (confirm) { - ux_flow_response_true(); - } else { - ux_flow_response_false(); - } -} - // Statuses -static void status_confirmation_callback(bool confirm) { - if (confirm) { - ux_flow_response_true(); - nbgl_useCaseStatus(transactionContext.confirmed_status, true, ui_menu_main); - } else { - ux_flow_response_false(); - nbgl_useCaseStatus(transactionContext.rejected_status, false, ui_menu_main); - } -} - static void status_cancel(void) { - status_confirmation_callback(false); + nbgl_useCaseStatus(rejected_status, false, ui_menu_main); + ux_flow_response_false(); } -static void confirm_cancel(void) { - nbgl_useCaseConfirm("Reject transaction?", - "", - "Yes, Reject", - "Go back to transaction", - status_cancel); +static void status_confirm(void) { + nbgl_useCaseStatus(confirmed_status, true, ui_menu_main); + ux_flow_response_true(); } -static void confirm_message_cancel(void) { - nbgl_useCaseConfirm("Reject message?", "", "Yes, Reject", "Go back to message", status_cancel); -} - -static void start_processing_callback_light(bool confirm) { +static void status_confirmation_callback(bool confirm) { if (confirm) { - ux_flow_response_true(); - nbgl_useCaseSpinner("Processing"); + status_confirm(); } else { - ux_flow_response_false(); - nbgl_useCaseStatus(transactionContext.rejected_status, false, ui_menu_main); + status_cancel(); } } static void start_processing_callback(bool confirm) { if (confirm) { - ux_flow_response_true(); nbgl_useCaseSpinner("Processing"); + ux_flow_response_true(); } else { - confirm_cancel(); + status_cancel(); } } -static void start_processing_message_callback(bool confirm) { +static void start_callback(bool confirm) { if (confirm) { ux_flow_response_true(); - nbgl_useCaseSpinner("Processing"); } else { - confirm_message_cancel(); + status_cancel(); } } -static void transaction_confirm_callback(int token, uint8_t index) { +static void generic_content_callback(int token, uint8_t index, int page) { (void) index; - + (void) page; switch (token) { - case CANCEL_TOKEN: - confirm_cancel(); - break; - case CONFIRM_TOKEN: - start_processing_callback(true); - break; - case SILENT_CONFIRM_TOKEN: - ux_flow_response(true); - break; - case BACK_TOKEN_TRANSACTION: - ui_accept_transaction_flow(false); - break; - case BACK_TOKEN_SELFTRANSFER: - ui_accept_transaction_flow(true); - break; - case BACK_TOKEN_MESSAGE: - decrease_streaming_index(); - ux_flow_response(true); - break; - case MESSAGE_DISPLAYABLE_TOKEN: - increase_streaming_index(); - ux_flow_response(true); - break; - case MESSAGE_NON_DISPLAYABLE_TOKEN: - ui_sign_message_confirm_flow(); - break; - case MESSAGE_CANCEL_TOKEN: - confirm_message_cancel(); + case REVIEW_CONFIRM: + status_confirmation_callback(true); break; default: PRINTF("Unhandled token : %d", token); } } -// Continue callbacks -static void continue_light_notify_callback(void) { - transactionContext.tagValueList.pairs = transactionContext.tagValuePair; - - transactionContext.infoLongPress.icon = &C_Bitcoin_64px; - transactionContext.infoLongPress.longPressText = "Approve"; - transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; - transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; - transactionContext.infoLongPress.text = transactionContext.confirm; - - nbgl_useCaseStaticReviewLight(&transactionContext.tagValueList, - &transactionContext.infoLongPress, - "Cancel", - status_confirmation_callback); -} - -static void continue_light_processing_callback(void) { - transactionContext.tagValueList.pairs = transactionContext.tagValuePair; - - transactionContext.infoLongPress.icon = &C_Bitcoin_64px; - transactionContext.infoLongPress.longPressText = "Approve"; - transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; - transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; - transactionContext.infoLongPress.text = transactionContext.confirm; - - nbgl_useCaseStaticReviewLight(&transactionContext.tagValueList, - &transactionContext.infoLongPress, - "Cancel", - start_processing_callback_light); -} - -static void continue_callback(void) { - transactionContext.tagValueList.pairs = transactionContext.tagValuePair; - - transactionContext.infoLongPress.icon = &C_Bitcoin_64px; - transactionContext.infoLongPress.longPressText = "Approve"; - transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; - transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; - transactionContext.infoLongPress.text = transactionContext.confirm; - - nbgl_useCaseStaticReview(&transactionContext.tagValueList, - &transactionContext.infoLongPress, - "Cancel", - start_processing_callback); -} - -static void continue_message_callback(void) { - transactionContext.tagValueList.pairs = transactionContext.tagValuePair; - - transactionContext.infoLongPress.icon = &C_Bitcoin_64px; - transactionContext.infoLongPress.longPressText = "Approve"; - transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; - transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; - transactionContext.infoLongPress.text = transactionContext.confirm; - - nbgl_useCaseStaticReview(&transactionContext.tagValueList, - &transactionContext.infoLongPress, - "Cancel", - start_processing_message_callback); -} - -// Transaction flow -static void transaction_confirm(int token, uint8_t index) { - (void) index; - - // If it's a self-transfer, the UX is slightly different - int backToken = - transactionContext.extOutputCount == 0 ? BACK_TOKEN_SELFTRANSFER : BACK_TOKEN_TRANSACTION; - - if (token == CONFIRM_TOKEN) { - nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.extOutputCount + 1, - .nbPages = transactionContext.extOutputCount + 2, - .navType = NAV_WITH_TAP, - .progressIndicator = true, - .navWithTap.backButton = true, - .navWithTap.backToken = backToken, - .navWithTap.nextPageText = NULL, - .navWithTap.quitText = "Reject transaction", - .quitToken = CANCEL_TOKEN, - .tuneId = TUNE_TAP_CASUAL}; - - nbgl_pageContent_t content = {.type = INFO_LONG_PRESS, - .infoLongPress.icon = &C_Bitcoin_64px, - .infoLongPress.text = transactionContext.confirm, - .infoLongPress.longPressText = "Hold to sign", - .infoLongPress.longPressToken = CONFIRM_TOKEN, - .infoLongPress.tuneId = TUNE_TAP_NEXT}; - - nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); - nbgl_refresh(); +static void finish_transaction_flow(bool choice) { + if (choice) { + nbgl_useCaseReviewStreamingFinish("Sign transaction\nto send Bitcoin?", + start_processing_callback); } else { - confirm_cancel(); + start_processing_callback(false); } } void ui_accept_transaction_flow(bool is_self_transfer) { + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.pairs = pairs; + if (!is_self_transfer) { - transactionContext.tagValuePair[0].item = "Fees"; - transactionContext.tagValuePair[0].value = g_ui_state.validate_transaction.fee; + pairs[0].item = "Fees"; + pairs[0].value = g_ui_state.validate_transaction.fee; - transactionContext.tagValueList.nbPairs = 1; + pairList.nbPairs = 1; } else { - transactionContext.tagValuePair[0].item = "Amount"; - transactionContext.tagValuePair[0].value = "Self-transfer"; - transactionContext.tagValuePair[1].item = "Fees"; - transactionContext.tagValuePair[1].value = g_ui_state.validate_transaction.fee; - - transactionContext.tagValueList.nbPairs = 2; - } - - transactionContext.confirm = "Sign transaction\nto send Bitcoin?"; - transactionContext.confirmed_status = "TRANSACTION\nSIGNED"; - transactionContext.rejected_status = "Transaction rejected"; + pairs[0].item = "Amount"; + pairs[0].value = "Self-transfer"; - nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.extOutputCount, - .nbPages = transactionContext.extOutputCount + 2, - .navType = NAV_WITH_TAP, - .progressIndicator = true, - .navWithTap.backButton = false, - .navWithTap.nextPageText = "Tap to continue", - .navWithTap.nextPageToken = CONFIRM_TOKEN, - .navWithTap.quitText = "Reject transaction", - .quitToken = CANCEL_TOKEN, - .tuneId = TUNE_TAP_CASUAL}; + pairs[1].item = "Fees"; + pairs[1].value = g_ui_state.validate_transaction.fee; - nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, - .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, - .tagValueList.pairs = transactionContext.tagValuePair}; - - nbgl_pageDrawGenericContent(&transaction_confirm, &info, &content); - nbgl_refresh(); -} - -void ui_display_transaction_prompt(const int external_outputs_total_count) { - transactionContext.currentOutput = 0; - transactionContext.extOutputCount = external_outputs_total_count; - - transactionContext.rejected_status = "Transaction rejected"; + pairList.nbPairs = 2; + } - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Review transaction\nto send Bitcoin", - "", - "Reject transaction", - ux_flow_response_true, - confirm_cancel); + nbgl_useCaseReviewStreamingContinue(&pairList, finish_transaction_flow); } -// Display outputs -static void display_output(void) { - transactionContext.rejected_status = "Transaction rejected"; - - nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.currentOutput - 1, - .nbPages = transactionContext.extOutputCount + 2, - .navType = NAV_WITH_TAP, - .progressIndicator = true, - .navWithTap.backButton = false, - .navWithTap.nextPageText = "Tap to continue", - .navWithTap.nextPageToken = SILENT_CONFIRM_TOKEN, - .navWithTap.quitText = "Reject transaction", - .quitToken = CANCEL_TOKEN, - .tuneId = TUNE_TAP_CASUAL}; - - nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, - .tagValueList.nbMaxLinesForValue = 8, - .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, - .tagValueList.pairs = transactionContext.tagValuePair}; +void ui_display_transaction_prompt(void) { + confirmed_status = "TRANSACTION\nSIGNED"; + rejected_status = "Transaction rejected"; - nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); - nbgl_refresh(); + nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION, + &C_Bitcoin_64px, + "Review transaction\nto send Bitcoin", + NULL, + start_callback); } void ui_display_output_address_amount_flow(int index) { @@ -322,347 +127,460 @@ void ui_display_output_address_amount_flow(int index) { "#%d", index); - transactionContext.currentOutput++; + pairs[0].item = "Output"; + pairs[0].value = g_ui_state.validate_output.index; - transactionContext.tagValuePair[0].item = "Output"; - transactionContext.tagValuePair[0].value = g_ui_state.validate_output.index; + pairs[1].item = "Amount"; + pairs[1].value = g_ui_state.validate_output.amount; - transactionContext.tagValuePair[1].item = "Amount"; - transactionContext.tagValuePair[1].value = g_ui_state.validate_output.amount; + pairs[2].item = "Address"; + pairs[2].value = g_ui_state.validate_output.address_or_description; - transactionContext.tagValuePair[2].item = "Address"; - transactionContext.tagValuePair[2].value = g_ui_state.validate_output.address_or_description; + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 3; + pairList.pairs = pairs; - transactionContext.tagValueList.nbPairs = 3; - - display_output(); + nbgl_useCaseReviewStreamingContinue(&pairList, start_callback); } void ui_display_output_address_amount_no_index_flow(int index) { (void) index; - transactionContext.currentOutput++; - transactionContext.tagValuePair[0].item = "Amount"; - transactionContext.tagValuePair[0].value = g_ui_state.validate_output.amount; + pairs[0].item = "Amount"; + pairs[0].value = g_ui_state.validate_output.amount; - transactionContext.tagValuePair[1].item = "Address"; - transactionContext.tagValuePair[1].value = g_ui_state.validate_output.address_or_description; + pairs[1].item = "Address"; + pairs[1].value = g_ui_state.validate_output.address_or_description; - transactionContext.tagValueList.nbPairs = 2; + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; - display_output(); + nbgl_useCaseReviewStreamingContinue(&pairList, start_callback); } // Continue light notify callback void ui_display_pubkey_flow(void) { - transactionContext.tagValuePair[0].item = "Path"; - transactionContext.tagValuePair[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + confirmed_status = "PUBLIC KEY\nAPPROVED"; + rejected_status = "Public key rejected"; + + pairs[0].item = "Path"; + pairs[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + + pairs[1].item = "Public key"; + pairs[1].value = g_ui_state.path_and_pubkey.pubkey; + + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; - transactionContext.tagValuePair[1].item = "Public key"; - transactionContext.tagValuePair[1].value = g_ui_state.path_and_pubkey.pubkey; - transactionContext.tagValueList.nbPairs = 2; + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Confirm public key"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; - transactionContext.confirm = "Approve public key"; - transactionContext.confirmed_status = "PUBLIC KEY\nAPPROVED"; - transactionContext.rejected_status = "Public key rejected"; + contentList[1].type = TAG_VALUE_LIST; + memcpy(&contentList[1].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[1].contentActionCallback = NULL; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Confirm public key", - "", - "Cancel", - continue_light_notify_callback, - status_cancel); + contentList[2].type = INFO_BUTTON; + contentList[2].content.infoButton.text = "Approve public key"; + contentList[2].content.infoButton.icon = &C_Bitcoin_64px; + contentList[2].content.infoButton.buttonText = "Approve"; + contentList[2].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[2].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[2].contentActionCallback = generic_content_callback; + + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 3; + + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); } void ui_display_receive_in_wallet_flow(void) { - transactionContext.tagValuePair[0].item = "Wallet name"; - transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; + confirmed_status = "ADDRESS\nCONFIRMED"; + rejected_status = "Address rejected"; + + // Setup list + pairs[0].item = "Wallet name"; + pairs[0].value = g_ui_state.wallet.wallet_name; + + pairs[1].item = "Wallet Address"; + pairs[1].value = g_ui_state.wallet.address; - transactionContext.tagValuePair[1].item = "Wallet Address"; - transactionContext.tagValuePair[1].value = g_ui_state.wallet.address; + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; - transactionContext.tagValueList.nbPairs = 2; + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Receive\nin known wallet"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; - transactionContext.confirm = "Confirm address"; - transactionContext.confirmed_status = "ADDRESS\nCONFIRMED"; - transactionContext.rejected_status = "Address rejected"; + contentList[1].type = TAG_VALUE_LIST; + memcpy(&contentList[1].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[1].contentActionCallback = NULL; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Receive\nin known wallet", - "", - "Cancel", - continue_light_notify_callback, - status_cancel); + contentList[2].type = INFO_BUTTON; + contentList[2].content.infoButton.text = "Confirm address"; + contentList[2].content.infoButton.icon = &C_Bitcoin_64px; + contentList[2].content.infoButton.buttonText = "Approve"; + contentList[2].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[2].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[2].contentActionCallback = generic_content_callback; + + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 3; + + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); } void ui_display_policy_map_cosigner_pubkey_flow(void) { - transactionContext.tagValuePair[0].item = "Index"; - transactionContext.tagValuePair[0].value = g_ui_state.cosigner_pubkey_and_index.signer_index; + confirmed_status = "COSIGNER\nREGISTERED"; + rejected_status = "Cosigner rejected"; - transactionContext.tagValuePair[1].item = "Public key"; - transactionContext.tagValuePair[1].value = g_ui_state.cosigner_pubkey_and_index.pubkey; + pairs[0].item = "Index"; + pairs[0].value = g_ui_state.cosigner_pubkey_and_index.signer_index; - transactionContext.tagValueList.nbPairs = 2; + pairs[1].item = "Public key"; + pairs[1].value = g_ui_state.cosigner_pubkey_and_index.pubkey; - transactionContext.confirm = "Confirm cosigner"; - transactionContext.confirmed_status = "COSIGNER\nREGISTERED"; - transactionContext.rejected_status = "Cosigner rejected"; + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Register cosigner", - "", - "Cancel", - continue_light_notify_callback, - ux_flow_response_false); -} + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Register cosigner"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; -static void suspicious_pubkey_warning(void) { - nbgl_useCaseReviewStart(&C_Important_Circle_64px, - "WARNING", - "The derivation path\nis unusual", - "Cancel", - continue_light_notify_callback, - ux_flow_response_false); -} + contentList[1].type = TAG_VALUE_LIST; + memcpy(&contentList[1].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[1].contentActionCallback = NULL; -void ui_display_pubkey_suspicious_flow(void) { - transactionContext.tagValuePair[0].item = "Path"; - transactionContext.tagValuePair[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + contentList[2].type = INFO_BUTTON; + contentList[2].content.infoButton.text = "Confirm cosigner"; + contentList[2].content.infoButton.icon = &C_Bitcoin_64px; + contentList[2].content.infoButton.buttonText = "Approve"; + contentList[2].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[2].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[2].contentActionCallback = generic_content_callback; - transactionContext.tagValuePair[1].item = "Public key"; - transactionContext.tagValuePair[1].value = g_ui_state.path_and_pubkey.pubkey; + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 3; - transactionContext.tagValueList.nbPairs = 2; + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); +} - transactionContext.confirm = "Approve public key"; - transactionContext.confirmed_status = "PUBLIC KEY\nAPPROVED"; - transactionContext.rejected_status = "Public key rejected"; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Confirm public key", - "", - "Cancel", - suspicious_pubkey_warning, - status_cancel); +void ui_display_pubkey_suspicious_flow(void) { + confirmed_status = "PUBLIC KEY\nAPPROVED"; + rejected_status = "Public key rejected"; + + pairs[0].item = "Path"; + pairs[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + + pairs[1].item = "Public key"; + pairs[1].value = g_ui_state.path_and_pubkey.pubkey; + + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; + + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Confirm public key"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; + + contentList[1].type = CENTERED_INFO; + contentList[1].content.centeredInfo.icon = &C_Important_Circle_64px; + contentList[1].content.centeredInfo.text1 = "WARNING"; + contentList[1].content.centeredInfo.text2 = "The derivation path\nis unusual"; + contentList[1].content.centeredInfo.text3 = NULL; + contentList[1].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[1].content.centeredInfo.offsetY = 0; + contentList[1].contentActionCallback = NULL; + + contentList[2].type = TAG_VALUE_LIST; + memcpy(&contentList[2].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[2].contentActionCallback = NULL; + + contentList[3].type = INFO_BUTTON; + contentList[3].content.infoButton.text = "Approve public key"; + contentList[3].content.infoButton.icon = &C_Bitcoin_64px; + contentList[3].content.infoButton.buttonText = "Approve"; + contentList[3].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[3].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[3].contentActionCallback = generic_content_callback; + + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 4; + + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); } // Continue light processing callback void ui_display_register_wallet_flow(void) { - transactionContext.tagValuePair[0].item = "Name"; - transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; + confirmed_status = "WALLET\nREGISTERED"; + rejected_status = "Wallet rejected"; + + pairs[0].item = "Name"; + pairs[0].value = g_ui_state.wallet.wallet_name; - transactionContext.tagValuePair[1].item = "Policy map"; - transactionContext.tagValuePair[1].value = g_ui_state.wallet.descriptor_template; + pairs[1].item = "Policy map"; + pairs[1].value = g_ui_state.wallet.descriptor_template; - transactionContext.tagValueList.nbPairs = 2; + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 2; + pairList.pairs = pairs; - transactionContext.confirm = "Register Wallet"; - transactionContext.confirmed_status = "WALLET\nREGISTERED"; - transactionContext.rejected_status = "Wallet rejected"; + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Register wallet"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Register wallet", - "", - "Cancel", - continue_light_processing_callback, - ux_flow_response_false); + contentList[1].type = TAG_VALUE_LIST; + memcpy(&contentList[1].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[1].contentActionCallback = NULL; + + contentList[2].type = INFO_BUTTON; + contentList[2].content.infoButton.text = "Register wallet"; + contentList[2].content.infoButton.icon = &C_Bitcoin_64px; + contentList[2].content.infoButton.buttonText = "Approve"; + contentList[2].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[2].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[2].contentActionCallback = generic_content_callback; + + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 3; + + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); } -static void ui_display_message_content_flow(bool displayable, uint8_t pageCount) { - uint8_t token; - if (displayable) { - token = MESSAGE_DISPLAYABLE_TOKEN; +static void message_finish_callback(bool confirm) { + if (confirm) { + nbgl_useCaseReviewStreamingFinish("Sign message?", start_processing_callback); } else { - token = MESSAGE_NON_DISPLAYABLE_TOKEN; + status_cancel(); } - - nbgl_pageNavigationInfo_t info = { - .activePage = get_streaming_index(), - .nbPages = pageCount + 1, - .navType = NAV_WITH_TAP, - .progressIndicator = true, - .navWithTap.backButton = displayable && get_streaming_index() != 0, - .navWithTap.backToken = BACK_TOKEN_MESSAGE, - .navWithTap.nextPageText = "Tap to continue", - .navWithTap.nextPageToken = token, - .navWithTap.quitText = "Reject message", - .quitToken = MESSAGE_CANCEL_TOKEN, - .tuneId = TUNE_TAP_CASUAL}; - - nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, - .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, - .tagValueList.pairs = transactionContext.tagValuePair, - .tagValueList.wrapping = true}; - - transactionContext.tagValueList.nbPairs = 0; - nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); - nbgl_refresh(); } -void ui_set_display_prompt(void) { - transactionContext.displayPrompt = true; +static void message_display_content_continue(bool confirm) { + if (confirm) { + increase_streaming_index(); + ux_flow_response_true(); + } else { + status_cancel(); + } } -static void display_message_content(void) { - ui_display_message_content_flow(true, transactionContext.extOutputCount); -} +static void message_display_content(bool confirm) { + if (confirm) { + pairList.pairs = pairs; + pairList.nbPairs = 0; -void ui_sign_message_content_flow(uint8_t pageCount) { - transactionContext.rejected_status = "Message rejected"; + if (get_streaming_index() == 0) { + pairs[0].item = "Path"; + pairs[0].value = g_ui_state.path_and_message.bip32_path_str; + pairList.nbPairs = 1; + } - if (get_streaming_index() == 0) { - transactionContext.tagValuePair[0].item = "Path"; - transactionContext.tagValuePair[0].value = g_ui_state.path_and_message.bip32_path_str; - transactionContext.tagValueList.nbPairs = 1; + pairs[pairList.nbPairs].item = "Message content"; + pairs[pairList.nbPairs].value = g_ui_state.path_and_message.message; + + pairList.wrapping = true; + pairList.nbPairs++; + + nbgl_useCaseReviewStreamingContinue(&pairList, message_display_content_continue); + } else { + status_cancel(); } +} - transactionContext.tagValuePair[transactionContext.tagValueList.nbPairs].item = - "Message content"; - transactionContext.tagValuePair[transactionContext.tagValueList.nbPairs].value = - g_ui_state.path_and_message.message; +static void message_display_path(bool confirm) { + if (confirm) { + pairs[0].item = "Path"; + pairs[0].value = g_ui_state.path_and_message.bip32_path_str; - transactionContext.tagValueList.nbPairs++; + pairs[1].item = "Message hash"; + pairs[1].value = g_ui_state.path_and_message.message; - transactionContext.extOutputCount = pageCount; + pairList.nbPairs = 2; + pairList.pairs = pairs; - if (transactionContext.displayPrompt) { - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Review message", - "", - "Cancel", - display_message_content, - status_cancel); - transactionContext.displayPrompt = false; + nbgl_useCaseReviewStreamingContinue(&pairList, message_finish_callback); } else { - display_message_content(); + status_cancel(); } } -void ui_sign_message_path_hash_and_confirm_flow(void) { - transactionContext.rejected_status = "Message rejected"; +void ui_sign_message_content_flow(void) { + confirmed_status = "MESSAGE\nSIGNED"; + rejected_status = "Message rejected"; - transactionContext.tagValuePair[0].item = "Path"; - transactionContext.tagValuePair[0].value = g_ui_state.path_and_message.bip32_path_str; - transactionContext.tagValuePair[1].item = "Message hash"; - transactionContext.tagValuePair[1].value = g_ui_state.path_and_message.message; + if (show_message_start_page == true) { + show_message_start_page = false; + nbgl_useCaseReviewStreamingStart(TYPE_MESSAGE, + &C_Bitcoin_64px, + "Review message", + NULL, + message_display_content); + } else { + message_display_content(true); + } +} - transactionContext.tagValueList.nbPairs = 2; +void ui_sign_message_path_hash_and_confirm_flow(void) { + confirmed_status = "MESSAGE\nSIGNED"; + rejected_status = "Message rejected"; - ui_display_message_content_flow(false, 0); + nbgl_useCaseReviewStreamingStart(TYPE_MESSAGE, + &C_Bitcoin_64px, + "Review message", + NULL, + message_display_path); } void ui_sign_message_confirm_flow(void) { - transactionContext.tagValueList.nbPairs = 0; - transactionContext.confirm = "Sign Message"; - transactionContext.confirmed_status = "MESSAGE\nSIGNED"; - transactionContext.rejected_status = "Message rejected"; + nbgl_useCaseReviewStreamingFinish("Sign message?", start_processing_callback); +} - continue_message_callback(); +void ui_set_display_prompt(void) { + show_message_start_page = true; } void ui_display_spend_from_wallet_flow(void) { - transactionContext.tagValuePair[0].item = "Wallet name"; - transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; - - transactionContext.tagValueList.nbPairs = 1; - - transactionContext.confirm = "Confirm wallet name"; - transactionContext.confirmed_status = "WALLET NAME\nCONFIRMED"; - transactionContext.rejected_status = "Wallet name rejected"; - - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Spend from\nknown wallet", - "", - "Cancel", - continue_callback, - ux_flow_response_false); + confirmed_status = "WALLET NAME\nCONFIRMED"; + rejected_status = "Wallet name rejected"; + + // Setup data to display + pairs[0].item = "Wallet name"; + pairs[0].value = g_ui_state.wallet.wallet_name; + + // Setup list + pairList.nbMaxLinesForValue = 0; + pairList.nbPairs = 1; + pairList.pairs = pairs; + + contentList[0].type = CENTERED_INFO; + contentList[0].content.centeredInfo.icon = &C_Bitcoin_64px; + contentList[0].content.centeredInfo.text1 = "Spend from\nknown wallet"; + contentList[0].content.centeredInfo.text2 = NULL; + contentList[0].content.centeredInfo.text3 = NULL; + contentList[0].content.centeredInfo.style = LARGE_CASE_BOLD_INFO; + contentList[0].content.centeredInfo.offsetY = 0; + contentList[0].contentActionCallback = NULL; + + contentList[1].type = TAG_VALUE_LIST; + memcpy(&contentList[1].content.tagValueList, &pairList, sizeof(nbgl_layoutTagValueList_t)); + contentList[1].contentActionCallback = NULL; + + contentList[2].type = INFO_BUTTON; + contentList[2].content.infoButton.text = "Confirm wallet name"; + contentList[2].content.infoButton.icon = &C_Bitcoin_64px; + contentList[2].content.infoButton.buttonText = "Approve"; + contentList[2].content.infoButton.buttonToken = REVIEW_CONFIRM; + contentList[2].content.infoButton.tuneId = TUNE_TAP_CASUAL; + contentList[2].contentActionCallback = generic_content_callback; + + genericContent.callbackCallNeeded = false; + genericContent.contentsList = contentList; + genericContent.nbContents = 3; + + nbgl_useCaseGenericReview(&genericContent, "Cancel", status_cancel); } // Address flow -static void address_display(void) { - nbgl_useCaseAddressConfirmation(g_ui_state.wallet.address, status_confirmation_callback); -} - void ui_display_default_wallet_address_flow(void) { - transactionContext.confirm = "Confirm address"; - transactionContext.confirmed_status = "ADDRESS\nVERIFIED"; - transactionContext.rejected_status = "Address verification\ncancelled"; + confirmed_status = "ADDRESS\nVERIFIED"; + rejected_status = "Address verification\ncancelled"; - nbgl_useCaseReviewStart(&C_Bitcoin_64px, - "Verify Bitcoin\naddress", - "", - "Cancel", - address_display, - status_cancel); + nbgl_useCaseAddressReview(g_ui_state.wallet.address, + NULL, + &C_Bitcoin_64px, + "Verify Bitcoin\naddress", + NULL, + status_confirmation_callback); } // Warning Flows void ui_warn_high_fee_flow(void) { + rejected_status = "Transaction rejected"; nbgl_useCaseChoice(&C_Important_Circle_64px, "Warning", "Fees are above 10%\n of total amount", "Continue", "Reject", - ux_flow_response); + start_callback); } void ui_display_warning_external_inputs_flow(void) { + rejected_status = "Transaction rejected"; nbgl_useCaseChoice(&C_Important_Circle_64px, "Warning", "There are external inputs", "Continue", "Reject if not sure", - ux_flow_response); + start_callback); } void ui_display_unverified_segwit_inputs_flows(void) { + rejected_status = "Transaction rejected"; nbgl_useCaseChoice(&C_Important_Circle_64px, "Warning", "Unverified inputs\nUpdate Ledger Live or\nthird party wallet software", "Continue", "Reject if not sure", - ux_flow_response); + start_callback); } void ui_display_nondefault_sighash_flow(void) { + rejected_status = "Transaction rejected"; nbgl_useCaseChoice(&C_Important_Circle_64px, "Warning", "Non-default sighash", "Continue", "Reject if not sure", - ux_flow_response); + start_callback); } // Statuses -void ui_display_post_processing_confirm_message(bool success) { - if (success) { - nbgl_useCaseStatus("MESSAGE\nSIGNED", true, ux_flow_response_true); - } else { - nbgl_useCaseStatus("Message rejected", false, ux_flow_response_false); - } -} - -void ui_display_post_processing_confirm_wallet_registation(bool success) { +void ui_display_post_processing(bool success) { if (success) { - nbgl_useCaseStatus("WALLET\nREGISTERED", true, ux_flow_response_true); + nbgl_useCaseStatus(confirmed_status, true, ux_flow_response_true); } else { - nbgl_useCaseStatus("Wallet rejected", false, ux_flow_response_false); + nbgl_useCaseStatus(rejected_status, false, ux_flow_response_false); } } - -void ui_display_post_processing_confirm_transaction(bool success) { - if (success) { - nbgl_useCaseStatus("TRANSACTION\nSIGNED", true, ux_flow_response_true); - } else { - nbgl_useCaseStatus("Transaction rejected", false, ux_flow_response_false); - } -} - -void ui_display_post_processing_confirm_wallet_spend(bool success) { - if (success) { - nbgl_useCaseStatus("WALLET NAME\nCONFIRMED", true, ux_flow_response_true); - } else { - nbgl_useCaseStatus("Wallet name rejected", false, ux_flow_response_false); - } -} - #endif // HAVE_NBGL diff --git a/src/ui/menu_nbgl.c b/src/ui/menu_nbgl.c index 4473a89bb..cd51a206a 100644 --- a/src/ui/menu_nbgl.c +++ b/src/ui/menu_nbgl.c @@ -21,41 +21,41 @@ #include "../globals.h" #include "menu.h" -static const char* const infoTypes[] = {"Version", "Developer", "Copyright"}; -static const char* const infoContents[] = {APPVERSION, "Ledger", "(c) 2024 Ledger"}; - -static bool navigation_cb(uint8_t page, nbgl_pageContent_t* content) { - UNUSED(page); - content->type = INFOS_LIST; - content->infosList.nbInfos = 3; - content->infosList.infoTypes = (const char**) infoTypes; - content->infosList.infoContents = (const char**) infoContents; - return true; -} +#define SETTING_INFO_NB 3 +static const char* const INFO_TYPES[SETTING_INFO_NB] = {"Version", "Developer", "Copyright"}; +static const char* const INFO_CONTENTS[SETTING_INFO_NB] = {APPVERSION, "Ledger", "(c) 2024 Ledger"}; + +static const nbgl_contentInfoList_t infoList = { + .nbInfos = SETTING_INFO_NB, + .infoTypes = INFO_TYPES, + .infoContents = INFO_CONTENTS, +}; static void exit(void) { os_sched_exit(-1); } -void ui_menu_about(void) { - nbgl_useCaseSettings("Bitcoin", 0, 1, false, ui_menu_main, navigation_cb, NULL); -} - -void ui_menu_about_testnet(void) { - nbgl_useCaseSettings("Bitcoin Testnet", 0, 1, false, ui_menu_main, navigation_cb, NULL); -} - void ui_menu_main_flow_bitcoin(void) { - nbgl_useCaseHome("Bitcoin", &C_Bitcoin_64px, NULL, false, ui_menu_about, exit); + nbgl_useCaseHomeAndSettings("Bitcoin", + &C_Bitcoin_64px, + NULL, + INIT_HOME_PAGE, + NULL, + &infoList, + NULL, + exit); } void ui_menu_main_flow_bitcoin_testnet(void) { - nbgl_useCaseHome("Bitcoin Testnet", - &C_Bitcoin_64px, - "This app enables signing\ntransactions on all the Bitcoin\ntest networks.", - false, - ui_menu_about_testnet, - exit); + nbgl_useCaseHomeAndSettings( + "Bitcoin Testnet", + &C_Bitcoin_64px, + "This app enables signing\ntransactions on all the Bitcoin\ntest networks.", + INIT_HOME_PAGE, + NULL, + &infoList, + NULL, + exit); } #endif // HAVE_NBGL