feat(suite): Add adding a new shamir group into an existing setup
@@ -88,13 +88,13 @@ Browser (User Agent), System and HW specifications, Suite version, instance id s
|
||||
major_version: 2,
|
||||
minor_version: 4,
|
||||
model: T,
|
||||
needs_backup: False,
|
||||
backup_availability: 0,
|
||||
no_backup: False,
|
||||
passphrase_always_on_device: False,
|
||||
passphrase_protection: True,
|
||||
patch_version: 2,
|
||||
pin_protection: True,
|
||||
recovery_mode: False,
|
||||
recovery_status: 0,
|
||||
revision: 9276b1702361f70e094286e2f89e919d8a230d5c,
|
||||
safety_checks: Strict,
|
||||
sd_card_present: False,
|
||||
|
||||
@@ -71,6 +71,7 @@ export type FlexProps = FrameProps & {
|
||||
flex?: Flex;
|
||||
flexWrap?: FlexWrap;
|
||||
isReversed?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Flex = ({
|
||||
@@ -83,6 +84,7 @@ const Flex = ({
|
||||
flex = 'auto',
|
||||
flexWrap = 'nowrap',
|
||||
isReversed = false,
|
||||
className,
|
||||
}: FlexProps) => {
|
||||
const frameProps = {
|
||||
margin,
|
||||
@@ -90,6 +92,7 @@ const Flex = ({
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={className}
|
||||
$gap={gap}
|
||||
$justifyContent={justifyContent}
|
||||
$alignItems={alignItems}
|
||||
|
||||
@@ -45,6 +45,8 @@ export type PngImage = keyof typeof PNG_IMAGES;
|
||||
export const PNG_IMAGES = {
|
||||
CLOUDY: 'cloudy.png',
|
||||
CLOUDY_2x: 'cloudy@2x.png',
|
||||
CREATE_SHAMIR_GROUP: 'create-shamir-group.png',
|
||||
CREATE_SHAMIR_GROUP_2x: 'create-shamir-group@2x.png',
|
||||
BACKUP: 'backup.png',
|
||||
BACKUP_2x: 'backup@2x.png',
|
||||
CHECK_SHIELD: 'check-shield.png',
|
||||
@@ -69,6 +71,8 @@ export const PNG_IMAGES = {
|
||||
PIN_LOCKED_2x: 'pin-locked@2x.png',
|
||||
RECOVERY: 'recovery.png',
|
||||
RECOVERY_2x: 'recovery@2x.png',
|
||||
SHAMIR_SHARES: 'shamir-shares.png',
|
||||
SHAMIR_SHARES_2x: 'shamir-shares@2x.png',
|
||||
UNDERSTAND: 'understand.png',
|
||||
UNDERSTAND_2x: 'understand@2x.png',
|
||||
WALLET: 'wallet.png',
|
||||
|
||||
@@ -15,6 +15,7 @@ export const ICONS = {
|
||||
BACK: require('../../../images/icons/back.svg'),
|
||||
BACKEND: require('../../../images/icons/backend.svg'),
|
||||
BACKUP: require('../../../images/icons/backup.svg'),
|
||||
BACKUP_2: require('../../../images/icons/backup_2.svg'),
|
||||
BINARY: require('../../../images/icons/binary.svg'),
|
||||
BLOCKED: require('../../../images/icons/blocked.svg'),
|
||||
BROADCAST: require('../../../images/icons/broadcast.svg'),
|
||||
@@ -50,12 +51,14 @@ export const ICONS = {
|
||||
ADDRESS_NEXT: require('../../../images/icons/address_next.svg'),
|
||||
ADDRESS_PIXEL_CONTINUES: require('../../../images/icons/address_pixel_continues.svg'),
|
||||
ADDRESS_PIXEL_NEXT: require('../../../images/icons/address_pixel_next.svg'),
|
||||
CAMERA_SLASH: require('../../../images/icons/camera_slash.svg'),
|
||||
DOWN: require('../../../images/icons/down.svg'),
|
||||
DROPBOX: require('../../../images/icons/dropbox.svg'),
|
||||
EVERSTAKE_LOGO: require('../../../images/icons/everstake_logo.svg'),
|
||||
EJECT: require('../../../images/icons/eject.svg'),
|
||||
EXPERIMENTAL: require('../../../images/icons/experimental.svg'),
|
||||
EXTERNAL_LINK: require('../../../images/icons/external_link.svg'),
|
||||
EYE_SLASH: require('../../../images/icons/eye_slash.svg'),
|
||||
FEEDBACK: require('../../../images/icons/feedback.svg'),
|
||||
FILE: require('../../../images/icons/file.svg'),
|
||||
FINGERPRINT: require('../../../images/icons/fingerprint.svg'),
|
||||
@@ -125,13 +128,10 @@ export const ICONS = {
|
||||
SIGNATURE: require('../../../images/icons/signature.svg'),
|
||||
SPINNER: require('../../../images/icons/spinner.svg'),
|
||||
STOP: require('../../../images/icons/stop.svg'),
|
||||
TREZOR_T1B1: require('../../../images/icons/trezor_t1b1.svg'),
|
||||
TREZOR_T2T1: require('../../../images/icons/trezor_t2t1.svg'),
|
||||
TREZOR_T2B1: require('../../../images/icons/trezor_t2b1.svg'),
|
||||
TREZOR_T3T1: require('../../../images/icons/trezor_t3t1.svg'),
|
||||
TABLE: require('../../../images/icons/table.svg'),
|
||||
TAG_MINIMAL: require('../../../images/icons/tag_minimal.svg'),
|
||||
TAG: require('../../../images/icons/tag.svg'),
|
||||
TIMER: require('../../../images/icons/timer.svg'),
|
||||
TOR_MINIMAL: require('../../../images/icons/tor_minimal.svg'),
|
||||
TOR: require('../../../images/icons/tor.svg'),
|
||||
TRANSFER: require('../../../images/icons/transfer.svg'),
|
||||
@@ -139,6 +139,10 @@ export const ICONS = {
|
||||
TREND_UP: require('../../../images/icons/trend_up.svg'),
|
||||
TREND_UP_THIN: require('../../../images/icons/trend_up_thin.svg'),
|
||||
TREZOR_LOGO: require('../../../images/icons/trezor_logo.svg'),
|
||||
TREZOR_T1B1: require('../../../images/icons/trezor_t1b1.svg'),
|
||||
TREZOR_T2T1: require('../../../images/icons/trezor_t2t1.svg'),
|
||||
TREZOR_T2B1: require('../../../images/icons/trezor_t2b1.svg'),
|
||||
TREZOR_T3T1: require('../../../images/icons/trezor_t3t1.svg'),
|
||||
UNLINK: require('../../../images/icons/unlink.svg'),
|
||||
UP: require('../../../images/icons/up.svg'),
|
||||
USERS: require('../../../images/icons/users.svg'),
|
||||
|
||||
3
packages/components/src/images/icons/backup_2.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 33 33" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.813 6.016c-.508.16-.979.587-1.221 1.106l-.154.33-.015 9.319c-.016 10.138-.029 9.675.286 10.173.317.502.798.829 1.377.936.457.084 22.206.084 22.663 0a2.039 2.039 0 0 0 1.377-.936c.315-.498.302-.035.286-10.173l-.014-9.319-.159-.342c-.184-.4-.588-.808-.997-1.008l-.274-.134L16.5 5.957c-9.105-.008-11.513.004-11.687.059M27.39 16.913v8.992H5.445V7.92H27.39v8.993M9.982 12.026c-.262.139-.402.293-.5.549a.983.983 0 0 0 .487 1.223l.241.117h6.207c6.132 0 6.211-.001 6.439-.112a.982.982 0 0 0 .021-1.76c-.208-.106-.348-.108-6.471-.106-5.598.002-6.276.012-6.424.089m-.086 4.049a1.152 1.152 0 0 0-.469.684c-.076.404.195.88.608 1.068.212.096.524.101 6.383.101 5.858 0 6.17-.005 6.382-.101.413-.188.684-.664.608-1.068a1.152 1.152 0 0 0-.469-.684l-.185-.125H10.081l-.185.125m.201 3.917c-.485.173-.783.716-.646 1.176.08.265.288.526.524.655.159.087.63.096 6.195.111 3.312.009 6.151.001 6.308-.017.649-.077 1.073-.671.898-1.255-.087-.29-.409-.613-.69-.691-.153-.042-2.025-.06-6.296-.058-5.063.001-6.114.015-6.293.079"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
packages/components/src/images/icons/camera_slash.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.661 4.054c-.538.229-.79.821-.572 1.339.042.099.367.498.723.887l.647.707-.256.001c-.971.007-1.708.293-2.315.9a2.852 2.852 0 0 0-.85 1.712c-.036.297-.05 2.953-.04 7.707l.015 7.253.12.351a3.13 3.13 0 0 0 1.534 1.759c.234.116.556.235.714.264.183.034 3.714.053 9.763.053h9.477l.426.448c.464.488.694.603 1.102.548.615-.082 1.008-.793.762-1.376-.088-.208-20.036-22.184-20.341-22.41-.224-.165-.686-.238-.909-.143m5.996.025c-.259.093-.449.282-.694.691-.305.51-.251.998.15 1.35.236.207.375.247.756.215a.774.774 0 0 0 .501-.193l.21-.169h6.87l.822 1.238c.452.681.891 1.318.976 1.416.318.367.242.357 2.779.386 2.386.028 2.394.029 2.658.26.055.048.139.159.187.247.08.146.09.786.115 7.093l.026 6.934.127.203c.201.324.474.474.86.474s.659-.15.86-.474l.127-.203.015-6.747c.011-4.588-.003-6.892-.042-7.2a2.937 2.937 0 0 0-1.653-2.283c-.611-.297-.918-.33-3.036-.33h-1.748l-.873-1.312c-.48-.721-.94-1.368-1.023-1.438a1.114 1.114 0 0 0-.349-.181c-.128-.036-1.637-.055-4.305-.053-3.377.001-4.143.015-4.316.076m-1.616 6.853c.96 1.056 1.746 1.944 1.746 1.974 0 .029-.109.202-.242.382a5.819 5.819 0 0 0-.855 1.832c-.758 3.003 1.012 5.958 4.03 6.725.551.14 2.047.142 2.56.003a6.079 6.079 0 0 0 1.464-.595c.232-.132.435-.238.452-.236.017.002.839.896 1.826 1.987l1.795 1.983-8.462.014c-9.444.016-8.729.045-9.095-.361-.103-.114-.202-.286-.219-.383-.017-.098-.025-3.433-.016-7.412L5.04 9.61l.161-.199c.311-.384.372-.398 1.813-.398h1.282l1.745 1.919m5.505 6.055 2.277 2.506-.368.174a3.466 3.466 0 0 1-3.935-.71c-.999-1.019-1.293-2.54-.736-3.81.191-.437.394-.764.443-.712l2.319 2.552"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
packages/components/src/images/icons/eye_slash.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.661 4.054c-.538.229-.791.822-.571 1.339.042.099.63.789 1.306 1.533.677.743 1.231 1.367 1.231 1.386 0 .019-.114.106-.254.193-2.068 1.29-4.182 3.499-5.615 5.865-.865 1.43-.897 1.598-.463 2.442 1.268 2.468 3.459 4.919 5.799 6.489 2.068 1.388 4.061 2.149 6.666 2.547.722.11 2.975.155 3.822.076a15.598 15.598 0 0 0 4.429-1.075l.496-.202 1.388 1.53c.763.841 1.467 1.584 1.564 1.651.613.421 1.455.003 1.514-.751a1.042 1.042 0 0 0-.058-.464c-.045-.102-.518-.666-1.053-1.253A3497.39 3497.39 0 0 1 9.794 7.68C8.132 5.847 6.68 4.279 6.569 4.197c-.223-.165-.685-.238-.908-.143m8.698 2.025c-.881.1-1.288.175-1.44.268a1 1 0 0 0-.186 1.575c.292.284.494.315 1.286.202 1.012-.146 3.123-.132 4.141.026 2.9.452 5.334 1.657 7.562 3.743 1.048.98 2.444 2.761 3.028 3.862l.13.245-.133.253c-.233.443-1.008 1.564-1.538 2.227-.281.352-.853.981-1.269 1.397-.718.718-.761.773-.824 1.052-.057.255-.053.325.027.535.188.492.722.766 1.21.62.252-.075.889-.632 1.571-1.372 1.523-1.655 3.063-4.028 3.063-4.721 0-.294-.319-.961-.909-1.901-1.849-2.948-4.574-5.384-7.49-6.694-1.38-.619-2.647-.982-4.348-1.245-.589-.091-3.277-.141-3.881-.072m-4.182 4.998c.592.651 1.076 1.198 1.076 1.216 0 .018-.099.179-.22.357-.323.475-.672 1.248-.833 1.842-.129.476-.141.608-.141 1.508s.012 1.032.141 1.508c.171.63.523 1.391.89 1.921.324.47 1.075 1.213 1.543 1.529a7.016 7.016 0 0 0 1.859.841c.469.127.617.141 1.481.144 1.247.005 1.793-.121 2.8-.642l.434-.223c.026-.013 1.128 1.165 1.768 1.891l.102.115-.739.241c-1.525.497-2.559.649-4.365.645-1.149-.003-1.514-.024-2.133-.12-3.043-.475-5.588-1.78-7.815-4.008a16.473 16.473 0 0 1-2.545-3.213c-.183-.299-.333-.583-.333-.63 0-.126.777-1.35 1.295-2.037a15.97 15.97 0 0 1 3.208-3.21c.703-.524 1.377-.949 1.42-.897.017.021.516.571 1.107 1.222m6.436-.918c-.256.09-.586.442-.633.673-.113.569.188 1.083.716 1.22l.437.112c.375.095.974.408 1.328.692a3.97 3.97 0 0 1 1.492 2.564c.08.501.21.758.467.929.43.284.928.22 1.283-.164.242-.263.299-.474.247-.919-.089-.763-.384-1.646-.758-2.27-.885-1.473-2.22-2.46-3.831-2.834-.425-.098-.474-.099-.748-.003m-1.311 6.559c2.92 3.215 2.689 2.837 1.898 3.104-.377.128-.51.145-1.147.147-.841.004-1.179-.069-1.866-.403-.383-.186-.538-.305-.988-.76-.586-.591-.826-.977-1.047-1.686-.163-.52-.177-1.59-.029-2.133.157-.575.485-1.23.57-1.139.024.025 1.198 1.316 2.609 2.87"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
3
packages/components/src/images/icons/timer.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.596.688a.738.738 0 0 0-.417.429.852.852 0 0 0 .022.571c.065.13.183.261.293.325.156.092.27.106.866.11.448.002.51.006.336.018-1.735.125-3.237 1.104-4.012 2.615a4.873 4.873 0 0 0-.498 1.505c-.051.326-.051 1.152 0 1.478.185 1.165.798 2.255 1.679 2.987.688.571 1.522.95 2.396 1.088.325.051 1.153.051 1.478 0a4.987 4.987 0 0 0 2.396-1.088c.881-.732 1.495-1.823 1.679-2.987.051-.325.051-1.153 0-1.478-.184-1.164-.798-2.255-1.679-2.987a4.917 4.917 0 0 0-2.831-1.133c-.174-.012-.112-.016.336-.018.596-.004.71-.018.866-.11a.847.847 0 0 0 .294-.325c.044-.086.05-.124.05-.308s-.006-.224-.052-.32A.825.825 0 0 0 7.44.702L7.33.65 6.02.645 4.71.641l-.114.047M6.42 3.649c.484.064.949.227 1.339.469.184.114.461.325.541.412.046.049.04.048-.057-.016a.741.741 0 0 0-.713-.034c-.104.05-.255.194-1.142 1.084C5.219 6.738 5.271 6.672 5.27 7c0 .164.007.205.053.304a.765.765 0 0 0 .354.361c.121.06.148.065.323.065.328-.001.261.052 1.436-1.118.89-.887 1.034-1.038 1.084-1.142a.741.741 0 0 0-.034-.713c-.064-.097-.065-.103-.016-.057.162.149.462.591.585.86.329.72.407 1.464.232 2.208a3.265 3.265 0 0 1-.866 1.579 3.362 3.362 0 0 1-4.51.304A3.398 3.398 0 0 1 2.819 8.13c-.537-1.503.052-3.162 1.425-4.014a3.396 3.396 0 0 1 2.176-.467"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -22,253 +22,268 @@ const customFirmwareBuild =
|
||||
// integration tests in trezor-firmware repo use 2.99.99 version
|
||||
process.env.TESTS_FIRMWARE === '2.99.99';
|
||||
|
||||
const tests = [
|
||||
{
|
||||
description: 'T2T1/T2B1 features',
|
||||
skip: ['1'],
|
||||
params: {},
|
||||
result: {
|
||||
device_id: expect.any(String),
|
||||
vendor: 'trezor.io',
|
||||
major_version: expect.any(Number),
|
||||
minor_version: expect.any(Number),
|
||||
patch_version: expect.any(Number),
|
||||
bootloader_mode: null,
|
||||
pin_protection: expect.any(Boolean),
|
||||
passphrase_protection: expect.any(Boolean),
|
||||
language: 'en-US',
|
||||
label: expect.any(String),
|
||||
initialized: true,
|
||||
revision: expect.any(String),
|
||||
bootloader_hash: null,
|
||||
imported: null,
|
||||
unlocked: expect.any(Boolean),
|
||||
firmware_present: null,
|
||||
backup_availability: expect.any(String),
|
||||
// flags: expect.any(Number), // flags may be changed by applyFlags test
|
||||
model: expect.any(String), // "T" | "R"
|
||||
internal_model: expect.any(String),
|
||||
fw_major: null,
|
||||
fw_minor: null,
|
||||
fw_patch: null,
|
||||
// fw_vendor: null, // "EMULATOR" since 2.5.1
|
||||
unfinished_backup: expect.any(Boolean),
|
||||
no_backup: expect.any(Boolean),
|
||||
recovery_status: 'Nothing',
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Monero',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
'Capability_PassphraseEntry',
|
||||
'Capability_NEM',
|
||||
'Capability_EOS',
|
||||
]),
|
||||
backup_type: 'Bip39',
|
||||
sd_card_present: expect.any(Boolean), // T2T1 true, T2B1 false
|
||||
sd_protection: false,
|
||||
wipe_code_protection: false,
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: false,
|
||||
flags: expect.any(Number),
|
||||
fw_vendor: expect.any(String),
|
||||
safety_checks: expect.any(String),
|
||||
auto_lock_delay_ms: expect.any(Number),
|
||||
display_rotation: expect.any(Number),
|
||||
experimental_features: expect.any(Boolean),
|
||||
},
|
||||
legacyResults: [
|
||||
{
|
||||
rules: ['<2.4.2'], // 2.4.2 removed Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
'Capability_PassphraseEntry',
|
||||
]),
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.2.1'], // 2.2.1 added PassphraseEntry capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
]),
|
||||
backup_type: 'Bip39',
|
||||
session_id: null,
|
||||
passphrase_always_on_device: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.1.6'], // 2.1.6 added ShamirGroups capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
]),
|
||||
backup_type: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.1.5'], // 2.1.5 added Shamir and Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
backup_type: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'T1B1 features',
|
||||
skip: ['2'],
|
||||
params: {},
|
||||
result: {
|
||||
device_id: expect.any(String),
|
||||
vendor: 'trezor.io',
|
||||
major_version: customFirmwareBuild ? expect.any(Number) : Number(major),
|
||||
minor_version: customFirmwareBuild ? expect.any(Number) : Number(minor),
|
||||
patch_version: customFirmwareBuild ? expect.any(Number) : Number(patch),
|
||||
bootloader_mode: null,
|
||||
pin_protection: expect.any(Boolean),
|
||||
passphrase_protection: expect.any(Boolean),
|
||||
language: 'en-US',
|
||||
label: expect.any(String),
|
||||
initialized: true,
|
||||
revision: expect.any(String),
|
||||
bootloader_hash: expect.any(String), // difference between T2T1
|
||||
imported: true, // difference between T2T1
|
||||
unlocked: expect.any(Boolean),
|
||||
firmware_present: null,
|
||||
backup_availability: expect.any(String),
|
||||
// flags: null, // flags may be changed by applyFlags test
|
||||
model: '1',
|
||||
internal_model: DeviceModelInternal.T1B1,
|
||||
fw_major: null,
|
||||
fw_minor: null,
|
||||
fw_patch: null,
|
||||
// fw_vendor: null, // "EMULATOR" since 1.11.1
|
||||
unfinished_backup: expect.any(Boolean),
|
||||
no_backup: expect.any(Boolean),
|
||||
recovery_status: 'Nothing', // difference between T2T1
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
recovery_type: undefined, // difference between T2T1
|
||||
sd_card_present: null, // no sdcard in T1B1
|
||||
sd_protection: null, // no sdcard in T1B1
|
||||
wipe_code_protection: false,
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: null,
|
||||
flags: expect.any(Number),
|
||||
fw_vendor: expect.any(String),
|
||||
safety_checks: expect.any(String),
|
||||
auto_lock_delay_ms: expect.any(Number),
|
||||
display_rotation: expect.any(Number),
|
||||
experimental_features: expect.any(Boolean),
|
||||
backup_type: 'Bip39',
|
||||
},
|
||||
legacyResults: [
|
||||
{
|
||||
rules: ['<1.10.3'], // 1.10.3 removed Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<1.8.3'], // 1.8.3 added Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
method: 'getFeatures',
|
||||
setup: {
|
||||
mnemonic: 'mnemonic_12',
|
||||
},
|
||||
tests: [
|
||||
{
|
||||
description: 'T2T1/T2B1 features',
|
||||
skip: ['1'],
|
||||
params: {},
|
||||
result: {
|
||||
device_id: expect.any(String),
|
||||
vendor: 'trezor.io',
|
||||
major_version: expect.any(Number),
|
||||
minor_version: expect.any(Number),
|
||||
patch_version: expect.any(Number),
|
||||
bootloader_mode: null,
|
||||
pin_protection: expect.any(Boolean),
|
||||
passphrase_protection: expect.any(Boolean),
|
||||
language: 'en-US',
|
||||
label: expect.any(String),
|
||||
initialized: true,
|
||||
revision: expect.any(String),
|
||||
bootloader_hash: null,
|
||||
imported: null,
|
||||
unlocked: expect.any(Boolean),
|
||||
firmware_present: null,
|
||||
needs_backup: expect.any(Boolean),
|
||||
// flags: expect.any(Number), // flags may be changed by applyFlags test
|
||||
model: expect.any(String), // "T" | "R"
|
||||
internal_model: expect.any(String),
|
||||
fw_major: null,
|
||||
fw_minor: null,
|
||||
fw_patch: null,
|
||||
// fw_vendor: null, // "EMULATOR" since 2.5.1
|
||||
unfinished_backup: expect.any(Boolean),
|
||||
no_backup: expect.any(Boolean),
|
||||
recovery_mode: false,
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Monero',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
'Capability_PassphraseEntry',
|
||||
'Capability_NEM',
|
||||
'Capability_EOS',
|
||||
]),
|
||||
backup_type: 'Bip39',
|
||||
sd_card_present: expect.any(Boolean), // T2T1 true, T2B1 false
|
||||
sd_protection: false,
|
||||
wipe_code_protection: false,
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: false,
|
||||
},
|
||||
legacyResults: [
|
||||
{
|
||||
rules: ['<2.4.2'], // 2.4.2 removed Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
'Capability_PassphraseEntry',
|
||||
]),
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.2.1'], // 2.2.1 added PassphraseEntry capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
'Capability_ShamirGroups',
|
||||
]),
|
||||
backup_type: 'Bip39',
|
||||
session_id: null,
|
||||
passphrase_always_on_device: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.1.6'], // 2.1.6 added ShamirGroups capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
'Capability_Shamir',
|
||||
]),
|
||||
backup_type: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<2.1.5'], // 2.1.5 added Shamir and Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Binance',
|
||||
'Capability_Cardano',
|
||||
'Capability_Crypto',
|
||||
'Capability_EOS',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Monero',
|
||||
'Capability_NEM',
|
||||
'Capability_Ripple',
|
||||
'Capability_Stellar',
|
||||
'Capability_Tezos',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
backup_type: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'T1B1 features',
|
||||
skip: ['2'],
|
||||
params: {},
|
||||
result: {
|
||||
device_id: expect.any(String),
|
||||
vendor: 'trezor.io',
|
||||
major_version: customFirmwareBuild ? expect.any(Number) : Number(major),
|
||||
minor_version: customFirmwareBuild ? expect.any(Number) : Number(minor),
|
||||
patch_version: customFirmwareBuild ? expect.any(Number) : Number(patch),
|
||||
bootloader_mode: null,
|
||||
pin_protection: expect.any(Boolean),
|
||||
passphrase_protection: expect.any(Boolean),
|
||||
language: 'en-US',
|
||||
label: expect.any(String),
|
||||
initialized: true,
|
||||
revision: expect.any(String),
|
||||
bootloader_hash: expect.any(String), // difference between T2T1
|
||||
imported: true, // difference between T2T1
|
||||
unlocked: expect.any(Boolean),
|
||||
firmware_present: null,
|
||||
needs_backup: expect.any(Boolean),
|
||||
// flags: null, // flags may be changed by applyFlags test
|
||||
model: '1',
|
||||
internal_model: DeviceModelInternal.T1B1,
|
||||
fw_major: null,
|
||||
fw_minor: null,
|
||||
fw_patch: null,
|
||||
// fw_vendor: null, // "EMULATOR" since 1.11.1
|
||||
unfinished_backup: expect.any(Boolean),
|
||||
no_backup: expect.any(Boolean),
|
||||
recovery_mode: null, // difference between T2T1
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
backup_type: undefined, // difference between T2T1
|
||||
sd_card_present: null, // no sdcard in T1B1
|
||||
sd_protection: null, // no sdcard in T1B1
|
||||
wipe_code_protection: false,
|
||||
session_id: expect.any(String),
|
||||
passphrase_always_on_device: null, // no passphrase input on T1B1 device
|
||||
},
|
||||
legacyResults: [
|
||||
{
|
||||
rules: ['<1.10.3'], // 1.10.3 removed Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_Lisk',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: ['<1.8.3'], // 1.8.3 added Lisk capability
|
||||
success: true,
|
||||
payload: {
|
||||
capabilities: expect.arrayContaining([
|
||||
'Capability_Bitcoin',
|
||||
'Capability_Bitcoin_like',
|
||||
'Capability_Crypto',
|
||||
'Capability_Ethereum',
|
||||
'Capability_NEM',
|
||||
'Capability_Stellar',
|
||||
'Capability_U2F',
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
tests,
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export const getDeviceFeatures = (feat?: Partial<Features>): Features => ({
|
||||
imported: null,
|
||||
unlocked: true,
|
||||
firmware_present: null,
|
||||
needs_backup: false,
|
||||
backup_availability: 'NotAvailable',
|
||||
flags: 0,
|
||||
model: 'T',
|
||||
internal_model: DeviceModelInternal.T2T1,
|
||||
@@ -29,7 +29,7 @@ export const getDeviceFeatures = (feat?: Partial<Features>): Features => ({
|
||||
fw_vendor: null,
|
||||
unfinished_backup: false,
|
||||
no_backup: false,
|
||||
recovery_mode: false,
|
||||
recovery_status: 'Nothing',
|
||||
capabilities: [],
|
||||
backup_type: 'Bip39',
|
||||
sd_card_present: false,
|
||||
|
||||
@@ -21,10 +21,11 @@ export default class RecoveryDevice extends AbstractMethod<'recoveryDevice', PRO
|
||||
language: payload.language,
|
||||
label: payload.label,
|
||||
enforce_wordlist: payload.enforce_wordlist,
|
||||
input_method: payload.input_method,
|
||||
type: payload.type,
|
||||
u2f_counter: payload.u2f_counter,
|
||||
dry_run: payload.dry_run,
|
||||
};
|
||||
|
||||
this.allowDeviceMode = [...this.allowDeviceMode, UI.INITIALIZE];
|
||||
this.useDeviceState = false;
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ const inner = async (method: AbstractMethod<any>, device: Device) => {
|
||||
}
|
||||
}
|
||||
|
||||
const deviceNeedsBackup = device.features.needs_backup;
|
||||
const deviceNeedsBackup = device.features.backup_availability === 'Required';
|
||||
if (deviceNeedsBackup) {
|
||||
if (
|
||||
method.noBackupConfirmationMode === 'always' ||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TrezorConnect } from '../../..';
|
||||
import { PROTO, TrezorConnect } from '../../..';
|
||||
|
||||
export const management = async (api: TrezorConnect) => {
|
||||
const reset = await api.resetDevice({
|
||||
@@ -65,8 +65,8 @@ export const management = async (api: TrezorConnect) => {
|
||||
passphrase_protection: true,
|
||||
pin_protection: true,
|
||||
label: 'My Trezor',
|
||||
type: 1,
|
||||
dry_run: true,
|
||||
input_method: PROTO.RecoveryDeviceInputMethod.Matrix,
|
||||
type: PROTO.RecoveryType.DryRun,
|
||||
word_count: 24,
|
||||
});
|
||||
if (recovery.success) recovery.payload.message.toLowerCase();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'@typescript-eslint/ban-types': 'off', // allow {} in protobuf.d.ts
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4450,8 +4450,8 @@
|
||||
"type": "bool",
|
||||
"id": 18
|
||||
},
|
||||
"needs_backup": {
|
||||
"type": "bool",
|
||||
"backup_availability": {
|
||||
"type": "BackupAvailability",
|
||||
"id": 19
|
||||
},
|
||||
"flags": {
|
||||
@@ -4486,8 +4486,8 @@
|
||||
"type": "bool",
|
||||
"id": 28
|
||||
},
|
||||
"recovery_mode": {
|
||||
"type": "bool",
|
||||
"recovery_status": {
|
||||
"type": "RecoveryStatus",
|
||||
"id": 29
|
||||
},
|
||||
"capabilities": {
|
||||
@@ -4584,9 +4584,31 @@
|
||||
"unit_packaging": {
|
||||
"type": "uint32",
|
||||
"id": 51
|
||||
},
|
||||
"haptic_feedback": {
|
||||
"type": "bool",
|
||||
"id": 52
|
||||
},
|
||||
"recovery_type": {
|
||||
"type": "RecoveryType",
|
||||
"id": 53
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"BackupAvailability": {
|
||||
"values": {
|
||||
"NotAvailable": 0,
|
||||
"Required": 1,
|
||||
"Available": 2
|
||||
}
|
||||
},
|
||||
"RecoveryStatus": {
|
||||
"values": {
|
||||
"Nothing": 0,
|
||||
"Recovery": 1,
|
||||
"Backup": 2
|
||||
}
|
||||
},
|
||||
"Capability": {
|
||||
"options": {
|
||||
"(has_bitcoin_only_values)": true
|
||||
@@ -4612,6 +4634,12 @@
|
||||
},
|
||||
"Capability_Translations": {
|
||||
"(bitcoin_only)": true
|
||||
},
|
||||
"Capability_Brightness": {
|
||||
"(bitcoin_only)": true
|
||||
},
|
||||
"Capability_Haptic": {
|
||||
"(bitcoin_only)": true
|
||||
}
|
||||
},
|
||||
"values": {
|
||||
@@ -4633,7 +4661,9 @@
|
||||
"Capability_ShamirGroups": 16,
|
||||
"Capability_PassphraseEntry": 17,
|
||||
"Capability_Solana": 18,
|
||||
"Capability_Translations": 19
|
||||
"Capability_Translations": 19,
|
||||
"Capability_Brightness": 20,
|
||||
"Capability_Haptic": 21
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4703,6 +4733,10 @@
|
||||
"hide_passphrase_from_host": {
|
||||
"type": "bool",
|
||||
"id": 11
|
||||
},
|
||||
"haptic_feedback": {
|
||||
"type": "bool",
|
||||
"id": 13
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5031,28 +5065,38 @@
|
||||
"type": "bool",
|
||||
"id": 6
|
||||
},
|
||||
"type": {
|
||||
"type": "RecoveryDeviceType",
|
||||
"input_method": {
|
||||
"type": "RecoveryDeviceInputMethod",
|
||||
"id": 8
|
||||
},
|
||||
"u2f_counter": {
|
||||
"type": "uint32",
|
||||
"id": 9
|
||||
},
|
||||
"dry_run": {
|
||||
"type": "bool",
|
||||
"id": 10
|
||||
"type": {
|
||||
"type": "RecoveryType",
|
||||
"id": 10,
|
||||
"options": {
|
||||
"default": "NormalRecovery"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nested": {
|
||||
"RecoveryDeviceType": {
|
||||
"RecoveryDeviceInputMethod": {
|
||||
"values": {
|
||||
"RecoveryDeviceType_ScrambledWords": 0,
|
||||
"RecoveryDeviceType_Matrix": 1
|
||||
"ScrambledWords": 0,
|
||||
"Matrix": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RecoveryType": {
|
||||
"values": {
|
||||
"NormalRecovery": 0,
|
||||
"DryRun": 1,
|
||||
"UnlockRepeatedBackup": 2
|
||||
}
|
||||
},
|
||||
"WordRequest": {
|
||||
"fields": {
|
||||
"type": {
|
||||
@@ -5188,6 +5232,14 @@
|
||||
"UnlockBootloader": {
|
||||
"fields": {}
|
||||
},
|
||||
"SetBrightness": {
|
||||
"fields": {
|
||||
"value": {
|
||||
"type": "uint32",
|
||||
"id": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"MoneroNetworkType": {
|
||||
"values": {
|
||||
"MAINNET": 0,
|
||||
@@ -8081,6 +8133,10 @@
|
||||
"(bitcoin_only)": true,
|
||||
"(wire_in)": true
|
||||
},
|
||||
"MessageType_SetBrightness": {
|
||||
"(bitcoin_only)": true,
|
||||
"(wire_in)": true
|
||||
},
|
||||
"MessageType_SetU2FCounter": {
|
||||
"(wire_in)": true
|
||||
},
|
||||
@@ -8765,6 +8821,7 @@
|
||||
"MessageType_ChangeLanguage": 990,
|
||||
"MessageType_TranslationDataRequest": 991,
|
||||
"MessageType_TranslationDataAck": 992,
|
||||
"MessageType_SetBrightness": 993,
|
||||
"MessageType_SetU2FCounter": 63,
|
||||
"MessageType_GetNextU2FCounter": 80,
|
||||
"MessageType_NextU2FCounter": 81,
|
||||
|
||||
@@ -48,7 +48,7 @@ const RULE_PATCH = {
|
||||
'Features.imported': 'required',
|
||||
'Features.unlocked': 'required',
|
||||
'Features.firmware_present': 'required',
|
||||
'Features.needs_backup': 'required',
|
||||
'Features.backup_availability': 'required',
|
||||
'Features.flags': 'required',
|
||||
'Features.fw_major': 'required',
|
||||
'Features.fw_minor': 'required',
|
||||
@@ -58,7 +58,7 @@ const RULE_PATCH = {
|
||||
'Features.internal_model': 'required',
|
||||
'Features.unfinished_backup': 'required',
|
||||
'Features.no_backup': 'required',
|
||||
'Features.recovery_mode': 'required',
|
||||
'Features.recovery_status': 'required',
|
||||
'Features.backup_type': 'required',
|
||||
'Features.sd_card_present': 'required',
|
||||
'Features.sd_protection': 'required',
|
||||
@@ -95,7 +95,7 @@ const TYPE_PATCH = {
|
||||
'Features.imported': 'boolean | null',
|
||||
'Features.unlocked': 'boolean | null',
|
||||
'Features.firmware_present': 'boolean | null',
|
||||
'Features.needs_backup': 'boolean | null',
|
||||
'Features.backup_availability': 'BackupAvailability | null',
|
||||
'Features.flags': 'number | null',
|
||||
'Features.fw_major': 'number | null',
|
||||
'Features.fw_minor': 'number | null',
|
||||
@@ -103,7 +103,7 @@ const TYPE_PATCH = {
|
||||
'Features.fw_vendor': 'string | null',
|
||||
'Features.unfinished_backup': 'boolean | null',
|
||||
'Features.no_backup': 'boolean | null',
|
||||
'Features.recovery_mode': 'boolean | null',
|
||||
'Features.recovery_status': 'RecoveryStatus | null',
|
||||
'Features.backup_type': 'BackupType | null',
|
||||
'Features.sd_card_present': 'boolean | null',
|
||||
'Features.sd_protection': 'boolean | null',
|
||||
@@ -204,6 +204,7 @@ const TYPE_PATCH = {
|
||||
'TezosDelegationOp.source': 'Uint8Array',
|
||||
'TezosDelegationOp.delegate': 'Uint8Array',
|
||||
'TezosSignTx.branch': 'Uint8Array',
|
||||
'Features.recovery_type': 'RecoveryType',
|
||||
};
|
||||
|
||||
const DEFINITION_PATCH = {
|
||||
|
||||
@@ -11,6 +11,13 @@ const {
|
||||
SINT_TYPE,
|
||||
} = require('./protobuf-patches');
|
||||
|
||||
CONSOLE_RED = "\x1b[31m"
|
||||
CONSOLE_RESET = "\x1b[0m"
|
||||
|
||||
const logError = (text) => {
|
||||
console.error(`Error: ${CONSOLE_RED}${text}${CONSOLE_RESET}`);
|
||||
}
|
||||
|
||||
const INDENT = ' '.repeat(4);
|
||||
|
||||
// proto types to javascript types
|
||||
@@ -38,6 +45,9 @@ const ENUM_KEYS = [
|
||||
'PinMatrixRequestType',
|
||||
'WordRequestType',
|
||||
'HomescreenFormat',
|
||||
"RecoveryStatus",
|
||||
"BackupAvailability",
|
||||
"RecoveryType",
|
||||
];
|
||||
|
||||
const parseEnum = (itemName, item) => {
|
||||
@@ -131,26 +141,40 @@ const parseMessage = (messageName, message, depth = 0) => {
|
||||
// top level messages and nested messages
|
||||
Object.keys(json.nested).map(e => parseMessage(e, json.nested[e]));
|
||||
|
||||
// types needs reordering (used before defined)
|
||||
// Types needs reordering (used before defined).
|
||||
// The Type in the Value NEEDs (depends on) the Type in the Key.
|
||||
const ORDER = {
|
||||
BinanceCoin: 'BinanceInputOutput',
|
||||
HDNodeType: 'HDNodePathType',
|
||||
CardanoAssetGroupType: 'CardanoTxOutputType',
|
||||
CardanoTokenType: 'CardanoAssetGroupType',
|
||||
TxAck: 'TxAckInputWrapper',
|
||||
EthereumFieldType: 'EthereumStructMember',
|
||||
EthereumDataType: 'EthereumFieldType',
|
||||
PaymentRequestMemo: 'TxAckPaymentRequest',
|
||||
RecoveryDevice: 'Features',
|
||||
RecoveryType: 'RecoveryDevice',
|
||||
RecoveryDeviceInputMethod: 'RecoveryType',
|
||||
};
|
||||
|
||||
Object.keys(ORDER).forEach(key => {
|
||||
if (key === ORDER[key]) {
|
||||
logError(`ORDER map cannot have key=value`)
|
||||
}
|
||||
|
||||
// find indexes
|
||||
const indexA = types.findIndex(t => t && t.name === key);
|
||||
const indexB = types.findIndex(t => t && t.name === ORDER[key]);
|
||||
const prevA = types[indexA];
|
||||
const indexOfDependency = types.findIndex(t => t && t.name === key);
|
||||
if (indexOfDependency === -1) {
|
||||
logError(`Type from key: '${key}' not found in the 'types' variable!`)
|
||||
}
|
||||
|
||||
const indexOfDependant = types.findIndex(t => t && t.name === ORDER[key]);
|
||||
if (indexOfDependant === -1) {
|
||||
logError(`Type from value: '${ORDER[key]}' not found in the 'types' variable!`)
|
||||
}
|
||||
|
||||
const dependency = types[indexOfDependency];
|
||||
// replace values
|
||||
delete types[indexA];
|
||||
types.splice(indexB, 0, prevA);
|
||||
delete types[indexOfDependency];
|
||||
types.splice(indexOfDependant, 0, dependency);
|
||||
});
|
||||
|
||||
// skip not needed types
|
||||
|
||||
@@ -2200,6 +2200,32 @@ export const Initialize = Type.Object(
|
||||
export type GetFeatures = Static<typeof GetFeatures>;
|
||||
export const GetFeatures = Type.Object({}, { $id: 'GetFeatures' });
|
||||
|
||||
export enum Enum_BackupAvailability {
|
||||
NotAvailable = 0,
|
||||
Required = 1,
|
||||
Available = 2,
|
||||
}
|
||||
|
||||
export type EnumEnum_BackupAvailability = Static<typeof EnumEnum_BackupAvailability>;
|
||||
export const EnumEnum_BackupAvailability = Type.Enum(Enum_BackupAvailability);
|
||||
|
||||
export type BackupAvailability = Static<typeof BackupAvailability>;
|
||||
export const BackupAvailability = Type.KeyOfEnum(Enum_BackupAvailability, {
|
||||
$id: 'BackupAvailability',
|
||||
});
|
||||
|
||||
export enum Enum_RecoveryStatus {
|
||||
Nothing = 0,
|
||||
Recovery = 1,
|
||||
Backup = 2,
|
||||
}
|
||||
|
||||
export type EnumEnum_RecoveryStatus = Static<typeof EnumEnum_RecoveryStatus>;
|
||||
export const EnumEnum_RecoveryStatus = Type.Enum(Enum_RecoveryStatus);
|
||||
|
||||
export type RecoveryStatus = Static<typeof RecoveryStatus>;
|
||||
export const RecoveryStatus = Type.KeyOfEnum(Enum_RecoveryStatus, { $id: 'RecoveryStatus' });
|
||||
|
||||
export enum Enum_Capability {
|
||||
Capability_Bitcoin = 1,
|
||||
Capability_Bitcoin_like = 2,
|
||||
@@ -2220,6 +2246,8 @@ export enum Enum_Capability {
|
||||
Capability_PassphraseEntry = 17,
|
||||
Capability_Solana = 18,
|
||||
Capability_Translations = 19,
|
||||
Capability_Brightness = 20,
|
||||
Capability_Haptic = 21,
|
||||
}
|
||||
|
||||
export type EnumEnum_Capability = Static<typeof EnumEnum_Capability>;
|
||||
@@ -2228,6 +2256,42 @@ export const EnumEnum_Capability = Type.Enum(Enum_Capability);
|
||||
export type Capability = Static<typeof Capability>;
|
||||
export const Capability = Type.KeyOfEnum(Enum_Capability, { $id: 'Capability' });
|
||||
|
||||
export enum RecoveryDeviceInputMethod {
|
||||
ScrambledWords = 0,
|
||||
Matrix = 1,
|
||||
}
|
||||
|
||||
export type EnumRecoveryDeviceInputMethod = Static<typeof EnumRecoveryDeviceInputMethod>;
|
||||
export const EnumRecoveryDeviceInputMethod = Type.Enum(RecoveryDeviceInputMethod);
|
||||
|
||||
export enum Enum_RecoveryType {
|
||||
NormalRecovery = 0,
|
||||
DryRun = 1,
|
||||
UnlockRepeatedBackup = 2,
|
||||
}
|
||||
|
||||
export type EnumEnum_RecoveryType = Static<typeof EnumEnum_RecoveryType>;
|
||||
export const EnumEnum_RecoveryType = Type.Enum(Enum_RecoveryType);
|
||||
|
||||
export type RecoveryType = Static<typeof RecoveryType>;
|
||||
export const RecoveryType = Type.KeyOfEnum(Enum_RecoveryType, { $id: 'RecoveryType' });
|
||||
|
||||
export type RecoveryDevice = Static<typeof RecoveryDevice>;
|
||||
export const RecoveryDevice = Type.Object(
|
||||
{
|
||||
word_count: Type.Optional(Type.Number()),
|
||||
passphrase_protection: Type.Optional(Type.Boolean()),
|
||||
pin_protection: Type.Optional(Type.Boolean()),
|
||||
language: Type.Optional(Type.String()),
|
||||
label: Type.Optional(Type.String()),
|
||||
enforce_wordlist: Type.Optional(Type.Boolean()),
|
||||
input_method: Type.Optional(EnumRecoveryDeviceInputMethod),
|
||||
u2f_counter: Type.Optional(Type.Number()),
|
||||
type: Type.Optional(RecoveryType),
|
||||
},
|
||||
{ $id: 'RecoveryDevice' },
|
||||
);
|
||||
|
||||
export type Features = Static<typeof Features>;
|
||||
export const Features = Type.Object(
|
||||
{
|
||||
@@ -2248,7 +2312,7 @@ export const Features = Type.Object(
|
||||
unlocked: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
_passphrase_cached: Type.Optional(Type.Boolean()),
|
||||
firmware_present: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
needs_backup: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
backup_availability: Type.Union([BackupAvailability, Type.Null()]),
|
||||
flags: Type.Union([Type.Number(), Type.Null()]),
|
||||
model: Type.String(),
|
||||
fw_major: Type.Union([Type.Number(), Type.Null()]),
|
||||
@@ -2257,7 +2321,7 @@ export const Features = Type.Object(
|
||||
fw_vendor: Type.Union([Type.String(), Type.Null()]),
|
||||
unfinished_backup: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
no_backup: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
recovery_mode: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
recovery_status: Type.Union([RecoveryStatus, Type.Null()]),
|
||||
capabilities: Type.Array(Capability),
|
||||
backup_type: Type.Union([BackupType, Type.Null()]),
|
||||
sd_card_present: Type.Union([Type.Boolean(), Type.Null()]),
|
||||
@@ -2280,6 +2344,8 @@ export const Features = Type.Object(
|
||||
bootloader_locked: Type.Optional(Type.Boolean()),
|
||||
language_version_matches: Type.Optional(Type.Boolean()),
|
||||
unit_packaging: Type.Optional(Type.Number()),
|
||||
haptic_feedback: Type.Optional(Type.Boolean()),
|
||||
recovery_type: Type.Optional(RecoveryType),
|
||||
},
|
||||
{ $id: 'Features' },
|
||||
);
|
||||
@@ -2312,6 +2378,7 @@ export const ApplySettings = Type.Object(
|
||||
safety_checks: Type.Optional(SafetyCheckLevel),
|
||||
experimental_features: Type.Optional(Type.Boolean()),
|
||||
hide_passphrase_from_host: Type.Optional(Type.Boolean()),
|
||||
haptic_feedback: Type.Optional(Type.Boolean()),
|
||||
},
|
||||
{ $id: 'ApplySettings' },
|
||||
);
|
||||
@@ -2493,30 +2560,6 @@ export const EntropyAck = Type.Object(
|
||||
{ $id: 'EntropyAck' },
|
||||
);
|
||||
|
||||
export enum RecoveryDeviceType {
|
||||
RecoveryDeviceType_ScrambledWords = 0,
|
||||
RecoveryDeviceType_Matrix = 1,
|
||||
}
|
||||
|
||||
export type EnumRecoveryDeviceType = Static<typeof EnumRecoveryDeviceType>;
|
||||
export const EnumRecoveryDeviceType = Type.Enum(RecoveryDeviceType);
|
||||
|
||||
export type RecoveryDevice = Static<typeof RecoveryDevice>;
|
||||
export const RecoveryDevice = Type.Object(
|
||||
{
|
||||
word_count: Type.Optional(Type.Number()),
|
||||
passphrase_protection: Type.Optional(Type.Boolean()),
|
||||
pin_protection: Type.Optional(Type.Boolean()),
|
||||
language: Type.Optional(Type.String()),
|
||||
label: Type.Optional(Type.String()),
|
||||
enforce_wordlist: Type.Optional(Type.Boolean()),
|
||||
type: Type.Optional(EnumRecoveryDeviceType),
|
||||
u2f_counter: Type.Optional(Type.Number()),
|
||||
dry_run: Type.Optional(Type.Boolean()),
|
||||
},
|
||||
{ $id: 'RecoveryDevice' },
|
||||
);
|
||||
|
||||
export enum Enum_WordRequestType {
|
||||
WordRequestType_Plain = 0,
|
||||
WordRequestType_Matrix9 = 1,
|
||||
@@ -2625,6 +2668,14 @@ export const ShowDeviceTutorial = Type.Object({}, { $id: 'ShowDeviceTutorial' })
|
||||
export type UnlockBootloader = Static<typeof UnlockBootloader>;
|
||||
export const UnlockBootloader = Type.Object({}, { $id: 'UnlockBootloader' });
|
||||
|
||||
export type SetBrightness = Static<typeof SetBrightness>;
|
||||
export const SetBrightness = Type.Object(
|
||||
{
|
||||
value: Type.Optional(Type.Number()),
|
||||
},
|
||||
{ $id: 'SetBrightness' },
|
||||
);
|
||||
|
||||
export enum MoneroNetworkType {
|
||||
MAINNET = 0,
|
||||
TESTNET = 1,
|
||||
@@ -3598,6 +3649,7 @@ export const MessageType = Type.Object(
|
||||
EthereumTypedDataSignature,
|
||||
Initialize,
|
||||
GetFeatures,
|
||||
RecoveryDevice,
|
||||
Features,
|
||||
LockDevice,
|
||||
SetBusy,
|
||||
@@ -3624,7 +3676,6 @@ export const MessageType = Type.Object(
|
||||
BackupDevice,
|
||||
EntropyRequest,
|
||||
EntropyAck,
|
||||
RecoveryDevice,
|
||||
WordRequest,
|
||||
WordAck,
|
||||
SetU2FCounter,
|
||||
@@ -3640,6 +3691,7 @@ export const MessageType = Type.Object(
|
||||
UnlockedPathRequest,
|
||||
ShowDeviceTutorial,
|
||||
UnlockBootloader,
|
||||
SetBrightness,
|
||||
NEMGetAddress,
|
||||
NEMAddress,
|
||||
NEMTransactionCommon,
|
||||
|
||||
@@ -1554,6 +1554,22 @@ export type Initialize = {
|
||||
// GetFeatures
|
||||
export type GetFeatures = {};
|
||||
|
||||
export enum Enum_BackupAvailability {
|
||||
NotAvailable = 0,
|
||||
Required = 1,
|
||||
Available = 2,
|
||||
}
|
||||
|
||||
export type BackupAvailability = keyof typeof Enum_BackupAvailability;
|
||||
|
||||
export enum Enum_RecoveryStatus {
|
||||
Nothing = 0,
|
||||
Recovery = 1,
|
||||
Backup = 2,
|
||||
}
|
||||
|
||||
export type RecoveryStatus = keyof typeof Enum_RecoveryStatus;
|
||||
|
||||
export enum Enum_Capability {
|
||||
Capability_Bitcoin = 1,
|
||||
Capability_Bitcoin_like = 2,
|
||||
@@ -1574,10 +1590,38 @@ export enum Enum_Capability {
|
||||
Capability_PassphraseEntry = 17,
|
||||
Capability_Solana = 18,
|
||||
Capability_Translations = 19,
|
||||
Capability_Brightness = 20,
|
||||
Capability_Haptic = 21,
|
||||
}
|
||||
|
||||
export type Capability = keyof typeof Enum_Capability;
|
||||
|
||||
export enum RecoveryDeviceInputMethod {
|
||||
ScrambledWords = 0,
|
||||
Matrix = 1,
|
||||
}
|
||||
|
||||
export enum Enum_RecoveryType {
|
||||
NormalRecovery = 0,
|
||||
DryRun = 1,
|
||||
UnlockRepeatedBackup = 2,
|
||||
}
|
||||
|
||||
export type RecoveryType = keyof typeof Enum_RecoveryType;
|
||||
|
||||
// RecoveryDevice
|
||||
export type RecoveryDevice = {
|
||||
word_count?: number;
|
||||
passphrase_protection?: boolean;
|
||||
pin_protection?: boolean;
|
||||
language?: string;
|
||||
label?: string;
|
||||
enforce_wordlist?: boolean;
|
||||
input_method?: RecoveryDeviceInputMethod;
|
||||
u2f_counter?: number;
|
||||
type?: RecoveryType;
|
||||
};
|
||||
|
||||
// Features
|
||||
export type Features = {
|
||||
vendor: string;
|
||||
@@ -1597,7 +1641,7 @@ export type Features = {
|
||||
unlocked: boolean | null;
|
||||
_passphrase_cached?: boolean;
|
||||
firmware_present: boolean | null;
|
||||
needs_backup: boolean | null;
|
||||
backup_availability: BackupAvailability | null;
|
||||
flags: number | null;
|
||||
model: string;
|
||||
fw_major: number | null;
|
||||
@@ -1606,7 +1650,7 @@ export type Features = {
|
||||
fw_vendor: string | null;
|
||||
unfinished_backup: boolean | null;
|
||||
no_backup: boolean | null;
|
||||
recovery_mode: boolean | null;
|
||||
recovery_status: RecoveryStatus | null;
|
||||
capabilities: Capability[];
|
||||
backup_type: BackupType | null;
|
||||
sd_card_present: boolean | null;
|
||||
@@ -1629,6 +1673,8 @@ export type Features = {
|
||||
bootloader_locked?: boolean;
|
||||
language_version_matches?: boolean;
|
||||
unit_packaging?: number;
|
||||
haptic_feedback?: boolean;
|
||||
recovery_type?: RecoveryType;
|
||||
};
|
||||
|
||||
// LockDevice
|
||||
@@ -1655,6 +1701,7 @@ export type ApplySettings = {
|
||||
safety_checks?: SafetyCheckLevel;
|
||||
experimental_features?: boolean;
|
||||
hide_passphrase_from_host?: boolean;
|
||||
haptic_feedback?: boolean;
|
||||
};
|
||||
|
||||
// ChangeLanguage
|
||||
@@ -1776,24 +1823,6 @@ export type EntropyAck = {
|
||||
entropy: string;
|
||||
};
|
||||
|
||||
export enum RecoveryDeviceType {
|
||||
RecoveryDeviceType_ScrambledWords = 0,
|
||||
RecoveryDeviceType_Matrix = 1,
|
||||
}
|
||||
|
||||
// RecoveryDevice
|
||||
export type RecoveryDevice = {
|
||||
word_count?: number;
|
||||
passphrase_protection?: boolean;
|
||||
pin_protection?: boolean;
|
||||
language?: string;
|
||||
label?: string;
|
||||
enforce_wordlist?: boolean;
|
||||
type?: RecoveryDeviceType;
|
||||
u2f_counter?: number;
|
||||
dry_run?: boolean;
|
||||
};
|
||||
|
||||
export enum Enum_WordRequestType {
|
||||
WordRequestType_Plain = 0,
|
||||
WordRequestType_Matrix9 = 1,
|
||||
@@ -1871,6 +1900,11 @@ export type ShowDeviceTutorial = {};
|
||||
// UnlockBootloader
|
||||
export type UnlockBootloader = {};
|
||||
|
||||
// SetBrightness
|
||||
export type SetBrightness = {
|
||||
value?: number;
|
||||
};
|
||||
|
||||
export enum MoneroNetworkType {
|
||||
MAINNET = 0,
|
||||
TESTNET = 1,
|
||||
@@ -2597,6 +2631,7 @@ export type MessageType = {
|
||||
EthereumTypedDataSignature: EthereumTypedDataSignature;
|
||||
Initialize: Initialize;
|
||||
GetFeatures: GetFeatures;
|
||||
RecoveryDevice: RecoveryDevice;
|
||||
Features: Features;
|
||||
LockDevice: LockDevice;
|
||||
SetBusy: SetBusy;
|
||||
@@ -2623,7 +2658,6 @@ export type MessageType = {
|
||||
BackupDevice: BackupDevice;
|
||||
EntropyRequest: EntropyRequest;
|
||||
EntropyAck: EntropyAck;
|
||||
RecoveryDevice: RecoveryDevice;
|
||||
WordRequest: WordRequest;
|
||||
WordAck: WordAck;
|
||||
SetU2FCounter: SetU2FCounter;
|
||||
@@ -2639,6 +2673,7 @@ export type MessageType = {
|
||||
UnlockedPathRequest: UnlockedPathRequest;
|
||||
ShowDeviceTutorial: ShowDeviceTutorial;
|
||||
UnlockBootloader: UnlockBootloader;
|
||||
SetBrightness: SetBrightness;
|
||||
NEMGetAddress: NEMGetAddress;
|
||||
NEMAddress: NEMAddress;
|
||||
NEMTransactionCommon: NEMTransactionCommon;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { generateForFile } from '../src/codegen';
|
||||
|
||||
describe('codegen', () => {
|
||||
it('should generate code for protobuf messages example', () => {
|
||||
const output = generateForFile(`${__dirname}/__snapshots__/codegen.input.ts`);
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
BIN
packages/suite-data/files/images/png/create-shamir-group.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
packages/suite-data/files/images/png/create-shamir-group@2x.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
packages/suite-data/files/images/png/shamir-shares.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
packages/suite-data/files/images/png/shamir-shares@2x.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -2033,6 +2033,7 @@
|
||||
"TR_VIEW_ONLY_RADIOS_ENABLED_TITLE": "Enabled",
|
||||
"TR_VIEW_ONLY_SEND_COINS_INFO": "You always need to connect Trezor to move coins.",
|
||||
"TR_VIEW_ONLY_TOOLTIP_BUTTON": "Got it",
|
||||
"TR_GOT_IT_BUTTON": "Got it",
|
||||
"TR_VIEW_ONLY_TOOLTIP_CHANGE_INFO": "You can change it here",
|
||||
"TR_VIEW_ONLY_TOOLTIP_DESCRIPTION": "You'll see wallet balances after disconnecting Trezor",
|
||||
"TR_VIEW_ONLY_TOOLTIP_TITLE": "View-only enabled",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { deviceActions, selectDevice } from '@suite-common/wallet-core';
|
||||
import TrezorConnect, { UI, RecoveryDevice, DeviceModelInternal } from '@trezor/connect';
|
||||
import TrezorConnect, { UI, RecoveryDevice, DeviceModelInternal, PROTO } from '@trezor/connect';
|
||||
import { analytics, EventType } from '@trezor/suite-analytics';
|
||||
|
||||
import { RECOVERY } from 'src/actions/recovery/constants';
|
||||
@@ -8,6 +8,7 @@ import * as routerActions from 'src/actions/suite/routerActions';
|
||||
import { Dispatch, GetState } from 'src/types/suite';
|
||||
import { WordCount } from 'src/types/recovery';
|
||||
import { DEFAULT_PASSPHRASE_PROTECTION } from 'src/constants/suite/device';
|
||||
import { isRecoveryInProgress } from '../../utils/device/isRecoveryInProgress';
|
||||
|
||||
export type SeedInputStatus =
|
||||
| 'initial'
|
||||
@@ -67,8 +68,10 @@ const checkSeed = () => async (dispatch: Dispatch, getState: GetState) => {
|
||||
}
|
||||
|
||||
const response = await TrezorConnect.recoveryDevice({
|
||||
dry_run: true,
|
||||
type: advancedRecovery ? 1 : 0,
|
||||
type: device.features.recovery_type ?? 'DryRun', // For old firmware, we assume DryRun as it was the only option before
|
||||
input_method: advancedRecovery
|
||||
? PROTO.RecoveryDeviceInputMethod.Matrix
|
||||
: PROTO.RecoveryDeviceInputMethod.ScrambledWords,
|
||||
word_count: wordsCount,
|
||||
enforce_wordlist: true,
|
||||
device: {
|
||||
@@ -105,7 +108,10 @@ const recoverDevice = () => async (dispatch: Dispatch, getState: GetState) => {
|
||||
}
|
||||
|
||||
const params: RecoveryDevice = {
|
||||
type: advancedRecovery ? 1 : 0,
|
||||
type: device.features.recovery_type ?? 'NormalRecovery', // For old firmware, we assume NormalRecovery as it was the only option before
|
||||
input_method: advancedRecovery
|
||||
? PROTO.RecoveryDeviceInputMethod.Matrix
|
||||
: PROTO.RecoveryDeviceInputMethod.ScrambledWords,
|
||||
word_count: wordsCount,
|
||||
passphrase_protection: DEFAULT_PASSPHRASE_PROTECTION,
|
||||
enforce_wordlist: true,
|
||||
@@ -167,7 +173,7 @@ const rerun = () => async (dispatch: Dispatch, getState: GetState) => {
|
||||
|
||||
const features = response.payload;
|
||||
|
||||
if (!features.recovery_mode) {
|
||||
if (!isRecoveryInProgress(features)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@ export const CheckSeedStep = ({ onClose, onSuccess, willBeWiped }: CheckSeedStep
|
||||
|
||||
const handleCheckboxClick = () => setIsChecked(prev => !prev);
|
||||
const getContent = () => {
|
||||
const isBackedUp = !device?.features?.needs_backup && !device?.features?.unfinished_backup;
|
||||
const isBackedUp =
|
||||
device?.features?.backup_availability !== 'Required' &&
|
||||
!device?.features?.unfinished_backup;
|
||||
|
||||
const noBackupHeading = (
|
||||
<Translation
|
||||
|
||||
@@ -18,14 +18,15 @@ interface LearnMoreButtonProps extends Omit<ButtonProps, 'children'> {
|
||||
|
||||
export const LearnMoreButton = ({
|
||||
children,
|
||||
url,
|
||||
className,
|
||||
size = 'tiny',
|
||||
url,
|
||||
...buttonProps
|
||||
}: LearnMoreButtonProps) => (
|
||||
<StyledTrezorLink variant="nostyle" href={url} className={className}>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="tiny"
|
||||
size={size}
|
||||
icon="EXTERNAL_LINK"
|
||||
iconAlignment="right"
|
||||
{...buttonProps}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { configureStore } from 'src/support/tests/configureStore';
|
||||
import { renderWithProviders, findByTestId } from 'src/support/tests/hooksHelper';
|
||||
|
||||
import { Preloader } from '../Preloader';
|
||||
import { AppState } from '../../../../reducers/store';
|
||||
import { DeepPartial } from '@trezor/type-utils';
|
||||
|
||||
// render only Translation.id in data-test attribute
|
||||
jest.mock('src/components/suite/Translation', () => ({
|
||||
@@ -186,14 +188,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Unacquired device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { type: 'unacquired' },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { type: 'unacquired' },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -205,14 +209,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Unreadable device: webusb HID', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'WebUsbTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -226,14 +232,16 @@ describe('Preloader component', () => {
|
||||
it('Unreadable device: missing udev on Linux', () => {
|
||||
jest.spyOn(envUtils, 'isLinux').mockImplementation(() => true);
|
||||
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -247,14 +255,16 @@ describe('Preloader component', () => {
|
||||
it('Unreadable device: missing udev on non-Linux os (should never happen)', () => {
|
||||
jest.spyOn(envUtils, 'isLinux').mockImplementation(() => false);
|
||||
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -266,14 +276,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Unreadable device: unknown error', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { type: 'unreadable', error: 'Unexpected error' },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { type: 'unreadable', error: 'Unexpected error' },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -285,14 +297,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Unknown device (should never happen)', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { features: undefined },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { features: null },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -304,14 +318,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Seedless device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { mode: 'seedless', features: {} },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { mode: 'seedless', features: {} },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -324,14 +340,18 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Recovery mode device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: {
|
||||
features: { recovery_status: 'Recovery' },
|
||||
},
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { features: { recovery_mode: true } },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -344,14 +364,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Not initialized device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { mode: 'initialize', features: {} },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { mode: 'initialize', features: {} },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -364,14 +386,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Bootloader device with installed firmware', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { mode: 'bootloader', features: { firmware_present: true } },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { mode: 'bootloader', features: { firmware_present: true } },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -384,14 +408,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Bootloader device without firmware', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { mode: 'bootloader', features: { firmware_present: false } },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { mode: 'bootloader', features: { firmware_present: false } },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
@@ -404,14 +430,16 @@ describe('Preloader component', () => {
|
||||
});
|
||||
|
||||
it('Required FW update device', () => {
|
||||
const device: DeepPartial<AppState['device']> = {
|
||||
selectedDevice: { firmware: 'required', features: {} },
|
||||
};
|
||||
|
||||
const store = initStore(
|
||||
getInitialState({
|
||||
suite: {
|
||||
transport: { type: 'BridgeTransport' },
|
||||
},
|
||||
device: {
|
||||
selectedDevice: { firmware: 'required', features: {} },
|
||||
},
|
||||
device,
|
||||
}),
|
||||
);
|
||||
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Translation, TroubleshootingTips } from 'src/components/suite';
|
||||
|
||||
export const MultiShareBackupInProgress = () => {
|
||||
return (
|
||||
<TroubleshootingTips
|
||||
label={<Translation id="TR_MULTI_SHARE_BACKUP_IN_PROGRESS" />}
|
||||
items={[
|
||||
{
|
||||
key: 'multi-share-backup-in-progress',
|
||||
heading: <Translation id="TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING" />,
|
||||
description: <Translation id="TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,7 @@ import { DeviceNoFirmware } from './DeviceNoFirmware';
|
||||
import { DeviceUpdateRequired } from './DeviceUpdateRequired';
|
||||
import { DeviceDisconnectRequired } from './DeviceDisconnectRequired';
|
||||
import { selectPrerequisite } from 'src/reducers/suite/suiteReducer';
|
||||
import { MultiShareBackupInProgress } from './MultiShareBackupInProgress';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
@@ -49,7 +50,7 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp
|
||||
const isWebUsbTransport = isWebUsb(transport);
|
||||
|
||||
const TipComponent = useMemo(
|
||||
() => () => {
|
||||
() => (): React.JSX.Element => {
|
||||
switch (prerequisite) {
|
||||
case 'transport-bridge':
|
||||
return <Transport />;
|
||||
@@ -77,9 +78,11 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp
|
||||
return <DeviceNoFirmware />;
|
||||
case 'firmware-required':
|
||||
return <DeviceUpdateRequired />;
|
||||
case 'multi-share-backup-in-progress':
|
||||
return <MultiShareBackupInProgress />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
case undefined:
|
||||
return <></>;
|
||||
}
|
||||
},
|
||||
[prerequisite, isWebUsbTransport, device],
|
||||
|
||||
@@ -57,7 +57,7 @@ export const SuiteBanners = () => {
|
||||
} else if (device?.features?.unfinished_backup) {
|
||||
banner = <FailedBackup />;
|
||||
priority = 90;
|
||||
} else if (device?.features?.needs_backup) {
|
||||
} else if (device?.features?.backup_availability === 'Required') {
|
||||
banner = <NoBackup />;
|
||||
priority = 70;
|
||||
} else if (device?.connected && device?.features?.safety_checks === 'PromptAlways') {
|
||||
|
||||
@@ -11,33 +11,27 @@ import { SwitchDeviceLegacy } from 'src/views/suite/SwitchDevice/SwitchDeviceLeg
|
||||
import { SwitchDevice } from 'src/views/suite/SwitchDevice/SwitchDevice';
|
||||
import type { ForegroundAppRoute } from 'src/types/suite';
|
||||
import { selectSuiteFlags } from 'src/reducers/suite/suiteReducer';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { MultiShareBackupModal } from '../ReduxModal/UserContextModal/MultiShareBackupModal/MultiShareBackupModal';
|
||||
|
||||
// would not work if defined directly in the switch
|
||||
const FirmwareType = () => <FirmwareUpdate shouldSwitchFirmwareType />;
|
||||
|
||||
const getForegroundApp = (app: ForegroundAppRoute['app'], isViewOnlyModeVisible: boolean) => {
|
||||
switch (app) {
|
||||
case 'firmware':
|
||||
return FirmwareUpdate;
|
||||
case 'firmware-type':
|
||||
return FirmwareType;
|
||||
case 'firmware-custom':
|
||||
return FirmwareCustom;
|
||||
case 'bridge':
|
||||
return InstallBridge;
|
||||
case 'udev':
|
||||
return UdevRules;
|
||||
case 'version':
|
||||
return Version;
|
||||
case 'switch-device':
|
||||
return isViewOnlyModeVisible ? SwitchDevice : SwitchDeviceLegacy;
|
||||
case 'recovery':
|
||||
return Recovery;
|
||||
case 'backup':
|
||||
return Backup;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
const map: Record<ForegroundAppRoute['app'], FunctionComponent<any>> = {
|
||||
firmware: FirmwareUpdate,
|
||||
'firmware-type': FirmwareType,
|
||||
'firmware-custom': FirmwareCustom,
|
||||
version: Version,
|
||||
bridge: InstallBridge,
|
||||
udev: UdevRules,
|
||||
'switch-device': isViewOnlyModeVisible ? SwitchDevice : SwitchDeviceLegacy,
|
||||
recovery: Recovery,
|
||||
backup: Backup,
|
||||
'create-multi-share-backup': MultiShareBackupModal,
|
||||
};
|
||||
|
||||
return map[app];
|
||||
};
|
||||
|
||||
type ForegroundAppModalProps = {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { Card, Icon, IconType } from '@trezor/components';
|
||||
import { borders, spacingsPx } from '@trezor/theme';
|
||||
|
||||
const StyledCard = styled(Card)<{ $isHorizontal: boolean }>`
|
||||
align-items: center;
|
||||
gap: ${spacingsPx.md};
|
||||
|
||||
${({ $isHorizontal }) =>
|
||||
$isHorizontal
|
||||
? css`
|
||||
flex-direction: row;
|
||||
`
|
||||
: css`
|
||||
text-align: center;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledIcon = styled(Icon)<{ $isCircled: boolean }>`
|
||||
${({ $isCircled }) =>
|
||||
$isCircled &&
|
||||
css`
|
||||
border: ${borders.widths.large} solid ${({ theme }) => theme.iconDefault};
|
||||
border-radius: ${borders.radii.full};
|
||||
padding: ${spacingsPx.xl};
|
||||
`}
|
||||
`;
|
||||
|
||||
interface BackupInstructionsCardProps {
|
||||
children: ReactNode;
|
||||
icon: IconType;
|
||||
isHorizontal?: boolean;
|
||||
}
|
||||
|
||||
export const BackupInstructionsCard = ({
|
||||
children,
|
||||
icon,
|
||||
isHorizontal,
|
||||
}: BackupInstructionsCardProps) => (
|
||||
<StyledCard $isHorizontal={!!isHorizontal}>
|
||||
<StyledIcon icon={icon} size={32} $isCircled={!isHorizontal} />
|
||||
{children}
|
||||
</StyledCard>
|
||||
);
|
||||
@@ -0,0 +1,118 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { TranslationKey } from '@suite-common/intl-types';
|
||||
import { H3, Icon, IconVariant, Paragraph, Row, Text, TextVariant } from '@trezor/components';
|
||||
import { borders, spacings, spacingsPx, typographyStylesBase } from '@trezor/theme';
|
||||
|
||||
import { Translation } from 'src/components/suite';
|
||||
|
||||
const Step = styled.div`
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: 0 ${spacingsPx.sm};
|
||||
grid-template: auto auto / min-content auto;
|
||||
`;
|
||||
|
||||
const StepNumber = styled.div<{ $subdued?: boolean; $isDone?: boolean }>`
|
||||
border: ${borders.widths.large} solid ${({ theme }) => theme.borderElevation2};
|
||||
border-radius: ${borders.radii.full};
|
||||
padding: ${spacingsPx.xxs};
|
||||
|
||||
background-color: ${({ theme, $isDone }) =>
|
||||
$isDone === true ? theme.backgroundPrimarySubtleOnElevation1 : undefined};
|
||||
|
||||
color: ${({ theme, $subdued }) => ($subdued === true ? theme.textSubdued : undefined)};
|
||||
`;
|
||||
|
||||
const StyledParagraph = styled(Paragraph)`
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: center;
|
||||
width: ${typographyStylesBase.highlight.lineHeight}px;
|
||||
`;
|
||||
|
||||
const Line = styled.div`
|
||||
border-left: ${borders.widths.large} dashed ${({ theme }) => theme.borderElevation2};
|
||||
place-self: stretch center;
|
||||
`;
|
||||
|
||||
const Body = styled.div<{ $isLast: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${spacingsPx.md};
|
||||
${({ $isLast }) =>
|
||||
$isLast
|
||||
? css`
|
||||
/* This is necessary when Line is not rendered and its cell must be empty */
|
||||
grid-column-start: 2;
|
||||
grid-row-start: 2;
|
||||
`
|
||||
: css`
|
||||
/* This cannot be done with gap because Line needs to reach the following step */
|
||||
margin-bottom: ${spacingsPx.xxxl};
|
||||
`}
|
||||
`;
|
||||
|
||||
export interface BackupInstructionsStepProps {
|
||||
children: ReactNode;
|
||||
description?: TranslationKey;
|
||||
heading: TranslationKey;
|
||||
isLast: boolean;
|
||||
stepNumber: number;
|
||||
time: number;
|
||||
completeness?: 'todo' | 'done';
|
||||
}
|
||||
|
||||
export const BackupInstructionsStep = ({
|
||||
children,
|
||||
description,
|
||||
heading,
|
||||
isLast,
|
||||
stepNumber,
|
||||
time,
|
||||
completeness,
|
||||
}: BackupInstructionsStepProps) => {
|
||||
const variantMap: Record<'todo' | 'done', TextVariant & IconVariant> = {
|
||||
todo: 'tertiary',
|
||||
done: 'primary',
|
||||
};
|
||||
|
||||
const variant = completeness !== undefined ? variantMap[completeness] : undefined;
|
||||
|
||||
return (
|
||||
<Step>
|
||||
<StepNumber $subdued={completeness === 'todo'} $isDone={completeness === 'done'}>
|
||||
{completeness === 'done' ? (
|
||||
<Icon icon="CHECK" variant="primary" size={24} />
|
||||
) : (
|
||||
<StyledParagraph typographyStyle="highlight">{stepNumber}</StyledParagraph>
|
||||
)}
|
||||
</StepNumber>
|
||||
<Row gap={spacings.sm}>
|
||||
<H3>
|
||||
<Text variant={variant}>
|
||||
<Translation id={heading} />
|
||||
</Text>
|
||||
</H3>
|
||||
{completeness === 'done' ? null : (
|
||||
<Row gap={spacings.xxs}>
|
||||
<Icon icon="TIMER" variant={variant ?? 'tertiary'} size={16} />
|
||||
<Text typographyStyle="hint" variant={variant ?? 'tertiary'}>
|
||||
<Translation id="TR_N_MIN" values={{ n: time }} />
|
||||
</Text>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
{!isLast && <Line />}
|
||||
|
||||
<Body $isLast={isLast}>
|
||||
{description ? (
|
||||
<Text typographyStyle="hint" variant="tertiary">
|
||||
<Translation id={description} />
|
||||
</Text>
|
||||
) : null}
|
||||
{children}
|
||||
</Body>
|
||||
</Step>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import { useState } from 'react';
|
||||
import { selectDevice } from '@suite-common/wallet-core';
|
||||
|
||||
import { Button, ModalProps } from '@trezor/components';
|
||||
import {
|
||||
HELP_CENTER_KEEPING_SEED_SAFE_URL,
|
||||
HELP_CENTER_MULTI_SHARE_BACKUP_URL,
|
||||
HELP_CENTER_SEED_CARD_URL,
|
||||
} from '@trezor/urls';
|
||||
|
||||
import { useSelector } from 'src/hooks/suite';
|
||||
import { Modal, Translation } from 'src/components/suite';
|
||||
import { LearnMoreButton } from 'src/components/suite/LearnMoreButton';
|
||||
import { MultiShareBackupStep1FirstInfo } from './MultiShareBackupStep1FirstInfo';
|
||||
import { MultiShareBackupStep2SecondInfo } from './MultiShareBackupStep2SecondInfo';
|
||||
import TrezorConnect, { PROTO } from '@trezor/connect';
|
||||
import { MultiShareBackupStep5Done } from './MultiShareBackupStep5Done';
|
||||
import { isAdditionalShamirBackupInProgress } from '../../../../../../utils/device/isRecoveryInProgress';
|
||||
import { MultiShareBackupStep3VerifyOwnership } from './MultiShareBackupStep3VerifyOwnership';
|
||||
import { MultiShareBackupStep4BackupSeed } from './MultiShareBackupStep4BackupSeed';
|
||||
|
||||
type Steps = 'first-info' | 'second-info' | 'verify-ownership' | 'backup-seed' | 'done';
|
||||
|
||||
export const MultiShareBackupModal = ({ onCancel }: { onCancel: () => void }) => {
|
||||
const device = useSelector(selectDevice);
|
||||
|
||||
const isInBackupMode =
|
||||
device?.features !== undefined && isAdditionalShamirBackupInProgress(device.features);
|
||||
|
||||
const [step, setStep] = useState<Steps>(isInBackupMode ? 'backup-seed' : 'first-info');
|
||||
|
||||
const [isChecked1, setIsChecked1] = useState(false);
|
||||
const [isChecked2, setIsChecked2] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
const closeWithCancelOnDevice = () => {
|
||||
TrezorConnect.cancel('cancel');
|
||||
onCancel();
|
||||
};
|
||||
|
||||
if (device === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getStepConfig = (): Partial<ModalProps> => {
|
||||
switch (step) {
|
||||
case 'first-info':
|
||||
const goToStepNextStep = () => {
|
||||
setIsSubmitted(true);
|
||||
if (isChecked1 && isChecked2) {
|
||||
setStep('second-info');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
heading: <Translation id="TR_CREATE_MULTI_SHARE_BACKUP" />,
|
||||
children: (
|
||||
<MultiShareBackupStep1FirstInfo
|
||||
isChecked1={isChecked1}
|
||||
isChecked2={isChecked2}
|
||||
isSubmitted={isSubmitted}
|
||||
setIsChecked1={setIsChecked1}
|
||||
setIsChecked2={setIsChecked2}
|
||||
/>
|
||||
),
|
||||
bottomBarComponents: (
|
||||
<>
|
||||
<Button onClick={goToStepNextStep}>
|
||||
<Translation id="TR_CREATE_MULTI_SHARE_BACKUP" />
|
||||
</Button>
|
||||
<LearnMoreButton url={HELP_CENTER_SEED_CARD_URL} size="medium" />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
case 'second-info':
|
||||
const enterBackup = async () => {
|
||||
setStep('verify-ownership');
|
||||
|
||||
const response = await TrezorConnect.recoveryDevice({
|
||||
type: 'UnlockRepeatedBackup',
|
||||
input_method: PROTO.RecoveryDeviceInputMethod.Matrix,
|
||||
enforce_wordlist: true,
|
||||
device: {
|
||||
path: device.path,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setStep('backup-seed');
|
||||
TrezorConnect.backupDevice().then(response => {
|
||||
if (response.success) {
|
||||
setStep('done');
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
heading: <Translation id="TR_NEXT_UP" />,
|
||||
children: <MultiShareBackupStep2SecondInfo />,
|
||||
bottomBarComponents: (
|
||||
<>
|
||||
<Button onClick={enterBackup}>
|
||||
<Translation id="TR_ENTER_EXISTING_BACKUP" />
|
||||
</Button>
|
||||
<LearnMoreButton url={HELP_CENTER_MULTI_SHARE_BACKUP_URL} size="medium">
|
||||
<Translation id="TR_DONT_HAVE_BACKUP" />
|
||||
</LearnMoreButton>
|
||||
</>
|
||||
),
|
||||
onBackClick: () => setStep('first-info'),
|
||||
};
|
||||
|
||||
case 'verify-ownership':
|
||||
return {
|
||||
children: <MultiShareBackupStep3VerifyOwnership />,
|
||||
heading: <Translation id="TR_CONFIRM_ON_TREZOR" />,
|
||||
onCancel: closeWithCancelOnDevice,
|
||||
};
|
||||
|
||||
case 'backup-seed':
|
||||
return {
|
||||
children: <MultiShareBackupStep4BackupSeed />,
|
||||
heading: <Translation id="TR_CONFIRM_ON_TREZOR" />,
|
||||
onCancel: closeWithCancelOnDevice,
|
||||
};
|
||||
|
||||
case 'done':
|
||||
return {
|
||||
heading: <Translation id="TR_CREATE_MULTI_SHARE_BACKUP_CREATED" />,
|
||||
children: <MultiShareBackupStep5Done />,
|
||||
bottomBarComponents: (
|
||||
<>
|
||||
<Button onClick={onCancel}>
|
||||
<Translation id="TR_GOT_IT_BUTTON" />
|
||||
</Button>
|
||||
<LearnMoreButton url={HELP_CENTER_KEEPING_SEED_SAFE_URL} size="medium">
|
||||
<Translation id="TR_MULTI_SHARE_TIPS_ON_STORING_BACKUP" />
|
||||
</LearnMoreButton>
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return <Modal isCancelable onCancel={onCancel} {...getStepConfig()}></Modal>;
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Checkbox, Image, Paragraph } from '@trezor/components';
|
||||
import { spacingsPx } from '@trezor/theme';
|
||||
|
||||
import { Translation } from 'src/components/suite';
|
||||
import { Body, Section } from './multiShareModalLayout';
|
||||
|
||||
const StyledImage = styled(Image)`
|
||||
margin-bottom: ${spacingsPx.md};
|
||||
`;
|
||||
|
||||
type MultiShareBackupStep1Props = {
|
||||
isChecked1: boolean;
|
||||
isChecked2: boolean;
|
||||
isSubmitted: boolean;
|
||||
setIsChecked1: Dispatch<SetStateAction<boolean>>;
|
||||
setIsChecked2: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const MultiShareBackupStep1FirstInfo = ({
|
||||
isChecked1,
|
||||
isChecked2,
|
||||
isSubmitted,
|
||||
setIsChecked1,
|
||||
setIsChecked2,
|
||||
}: MultiShareBackupStep1Props) => {
|
||||
const getCheckboxVariant = (isChecked: boolean) =>
|
||||
isSubmitted && !isChecked ? 'destructive' : undefined;
|
||||
|
||||
const checkboxVariant1 = getCheckboxVariant(isChecked1);
|
||||
const checkboxVariant2 = getCheckboxVariant(isChecked2);
|
||||
|
||||
const toggleCheckbox1 = () => setIsChecked1(prev => !prev);
|
||||
const toggleCheckbox2 = () => setIsChecked2(prev => !prev);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledImage image="CREATE_SHAMIR_GROUP" />
|
||||
<Body>
|
||||
<Section>
|
||||
<Paragraph typographyStyle="callout">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_CALLOUT_1" />
|
||||
</Paragraph>
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_EXPLANATION_1" />
|
||||
</Section>
|
||||
<Section>
|
||||
<Paragraph typographyStyle="callout">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_CALLOUT_2" />
|
||||
</Paragraph>
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_EXPLANATION_2" />
|
||||
</Section>
|
||||
<Section>
|
||||
<Paragraph typographyStyle="callout">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_CALLOUT_3" />
|
||||
</Paragraph>
|
||||
<Checkbox
|
||||
isChecked={isChecked1}
|
||||
onClick={toggleCheckbox1}
|
||||
variant={checkboxVariant1}
|
||||
>
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_CHECKBOX_1" />
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={isChecked2}
|
||||
onClick={toggleCheckbox2}
|
||||
variant={checkboxVariant2}
|
||||
>
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_CHECKBOX_2" />
|
||||
</Checkbox>
|
||||
</Section>
|
||||
</Body>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { BackupInstructionsStep } from './BackupInstructionsStep';
|
||||
import {
|
||||
InstructionBaseConfig,
|
||||
createSharesInstruction,
|
||||
verifyTrezorOwnershipInstruction,
|
||||
} from './instructionSteps';
|
||||
|
||||
export const MultiShareBackupStep2SecondInfo = () => {
|
||||
const instructions: InstructionBaseConfig[] = [
|
||||
verifyTrezorOwnershipInstruction,
|
||||
createSharesInstruction,
|
||||
];
|
||||
|
||||
return instructions.map((content, i) => (
|
||||
<BackupInstructionsStep
|
||||
key={i}
|
||||
stepNumber={i + 1}
|
||||
isLast={instructions.length === i + 1}
|
||||
{...content}
|
||||
/>
|
||||
));
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BackupInstructionsStep } from './BackupInstructionsStep';
|
||||
import {
|
||||
InstructionBaseConfig,
|
||||
createSharesInstruction,
|
||||
verifyTrezorOwnershipInstruction,
|
||||
} from './instructionSteps';
|
||||
|
||||
export const MultiShareBackupStep3VerifyOwnership = () => {
|
||||
const instructions: InstructionBaseConfig[] = [
|
||||
verifyTrezorOwnershipInstruction,
|
||||
{
|
||||
...createSharesInstruction,
|
||||
description: undefined,
|
||||
children: null,
|
||||
completeness: 'todo',
|
||||
},
|
||||
];
|
||||
|
||||
return instructions.map((content, i) => (
|
||||
<BackupInstructionsStep
|
||||
key={i}
|
||||
stepNumber={i + 1}
|
||||
isLast={instructions.length === i + 1}
|
||||
{...content}
|
||||
/>
|
||||
));
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BackupInstructionsStep } from './BackupInstructionsStep';
|
||||
import {
|
||||
InstructionBaseConfig,
|
||||
createSharesInstruction,
|
||||
verifyTrezorOwnershipInstruction,
|
||||
} from './instructionSteps';
|
||||
|
||||
export const MultiShareBackupStep4BackupSeed = () => {
|
||||
const instructions: InstructionBaseConfig[] = [
|
||||
{
|
||||
...verifyTrezorOwnershipInstruction,
|
||||
description: undefined,
|
||||
children: null,
|
||||
completeness: 'done',
|
||||
},
|
||||
createSharesInstruction,
|
||||
];
|
||||
|
||||
return instructions.map((content, i) => (
|
||||
<BackupInstructionsStep
|
||||
key={i}
|
||||
stepNumber={i + 1}
|
||||
isLast={instructions.length === i + 1}
|
||||
{...content}
|
||||
/>
|
||||
));
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import { Card, Row, Icon, Column, Text } from '@trezor/components';
|
||||
|
||||
import { Translation } from 'src/components/suite';
|
||||
import { Body, Section } from './multiShareModalLayout';
|
||||
import { borders, spacings, spacingsPx } from '@trezor/theme';
|
||||
import { ReactNode } from 'react';
|
||||
import { TranslationKey } from '@suite-common/intl-types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const GradientCallout = styled.div`
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
${({ theme }) => theme.backgroundPrimarySubtleOnElevation1},
|
||||
${({ theme }) => theme.backgroundAlertYellowSubtleOnElevation1}
|
||||
);
|
||||
|
||||
border-radius: ${borders.radii.xxs};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const GradientCalloutCard = styled.div`
|
||||
flex: 1;
|
||||
padding: ${spacingsPx.lg} ${spacingsPx.md};
|
||||
`;
|
||||
|
||||
const IconQuestionMarkWrapper = styled.div`
|
||||
position: relative;
|
||||
margin-bottom: ${spacingsPx.xxxl};
|
||||
`;
|
||||
|
||||
const IconQuestionMark = styled(Icon)`
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 25px;
|
||||
`;
|
||||
|
||||
const TextDiv = styled(Text)`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const Callout = ({ items, header }: { items: ReactNode[]; header: TranslationKey }) => (
|
||||
<Card>
|
||||
<Column alignItems="start" gap={spacings.xs}>
|
||||
<Text typographyStyle="highlight">
|
||||
<Translation id={header} />
|
||||
</Text>
|
||||
{items.map((item, i) => (
|
||||
<Row key={i} alignItems="start" gap={spacings.xs}>
|
||||
{item}
|
||||
</Row>
|
||||
))}
|
||||
</Column>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export const MultiShareBackupStep5Done = () => (
|
||||
<Body>
|
||||
<Section>
|
||||
<Text typographyStyle="callout" variant="primary">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_GREAT" />
|
||||
</Text>
|
||||
<Translation id="TR_CREATE_MULTI_SHARE_BACKUP_CREATED_INFO_TEXT" />
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Text typographyStyle="callout">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_BACKUPS" />
|
||||
</Text>
|
||||
<Row gap={spacings.lg} alignItems="stretch">
|
||||
<Callout
|
||||
header="TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_HEADER"
|
||||
items={[
|
||||
<>
|
||||
<Icon icon="COINS" />
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE1" />
|
||||
</>,
|
||||
<>
|
||||
<Icon icon="EYE_SLASH" />
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE2" />
|
||||
</>,
|
||||
]}
|
||||
/>
|
||||
<Callout
|
||||
header="TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_HEADER"
|
||||
items={[
|
||||
<>
|
||||
<Icon icon="COINS" />
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE1" />
|
||||
</>,
|
||||
<>
|
||||
<Icon icon="EYE_SLASH" />
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE2" />
|
||||
</>,
|
||||
]}
|
||||
/>
|
||||
</Row>
|
||||
</Section>
|
||||
<Section>
|
||||
<Text typographyStyle="callout">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_SUCCESS_WHY_IS_BACKUP_IMPORTANT" />
|
||||
</Text>
|
||||
<GradientCallout>
|
||||
<Row gap={spacings.lg} alignItems="stretch">
|
||||
<GradientCalloutCard>
|
||||
<IconQuestionMarkWrapper>
|
||||
<Icon icon="TREZOR_T2T1" size={40} />
|
||||
<IconQuestionMark icon="QUESTION_FILLED" size={24} variant="primary" />
|
||||
</IconQuestionMarkWrapper>
|
||||
|
||||
<TextDiv variant="primary">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR" />
|
||||
</TextDiv>
|
||||
<TextDiv color="subdued">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR_INFO_TEXT" />
|
||||
</TextDiv>
|
||||
</GradientCalloutCard>
|
||||
<GradientCalloutCard>
|
||||
<IconQuestionMarkWrapper>
|
||||
<Icon icon="BACKUP_2" size={40} />
|
||||
<IconQuestionMark icon="QUESTION_FILLED" size={24} variant="warning" />
|
||||
</IconQuestionMarkWrapper>
|
||||
|
||||
<TextDiv variant="warning">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR" />
|
||||
</TextDiv>
|
||||
|
||||
<TextDiv color="subdued">
|
||||
<Translation id="TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT" />
|
||||
</TextDiv>
|
||||
</GradientCalloutCard>
|
||||
</Row>
|
||||
</GradientCallout>
|
||||
</Section>
|
||||
</Body>
|
||||
);
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Row } from '@trezor/components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Image, Text } from '@trezor/components';
|
||||
import { borders, spacings, spacingsPx } from '@trezor/theme';
|
||||
import { ESHOP_KEEP_METAL_URL, HELP_CENTER_SEED_CARD_URL } from '@trezor/urls';
|
||||
|
||||
import { Translation, TrezorLink } from 'src/components/suite';
|
||||
import { BackupInstructionsCard } from './BackupInstructionsCard';
|
||||
import { BackupInstructionsStepProps } from './BackupInstructionsStep';
|
||||
|
||||
const CardWrapper = styled.div`
|
||||
display: grid;
|
||||
gap: ${spacingsPx.sm};
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
`;
|
||||
|
||||
const Illustration = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border: ${borders.widths.small} solid ${({ theme }) => theme.borderElevation0};
|
||||
border-radius: ${borders.radii.md};
|
||||
padding-bottom: ${spacingsPx.lg};
|
||||
`;
|
||||
|
||||
export type InstructionBaseConfig = Pick<
|
||||
BackupInstructionsStepProps,
|
||||
'children' | 'description' | 'heading' | 'time' | 'completeness'
|
||||
>;
|
||||
|
||||
export const verifyTrezorOwnershipInstruction: InstructionBaseConfig = {
|
||||
heading: 'TR_VERIFY_TREZOR_OWNERSHIP',
|
||||
time: 2,
|
||||
description: 'TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION',
|
||||
children: (
|
||||
<Row gap={spacings.sm}>
|
||||
<BackupInstructionsCard isHorizontal icon="BACKUP_2">
|
||||
<Translation id="TR_VERIFY_TREZOR_OWNERSHIP_CARD_1" />
|
||||
</BackupInstructionsCard>
|
||||
<BackupInstructionsCard isHorizontal icon="CAMERA_SLASH">
|
||||
<Translation id="TR_VERIFY_TREZOR_OWNERSHIP_CARD_2" />
|
||||
</BackupInstructionsCard>
|
||||
</Row>
|
||||
),
|
||||
};
|
||||
|
||||
export const createSharesInstruction: InstructionBaseConfig = {
|
||||
heading: 'TR_CREATE_SHARES',
|
||||
time: 10,
|
||||
description: 'TR_CREATE_SHARES_EXPLANATION',
|
||||
children: (
|
||||
<>
|
||||
<Illustration>
|
||||
<Image image="SHAMIR_SHARES" />
|
||||
<Text typographyStyle="hint" variant="tertiary">
|
||||
<Translation id="TR_CREATE_SHARES_EXAMPLE" />
|
||||
</Text>
|
||||
</Illustration>
|
||||
<CardWrapper>
|
||||
<BackupInstructionsCard icon="PENCIL">
|
||||
<Translation
|
||||
id="TR_CREATE_SHARES_CARD_1"
|
||||
values={{
|
||||
cardsLink: chunks => (
|
||||
<TrezorLink href={HELP_CENTER_SEED_CARD_URL} variant="underline">
|
||||
{chunks}
|
||||
</TrezorLink>
|
||||
),
|
||||
keepLink: chunks => (
|
||||
<TrezorLink href={ESHOP_KEEP_METAL_URL} variant="underline">
|
||||
{chunks}
|
||||
</TrezorLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</BackupInstructionsCard>
|
||||
<BackupInstructionsCard icon="CAMERA_SLASH">
|
||||
<Translation id="TR_CREATE_SHARES_CARD_2" />
|
||||
</BackupInstructionsCard>
|
||||
<BackupInstructionsCard icon="EYE_SLASH">
|
||||
<Translation id="TR_CREATE_SHARES_CARD_3" />
|
||||
</BackupInstructionsCard>
|
||||
</CardWrapper>
|
||||
</>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Column } from '@trezor/components';
|
||||
import { spacings } from '@trezor/theme';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export const Body = ({ children }: { children: ReactNode }) => (
|
||||
<Column gap={spacings.lg} alignItems="start">
|
||||
{children}
|
||||
</Column>
|
||||
);
|
||||
|
||||
export const Section = ({ children }: { children: ReactNode }) => (
|
||||
<Column gap={spacings.xs} alignItems="start" flex={1}>
|
||||
{children}
|
||||
</Column>
|
||||
);
|
||||
@@ -46,3 +46,4 @@ export { StakeEthInANutshellModal } from './ReduxModal/UserContextModal/StakeEth
|
||||
export { StakeModal } from './ReduxModal/UserContextModal/StakeModal/StakeModal';
|
||||
export { UnstakeModal } from './ReduxModal/UserContextModal/UnstakeModal/UnstakeModal';
|
||||
export { ClaimModal } from './ReduxModal/UserContextModal/ClaimModal/ClaimModal';
|
||||
export { MultiShareBackupModal } from './ReduxModal/UserContextModal/MultiShareBackupModal/MultiShareBackupModal';
|
||||
|
||||
@@ -8,22 +8,26 @@ import { ModalAppParams } from 'src/utils/suite/router';
|
||||
const isForegroundApp = (route: Route): route is ForegroundAppRoute =>
|
||||
!route.isFullscreenApp && !!route.isForegroundApp;
|
||||
|
||||
// Firmware, FirmwareCustom, Bridge, Udev, Version - always beats redux modals
|
||||
// Backup, SwitchDevice - always get beaten by redux modals
|
||||
// Recovery - beats redux modals with some exceptions (raw-rendered)
|
||||
const hasPriority = (route: ForegroundAppRoute) => {
|
||||
switch (route.app) {
|
||||
case 'bridge':
|
||||
case 'firmware':
|
||||
case 'firmware-type':
|
||||
case 'firmware-custom':
|
||||
case 'recovery':
|
||||
case 'udev':
|
||||
case 'version':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
const map: Record<ForegroundAppRoute['app'], boolean> = {
|
||||
// Firmware, FirmwareCustom, Bridge, Udev, Version, Create New Multi-share Backup - always beats redux modals
|
||||
firmware: true,
|
||||
'firmware-type': true,
|
||||
'firmware-custom': true,
|
||||
bridge: true,
|
||||
udev: true,
|
||||
version: true,
|
||||
'create-multi-share-backup': true,
|
||||
|
||||
// Recovery - beats redux modals with some exceptions (raw-rendered)
|
||||
recovery: true,
|
||||
|
||||
// Backup, SwitchDevice - always get beaten by redux modals
|
||||
'switch-device': false,
|
||||
backup: false,
|
||||
};
|
||||
|
||||
return map[route.app];
|
||||
};
|
||||
|
||||
const getForegroundAppAction = (route: ForegroundAppRoute, params: Partial<ModalAppParams>) =>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SUITE } from 'src/actions/suite/constants';
|
||||
import * as recoveryActions from 'src/actions/recovery/recoveryActions';
|
||||
import * as onboardingActions from 'src/actions/onboarding/onboardingActions';
|
||||
import { AppState, Action, Dispatch } from 'src/types/suite';
|
||||
import { isRecoveryInProgress } from '../../utils/device/isRecoveryInProgress';
|
||||
|
||||
const recovery =
|
||||
(api: MiddlewareAPI<Dispatch, AppState>) =>
|
||||
@@ -31,7 +32,8 @@ const recovery =
|
||||
|
||||
if (
|
||||
deviceActions.updateSelectedDevice.match(action) &&
|
||||
action.payload?.features?.recovery_mode &&
|
||||
action.payload?.features !== undefined &&
|
||||
isRecoveryInProgress(action.payload?.features) &&
|
||||
recovery.status !== 'in-progress'
|
||||
) {
|
||||
api.dispatch(
|
||||
@@ -41,7 +43,7 @@ const recovery =
|
||||
}),
|
||||
);
|
||||
if (!analytics.confirmed) {
|
||||
// If you connect T2T1 in recovery mode to fresh Suite, you should see analytics optout option first.
|
||||
// If you connect T2T1 in recovery mode to fresh Suite, you should see analytics opt-out option first.
|
||||
api.dispatch(recoveryActions.setStatus('in-progress'));
|
||||
} else {
|
||||
api.dispatch(recoveryActions.rerun());
|
||||
|
||||
@@ -2100,7 +2100,7 @@ export default defineMessages({
|
||||
},
|
||||
TR_BACKUP_SUBHEADING_1: {
|
||||
defaultMessage:
|
||||
"A wallet backup is a series of randomly generated words created by your Trezor. It’s important to write down your wallet backup and keep it safe, as it's the only way to recover and access your funds.",
|
||||
"A wallet backup is a series of randomly generated words created by your Trezor. It's important to write down your wallet backup and keep it safe, as it's the only way to recover and access your funds.",
|
||||
description: 'Explanation what recovery seed is',
|
||||
id: 'TR_BACKUP_SUBHEADING_1',
|
||||
},
|
||||
@@ -2123,6 +2123,189 @@ export default defineMessages({
|
||||
description: 'Enter words on your computer, recovery takes about 2 minutes.',
|
||||
id: 'TR_BASIC_RECOVERY_OPTION',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP: {
|
||||
defaultMessage: 'Multi-share Backup',
|
||||
id: 'TR_MULTI_SHARE_BACKUP',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_DESCRIPTION: {
|
||||
defaultMessage:
|
||||
'Generate multiple shares, each containing 20 words. These shares are used collectively to recover your funds.',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_DESCRIPTION',
|
||||
},
|
||||
|
||||
TR_MULTI_SHARE_BACKUP_IN_PROGRESS: {
|
||||
defaultMessage: 'Multi-share Backup creation in progress',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_IN_PROGRESS',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING: {
|
||||
defaultMessage: 'Please continue creating shares on your Trezor',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_IN_PROGRESS_HEADING',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION: {
|
||||
defaultMessage:
|
||||
'After successfully creating a Multi-share Backup, you will be able to continue using your device with Trezor Suite.',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_IN_PROGRESS_DESCRIPTION',
|
||||
},
|
||||
|
||||
TR_CREATE_MULTI_SHARE_BACKUP: {
|
||||
defaultMessage: 'Create multi-share backup',
|
||||
id: 'TR_CREATE_MULTI_SHARE_BACKUP',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_CALLOUT_1: {
|
||||
defaultMessage: 'What does it do?',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_CALLOUT_1',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_CALLOUT_2: {
|
||||
defaultMessage: 'What about my current backup?',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_CALLOUT_2',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_CALLOUT_3: {
|
||||
defaultMessage: 'Please note',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_CALLOUT_3',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_EXPLANATION_1: {
|
||||
defaultMessage:
|
||||
'Multi backup creates multiple 20 word shares that will be needed for recovering your Trezor. You can distribute these among your family, friends, or hide them in different places. If the time comes to recover your Trezor, multiple shares must be used collectively to regain access to your funds.',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_EXPLANATION_1',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_EXPLANATION_2: {
|
||||
defaultMessage:
|
||||
'You existing backup will still allow you to recover your funds. You should hide it well, ideally away from the new backup shares.',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_EXPLANATION_2',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_CHECKBOX_1: {
|
||||
defaultMessage: 'This is an advanced feature, and I understand the extra responsibility',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_CHECKBOX_1',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_CHECKBOX_2: {
|
||||
defaultMessage: 'My current backup will still be able to recover my wallet',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_CHECKBOX_2',
|
||||
},
|
||||
TR_MULTI_SHARE_TIPS_ON_STORING_BACKUP: {
|
||||
defaultMessage: 'Tips on storing backup',
|
||||
id: 'TR_MULTI_SHARE_TIPS_ON_STORING_BACKUP',
|
||||
},
|
||||
TR_CREATE_MULTI_SHARE_BACKUP_CREATED: {
|
||||
defaultMessage: 'Multi-share Backup created',
|
||||
id: 'TR_CREATE_MULTI_SHARE_BACKUP_CREATED',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_GREAT: {
|
||||
defaultMessage: 'Great!',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_GREAT',
|
||||
},
|
||||
TR_CREATE_MULTI_SHARE_BACKUP_CREATED_INFO_TEXT: {
|
||||
defaultMessage:
|
||||
'You’ve done a huge step for improving your security. Don’t forget to hide and distribute your backup well.',
|
||||
id: 'TR_CREATE_MULTI_SHARE_BACKUP_CREATED_INFO_TEXT',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_BACKUPS: {
|
||||
defaultMessage: 'Backups',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_BACKUPS',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_HEADER: {
|
||||
defaultMessage: 'My previous backup',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE1: {
|
||||
defaultMessage: 'Still recovers you funds',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE1',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE2: {
|
||||
defaultMessage: 'Hide it well',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_LEFT_LINE2',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_HEADER: {
|
||||
defaultMessage: 'My new Multi-share Backup',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE1: {
|
||||
defaultMessage: 'Collect the number of shares set as the threshold to recover your funds',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE1',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE2: {
|
||||
defaultMessage: 'Hide them well. Can be different places, different people',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_RIGHT_LINE2',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_SUCCESS_WHY_IS_BACKUP_IMPORTANT: {
|
||||
defaultMessage: 'Why is backup important',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_SUCCESS_WHY_IS_BACKUP_IMPORTANT',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR: {
|
||||
defaultMessage: 'Lost your Trezor?',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR_INFO_TEXT: {
|
||||
defaultMessage: 'Not a problem, recover your coins with Backup!',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_LOST_YOUR_TREZOR_INFO_TEXT',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP: {
|
||||
defaultMessage: 'Lost your Backup?',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP',
|
||||
},
|
||||
TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT: {
|
||||
defaultMessage:
|
||||
'That could be very bad if you also lost your Trezor. Contact support if that happens.',
|
||||
id: 'TR_MULTI_SHARE_BACKUP_LOST_YOUR_BACKUP_INFO_TEXT',
|
||||
},
|
||||
TR_NEXT_UP: {
|
||||
defaultMessage: 'Next up',
|
||||
id: 'TR_NEXT_UP',
|
||||
},
|
||||
TR_N_MIN: {
|
||||
defaultMessage: '{n} min',
|
||||
id: 'TR_N_MIN',
|
||||
},
|
||||
TR_VERIFY_TREZOR_OWNERSHIP: {
|
||||
defaultMessage: 'Verify Trezor ownership',
|
||||
id: 'TR_VERIFY_TREZOR_OWNERSHIP',
|
||||
},
|
||||
TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION: {
|
||||
defaultMessage:
|
||||
'We need you to prove you own this wallet by entering your existing wallet backup.',
|
||||
id: 'TR_VERIFY_TREZOR_OWNERSHIP_EXPLANATION',
|
||||
},
|
||||
TR_VERIFY_TREZOR_OWNERSHIP_CARD_1: {
|
||||
defaultMessage: 'Grab your existing wallet backup',
|
||||
id: 'TR_VERIFY_TREZOR_OWNERSHIP_CARD_1',
|
||||
},
|
||||
TR_VERIFY_TREZOR_OWNERSHIP_CARD_2: {
|
||||
defaultMessage: "Don't take photos, or digital copies of backup",
|
||||
id: 'TR_VERIFY_TREZOR_OWNERSHIP_CARD_2',
|
||||
},
|
||||
TR_CREATE_SHARES: {
|
||||
defaultMessage: 'Create shares on Trezor',
|
||||
id: 'TR_CREATE_SHARES',
|
||||
},
|
||||
TR_CREATE_SHARES_EXPLANATION: {
|
||||
defaultMessage:
|
||||
"Now, you'll be selecting amount of shares, and minimum of shares required to recover your Trezor.",
|
||||
id: 'TR_CREATE_SHARES_EXPLANATION',
|
||||
},
|
||||
TR_CREATE_SHARES_EXAMPLE: {
|
||||
defaultMessage: 'e.g. 5 shares total, at least any 3 for recovery',
|
||||
id: 'TR_CREATE_SHARES_EXAMPLE',
|
||||
},
|
||||
TR_CREATE_SHARES_CARD_1: {
|
||||
defaultMessage:
|
||||
'Grab pen and paper. Or print <cardsLink>Trezor cards</cardsLink>, or use <keepLink>Trezor Keep</keepLink>',
|
||||
id: 'TR_CREATE_SHARES_CARD_1',
|
||||
},
|
||||
TR_CREATE_SHARES_CARD_2: {
|
||||
defaultMessage: "Don't take photos, or digital copies of backup",
|
||||
id: 'TR_CREATE_SHARES_CARD_2',
|
||||
},
|
||||
TR_CREATE_SHARES_CARD_3: {
|
||||
defaultMessage: "Make sure it's just you, no curious onlookers",
|
||||
id: 'TR_CREATE_SHARES_CARD_3',
|
||||
},
|
||||
TR_ENTER_EXISTING_BACKUP: {
|
||||
defaultMessage: 'Enter existing backup on Trezor',
|
||||
id: 'TR_ENTER_EXISTING_BACKUP',
|
||||
},
|
||||
TR_DONT_HAVE_BACKUP: {
|
||||
defaultMessage: "I don't have a backup",
|
||||
id: 'TR_DONT_HAVE_BACKUP',
|
||||
},
|
||||
TR_BCH_ADDRESS_INFO: {
|
||||
defaultMessage:
|
||||
'Bitcoin Cash changed the address format to cashaddr. Find more info about how to convert your address on our blog. {TR_LEARN_MORE}',
|
||||
@@ -8969,8 +9152,8 @@ export default defineMessages({
|
||||
id: 'TR_VIEW_ONLY_TOOLTIP_CHANGE_INFO',
|
||||
defaultMessage: 'You can change it here',
|
||||
},
|
||||
TR_VIEW_ONLY_TOOLTIP_BUTTON: {
|
||||
id: 'TR_VIEW_ONLY_TOOLTIP_BUTTON',
|
||||
TR_GOT_IT_BUTTON: {
|
||||
id: 'TR_GOT_IT_BUTTON',
|
||||
defaultMessage: 'Got it',
|
||||
},
|
||||
TR_VIEW_ONLY_ENABLED: {
|
||||
|
||||
27
packages/suite/src/utils/device/isRecoveryInProgress.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { PROTO } from '@trezor/connect';
|
||||
|
||||
export const isAdditionalShamirBackupInProgress = (features: PROTO.Features) =>
|
||||
features.recovery_status === 'Backup' &&
|
||||
features.recovery_type === undefined &&
|
||||
features.backup_availability === 'Available';
|
||||
|
||||
export const isRecoveryInProgress = (features: PROTO.Features) => {
|
||||
const { recovery_status, backup_availability } = features;
|
||||
|
||||
if (recovery_status === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recovery_status === 'Recovery') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isShamirAdditionalBackupRecovery =
|
||||
recovery_status === 'Backup' && backup_availability === 'NotAvailable';
|
||||
|
||||
if (isShamirAdditionalBackupRecovery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -2,6 +2,10 @@ import type { TransportInfo } from '@trezor/connect';
|
||||
import { DefinedUnionMember } from '@trezor/type-utils';
|
||||
import { RouterState } from 'src/reducers/suite/routerReducer';
|
||||
import type { TrezorDevice, AppState } from 'src/types/suite';
|
||||
import {
|
||||
isAdditionalShamirBackupInProgress,
|
||||
isRecoveryInProgress,
|
||||
} from '../device/isRecoveryInProgress';
|
||||
|
||||
type GetPrerequisiteNameParams = {
|
||||
router: AppState['router'];
|
||||
@@ -38,7 +42,13 @@ export const getPrerequisiteName = ({ router, device, transport }: GetPrerequisi
|
||||
// similar to initialize, there is no seed in device
|
||||
// difference is it is in recovery mode.
|
||||
// todo: this could be added to @trezor/connect to device.mode I think.
|
||||
if (device.features.recovery_mode) return 'device-recovery-mode';
|
||||
if (isRecoveryInProgress(device.features)) {
|
||||
return 'device-recovery-mode';
|
||||
}
|
||||
|
||||
if (isAdditionalShamirBackupInProgress(device.features)) {
|
||||
return 'multi-share-backup-in-progress';
|
||||
}
|
||||
|
||||
// device is not initialized
|
||||
// todo: should not happen and redirect to onboarding instead?
|
||||
@@ -66,6 +76,7 @@ export const getExcludedPrerequisites = (router: RouterState): PrerequisiteType[
|
||||
'device-bootloader',
|
||||
'firmware-missing',
|
||||
'firmware-required',
|
||||
'multi-share-backup-in-progress',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Paragraph, Button, Image, Row } from '@trezor/components';
|
||||
import { HELP_CENTER_FAILED_BACKUP_URL } from '@trezor/urls';
|
||||
import { HELP_CENTER_RECOVERY_ISSUES_URL } from '@trezor/urls';
|
||||
import { selectDevice } from '@suite-common/wallet-core';
|
||||
|
||||
import { useDispatch, useSelector } from 'src/hooks/suite';
|
||||
@@ -112,7 +112,7 @@ export const Backup = ({ cancelable, onCancel }: ForegroundAppProps) => {
|
||||
if (
|
||||
backup.status !== 'finished' &&
|
||||
!backup.error &&
|
||||
device.features.needs_backup === false &&
|
||||
device.features.backup_availability !== 'Required' &&
|
||||
device.features.unfinished_backup !== null
|
||||
) {
|
||||
return (
|
||||
@@ -127,7 +127,7 @@ export const Backup = ({ cancelable, onCancel }: ForegroundAppProps) => {
|
||||
<StyledImage image="UNI_ERROR" />
|
||||
<StyledP data-test="@backup/already-failed-message">
|
||||
<Translation id="BACKUP_BACKUP_ALREADY_FAILED_DESCRIPTION" />
|
||||
<TrezorLink icon="EXTERNAL_LINK" href={HELP_CENTER_FAILED_BACKUP_URL}>
|
||||
<TrezorLink icon="EXTERNAL_LINK" href={HELP_CENTER_RECOVERY_ISSUES_URL}>
|
||||
<Translation id="TR_LEARN_MORE" />
|
||||
</TrezorLink>
|
||||
</StyledP>
|
||||
|
||||
@@ -51,7 +51,8 @@ const SecurityFeatures = () => {
|
||||
if (device && device.features) {
|
||||
// TODO: add "error - backup failed" instead of needsBackup
|
||||
// TODO: add "enable passphrase" instead of hiddenWalletCreated
|
||||
needsBackup = device.features.needs_backup || device.features.unfinished_backup;
|
||||
needsBackup =
|
||||
device.features.backup_availability === 'Required' || device.features.unfinished_backup;
|
||||
pinEnabled = device.features.pin_protection;
|
||||
hiddenWalletCreated = device.features.passphrase_protection;
|
||||
backupFailed = device.features.unfinished_backup;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HELP_CENTER_FAILED_BACKUP_URL } from '@trezor/urls';
|
||||
import { HELP_CENTER_RECOVERY_ISSUES_URL } from '@trezor/urls';
|
||||
|
||||
import { SettingsSectionItem } from 'src/components/settings';
|
||||
import { ActionButton, ActionColumn, TextColumn, Translation } from 'src/components/suite';
|
||||
@@ -10,7 +10,7 @@ export const BackupFailed = () => {
|
||||
<TextColumn
|
||||
title={<Translation id="TR_BACKUP_RECOVERY_SEED_FAILED_TITLE" />}
|
||||
description={<Translation id="TR_BACKUP_RECOVERY_SEED_FAILED_DESC" />}
|
||||
buttonLink={HELP_CENTER_FAILED_BACKUP_URL}
|
||||
buttonLink={HELP_CENTER_RECOVERY_ISSUES_URL}
|
||||
/>
|
||||
<ActionColumn>
|
||||
<ActionButton isDisabled>
|
||||
|
||||
@@ -14,7 +14,7 @@ export const BackupRecoverySeed = ({ isDeviceLocked }: BackupRecoverySeedProps)
|
||||
const dispatch = useDispatch();
|
||||
const { device } = useDevice();
|
||||
|
||||
const needsBackup = !!device?.features?.needs_backup;
|
||||
const needsBackup = device?.features?.backup_availability === 'Required';
|
||||
|
||||
const handleClick = () => dispatch(goto('backup-index', { params: { cancelable: true } }));
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const CheckRecoverySeed = ({ isDeviceLocked }: CheckRecoverySeedProps) =>
|
||||
const dispatch = useDispatch();
|
||||
const { device } = useDevice();
|
||||
|
||||
const needsBackup = !!device?.features?.needs_backup;
|
||||
const needsBackup = device?.features?.backup_availability === 'Required';
|
||||
const learnMoreUrl = getCheckBackupUrl(device);
|
||||
|
||||
const handleClick = () => dispatch(goto('recovery-index', { params: { cancelable: true } }));
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { HELP_CENTER_SEED_CARD_URL } from '@trezor/urls';
|
||||
import {
|
||||
ActionButton,
|
||||
ActionColumn,
|
||||
SectionItem,
|
||||
TextColumn,
|
||||
Translation,
|
||||
} from 'src/components/suite';
|
||||
import { useDispatch, useSelector } from 'src/hooks/suite';
|
||||
import { selectDevice } from '@suite-common/wallet-core';
|
||||
import { TrezorDevice } from '@suite-common/suite-types';
|
||||
import { goto } from '../../../actions/suite/routerActions';
|
||||
|
||||
const doesSupportMultiShare = (device: TrezorDevice | undefined): boolean => {
|
||||
if (device?.features === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device.features.capabilities?.includes('Capability_Shamir')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
device.features.backup_type !== null &&
|
||||
[
|
||||
'Slip39_Single_Extendable',
|
||||
'Slip39_Basic_Extendable',
|
||||
'Slip39_Advanced_Extendable',
|
||||
].includes(device.features.backup_type)
|
||||
);
|
||||
};
|
||||
|
||||
export const MultiShareBackup = () => {
|
||||
const device = useSelector(selectDevice);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!doesSupportMultiShare(device)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleClick = () => dispatch(goto('create-multi-share-backup'));
|
||||
|
||||
return (
|
||||
<SectionItem>
|
||||
<TextColumn
|
||||
title={<Translation id="TR_MULTI_SHARE_BACKUP" />}
|
||||
description={<Translation id="TR_MULTI_SHARE_BACKUP_DESCRIPTION" />}
|
||||
buttonLink={HELP_CENTER_SEED_CARD_URL}
|
||||
/>
|
||||
<ActionColumn>
|
||||
<ActionButton
|
||||
variant="secondary"
|
||||
data-test="@settings/device/create-multi-share-backup-button"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Translation id="TR_CREATE_MULTI_SHARE_BACKUP" />
|
||||
</ActionButton>
|
||||
</ActionColumn>
|
||||
</SectionItem>
|
||||
);
|
||||
};
|
||||
@@ -20,6 +20,7 @@ import { DisplayRotation } from './DisplayRotation';
|
||||
import { FirmwareTypeChange } from './FirmwareTypeChange';
|
||||
import { FirmwareVersion } from './FirmwareVersion';
|
||||
import { Homescreen } from './Homescreen';
|
||||
import { MultiShareBackup } from './MultiShareBackup';
|
||||
import { Passphrase } from './Passphrase';
|
||||
import { PinProtection } from './PinProtection';
|
||||
import { SafetyChecks } from './SafetyChecks';
|
||||
@@ -28,12 +29,14 @@ import { WipeDevice } from './WipeDevice';
|
||||
import { ChangeLanguage } from './ChangeLanguage';
|
||||
import { EnableViewOnly } from './EnableViewOnly';
|
||||
import { selectSuiteFlags } from 'src/reducers/suite/suiteReducer';
|
||||
import { isRecoveryInProgress } from '../../../utils/device/isRecoveryInProgress';
|
||||
|
||||
const deviceSettingsUnavailable = (device?: TrezorDevice, transport?: Partial<TransportInfo>) => {
|
||||
const noTransportAvailable = transport && !transport.type;
|
||||
const wrongDeviceType = device?.type && ['unacquired', 'unreadable'].includes(device.type);
|
||||
const wrongDeviceMode =
|
||||
(device?.mode && ['seedless'].includes(device.mode)) || device?.features?.recovery_mode;
|
||||
(device?.mode && ['seedless'].includes(device.mode)) ||
|
||||
(device?.features !== undefined && isRecoveryInProgress(device?.features));
|
||||
const firmwareUpdateRequired = device?.firmware === 'required';
|
||||
|
||||
return noTransportAvailable || wrongDeviceType || wrongDeviceMode || firmwareUpdateRequired;
|
||||
@@ -120,6 +123,7 @@ export const SettingsDevice = () => {
|
||||
) : (
|
||||
<>
|
||||
<BackupRecoverySeed isDeviceLocked={isDeviceLocked} />
|
||||
<MultiShareBackup />
|
||||
<CheckRecoverySeed isDeviceLocked={isDeviceLocked} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ViewOnlyTooltip = ({ children }: ViewOnlyTooltipProps) => {
|
||||
</Text>
|
||||
</TextContent>
|
||||
<Button variant="tertiary" onClick={handleClose}>
|
||||
<Translation id="TR_VIEW_ONLY_TOOLTIP_BUTTON" />
|
||||
<Translation id="TR_GOT_IT_BUTTON" />
|
||||
</Button>
|
||||
</Notification>
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export const HELP_CENTER_FW_DOWNGRADE_T2B1_URL =
|
||||
'https://trezor.io/learn/a/downgrade-firmware-trezor-safe-3';
|
||||
export const HELP_CENTER_FW_DOWNGRADE_T3T1_URL =
|
||||
'https://trezor.io/learn/a/downgrade-firmware-trezor-safe-5';
|
||||
export const HELP_CENTER_FAILED_BACKUP_URL = 'https://trezor.io/support/a/trezor-recovery-issues';
|
||||
export const HELP_CENTER_RECOVERY_ISSUES_URL = 'https://trezor.io/support/a/trezor-recovery-issues';
|
||||
export const HELP_CENTER_ADVANCED_RECOVERY_URL =
|
||||
'https://trezor.io/learn/a/advanced-recovery-on-trezor-model-one';
|
||||
export const HELP_CENTER_XPUB_URL = 'https://trezor.io/learn/a/trezor-suite-app-public-keys-xpub';
|
||||
@@ -71,6 +71,11 @@ export const HELP_CENTER_DEVICE_AUTHENTICATION =
|
||||
'https://trezor.io/learn/a/trezor-safe-3-authentication-check';
|
||||
export const HELP_CENTER_ETH_STAKING =
|
||||
'https://trezor.io/learn/a/stake-ethereum-eth-in-trezor-suite';
|
||||
export const HELP_CENTER_SEED_CARD_URL = 'https://trezor.io/learn/a/recovery-seed-card';
|
||||
export const HELP_CENTER_MULTI_SHARE_BACKUP_URL =
|
||||
'https://trezor.io/learn/a/introducing-multi-share-backup';
|
||||
export const HELP_CENTER_KEEPING_SEED_SAFE_URL =
|
||||
'https://trezor.io/learn/a/keeping-your-recovery-seed-safe';
|
||||
|
||||
export const INVITY_URL = 'https://invity.io/';
|
||||
export const INVITY_SCHEDULE_OF_FEES = 'https://blog.invity.io/schedule-of-fees';
|
||||
@@ -95,5 +100,4 @@ export const ZKSNACKS_TERMS_URL =
|
||||
'https://github.com/zkSNACKs/WalletWasabi/blob/master/WalletWasabi/Legal/Assets/LegalDocumentsWw2.txt';
|
||||
export const CROWDIN_URL = 'https://crowdin.com/project/trezor-suite';
|
||||
|
||||
export const HELP_CENTER_MULTI_SHARE_BACKUP_URL =
|
||||
'https://trezor.io/learn/a/introducing-multi-share-backup';
|
||||
export const ESHOP_KEEP_METAL_URL = 'https://trezor.io/trezor-keep-metal';
|
||||
|
||||
@@ -113,6 +113,13 @@ export const routes = [
|
||||
isForegroundApp: true,
|
||||
params: modalAppParams,
|
||||
},
|
||||
{
|
||||
name: 'create-multi-share-backup',
|
||||
pattern: '/create-multi-share-backup',
|
||||
app: 'create-multi-share-backup',
|
||||
isForegroundApp: true,
|
||||
params: modalAppParams,
|
||||
},
|
||||
{
|
||||
name: 'wallet-index',
|
||||
pattern: '/accounts',
|
||||
|
||||
@@ -113,7 +113,7 @@ const getDeviceFeatures = (feat?: Partial<Features>): Features => ({
|
||||
imported: null,
|
||||
unlocked: true,
|
||||
firmware_present: null,
|
||||
needs_backup: false,
|
||||
backup_availability: 'NotAvailable',
|
||||
flags: 0,
|
||||
model: 'T',
|
||||
internal_model: DeviceModelInternal.T2T1,
|
||||
@@ -123,7 +123,7 @@ const getDeviceFeatures = (feat?: Partial<Features>): Features => ({
|
||||
fw_vendor: null,
|
||||
unfinished_backup: false,
|
||||
no_backup: false,
|
||||
recovery_mode: false,
|
||||
recovery_status: 'Nothing',
|
||||
capabilities: [],
|
||||
backup_type: 'Bip39',
|
||||
sd_card_present: false,
|
||||
|
||||
@@ -35,7 +35,7 @@ export const portfolioTrackerDevice: TrezorDevice = {
|
||||
imported: null,
|
||||
unlocked: true,
|
||||
firmware_present: null,
|
||||
needs_backup: false,
|
||||
backup_availability: 'NotAvailable',
|
||||
flags: 0,
|
||||
model: 'T',
|
||||
internal_model: DeviceModelInternal.T2T1,
|
||||
@@ -45,7 +45,7 @@ export const portfolioTrackerDevice: TrezorDevice = {
|
||||
fw_vendor: null,
|
||||
unfinished_backup: false,
|
||||
no_backup: false,
|
||||
recovery_mode: false,
|
||||
recovery_status: 'Nothing',
|
||||
capabilities: [],
|
||||
backup_type: 'Bip39',
|
||||
sd_card_present: false,
|
||||
|
||||