1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-24 13:22:05 +00:00

feat(eckhart): share words flow

This commit is contained in:
Lukas Bielesch 2025-02-14 15:18:33 +01:00 committed by Lukáš Bielesch
parent 4c6a0d038e
commit 1b34921162
17 changed files with 215 additions and 34 deletions

View File

@ -532,6 +532,7 @@ static void _librust_qstrs(void) {
MP_QSTR_reset__set_it_to_count_template; MP_QSTR_reset__set_it_to_count_template;
MP_QSTR_reset__share_checked_successfully_template; MP_QSTR_reset__share_checked_successfully_template;
MP_QSTR_reset__share_completed_template; MP_QSTR_reset__share_completed_template;
MP_QSTR_reset__share_words_first;
MP_QSTR_reset__share_words_title; MP_QSTR_reset__share_words_title;
MP_QSTR_reset__slip39_checklist_more_info_threshold; MP_QSTR_reset__slip39_checklist_more_info_threshold;
MP_QSTR_reset__slip39_checklist_more_info_threshold_example_template; MP_QSTR_reset__slip39_checklist_more_info_threshold_example_template;
@ -651,7 +652,7 @@ static void _librust_qstrs(void) {
MP_QSTR_show_progress_coinjoin; MP_QSTR_show_progress_coinjoin;
MP_QSTR_show_remaining_shares; MP_QSTR_show_remaining_shares;
MP_QSTR_show_share_words; MP_QSTR_show_share_words;
MP_QSTR_show_share_words_delizia; MP_QSTR_show_share_words_extended;
MP_QSTR_show_simple; MP_QSTR_show_simple;
MP_QSTR_show_success; MP_QSTR_show_success;
MP_QSTR_show_wait_text; MP_QSTR_show_wait_text;

View File

@ -855,7 +855,7 @@ pub enum TranslatedString {
reset__recovery_share_title_template = 567, // "Recovery share #{0}" reset__recovery_share_title_template = 567, // "Recovery share #{0}"
reset__required_number_of_groups = 568, // "The required number of groups for recovery." reset__required_number_of_groups = 568, // "The required number of groups for recovery."
reset__select_correct_word = 569, // "Select the correct word for each position." reset__select_correct_word = 569, // "Select the correct word for each position."
reset__select_word_template = 570, // "Select {0} word" reset__select_word_template = 570, // {"Bolt": "Select {0} word", "Caesar": "Select {0} word", "Delizia": "Select {0} word", "Eckhart": "Select word #{0} from your wallet backup"}
reset__select_word_x_of_y_template = 571, // "Select word {0} of {1}:" reset__select_word_x_of_y_template = 571, // "Select word {0} of {1}:"
reset__set_it_to_count_template = 572, // "Set it to {0} and you will need " reset__set_it_to_count_template = 572, // "Set it to {0} and you will need "
reset__share_checked_successfully_template = 573, // "Share #{0} checked successfully." reset__share_checked_successfully_template = 573, // "Share #{0} checked successfully."
@ -1339,7 +1339,7 @@ pub enum TranslatedString {
reset__repeat_for_all_shares = 938, // "Repeat for all shares." reset__repeat_for_all_shares = 938, // "Repeat for all shares."
homescreen__settings_subtitle = 939, // "Settings" homescreen__settings_subtitle = 939, // "Settings"
homescreen__settings_title = 940, // "Homescreen" homescreen__settings_title = 940, // "Homescreen"
reset__the_word_is_repeated = 941, // "The word is repeated" reset__the_word_is_repeated = 941, // {"Bolt": "The word is repeated", "Caesar": "The word is repeated", "Delizia": "The word is repeated", "Eckhart": "The word appears multiple times in the backup."}
tutorial__title_lets_begin = 942, // "Let's begin" tutorial__title_lets_begin = 942, // "Let's begin"
tutorial__did_you_know = 943, // "Did you know?" tutorial__did_you_know = 943, // "Did you know?"
tutorial__first_wallet = 944, // "The Trezor Model One, created in 2013,\nwas the world's first hardware wallet." tutorial__first_wallet = 944, // "The Trezor Model One, created in 2013,\nwas the world's first hardware wallet."
@ -1383,6 +1383,7 @@ pub enum TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
ethereum__unknown_contract_address_short = 974, // "Unknown contract address." ethereum__unknown_contract_address_short = 974, // "Unknown contract address."
instructions__keep_holding = 975, // "Keep holding" instructions__keep_holding = 975, // "Keep holding"
reset__share_words_first = 976, // "Write down the first word from the backup."
} }
impl TranslatedString { impl TranslatedString {
@ -2234,7 +2235,14 @@ impl TranslatedString {
Self::reset__recovery_share_title_template => "Recovery share #{0}", Self::reset__recovery_share_title_template => "Recovery share #{0}",
Self::reset__required_number_of_groups => "The required number of groups for recovery.", Self::reset__required_number_of_groups => "The required number of groups for recovery.",
Self::reset__select_correct_word => "Select the correct word for each position.", Self::reset__select_correct_word => "Select the correct word for each position.",
#[cfg(feature = "layout_bolt")]
Self::reset__select_word_template => "Select {0} word", Self::reset__select_word_template => "Select {0} word",
#[cfg(feature = "layout_caesar")]
Self::reset__select_word_template => "Select {0} word",
#[cfg(feature = "layout_delizia")]
Self::reset__select_word_template => "Select {0} word",
#[cfg(feature = "layout_eckhart")]
Self::reset__select_word_template => "Select word #{0} from your wallet backup",
Self::reset__select_word_x_of_y_template => "Select word {0} of {1}:", Self::reset__select_word_x_of_y_template => "Select word {0} of {1}:",
Self::reset__set_it_to_count_template => "Set it to {0} and you will need ", Self::reset__set_it_to_count_template => "Set it to {0} and you will need ",
Self::reset__share_checked_successfully_template => "Share #{0} checked successfully.", Self::reset__share_checked_successfully_template => "Share #{0} checked successfully.",
@ -2718,7 +2726,14 @@ impl TranslatedString {
Self::reset__repeat_for_all_shares => "Repeat for all shares.", Self::reset__repeat_for_all_shares => "Repeat for all shares.",
Self::homescreen__settings_subtitle => "Settings", Self::homescreen__settings_subtitle => "Settings",
Self::homescreen__settings_title => "Homescreen", Self::homescreen__settings_title => "Homescreen",
#[cfg(feature = "layout_bolt")]
Self::reset__the_word_is_repeated => "The word is repeated", Self::reset__the_word_is_repeated => "The word is repeated",
#[cfg(feature = "layout_caesar")]
Self::reset__the_word_is_repeated => "The word is repeated",
#[cfg(feature = "layout_delizia")]
Self::reset__the_word_is_repeated => "The word is repeated",
#[cfg(feature = "layout_eckhart")]
Self::reset__the_word_is_repeated => "The word appears multiple times in the backup.",
Self::tutorial__title_lets_begin => "Let's begin", Self::tutorial__title_lets_begin => "Let's begin",
Self::tutorial__did_you_know => "Did you know?", Self::tutorial__did_you_know => "Did you know?",
Self::tutorial__first_wallet => "The Trezor Model One, created in 2013,\nwas the world's first hardware wallet.", Self::tutorial__first_wallet => "The Trezor Model One, created in 2013,\nwas the world's first hardware wallet.",
@ -2762,6 +2777,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Self::ethereum__unknown_contract_address_short => "Unknown contract address.", Self::ethereum__unknown_contract_address_short => "Unknown contract address.",
Self::instructions__keep_holding => "Keep holding", Self::instructions__keep_holding => "Keep holding",
Self::reset__share_words_first => "Write down the first word from the backup.",
} }
} }
@ -4140,6 +4156,7 @@ impl TranslatedString {
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
Qstr::MP_QSTR_ethereum__unknown_contract_address_short => Some(Self::ethereum__unknown_contract_address_short), Qstr::MP_QSTR_ethereum__unknown_contract_address_short => Some(Self::ethereum__unknown_contract_address_short),
Qstr::MP_QSTR_instructions__keep_holding => Some(Self::instructions__keep_holding), Qstr::MP_QSTR_instructions__keep_holding => Some(Self::instructions__keep_holding),
Qstr::MP_QSTR_reset__share_words_first => Some(Self::reset__share_words_first),
_ => None, _ => None,
} }
} }

View File

@ -878,7 +878,7 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
} }
extern "C" fn new_show_share_words_delizia( extern "C" fn new_show_share_words_extended(
n_args: usize, n_args: usize,
args: *const Obj, args: *const Obj,
kwargs: *mut Map, kwargs: *mut Map,
@ -898,7 +898,7 @@ extern "C" fn new_show_share_words_delizia(
let words: Vec<TString, 33> = util::iter_into_vec(words)?; let words: Vec<TString, 33> = util::iter_into_vec(words)?;
let layout = ModelUI::show_share_words_delizia( let layout = ModelUI::show_share_words_extended(
words, words,
subtitle, subtitle,
instructions, instructions,
@ -1589,7 +1589,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// """Show mnemonic for backup.""" /// """Show mnemonic for backup."""
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
/// def show_share_words_delizia( /// def show_share_words_extended(
/// *, /// *,
/// words: Iterable[str], /// words: Iterable[str],
/// subtitle: str | None, /// subtitle: str | None,
@ -1599,7 +1599,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:
/// """Show mnemonic for wallet backup preceded by an instruction screen and followed by a /// """Show mnemonic for wallet backup preceded by an instruction screen and followed by a
/// confirmation screen.""" /// confirmation screen."""
Qstr::MP_QSTR_show_share_words_delizia => obj_fn_kw!(0, new_show_share_words_delizia).as_obj(), Qstr::MP_QSTR_show_share_words_extended => obj_fn_kw!(0, new_show_share_words_extended).as_obj(),
/// def show_simple( /// def show_simple(
/// *, /// *,

View File

@ -1013,7 +1013,7 @@ impl FirmwareUI for UIBolt {
Ok(layout) Ok(layout)
} }
fn show_share_words_delizia( fn show_share_words_extended(
_words: heapless::Vec<TString<'static>, 33>, _words: heapless::Vec<TString<'static>, 33>,
_subtitle: Option<TString<'static>>, _subtitle: Option<TString<'static>>,
_instructions: Obj, _instructions: Obj,

View File

@ -1133,7 +1133,7 @@ impl FirmwareUI for UICaesar {
Ok(layout) Ok(layout)
} }
fn show_share_words_delizia( fn show_share_words_extended(
_words: heapless::Vec<TString<'static>, 33>, _words: heapless::Vec<TString<'static>, 33>,
_subtitle: Option<TString<'static>>, _subtitle: Option<TString<'static>>,
_instructions: Obj, _instructions: Obj,

View File

@ -996,11 +996,11 @@ impl FirmwareUI for UIDelizia {
_title: Option<TString<'static>>, _title: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError( Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(
c"use show_share_words_delizia instead", c"use show_share_words_extended instead",
)) ))
} }
fn show_share_words_delizia( fn show_share_words_extended(
words: heapless::Vec<TString<'static>, 33>, words: heapless::Vec<TString<'static>, 33>,
subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
instructions: Obj, instructions: Obj,

View File

@ -1,3 +1,5 @@
pub mod eckhart_swipe_flow_test; pub mod eckhart_swipe_flow_test;
pub mod show_share_words;
pub use eckhart_swipe_flow_test::new_eckhart_swipe_flow; pub use eckhart_swipe_flow_test::new_eckhart_swipe_flow;
pub use show_share_words::new_show_share_words_flow;

View File

@ -0,0 +1,111 @@
use crate::{
error,
strutil::TString,
translations::TR,
ui::{
component::{
text::{
op::OpTextLayout,
paragraphs::{Paragraph, ParagraphSource},
},
ComponentExt, FormattedText,
},
flow::{
base::{Decision, DecisionBuilder as _},
FlowController, FlowMsg, SwipeFlow,
},
geometry::{Direction, LinearPlacement},
},
};
use heapless::Vec;
use super::super::{
component::{
ActionBar, Button, Header, ShareWordsScreen, ShareWordsScreenMsg, TextScreen, TextScreenMsg,
},
fonts, theme,
};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ShowShareWords {
Instruction,
ShareWords,
Confirm,
}
impl FlowController for ShowShareWords {
#[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: Direction) -> Decision {
match (self, direction) {
_ => self.do_nothing(),
}
}
fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) {
(Self::Instruction, FlowMsg::Cancelled) => self.return_msg(FlowMsg::Cancelled),
(Self::Instruction, FlowMsg::Confirmed) => Self::ShareWords.goto(),
(Self::ShareWords, FlowMsg::Cancelled) => Self::Instruction.goto(),
(Self::ShareWords, FlowMsg::Confirmed) => Self::Confirm.goto(),
(Self::Confirm, FlowMsg::Cancelled) => Self::ShareWords.goto(),
(Self::Confirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
_ => self.do_nothing(),
}
}
}
pub fn new_show_share_words_flow(
words: Vec<TString<'static>, 33>,
_subtitle: TString<'static>,
instruction: Paragraph<'static>,
text_confirm: TString<'static>,
) -> Result<SwipeFlow, error::Error> {
let instruction = TextScreen::new(
instruction
.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(Header::new(TR::reset__recovery_wallet_backup_title.into()))
.with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CHEVRON_UP),
Button::with_text(TR::buttons__continue.into()),
))
.map(|msg| match msg {
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
_ => Some(FlowMsg::Cancelled),
});
let share_words = ShareWordsScreen::new(words).map(|msg| match msg {
ShareWordsScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
ShareWordsScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
});
let op_confirm =
OpTextLayout::new(theme::TEXT_NORMAL).text(text_confirm, fonts::FONT_SATOSHI_REGULAR_38);
let confirm = TextScreen::new(FormattedText::new(op_confirm))
.with_header(Header::new(TR::reset__recovery_wallet_backup_title.into()))
.with_action_bar(ActionBar::new_double(
Button::with_icon(theme::ICON_CHEVRON_LEFT),
Button::with_text(TR::buttons__hold_to_confirm.into())
.styled(theme::button_confirm())
.with_long_press(theme::CONFIRM_HOLD_DURATION),
))
.map(|msg| match msg {
TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
TextScreenMsg::Menu => Some(FlowMsg::Cancelled),
});
let res = SwipeFlow::new(&ShowShareWords::Instruction)?
.with_page(&ShowShareWords::Instruction, instruction)?
.with_page(&ShowShareWords::ShareWords, share_words)?
.with_page(&ShowShareWords::Confirm, confirm)?;
Ok(res)
}

View File

@ -25,7 +25,7 @@ use crate::{
use super::{ use super::{
component::{ActionBar, Button, Header, HeaderMsg, Hint, TextScreen}, component::{ActionBar, Button, Header, HeaderMsg, Hint, TextScreen},
fonts, theme, UIEckhart, flow, fonts, theme, UIEckhart,
}; };
impl FirmwareUI for UIEckhart { impl FirmwareUI for UIEckhart {
@ -553,17 +553,33 @@ impl FirmwareUI for UIEckhart {
_words: heapless::Vec<TString<'static>, 33>, _words: heapless::Vec<TString<'static>, 33>,
_title: Option<TString<'static>>, _title: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(
c"use show_share_words_extended instead",
))
} }
fn show_share_words_delizia( fn show_share_words_extended(
_words: heapless::Vec<TString<'static>, 33>, words: heapless::Vec<TString<'static>, 33>,
_subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
_instructions: Obj, instructions: Obj,
// Irrelevant for Eckhart because the footer is dynamic
_text_footer: Option<TString<'static>>, _text_footer: Option<TString<'static>>,
_text_confirm: TString<'static>, text_confirm: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented")) // TODO: add support for multiple instructions
let instruction: TString = IterBuf::new()
.try_iterate(instructions)?
.next()
.unwrap()
.try_into()?;
let flow = flow::show_share_words::new_show_share_words_flow(
words,
subtitle.unwrap_or(TString::empty()),
Paragraph::new(&theme::TEXT_REGULAR, instruction),
text_confirm,
)?;
Ok(flow)
} }
fn show_remaining_shares(_pages_iterable: Obj) -> Result<impl LayoutMaybeTrace, Error> { fn show_remaining_shares(_pages_iterable: Obj) -> Result<impl LayoutMaybeTrace, Error> {

View File

@ -340,8 +340,8 @@ pub trait FirmwareUI {
) -> Result<impl LayoutMaybeTrace, Error>; ) -> Result<impl LayoutMaybeTrace, Error>;
// TODO: merge with `show_share_words` instead of having specific version for // TODO: merge with `show_share_words` instead of having specific version for
// Delizia UI // Delizia/Eckhart UI
fn show_share_words_delizia( fn show_share_words_extended(
words: Vec<TString<'static>, 33>, words: Vec<TString<'static>, 33>,
subtitle: Option<TString<'static>>, subtitle: Option<TString<'static>>,
instructions: Obj, // TODO: replace Obj instructions: Obj, // TODO: replace Obj

View File

@ -603,7 +603,7 @@ def show_share_words(
# rust/src/ui/api/firmware_micropython.rs # rust/src/ui/api/firmware_micropython.rs
def show_share_words_delizia( def show_share_words_extended(
*, *,
words: Iterable[str], words: Iterable[str],
subtitle: str | None, subtitle: str | None,

View File

@ -665,6 +665,7 @@ class TR:
reset__set_it_to_count_template: str = "Set it to {0} and you will need " reset__set_it_to_count_template: str = "Set it to {0} and you will need "
reset__share_checked_successfully_template: str = "Share #{0} checked successfully." reset__share_checked_successfully_template: str = "Share #{0} checked successfully."
reset__share_completed_template: str = "Share #{0} completed" reset__share_completed_template: str = "Share #{0} completed"
reset__share_words_first: str = "Write down the first word from the backup."
reset__share_words_title: str = "Standard backup" reset__share_words_title: str = "Standard backup"
reset__slip39_checklist_more_info_threshold: str = "The threshold sets the minumum number of shares needed to recover your wallet." reset__slip39_checklist_more_info_threshold: str = "The threshold sets the minumum number of shares needed to recover your wallet."
reset__slip39_checklist_more_info_threshold_example_template: str = "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet." reset__slip39_checklist_more_info_threshold_example_template: str = "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet."

View File

@ -38,7 +38,7 @@ def show_share_words(
text_confirm = TR.reset__words_written_down_template.format(words_count) text_confirm = TR.reset__words_written_down_template.format(words_count)
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui_api.show_share_words_delizia( trezorui_api.show_share_words_extended(
words=share_words, words=share_words,
subtitle=subtitle, subtitle=subtitle,
instructions=instructions, instructions=instructions,

View File

@ -16,8 +16,31 @@ def show_share_words(
share_index: int | None = None, share_index: int | None = None,
group_index: int | None = None, group_index: int | None = None,
) -> Awaitable[None]: ) -> Awaitable[None]:
# FIXME: not implemented if share_index is None:
raise NotImplemented subtitle = None
elif group_index is None:
subtitle = TR.reset__recovery_share_title_template.format(share_index + 1)
else:
subtitle = TR.reset__group_share_title_template.format(
group_index + 1, share_index + 1
)
words_count = len(share_words)
description = None
# Eckhart currently has only one instruction, other are shown in the hint area
instructions = [TR.reset__write_down_words_template.format(words_count)]
assert len(instructions) == 1
text_confirm = TR.reset__words_written_down_template.format(words_count)
return raise_if_not_confirmed(
trezorui_api.show_share_words_extended(
words=share_words,
subtitle=subtitle,
instructions=instructions,
text_footer=description,
text_confirm=text_confirm,
),
None,
)
async def select_word( async def select_word(
@ -46,9 +69,7 @@ async def select_word(
result = await interact( result = await interact(
trezorui_api.select_word( trezorui_api.select_word(
title=title, title=title,
description=TR.reset__select_word_x_of_y_template.format( description=TR.reset__select_word_template.format(checked_index + 1),
checked_index + 1, count
),
words=(words[0], words[1], words[2]), words=(words[0], words[1], words[2]),
), ),
None, None,

View File

@ -662,12 +662,18 @@
"reset__required_number_of_groups": "The required number of groups for recovery.", "reset__required_number_of_groups": "The required number of groups for recovery.",
"reset__select_correct_word": "Select the correct word for each position.", "reset__select_correct_word": "Select the correct word for each position.",
"reset__select_threshold": "Select the minimum shares required to recover your wallet.", "reset__select_threshold": "Select the minimum shares required to recover your wallet.",
"reset__select_word_template": "Select {0} word", "reset__select_word_template": {
"Bolt": "Select {0} word",
"Caesar": "Select {0} word",
"Delizia": "Select {0} word",
"Eckhart": "Select word #{0} from your wallet backup"
},
"reset__select_word_x_of_y_template": "Select word {0} of {1}:", "reset__select_word_x_of_y_template": "Select word {0} of {1}:",
"reset__set_it_to_count_template": "Set it to {0} and you will need ", "reset__set_it_to_count_template": "Set it to {0} and you will need ",
"reset__share_checked_successfully_template": "Share #{0} checked successfully.", "reset__share_checked_successfully_template": "Share #{0} checked successfully.",
"reset__share_completed_template": "Share #{0} completed", "reset__share_completed_template": "Share #{0} completed",
"reset__share_words_title": "Standard backup", "reset__share_words_title": "Standard backup",
"reset__share_words_first": "Write down the first word from the backup.",
"reset__slip39_checklist_more_info_threshold": "The threshold sets the minumum number of shares needed to recover your wallet.", "reset__slip39_checklist_more_info_threshold": "The threshold sets the minumum number of shares needed to recover your wallet.",
"reset__slip39_checklist_more_info_threshold_example_template": "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet.", "reset__slip39_checklist_more_info_threshold_example_template": "If you set {0} out of {1} shares, you'll need {2} backup shares to recover your wallet.",
"reset__slip39_checklist_num_groups": "Number of groups", "reset__slip39_checklist_num_groups": "Number of groups",
@ -684,7 +690,12 @@
"reset__slip39_checklist_write_down": "Write down and check all shares", "reset__slip39_checklist_write_down": "Write down and check all shares",
"reset__slip39_checklist_write_down_recovery": "Write down & check all wallet backup shares", "reset__slip39_checklist_write_down_recovery": "Write down & check all wallet backup shares",
"reset__the_threshold_sets_the_number_of_shares": "The threshold sets the number of shares ", "reset__the_threshold_sets_the_number_of_shares": "The threshold sets the number of shares ",
"reset__the_word_is_repeated": "The word is repeated", "reset__the_word_is_repeated": {
"Bolt": "The word is repeated",
"Caesar": "The word is repeated",
"Delizia": "The word is repeated",
"Eckhart": "The word appears multiple times in the backup."
},
"reset__threshold_info": "= minimum number of unique word lists used for recovery.", "reset__threshold_info": "= minimum number of unique word lists used for recovery.",
"reset__title_backup_is_done": "Backup is done", "reset__title_backup_is_done": "Backup is done",
"reset__title_create_wallet": "Create wallet", "reset__title_create_wallet": "Create wallet",

View File

@ -974,5 +974,6 @@
"972": "ethereum__interaction_contract", "972": "ethereum__interaction_contract",
"973": "misc__enable_labeling", "973": "misc__enable_labeling",
"974": "ethereum__unknown_contract_address_short", "974": "ethereum__unknown_contract_address_short",
"975": "instructions__keep_holding" "975": "instructions__keep_holding",
"976": "reset__share_words_first"
} }

View File

@ -1,8 +1,8 @@
{ {
"current": { "current": {
"merkle_root": "43c08e81d71c1c28d77b1650fc96b0dfcd473fde0c922717e5588baeeb581bd3", "merkle_root": "31454a46346717afd55e29f2a23f6cf64e4e23679af127d774895aa3a700c764",
"datetime": "2025-02-14T16:12:57.065880", "datetime": "2025-02-18T22:33:29.769368",
"commit": "3dabb94653e04856efc89d07c67b7e6f0c587f8c" "commit": "778d5265dc258c1b0b74e8433acbd08d090b8746"
}, },
"history": [ "history": [
{ {