From 6625648771d6d92494a98b8f79caf4d94de3efe0 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 27 Jul 2026 12:07:07 -0300 Subject: [PATCH 1/3] feat(passkey): onboarding UI + PIN-less wallet integration Wires the passkey core into the app (stacked on feat/passkey-core): - Onboarding button + passkey lock screen; InitWallet/App mounting & routing. - xpub-only wallet start + external-signer registration; push-notification and wallet-service gating for passkey wallets. - PIN-less send / create-token / swap / reown; Security screen hides PIN & biometry for passkey wallets. - Re-adds the ttag wrapping to passkeySigner's error strings and the extracted translations (pt-br filled; da/ru-ru English fallback). All behind the passkey-onboarding.rollout flag (off by default). --- locale/da/texts.po | 254 +++++++++++++------- locale/pt-br/texts.po | 272 +++++++++++++++------- locale/ru-ru/texts.po | 248 +++++++++++++------- locale/texts.pot | 238 +++++++++++++------ src/App.js | 4 + src/components/PasskeyOnboardingButton.js | 225 ++++++++++++++++++ src/passkey/passkeySigner.js | 9 +- src/sagas/pushNotification.js | 10 + src/sagas/reown.js | 53 +++++ src/sagas/wallet.js | 38 ++- src/screens/CreateTokenConfirm.js | 15 +- src/screens/InitWallet.js | 12 +- src/screens/PasskeyLockScreen.js | 154 ++++++++++++ src/screens/Security.js | 35 +-- src/screens/SendConfirmScreen.js | 15 +- src/screens/TokenSwapReview.js | 18 +- 16 files changed, 1245 insertions(+), 355 deletions(-) create mode 100644 src/components/PasskeyOnboardingButton.js create mode 100644 src/screens/PasskeyLockScreen.js diff --git a/locale/da/texts.po b/locale/da/texts.po index adef483ec..d3a1e6d8c 100644 --- a/locale/da/texts.po +++ b/locale/da/texts.po @@ -73,7 +73,7 @@ msgstr "" msgid "CREATE FEE TOKEN" msgstr "" -#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:87 +#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:88 #: src/screens/CreateTokenDepositNotice.js:42 #: src/screens/CreateTokenTypeNotice.js:94 src/utils.js:654 msgid "CREATE TOKEN" @@ -141,11 +141,11 @@ msgstr "OM" msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Denne app er udviklet af Hathor Labs og distribueres gratis." -#: src/screens/About.js:99 src/screens/InitWallet.js:65 +#: src/screens/About.js:99 src/screens/InitWallet.js:66 msgid "This wallet is connected to the **mainnet**." msgstr "Denne wallet er tilsluttet til ** mainnet **." -#: src/screens/About.js:102 src/screens/InitWallet.js:68 +#: src/screens/About.js:102 src/screens/InitWallet.js:69 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -157,8 +157,8 @@ msgstr "" #: src/screens/About.js:107 msgid "" -"For further information, check out the |link1:Terms of Service| and |" -"link2:Privacy Policy|, or our website |link3:https://hathor.network/|." +"For further information, check out the |link1:Terms of Service| and |link2:" +"Privacy Policy|, or our website |link3:https://hathor.network/|." msgstr "" #: src/screens/AddressMode.js:122 @@ -319,6 +319,7 @@ msgstr "" msgid "Import tokens" msgstr "" +#: src/components/PasskeyOnboardingButton.js:176 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -353,73 +354,73 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:229 -#: src/screens/InitWallet.js:382 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 +#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Næste" -#: src/screens/CreateTokenConfirm.js:31 +#: src/screens/CreateTokenConfirm.js:32 msgid "" "You chose to create a **Deposit-Based Token**, which requires a 1% HTR " "deposit." msgstr "" -#: src/screens/CreateTokenConfirm.js:33 +#: src/screens/CreateTokenConfirm.js:34 msgid "" "You chose to create a **Fee-Based Token**, so a small fee will be applied to " "each future transaction of this token." msgstr "" -#: src/screens/CreateTokenConfirm.js:145 +#: src/screens/CreateTokenConfirm.js:146 msgid "Creating token" msgstr "Opretter token" -#: src/screens/CreateTokenConfirm.js:151 +#: src/screens/CreateTokenConfirm.js:152 msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:166 +#: src/screens/CreateTokenConfirm.js:174 msgid "Enter your 6-digit pin to create your token" msgstr "Indtast din 6-cifrede pin for at oprette din token" -#: src/screens/CreateTokenConfirm.js:167 +#: src/screens/CreateTokenConfirm.js:175 msgid "Authorize token creation" msgstr "Autoriser oprettelse af token" -#: src/screens/CreateTokenConfirm.js:169 src/screens/SendConfirmScreen.js:196 -#: src/screens/TokenSwapReview.js:221 +#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 +#: src/screens/TokenSwapReview.js:237 msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:210 +#: src/screens/CreateTokenConfirm.js:218 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:222 +#: src/screens/CreateTokenConfirm.js:235 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:236 +#: src/screens/CreateTokenConfirm.js:249 msgid "Token name" msgstr "Token navn" -#: src/screens/CreateTokenConfirm.js:242 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Token symbol" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:262 msgid "Deposit" msgstr "Indsæt" -#: src/screens/CreateTokenConfirm.js:257 +#: src/screens/CreateTokenConfirm.js:270 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:266 +#: src/screens/CreateTokenConfirm.js:279 msgid "Create token" msgstr "Opret token" @@ -429,9 +430,9 @@ msgid "" "When creating new tokens, a |fn:deposit of ${ depositPercentage }%| in HTR " "is required - e.g. if you create 1000 NewCoins, 10 HTR are needed as deposit." msgstr "" -"Når du opretter nye tokens, kræves der et |fn: depositum på $" -"{ depositPercentage }%| i HTR - f.eks. hvis du opretter 1000 NewCoins, er 10 " -"HTR nødvendige som depositum." +"Når du opretter nye tokens, kræves der et |fn: depositum på " +"${ depositPercentage }%| i HTR - f.eks. hvis du opretter 1000 NewCoins, er " +"10 HTR nødvendige som depositum." #: src/screens/CreateTokenDepositNotice.js:55 msgid "" @@ -592,36 +593,36 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:63 msgid "Welcome to Hathor Wallet!" msgstr "Velkommen til Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:74 msgid "" -"For further information, check out our website |link:https://" -"hathor.network/|." +"For further information, check out our website |link:https://hathor." +"network/|." msgstr "" "For yderligere information, se vores websted |link:https://hathor.network/|." -#: src/screens/InitWallet.js:86 +#: src/screens/InitWallet.js:87 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:98 +#: src/screens/InitWallet.js:99 msgid "Start" msgstr "Start" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:116 msgid "To start," msgstr "For at begynde," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:118 msgid "You need to **initialize your wallet**." msgstr "Skal du **initialisere din wallet**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:121 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -629,23 +630,23 @@ msgstr "" "Du kan enten **oprette en ny wallet** eller **importere en wallet**, der " "allerede findes." -#: src/screens/InitWallet.js:123 +#: src/screens/InitWallet.js:124 msgid "To import a wallet, you will need to provide your seed words." msgstr "For at importere en wallet skal du angive dine seed-ord." -#: src/screens/InitWallet.js:128 -msgid "Import Wallet" -msgstr "Importer wallet" - -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:129 msgid "New Wallet" msgstr "Ny wallet" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:134 +msgid "Import Wallet" +msgstr "Importer wallet" + +#: src/screens/InitWallet.js:224 msgid "Your wallet has been created!" msgstr "Din wallet er oprettet!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:226 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -653,11 +654,11 @@ msgstr "" "Du skal **lave en sikkerhedskopi** og gemme nedenstående ord **i samme " "rækkefølge, som de vises**." -#: src/screens/InitWallet.js:343 +#: src/screens/InitWallet.js:347 msgid "To import a wallet," msgstr "Hvis du vil importere en wallet," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:349 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -666,11 +667,11 @@ msgstr "" "Skal du **skrive ${ this.numberOfWords } seed-ord** i din wallet adskilt med " "mellemrum." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:352 msgid "Words" msgstr "Ord" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:357 msgid "Enter your seed words separated by space" msgstr "Indtast dine seed-ord adskilt med mellemrum" @@ -703,8 +704,8 @@ msgstr "**${ props.loadedData.addresses } adresser** fundet" msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:318 -#: src/screens/Settings.js:177 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Nulstil wallet" @@ -758,6 +759,23 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" +#: src/screens/PasskeyLockScreen.js:61 +msgid "Unlock cancelled." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:103 +#, javascript-format +msgid "Unlock with the passkey \"${ passkeyLabel }\"" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:104 +msgid "Unlock with your passkey" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:124 +msgid "Unlock" +msgstr "" + #: src/screens/PinScreen.js:272 msgid "Incorrect PIN Code. Try again." msgstr "Forkert PIN-kode. Prøv igen." @@ -946,15 +964,15 @@ msgstr "Ingen biometri understøttet" msgid "Use ${ this.supportedBiometry }" msgstr "Brug ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:195 msgid "SECURITY" msgstr "SIKKERHED" -#: src/screens/Security.js:213 +#: src/screens/Security.js:221 msgid "Change PIN" msgstr "Skift pinkode" -#: src/screens/Security.js:218 +#: src/screens/Security.js:226 msgid "Lock wallet" msgstr "Lås wallet" @@ -977,99 +995,99 @@ msgid_plural "${ amountAndToken } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:287 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 msgid "SEND ${ tokenNameUpperCase }" msgstr "SEND ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:42 src/screens/TokenSwapReview.js:320 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 msgid "No fee" msgstr "" -#: src/screens/SendConfirmScreen.js:54 +#: src/screens/SendConfirmScreen.js:55 msgid "Insufficient HTR to cover the network fee." msgstr "" -#: src/screens/SendConfirmScreen.js:57 +#: src/screens/SendConfirmScreen.js:58 msgid "Insufficient balance to send this transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:59 +#: src/screens/SendConfirmScreen.js:60 msgid "Failed to process transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:178 src/screens/TokenSwapReview.js:205 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 msgid "Your transfer is being processed" msgstr "Din overførsel behandles" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:194 -#: src/screens/TokenSwapReview.js:219 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 +#: src/screens/TokenSwapReview.js:235 msgid "Enter your 6-digit pin to authorize operation" msgstr "Indtast din 6-cifrede pin for at godkende overførslen" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:195 -#: src/screens/TokenSwapReview.js:220 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 +#: src/screens/TokenSwapReview.js:236 msgid "Authorize operation" msgstr "Autoriserer overførslen" -#: src/screens/SendConfirmScreen.js:235 +#: src/screens/SendConfirmScreen.js:243 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:258 +#: src/screens/SendConfirmScreen.js:266 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:261 +#: src/screens/SendConfirmScreen.js:269 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:264 +#: src/screens/SendConfirmScreen.js:272 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:274 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:271 +#: src/screens/SendConfirmScreen.js:279 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:293 +#: src/screens/SendConfirmScreen.js:301 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:311 +#: src/screens/SendConfirmScreen.js:319 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:322 +#: src/screens/SendConfirmScreen.js:335 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:344 +#: src/screens/SendConfirmScreen.js:357 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:347 +#: src/screens/SendConfirmScreen.js:360 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:352 +#: src/screens/SendConfirmScreen.js:365 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:373 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:367 +#: src/screens/SendConfirmScreen.js:380 msgid "Send" msgstr "Send" @@ -1156,25 +1174,25 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:231 +#: src/screens/TokenSwapReview.js:247 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:237 +#: src/screens/TokenSwapReview.js:253 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:255 +#: src/screens/TokenSwapReview.js:271 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:316 +#: src/screens/TokenSwapReview.js:332 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:328 +#: src/screens/TokenSwapReview.js:344 msgid "SWAP" msgstr "" @@ -1620,24 +1638,44 @@ msgstr "Transaktion" msgid "Open" msgstr "Åben" -#: src/sagas/wallet.js:908 +#: src/sagas/wallet.js:932 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:922 +#: src/sagas/wallet.js:946 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:956 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:948 +#: src/sagas/wallet.js:972 msgid "There was an error while loading first wallet address. Try again." msgstr "" +#: src/passkey/passkeySigner.js:35 +msgid "Passkey authorization was cancelled. Nothing was sent." +msgstr "" + +#: src/passkey/passkeySigner.js:45 +#, javascript-format +msgid "" +"This passkey opens a different wallet. Use the passkey named \"${ label }\"." +msgstr "" + +#: src/passkey/passkeySigner.js:46 +msgid "" +"This passkey opens a different wallet. Use the passkey that created this " +"wallet." +msgstr "" + +#: src/passkey/passkeySigner.js:54 +msgid "Another passkey authorization is already in progress." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1758,6 +1796,54 @@ msgstr "" msgid "No internet connection" msgstr "Ingen internetforbindelse" +#: src/components/PasskeyOnboardingButton.js:98 +msgid "Passkey unavailable" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:121 +msgid "Passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:128 +msgid "Continue with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Working…" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +msgid "Sign in with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:139 +msgid "Create passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:144 +#, javascript-format +msgid "" +"Your wallet is protected by this passkey. Keep it backed up by syncing it to " +"${ passwordManager } so you don't lose access." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:149 +msgid "Name your wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:151 +msgid "This name identifies the passkey when you sign in later." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:158 +msgid "e.g. Savings" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Create" +msgstr "" + #: src/components/PublicExplorerListButton.js:17 msgid "Public Explorer" msgstr "Public Explorer" diff --git a/locale/pt-br/texts.po b/locale/pt-br/texts.po index e56992ea1..24e47de09 100644 --- a/locale/pt-br/texts.po +++ b/locale/pt-br/texts.po @@ -73,7 +73,7 @@ msgstr "CRIAR TOKEN DE DEPÓSITO" msgid "CREATE FEE TOKEN" msgstr "CRIAR TOKEN DE TAXA" -#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:87 +#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:88 #: src/screens/CreateTokenDepositNotice.js:42 #: src/screens/CreateTokenTypeNotice.js:94 src/utils.js:654 msgid "CREATE TOKEN" @@ -148,11 +148,11 @@ msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" "Este aplicativo foi desenvolvido pela Hathor Labs e é distribuído de graça." -#: src/screens/About.js:99 src/screens/InitWallet.js:65 +#: src/screens/About.js:99 src/screens/InitWallet.js:66 msgid "This wallet is connected to the **mainnet**." msgstr "Esta wallet está conectada à **mainnet**." -#: src/screens/About.js:102 src/screens/InitWallet.js:68 +#: src/screens/About.js:102 src/screens/InitWallet.js:69 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -164,8 +164,8 @@ msgstr "" #: src/screens/About.js:107 msgid "" -"For further information, check out the |link1:Terms of Service| and |" -"link2:Privacy Policy|, or our website |link3:https://hathor.network/|." +"For further information, check out the |link1:Terms of Service| and |link2:" +"Privacy Policy|, or our website |link3:https://hathor.network/|." msgstr "" "Para mais informações, veja os |link1:Termos de Serviço| e |link2:Política " "de Privacidade|, ou visite nosso site |link3:https://hathor.network/|." @@ -332,6 +332,7 @@ msgstr "Você está prestes a adicionar esses tokens à sua carteira:" msgid "Import tokens" msgstr "Importar tokens" +#: src/components/PasskeyOnboardingButton.js:176 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -366,13 +367,13 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "Quantidade de ${ name } (${ symbol })" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:229 -#: src/screens/InitWallet.js:382 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 +#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Próximo" -#: src/screens/CreateTokenConfirm.js:31 +#: src/screens/CreateTokenConfirm.js:32 msgid "" "You chose to create a **Deposit-Based Token**, which requires a 1% HTR " "deposit." @@ -380,7 +381,7 @@ msgstr "" "Você escolheu criar um **Token Baseado em Depósito**, que requer um depósito " "de 1% em HTR." -#: src/screens/CreateTokenConfirm.js:33 +#: src/screens/CreateTokenConfirm.js:34 msgid "" "You chose to create a **Fee-Based Token**, so a small fee will be applied to " "each future transaction of this token." @@ -388,55 +389,55 @@ msgstr "" "Você escolheu criar um **Token com Taxa**, então uma pequena taxa será " "aplicada a cada transação futura deste token." -#: src/screens/CreateTokenConfirm.js:145 +#: src/screens/CreateTokenConfirm.js:146 msgid "Creating token" msgstr "Criando um token" -#: src/screens/CreateTokenConfirm.js:151 +#: src/screens/CreateTokenConfirm.js:152 msgid "Building the transaction" msgstr "Criando a transação" -#: src/screens/CreateTokenConfirm.js:166 +#: src/screens/CreateTokenConfirm.js:174 msgid "Enter your 6-digit pin to create your token" msgstr "Digite seu PIN de 6 dígitos para criar o seu token" -#: src/screens/CreateTokenConfirm.js:167 +#: src/screens/CreateTokenConfirm.js:175 msgid "Authorize token creation" msgstr "Autorizar criação de token" -#: src/screens/CreateTokenConfirm.js:169 src/screens/SendConfirmScreen.js:196 -#: src/screens/TokenSwapReview.js:221 +#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 +#: src/screens/TokenSwapReview.js:237 msgid "Building transaction" msgstr "Criando transação" -#: src/screens/CreateTokenConfirm.js:210 +#: src/screens/CreateTokenConfirm.js:218 #, javascript-format msgid "**${ name }** created successfully" msgstr "**${ name }** criado com sucesso" -#: src/screens/CreateTokenConfirm.js:222 +#: src/screens/CreateTokenConfirm.js:235 msgid "Amount of ${ name }" msgstr "Quantidade de ${ name }" -#: src/screens/CreateTokenConfirm.js:236 +#: src/screens/CreateTokenConfirm.js:249 msgid "Token name" msgstr "Nome do token" -#: src/screens/CreateTokenConfirm.js:242 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Símbolo do token" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:262 msgid "Deposit" msgstr "Depósito" -#: src/screens/CreateTokenConfirm.js:257 +#: src/screens/CreateTokenConfirm.js:270 msgid "Network fee" msgstr "Taxa de Rede" -#: src/screens/CreateTokenConfirm.js:266 +#: src/screens/CreateTokenConfirm.js:279 msgid "Create token" msgstr "Criar um token" @@ -553,8 +554,8 @@ msgid "" "Once selected, the token type cannot be changed later. |link:Learn more " "about deposits and fees here|" msgstr "" -"Uma vez selecionado, o tipo de token não pode ser alterado depois. |" -"link:Saiba mais sobre depósitos e taxas aqui|" +"Uma vez selecionado, o tipo de token não pode ser alterado depois. |link:" +"Saiba mais sobre depósitos e taxas aqui|" #: src/screens/CreateTokenTypeNotice.js:133 msgid "DEPOSIT TOKEN" @@ -614,18 +615,18 @@ msgstr "Tokens encontrados" msgid "Continue" msgstr "Continuar" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:63 msgid "Welcome to Hathor Wallet!" msgstr "Bem vindo à Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:74 msgid "" -"For further information, check out our website |link:https://" -"hathor.network/|." +"For further information, check out our website |link:https://hathor." +"network/|." msgstr "" "Para mais informações, visite nosso site |link:https://hathor.network/|." -#: src/screens/InitWallet.js:86 +#: src/screens/InitWallet.js:87 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" @@ -633,19 +634,19 @@ msgstr "" "Eu concordo com os |link1:Termos de Serviço| e |link2:Política de " "Privacidade| e entendo os riscos de usar uma wallet de celular" -#: src/screens/InitWallet.js:98 +#: src/screens/InitWallet.js:99 msgid "Start" msgstr "Iniciar" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:116 msgid "To start," msgstr "Para começar," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:118 msgid "You need to **initialize your wallet**." msgstr "Você precisa **inicializar sua wallet**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:121 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -653,23 +654,23 @@ msgstr "" "Você pode tanto **iniciar uma nova wallet** ou **importar uma wallet** que " "já existe." -#: src/screens/InitWallet.js:123 +#: src/screens/InitWallet.js:124 msgid "To import a wallet, you will need to provide your seed words." msgstr "Para importar uma wallet, você deve entrar com as suas palavras." -#: src/screens/InitWallet.js:128 -msgid "Import Wallet" -msgstr "Importar Wallet" - -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:129 msgid "New Wallet" msgstr "Nova Wallet" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:134 +msgid "Import Wallet" +msgstr "Importar Wallet" + +#: src/screens/InitWallet.js:224 msgid "Your wallet has been created!" msgstr "Sua wallet foi criada!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:226 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -677,11 +678,11 @@ msgstr "" "Você deve **realizar o backup** e salvar as palavras abaixo **na mesma ordem " "em que elas aparecem**." -#: src/screens/InitWallet.js:343 +#: src/screens/InitWallet.js:347 msgid "To import a wallet," msgstr "Para importar sua wallet," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:349 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -690,11 +691,11 @@ msgstr "" "Você precisa **digitar as ${ this.numberOfWords } palavras** da sua wallet, " "separadas por espaço." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:352 msgid "Words" msgstr "Palavras" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:357 msgid "Enter your seed words separated by space" msgstr "Digite suas palavras separadas por espaços" @@ -727,8 +728,8 @@ msgstr "**${ loadedData.addresses } endereços** encontrados" msgid "There's been an error connecting to the server." msgstr "Ocorreu um erro ao conectar com o servidor." -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:318 -#: src/screens/Settings.js:177 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Resetar Wallet" @@ -784,6 +785,23 @@ msgstr "Registrar Nano Contract" msgid "Scan the nano contract ID QR code" msgstr "Escaneie o QR code do ID do Nano Contract" +#: src/screens/PasskeyLockScreen.js:61 +msgid "Unlock cancelled." +msgstr "Desbloqueamento cancelado." + +#: src/screens/PasskeyLockScreen.js:103 +#, javascript-format +msgid "Unlock with the passkey \"${ passkeyLabel }\"" +msgstr "Desbloquear com passkey \"${ passkeyLabel }\"" + +#: src/screens/PasskeyLockScreen.js:104 +msgid "Unlock with your passkey" +msgstr "Desbloquear com sua passkey" + +#: src/screens/PasskeyLockScreen.js:124 +msgid "Unlock" +msgstr "Desbloquear" + #: src/screens/PinScreen.js:272 msgid "Incorrect PIN Code. Try again." msgstr "PIN incorreto. Tente novamente." @@ -975,15 +993,15 @@ msgstr "Nenhuma biometria suportada" msgid "Use ${ this.supportedBiometry }" msgstr "Usar ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:195 msgid "SECURITY" msgstr "SEGURANÇA" -#: src/screens/Security.js:213 +#: src/screens/Security.js:221 msgid "Change PIN" msgstr "Mudar PIN" -#: src/screens/Security.js:218 +#: src/screens/Security.js:226 msgid "Lock wallet" msgstr "Bloquear a wallet" @@ -1006,99 +1024,99 @@ msgid_plural "${ amountAndToken } available" msgstr[0] "${ amountAndToken } disponível" msgstr[1] "${ amountAndToken } disponíveis" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:287 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 msgid "SEND ${ tokenNameUpperCase }" msgstr "ENVIAR ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:42 src/screens/TokenSwapReview.js:320 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 msgid "No fee" msgstr "Sem taxa" -#: src/screens/SendConfirmScreen.js:54 +#: src/screens/SendConfirmScreen.js:55 msgid "Insufficient HTR to cover the network fee." msgstr "Saldo insuficiente de HTR para cobrir a taxa da rede." -#: src/screens/SendConfirmScreen.js:57 +#: src/screens/SendConfirmScreen.js:58 msgid "Insufficient balance to send this transaction." msgstr "Saldo insuficiente para completar a transação." -#: src/screens/SendConfirmScreen.js:59 +#: src/screens/SendConfirmScreen.js:60 msgid "Failed to process transaction." msgstr "Falha ao processar a transação" -#: src/screens/SendConfirmScreen.js:178 src/screens/TokenSwapReview.js:205 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 msgid "Your transfer is being processed" msgstr "Sua transferência está sendo processada" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:194 -#: src/screens/TokenSwapReview.js:219 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 +#: src/screens/TokenSwapReview.js:235 msgid "Enter your 6-digit pin to authorize operation" msgstr "Digite seu PIN de 6 dígitos para autorizar a operação" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:195 -#: src/screens/TokenSwapReview.js:220 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 +#: src/screens/TokenSwapReview.js:236 msgid "Authorize operation" msgstr "Autorizar operação" -#: src/screens/SendConfirmScreen.js:235 +#: src/screens/SendConfirmScreen.js:243 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "Você tem ${ availablePretty } disponível" msgstr[1] "Você tem ${ availablePretty } disponíveis" -#: src/screens/SendConfirmScreen.js:258 +#: src/screens/SendConfirmScreen.js:266 msgid "Loading fee information..." msgstr "Carregando informações da taxa..." -#: src/screens/SendConfirmScreen.js:261 +#: src/screens/SendConfirmScreen.js:269 msgid "This is the native token, no network fees are charged." msgstr "Token nativo, nenhuma taxa aplicada." -#: src/screens/SendConfirmScreen.js:264 +#: src/screens/SendConfirmScreen.js:272 msgid "This token is Deposit Based, no network fee will be charged." msgstr "Este token é de Depósito, nenhuma taxa de rede será cobrada." -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:274 msgid "This fee is fixed and required for every transfer of this token." msgstr "Esta taxa é fixa e obrigatória para cada transação deste token." #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:271 +#: src/screens/SendConfirmScreen.js:279 msgid "Loading..." msgstr "Carregando..." -#: src/screens/SendConfirmScreen.js:293 +#: src/screens/SendConfirmScreen.js:301 msgid "Building your transaction" msgstr "Criando sua transação" -#: src/screens/SendConfirmScreen.js:311 +#: src/screens/SendConfirmScreen.js:319 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Sua transferência de **${ amountAndToken }** foi confirmada" -#: src/screens/SendConfirmScreen.js:322 +#: src/screens/SendConfirmScreen.js:335 msgid "Read more." msgstr "Ler mais." -#: src/screens/SendConfirmScreen.js:344 +#: src/screens/SendConfirmScreen.js:357 msgid "**Transaction summary**" msgstr "**Resumo da transação**" -#: src/screens/SendConfirmScreen.js:347 +#: src/screens/SendConfirmScreen.js:360 msgid "**To**" msgstr "**Para**" -#: src/screens/SendConfirmScreen.js:352 +#: src/screens/SendConfirmScreen.js:365 msgid "**Network Fee**" msgstr "**Taxa de Rede**" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:373 msgid "**Total**" msgstr "**Total**" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:367 +#: src/screens/SendConfirmScreen.js:380 msgid "Send" msgstr "Enviar" @@ -1185,25 +1203,25 @@ msgstr "Ocorreu um erro ao carregar o protocolo de troca de tokens." msgid "Loading Token Swap" msgstr "Carregando troca de tokens" -#: src/screens/TokenSwapReview.js:231 +#: src/screens/TokenSwapReview.js:247 msgid "REVIEW TOKEN SWAP" msgstr "REVISAR TROCA DE TOKENS" -#: src/screens/TokenSwapReview.js:237 +#: src/screens/TokenSwapReview.js:253 msgid "Building the swap transaction for your review" msgstr "Criando transação de swap para ser revisada" -#: src/screens/TokenSwapReview.js:255 +#: src/screens/TokenSwapReview.js:271 msgid "Your swap was successful" msgstr "Sua troca foi bem-sucedida" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:316 +#: src/screens/TokenSwapReview.js:332 msgid "Network Fee" msgstr "Taxa de Rede" -#: src/screens/TokenSwapReview.js:328 +#: src/screens/TokenSwapReview.js:344 msgid "SWAP" msgstr "TROCAR" @@ -1663,25 +1681,47 @@ msgstr "Transação" msgid "Open" msgstr "Abrir" -#: src/sagas/wallet.js:908 +#: src/sagas/wallet.js:932 msgid "Wallet is not ready to load addresses." msgstr "A wallet não está pronta para carregar os endereços." #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:922 +#: src/sagas/wallet.js:946 msgid "There was an error while loading wallet addresses. Try again." msgstr "Ocorreu um erro ao carregar os endereços da wallet. Tente novamente." -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:956 msgid "Wallet is not ready to load the first address." msgstr "A wallet não está pronta para carregar o primeiro endereço." #. This will show the message in the feedback content -#: src/sagas/wallet.js:948 +#: src/sagas/wallet.js:972 msgid "There was an error while loading first wallet address. Try again." msgstr "" "Ocorreu um erro ao carregar o primeiro endereço da wallet. Tente novamente." +#: src/passkey/passkeySigner.js:35 +msgid "Passkey authorization was cancelled. Nothing was sent." +msgstr "Autorização da passkey cancelada. Nada foi enviado." + +#: src/passkey/passkeySigner.js:45 +#, javascript-format +msgid "" +"This passkey opens a different wallet. Use the passkey named \"${ label }\"." +msgstr "" +"Essa passkey é de uma carteira diferente. Use a passkey \"${ label }\"." + +#: src/passkey/passkeySigner.js:46 +msgid "" +"This passkey opens a different wallet. Use the passkey that created this " +"wallet." +msgstr "" +"Essa passkey é de uma carteira diferente. Use a passkey que criou essa wallet" + +#: src/passkey/passkeySigner.js:54 +msgid "Another passkey authorization is already in progress." +msgstr "Outra autorização de passkey em progresso." + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "Máx" @@ -1805,6 +1845,56 @@ msgstr "Adicionar tokens falhou" msgid "No internet connection" msgstr "Sem conexão com a internet" +#: src/components/PasskeyOnboardingButton.js:98 +msgid "Passkey unavailable" +msgstr "Passkey indisponível." + +#: src/components/PasskeyOnboardingButton.js:121 +msgid "Passkey" +msgstr "Passkey" + +#: src/components/PasskeyOnboardingButton.js:128 +msgid "Continue with passkey" +msgstr "Continuar com passkey" + +#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Working…" +msgstr "Processando..." + +#: src/components/PasskeyOnboardingButton.js:133 +msgid "Sign in with passkey" +msgstr "Entrar com passkey" + +#: src/components/PasskeyOnboardingButton.js:139 +msgid "Create passkey wallet" +msgstr "Criar uma carteira com passkey" + +#: src/components/PasskeyOnboardingButton.js:144 +#, javascript-format +msgid "" +"Your wallet is protected by this passkey. Keep it backed up by syncing it to " +"${ passwordManager } so you don't lose access." +msgstr "" +"Sua carteira está protegida por esse passkey. Mantenha um backup dele " +"sincronizando o ${ passwordManager } para não perder acesso." + +#: src/components/PasskeyOnboardingButton.js:149 +msgid "Name your wallet" +msgstr "Nome da sua carteira" + +#: src/components/PasskeyOnboardingButton.js:151 +msgid "This name identifies the passkey when you sign in later." +msgstr "Esse nome identifica a passkey quando você entrar depois." + +#: src/components/PasskeyOnboardingButton.js:158 +msgid "e.g. Savings" +msgstr "e.g. Poupança" + +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Create" +msgstr "Criar" + #: src/components/PublicExplorerListButton.js:17 msgid "Public Explorer" msgstr "Explorer Público" @@ -2810,6 +2900,22 @@ msgstr "${ tokenSymbol } Conceder Authority" msgid "${ tokenSymbol } Acquire Authority" msgstr "${ tokenSymbol } Obter Authority" +#~ msgid "No seed phrase to back up" +#~ msgstr "Não há nenhum conjunto de palavras para anotar como backup." + +#~ msgid "" +#~ "This wallet is protected only by your passkey — there is no seed phrase " +#~ "and no PIN. If you lose your passkey and it isn't synced to your password " +#~ "manager (iCloud Keychain or Google Password Manager), your funds cannot " +#~ "be recovered." +#~ msgstr "" +#~ "Essa carteira é protegida apenas pelo seu passkey - não há conjunto de " +#~ "palavras ou PIN. Se você perder sua passkey e não estiver sincronizada " +#~ "com sua conta do iCloud ou Google, seus fundos não podem ser recuperados." + +#~ msgid "Create wallet" +#~ msgstr "Criar carteira" + #~ msgid "Calculating network fee..." #~ msgstr "Calculando taxa da rede..." diff --git a/locale/ru-ru/texts.po b/locale/ru-ru/texts.po index fdac4baaf..20e6a24c3 100644 --- a/locale/ru-ru/texts.po +++ b/locale/ru-ru/texts.po @@ -74,7 +74,7 @@ msgstr "" msgid "CREATE FEE TOKEN" msgstr "" -#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:87 +#: src/screens/CreateTokenAmount.js:36 src/screens/CreateTokenConfirm.js:88 #: src/screens/CreateTokenDepositNotice.js:42 #: src/screens/CreateTokenTypeNotice.js:94 src/utils.js:654 msgid "CREATE TOKEN" @@ -143,11 +143,11 @@ msgstr "О НАС" msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Это приложение разработано Hathor Labs и распространяется бесплатно." -#: src/screens/About.js:99 src/screens/InitWallet.js:65 +#: src/screens/About.js:99 src/screens/InitWallet.js:66 msgid "This wallet is connected to the **mainnet**." msgstr "Этот кошелек подключен к **mainnet**." -#: src/screens/About.js:102 src/screens/InitWallet.js:68 +#: src/screens/About.js:102 src/screens/InitWallet.js:69 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -158,8 +158,8 @@ msgstr "" #: src/screens/About.js:107 msgid "" -"For further information, check out the |link1:Terms of Service| and |" -"link2:Privacy Policy|, or our website |link3:https://hathor.network/|." +"For further information, check out the |link1:Terms of Service| and |link2:" +"Privacy Policy|, or our website |link3:https://hathor.network/|." msgstr "" #: src/screens/AddressMode.js:122 @@ -320,6 +320,7 @@ msgstr "" msgid "Import tokens" msgstr "" +#: src/components/PasskeyOnboardingButton.js:176 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -354,73 +355,73 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:229 -#: src/screens/InitWallet.js:382 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 +#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Далее" -#: src/screens/CreateTokenConfirm.js:31 +#: src/screens/CreateTokenConfirm.js:32 msgid "" "You chose to create a **Deposit-Based Token**, which requires a 1% HTR " "deposit." msgstr "" -#: src/screens/CreateTokenConfirm.js:33 +#: src/screens/CreateTokenConfirm.js:34 msgid "" "You chose to create a **Fee-Based Token**, so a small fee will be applied to " "each future transaction of this token." msgstr "" -#: src/screens/CreateTokenConfirm.js:145 +#: src/screens/CreateTokenConfirm.js:146 msgid "Creating token" msgstr "Создать токен" -#: src/screens/CreateTokenConfirm.js:151 +#: src/screens/CreateTokenConfirm.js:152 msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:166 +#: src/screens/CreateTokenConfirm.js:174 msgid "Enter your 6-digit pin to create your token" msgstr "Введите 6-значный PIN-код для создания токена" -#: src/screens/CreateTokenConfirm.js:167 +#: src/screens/CreateTokenConfirm.js:175 msgid "Authorize token creation" msgstr "Авторизовать создание токена" -#: src/screens/CreateTokenConfirm.js:169 src/screens/SendConfirmScreen.js:196 -#: src/screens/TokenSwapReview.js:221 +#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 +#: src/screens/TokenSwapReview.js:237 msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:210 +#: src/screens/CreateTokenConfirm.js:218 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:222 +#: src/screens/CreateTokenConfirm.js:235 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:236 +#: src/screens/CreateTokenConfirm.js:249 msgid "Token name" msgstr "Имя токена" -#: src/screens/CreateTokenConfirm.js:242 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Символ токена" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:262 msgid "Deposit" msgstr "Депозит" -#: src/screens/CreateTokenConfirm.js:257 +#: src/screens/CreateTokenConfirm.js:270 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:266 +#: src/screens/CreateTokenConfirm.js:279 msgid "Create token" msgstr "Создать токен" @@ -593,37 +594,37 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:63 msgid "Welcome to Hathor Wallet!" msgstr "Добро пожаловать в Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:74 msgid "" -"For further information, check out our website |link:https://" -"hathor.network/|." +"For further information, check out our website |link:https://hathor." +"network/|." msgstr "" "Для получения дополнительной информации, посетите наш веб-сайт |link:https://" "hathor.network/|." -#: src/screens/InitWallet.js:86 +#: src/screens/InitWallet.js:87 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:98 +#: src/screens/InitWallet.js:99 msgid "Start" msgstr "Начать" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:116 msgid "To start," msgstr "Начать," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:118 msgid "You need to **initialize your wallet**." msgstr "Вам нужно **инициализировать свой кошелек**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:121 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -631,23 +632,23 @@ msgstr "" "Вы можете **создать новый кошелек**, либо **импортировать кошелек**, который " "уже существует." -#: src/screens/InitWallet.js:123 +#: src/screens/InitWallet.js:124 msgid "To import a wallet, you will need to provide your seed words." msgstr "Чтобы импортировать кошелек, необходимо ввести seed-фразу." -#: src/screens/InitWallet.js:128 -msgid "Import Wallet" -msgstr "Импортировать Кошелек" - -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:129 msgid "New Wallet" msgstr "Новый Кошелек" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:134 +msgid "Import Wallet" +msgstr "Импортировать Кошелек" + +#: src/screens/InitWallet.js:224 msgid "Your wallet has been created!" msgstr "Ваш кошелек создан!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:226 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -655,11 +656,11 @@ msgstr "" "Вы должны **сделать резервную копию** и сохранить слова ниже **в том же " "порядке, в котором они появились**." -#: src/screens/InitWallet.js:343 +#: src/screens/InitWallet.js:347 msgid "To import a wallet," msgstr "Чтобы импортировать кошелек," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:349 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -667,11 +668,11 @@ msgid "" msgstr "" "Вам нужно **записать ${ this.numberOfWords } seed-фразу** вашего кошелька." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:352 msgid "Words" msgstr "Слова" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:357 msgid "Enter your seed words separated by space" msgstr "Введите seed-фразу" @@ -704,8 +705,8 @@ msgstr "**${ loadedData.addresses } адресов** найдено" msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:318 -#: src/screens/Settings.js:177 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Сбросить кошелек" @@ -760,6 +761,23 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" +#: src/screens/PasskeyLockScreen.js:61 +msgid "Unlock cancelled." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:103 +#, javascript-format +msgid "Unlock with the passkey \"${ passkeyLabel }\"" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:104 +msgid "Unlock with your passkey" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:124 +msgid "Unlock" +msgstr "" + #: src/screens/PinScreen.js:272 msgid "Incorrect PIN Code. Try again." msgstr "Неверный PIN-код. Попробуйте еще раз." @@ -947,15 +965,15 @@ msgstr "Биометрия не поддерживается" msgid "Use ${ this.supportedBiometry }" msgstr "Использовать ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:195 msgid "SECURITY" msgstr "БЕЗОПАСНОСТЬ" -#: src/screens/Security.js:213 +#: src/screens/Security.js:221 msgid "Change PIN" msgstr "Изменить PIN-код" -#: src/screens/Security.js:218 +#: src/screens/Security.js:226 msgid "Lock wallet" msgstr "Заблокировать кошелек" @@ -979,41 +997,41 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:287 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 msgid "SEND ${ tokenNameUpperCase }" msgstr "ОТПРАВИТЬ ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:42 src/screens/TokenSwapReview.js:320 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 msgid "No fee" msgstr "" -#: src/screens/SendConfirmScreen.js:54 +#: src/screens/SendConfirmScreen.js:55 msgid "Insufficient HTR to cover the network fee." msgstr "" -#: src/screens/SendConfirmScreen.js:57 +#: src/screens/SendConfirmScreen.js:58 msgid "Insufficient balance to send this transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:59 +#: src/screens/SendConfirmScreen.js:60 msgid "Failed to process transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:178 src/screens/TokenSwapReview.js:205 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 msgid "Your transfer is being processed" msgstr "Ваш перевод обрабатывается" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:194 -#: src/screens/TokenSwapReview.js:219 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 +#: src/screens/TokenSwapReview.js:235 msgid "Enter your 6-digit pin to authorize operation" msgstr "Введите 6-значный PIN-код для авторизации операции" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:195 -#: src/screens/TokenSwapReview.js:220 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 +#: src/screens/TokenSwapReview.js:236 msgid "Authorize operation" msgstr "Авторизовать операцию" -#: src/screens/SendConfirmScreen.js:235 +#: src/screens/SendConfirmScreen.js:243 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" @@ -1021,58 +1039,58 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendConfirmScreen.js:258 +#: src/screens/SendConfirmScreen.js:266 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:261 +#: src/screens/SendConfirmScreen.js:269 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:264 +#: src/screens/SendConfirmScreen.js:272 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:274 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:271 +#: src/screens/SendConfirmScreen.js:279 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:293 +#: src/screens/SendConfirmScreen.js:301 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:311 +#: src/screens/SendConfirmScreen.js:319 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Ваш перевод **${ amountAndToken }** был подтвержден" -#: src/screens/SendConfirmScreen.js:322 +#: src/screens/SendConfirmScreen.js:335 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:344 +#: src/screens/SendConfirmScreen.js:357 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:347 +#: src/screens/SendConfirmScreen.js:360 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:352 +#: src/screens/SendConfirmScreen.js:365 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:373 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:367 +#: src/screens/SendConfirmScreen.js:380 msgid "Send" msgstr "Отправить" @@ -1159,25 +1177,25 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:231 +#: src/screens/TokenSwapReview.js:247 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:237 +#: src/screens/TokenSwapReview.js:253 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:255 +#: src/screens/TokenSwapReview.js:271 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:316 +#: src/screens/TokenSwapReview.js:332 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:328 +#: src/screens/TokenSwapReview.js:344 msgid "SWAP" msgstr "" @@ -1625,24 +1643,44 @@ msgstr "" msgid "Open" msgstr "Открыть" -#: src/sagas/wallet.js:908 +#: src/sagas/wallet.js:932 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:922 +#: src/sagas/wallet.js:946 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:956 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:948 +#: src/sagas/wallet.js:972 msgid "There was an error while loading first wallet address. Try again." msgstr "" +#: src/passkey/passkeySigner.js:35 +msgid "Passkey authorization was cancelled. Nothing was sent." +msgstr "" + +#: src/passkey/passkeySigner.js:45 +#, javascript-format +msgid "" +"This passkey opens a different wallet. Use the passkey named \"${ label }\"." +msgstr "" + +#: src/passkey/passkeySigner.js:46 +msgid "" +"This passkey opens a different wallet. Use the passkey that created this " +"wallet." +msgstr "" + +#: src/passkey/passkeySigner.js:54 +msgid "Another passkey authorization is already in progress." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1749,6 +1787,54 @@ msgstr "" msgid "No internet connection" msgstr "Нет соединения с интернетом" +#: src/components/PasskeyOnboardingButton.js:98 +msgid "Passkey unavailable" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:121 +msgid "Passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:128 +msgid "Continue with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Working…" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +msgid "Sign in with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:139 +msgid "Create passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:144 +#, javascript-format +msgid "" +"Your wallet is protected by this passkey. Keep it backed up by syncing it to " +"${ passwordManager } so you don't lose access." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:149 +msgid "Name your wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:151 +msgid "This name identifies the passkey when you sign in later." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:158 +msgid "e.g. Savings" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Create" +msgstr "" + #: src/components/PublicExplorerListButton.js:17 msgid "Public Explorer" msgstr "Public Explorer" diff --git a/locale/texts.pot b/locale/texts.pot index cb3022d20..0eccf91ba 100644 --- a/locale/texts.pot +++ b/locale/texts.pot @@ -67,7 +67,7 @@ msgid "CREATE FEE TOKEN" msgstr "" #: src/screens/CreateTokenAmount.js:36 -#: src/screens/CreateTokenConfirm.js:87 +#: src/screens/CreateTokenConfirm.js:88 #: src/screens/CreateTokenDepositNotice.js:42 #: src/screens/CreateTokenTypeNotice.js:94 #: src/utils.js:654 @@ -137,12 +137,12 @@ msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" #: src/screens/About.js:99 -#: src/screens/InitWallet.js:65 +#: src/screens/InitWallet.js:66 msgid "This wallet is connected to the **mainnet**." msgstr "" #: src/screens/About.js:102 -#: src/screens/InitWallet.js:68 +#: src/screens/InitWallet.js:69 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as " @@ -316,6 +316,7 @@ msgstr "" msgid "Import tokens" msgstr "" +#: src/components/PasskeyOnboardingButton.js:176 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -353,76 +354,76 @@ msgstr "" #: src/screens/CreateTokenAmount.js:211 #: src/screens/CreateTokenName.js:67 #: src/screens/CreateTokenSymbol.js:86 -#: src/screens/InitWallet.js:229 -#: src/screens/InitWallet.js:382 +#: src/screens/InitWallet.js:233 +#: src/screens/InitWallet.js:386 #: src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "" -#: src/screens/CreateTokenConfirm.js:31 +#: src/screens/CreateTokenConfirm.js:32 msgid "" "You chose to create a **Deposit-Based Token**, which requires a 1% HTR " "deposit." msgstr "" -#: src/screens/CreateTokenConfirm.js:33 +#: src/screens/CreateTokenConfirm.js:34 msgid "" "You chose to create a **Fee-Based Token**, so a small fee will be applied " "to each future transaction of this token." msgstr "" -#: src/screens/CreateTokenConfirm.js:145 +#: src/screens/CreateTokenConfirm.js:146 msgid "Creating token" msgstr "" -#: src/screens/CreateTokenConfirm.js:151 +#: src/screens/CreateTokenConfirm.js:152 msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:166 +#: src/screens/CreateTokenConfirm.js:174 msgid "Enter your 6-digit pin to create your token" msgstr "" -#: src/screens/CreateTokenConfirm.js:167 +#: src/screens/CreateTokenConfirm.js:175 msgid "Authorize token creation" msgstr "" -#: src/screens/CreateTokenConfirm.js:169 -#: src/screens/SendConfirmScreen.js:196 -#: src/screens/TokenSwapReview.js:221 +#: src/screens/CreateTokenConfirm.js:177 +#: src/screens/SendConfirmScreen.js:204 +#: src/screens/TokenSwapReview.js:237 msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:210 +#: src/screens/CreateTokenConfirm.js:218 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:222 +#: src/screens/CreateTokenConfirm.js:235 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:236 +#: src/screens/CreateTokenConfirm.js:249 msgid "Token name" msgstr "" -#: src/screens/CreateTokenConfirm.js:242 +#: src/screens/CreateTokenConfirm.js:255 #: src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:262 msgid "Deposit" msgstr "" -#: src/screens/CreateTokenConfirm.js:257 +#: src/screens/CreateTokenConfirm.js:270 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:266 +#: src/screens/CreateTokenConfirm.js:279 msgid "Create token" msgstr "" @@ -593,78 +594,78 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:63 msgid "Welcome to Hathor Wallet!" msgstr "" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:74 msgid "" "For further information, check out our website " "|link:https://hathor.network/|." msgstr "" -#: src/screens/InitWallet.js:86 +#: src/screens/InitWallet.js:87 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:98 +#: src/screens/InitWallet.js:99 msgid "Start" msgstr "" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:116 msgid "To start," msgstr "" -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:118 msgid "You need to **initialize your wallet**." msgstr "" -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:121 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." msgstr "" -#: src/screens/InitWallet.js:123 +#: src/screens/InitWallet.js:124 msgid "To import a wallet, you will need to provide your seed words." msgstr "" -#: src/screens/InitWallet.js:128 -msgid "Import Wallet" +#: src/screens/InitWallet.js:129 +msgid "New Wallet" msgstr "" #: src/screens/InitWallet.js:134 -msgid "New Wallet" +msgid "Import Wallet" msgstr "" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:224 msgid "Your wallet has been created!" msgstr "" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:226 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." msgstr "" -#: src/screens/InitWallet.js:343 +#: src/screens/InitWallet.js:347 msgid "To import a wallet," msgstr "" -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:349 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " "wallet, separated by space." msgstr "" -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:352 msgid "Words" msgstr "" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:357 msgid "Enter your seed words separated by space" msgstr "" @@ -700,6 +701,7 @@ msgid "There's been an error connecting to the server." msgstr "" #: src/screens/LoadWalletErrorScreen.js:28 +#: src/screens/PasskeyLockScreen.js:139 #: src/screens/PinScreen.js:318 #: src/screens/Settings.js:177 msgid "Reset wallet" @@ -757,6 +759,23 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" +#: src/screens/PasskeyLockScreen.js:61 +msgid "Unlock cancelled." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:103 +#, javascript-format +msgid "Unlock with the passkey \"${ passkeyLabel }\"" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:104 +msgid "Unlock with your passkey" +msgstr "" + +#: src/screens/PasskeyLockScreen.js:124 +msgid "Unlock" +msgstr "" + #: src/screens/PinScreen.js:272 msgid "Incorrect PIN Code. Try again." msgstr "" @@ -950,15 +969,15 @@ msgstr "" msgid "Use ${ this.supportedBiometry }" msgstr "" -#: src/screens/Security.js:191 +#: src/screens/Security.js:195 msgid "SECURITY" msgstr "" -#: src/screens/Security.js:213 +#: src/screens/Security.js:221 msgid "Change PIN" msgstr "" -#: src/screens/Security.js:218 +#: src/screens/Security.js:226 msgid "Lock wallet" msgstr "" @@ -983,103 +1002,103 @@ msgstr[0] "" msgstr[1] "" #: src/screens/SendAmountInput.js:137 -#: src/screens/SendConfirmScreen.js:287 +#: src/screens/SendConfirmScreen.js:295 msgid "SEND ${ tokenNameUpperCase }" msgstr "" -#: src/screens/SendConfirmScreen.js:42 -#: src/screens/TokenSwapReview.js:320 +#: src/screens/SendConfirmScreen.js:43 +#: src/screens/TokenSwapReview.js:336 msgid "No fee" msgstr "" -#: src/screens/SendConfirmScreen.js:54 +#: src/screens/SendConfirmScreen.js:55 msgid "Insufficient HTR to cover the network fee." msgstr "" -#: src/screens/SendConfirmScreen.js:57 +#: src/screens/SendConfirmScreen.js:58 msgid "Insufficient balance to send this transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:59 +#: src/screens/SendConfirmScreen.js:60 msgid "Failed to process transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:178 -#: src/screens/TokenSwapReview.js:205 +#: src/screens/SendConfirmScreen.js:179 +#: src/screens/TokenSwapReview.js:212 msgid "Your transfer is being processed" msgstr "" #: src/sagas/helpers.js:147 -#: src/screens/SendConfirmScreen.js:194 -#: src/screens/TokenSwapReview.js:219 +#: src/screens/SendConfirmScreen.js:202 +#: src/screens/TokenSwapReview.js:235 msgid "Enter your 6-digit pin to authorize operation" msgstr "" #: src/sagas/helpers.js:148 -#: src/screens/SendConfirmScreen.js:195 -#: src/screens/TokenSwapReview.js:220 +#: src/screens/SendConfirmScreen.js:203 +#: src/screens/TokenSwapReview.js:236 msgid "Authorize operation" msgstr "" -#: src/screens/SendConfirmScreen.js:235 +#: src/screens/SendConfirmScreen.js:243 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:258 +#: src/screens/SendConfirmScreen.js:266 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:261 +#: src/screens/SendConfirmScreen.js:269 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:264 +#: src/screens/SendConfirmScreen.js:272 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:274 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:271 +#: src/screens/SendConfirmScreen.js:279 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:293 +#: src/screens/SendConfirmScreen.js:301 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:311 +#: src/screens/SendConfirmScreen.js:319 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:322 +#: src/screens/SendConfirmScreen.js:335 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:344 +#: src/screens/SendConfirmScreen.js:357 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:347 +#: src/screens/SendConfirmScreen.js:360 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:352 +#: src/screens/SendConfirmScreen.js:365 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:373 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:367 +#: src/screens/SendConfirmScreen.js:380 msgid "Send" msgstr "" @@ -1167,25 +1186,25 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:231 +#: src/screens/TokenSwapReview.js:247 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:237 +#: src/screens/TokenSwapReview.js:253 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:255 +#: src/screens/TokenSwapReview.js:271 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:316 +#: src/screens/TokenSwapReview.js:332 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:328 +#: src/screens/TokenSwapReview.js:344 msgid "SWAP" msgstr "" @@ -1633,24 +1652,43 @@ msgstr "" msgid "Open" msgstr "" -#: src/sagas/wallet.js:908 +#: src/sagas/wallet.js:932 msgid "Wallet is not ready to load addresses." msgstr "" -#: src/sagas/wallet.js:922 +#: src/sagas/wallet.js:946 #. This will show the message in the feedback content at SelectAddressModal msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:956 msgid "Wallet is not ready to load the first address." msgstr "" -#: src/sagas/wallet.js:948 +#: src/sagas/wallet.js:972 #. This will show the message in the feedback content msgid "There was an error while loading first wallet address. Try again." msgstr "" +#: src/passkey/passkeySigner.js:35 +msgid "Passkey authorization was cancelled. Nothing was sent." +msgstr "" + +#: src/passkey/passkeySigner.js:45 +#, javascript-format +msgid "This passkey opens a different wallet. Use the passkey named \"${ label }\"." +msgstr "" + +#: src/passkey/passkeySigner.js:46 +msgid "" +"This passkey opens a different wallet. Use the passkey that created this " +"wallet." +msgstr "" + +#: src/passkey/passkeySigner.js:54 +msgid "Another passkey authorization is already in progress." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1757,6 +1795,54 @@ msgstr "" msgid "No internet connection" msgstr "" +#: src/components/PasskeyOnboardingButton.js:98 +msgid "Passkey unavailable" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:121 +msgid "Passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:128 +msgid "Continue with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Working…" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:133 +msgid "Sign in with passkey" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:139 +msgid "Create passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:144 +#, javascript-format +msgid "" +"Your wallet is protected by this passkey. Keep it backed up by syncing it " +"to ${ passwordManager } so you don't lose access." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:149 +msgid "Name your wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:151 +msgid "This name identifies the passkey when you sign in later." +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:158 +msgid "e.g. Savings" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:170 +msgid "Create" +msgstr "" + #: src/components/PublicExplorerListButton.js:17 msgid "Public Explorer" msgstr "" diff --git a/src/App.js b/src/App.js index c03748554..ad18bc8fa 100644 --- a/src/App.js +++ b/src/App.js @@ -50,6 +50,7 @@ import { import ChoosePinScreen from './screens/ChoosePinScreen'; import BackupWords from './screens/BackupWords'; import PinScreen from './screens/PinScreen'; +import PasskeyLockScreen from './screens/PasskeyLockScreen'; import ResetWallet from './screens/ResetWallet'; import LoadHistoryScreen from './screens/LoadHistoryScreen'; import LoadWalletErrorScreen from './screens/LoadWalletErrorScreen'; @@ -750,6 +751,9 @@ class _AppStackWrapper extends React.Component { */ if (this.props.isResetOnScreenLocked) { screen = ; + } else if (STORE.isPasskeyWallet()) { + // Passkey wallets have no PIN: unlocking is a passkey ceremony instead. + screen = ; } else { screen = ; } diff --git a/src/components/PasskeyOnboardingButton.js b/src/components/PasskeyOnboardingButton.js new file mode 100644 index 000000000..561831ad7 --- /dev/null +++ b/src/components/PasskeyOnboardingButton.js @@ -0,0 +1,225 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useEffect, useState } from 'react'; +import { + Alert, Keyboard, Platform, StyleSheet, Text, TextInput, View, +} from 'react-native'; +import { t } from 'ttag'; +import { useDispatch, useSelector } from 'react-redux'; +import { get } from 'lodash'; +import NewHathorButton from './NewHathorButton'; +import HathorModal from './HathorModal'; +import { COLORS } from '../styles/themes'; +import { PASSKEY_ONBOARDING_FEATURE_TOGGLE } from '../constants'; +import { + createWalletWordsFromPasskey, + signInWalletWordsFromPasskey, +} from '../passkey/passkeyService'; +import { derivePasskeyXpub } from '../passkey/passkeySigner'; +import { startWalletRequested, unlockScreen } from '../actions'; +import { STORE } from '../store'; +import NavigationService from '../NavigationService'; + +/** + * Passkey onboarding (passkey PRF -> seed). + * + * Renders a single "Passkey" button on the initial screen; tapping it opens a bottom-sheet with the + * two real actions, so the initial screen stays uncluttered: + * - "Sign in with passkey" -> discoverable assertion; the OS lists existing (iCloud/GPM-synced) + * passkeys to recover the SAME wallet. + * - "Create passkey wallet" -> in-modal name input (cross-platform; Alert.prompt is iOS-only), + * then mint a NEW passkey + NEW wallet. The name becomes the + * passkey displayName shown by the OS sign-in picker. + * + * Gated by the PASSKEY_ONBOARDING_FEATURE_TOGGLE flag (renders nothing when off). Both actions + * derive the seed words EPHEMERALLY, persist only the account xpub + passkey label (never the + * words — passkey wallets are PIN-less and secret-less), and start the wallet directly. Every + * later operation that needs a private key runs a fresh passkey ceremony instead of a PIN. + */ +const PasskeyOnboardingButton = () => { + const dispatch = useDispatch(); + const featureToggles = useSelector((state) => state.featureToggles); + // Gated by the Unleash feature toggle (off by default). + const enabled = get(featureToggles, PASSKEY_ONBOARDING_FEATURE_TOGGLE, false); + const [busy, setBusy] = useState(false); + const [modalVisible, setModalVisible] = useState(false); + // 'actions' -> Sign in / Create choice; 'name' -> wallet-name input before creating. + const [step, setStep] = useState('actions'); + const [walletName, setWalletName] = useState(''); + // iOS: the sheet is a pure View (no native Modal), so grow its bottom padding to keep the name + // input above the keyboard. Android pans the window instead (windowSoftInputMode="adjustPan"). + const [kbHeight, setKbHeight] = useState(0); + + useEffect(() => { + if (Platform.OS !== 'ios') return undefined; + const show = Keyboard.addListener('keyboardWillShow', (e) => setKbHeight(e.endCoordinates.height)); + const hide = Keyboard.addListener('keyboardWillHide', () => setKbHeight(0)); + return () => { + show.remove(); + hide.remove(); + }; + }, []); + + if (!enabled) return null; + + const openModal = () => { + setStep('actions'); + setWalletName(''); + setModalVisible(true); + }; + + const run = async (deriveWords) => { + setBusy(true); + try { + const { words, label, credentialId } = await deriveWords(); + // The words exist only in this scope: derive the account xpub, persist it with the + // wallet metadata, and start the wallet read-only. No PIN, no stored secret. + const xpub = derivePasskeyXpub(words); + await STORE.initPasskeyStorage(xpub, { + passkeyLabel: label || 'Passkey wallet', + credentialId: credentialId ?? null, + }); + setModalVisible(false); + // Same sequence ChoosePinScreen uses: unlock before entering the main stack, + // then request the wallet start (the saga re-reads the persisted walletMeta). + dispatch(unlockScreen()); + dispatch(startWalletRequested({ walletType: 'passkey' })); + NavigationService.resetToMain(); + } catch (e) { + // react-native-passkey throws a PasskeyError with a `.error` code — surface it so a + // generic "unknown error" becomes actionable (e.g. BadConfiguration = domain not verified). + console.warn('[passkey] failed:', e?.error, e?.message, e); + const code = e?.error ? `[${e.error}] ` : ''; + Alert.alert(t`Passkey unavailable`, `${code}${e?.message ?? String(e)}`); + } finally { + setBusy(false); + } + }; + + const onCreate = () => { + const name = walletName.trim() || 'Hathor Wallet'; + run(() => createWalletWordsFromPasskey(name)); + }; + + // A passkey wallet has no seed phrase and no PIN — its recovery is the OS syncing the passkey to + // the user's password manager. Name it per platform so the disclosure below is concrete. + const passwordManager = Platform.OS === 'ios' ? 'iCloud Keychain' : 'Google Password Manager'; + + const onDismiss = () => { + if (!busy) setModalVisible(false); + }; + + return ( + <> + + {modalVisible && ( + + {step === 'actions' ? ( + <> + {t`Continue with passkey`} + + run(signInWalletWordsFromPasskey)} + disabled={busy} + title={busy ? t`Working…` : t`Sign in with passkey`} + style={{ marginBottom: 16 }} + /> + setStep('name')} + disabled={busy} + title={t`Create passkey wallet`} + secondary + /> + + + {t`Your wallet is protected by this passkey. Keep it backed up by syncing it to ${passwordManager} so you don't lose access.`} + + + ) : ( + <> + {t`Name your wallet`} + + {t`This name identifies the passkey when you sign in later.`} + + + + + setStep('actions')} + disabled={busy} + title={t`Back`} + secondary + /> + + + )} + + )} + + ); +}; + +const styles = StyleSheet.create({ + title: { + fontSize: 18, + fontWeight: 'bold', + color: COLORS.textColor, + marginBottom: 8, + }, + subtitle: { + fontSize: 14, + color: COLORS.midContrastDetail, + marginBottom: 16, + paddingHorizontal: 24, + textAlign: 'center', + }, + syncNote: { + fontSize: 13, + color: COLORS.midContrastDetail, + textAlign: 'center', + paddingHorizontal: 24, + marginTop: 16, + }, + buttons: { + width: '100%', + paddingHorizontal: 24, + }, + input: { + borderWidth: 1, + borderColor: COLORS.midContrastDetail, + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 10, + fontSize: 16, + color: COLORS.textColor, + marginBottom: 16, + }, +}); + +export default PasskeyOnboardingButton; diff --git a/src/passkey/passkeySigner.js b/src/passkey/passkeySigner.js index 170665342..d03b4ba07 100644 --- a/src/passkey/passkeySigner.js +++ b/src/passkey/passkeySigner.js @@ -17,6 +17,7 @@ * wallet.setExternalTxSigningMethod() BEFORE wallet.start(). */ +import { t } from 'ttag'; import { constants as hathorConstants, transactionUtils, walletUtils } from '@hathor/wallet-lib'; import { NETWORK_MAINNET } from '../constants'; import { STORE } from '../store'; @@ -31,7 +32,7 @@ const log = logger('passkey-signer'); export class PasskeyCancelledError extends Error { constructor() { - super(`Passkey authorization was cancelled. Nothing was sent.`); + super(t`Passkey authorization was cancelled. Nothing was sent.`); this.name = 'PasskeyCancelledError'; } } @@ -41,8 +42,8 @@ export class PasskeyXpubMismatchError extends Error { // Labels persisted from old-format credentials may be decode garbage; never show those. const label = sanitizePasskeyLabel(storedLabel); super(label - ? `This passkey opens a different wallet. Use the passkey named "${label}".` - : `This passkey opens a different wallet. Use the passkey that created this wallet.`); + ? t`This passkey opens a different wallet. Use the passkey named "${label}".` + : t`This passkey opens a different wallet. Use the passkey that created this wallet.`); this.name = 'PasskeyXpubMismatchError'; this.storedLabel = label; } @@ -50,7 +51,7 @@ export class PasskeyXpubMismatchError extends Error { export class PasskeyBusyError extends Error { constructor() { - super(`Another passkey authorization is already in progress.`); + super(t`Another passkey authorization is already in progress.`); this.name = 'PasskeyBusyError'; } } diff --git a/src/sagas/pushNotification.js b/src/sagas/pushNotification.js index eb4360a2b..5a2a57d86 100644 --- a/src/sagas/pushNotification.js +++ b/src/sagas/pushNotification.js @@ -377,6 +377,16 @@ export function* setAvailablePushNotification(action) { * load the wallet service, otherwise it will use the wallet already loaded in the redux store. */ export function* loadWallet() { + // Passkey (xpub-only) wallets have no PIN and no stored seed words, which this flow + // requires. Push notifications are already gated off for them at startWallet; this guard + // only protects against unexpected entry paths. + if (STORE.isPasskeyWallet()) { + yield put(pushLoadWalletFailed({ + error: new Error('Push notifications are not supported for passkey wallets.'), + })); + return; + } + // This is a work-around so we can dispatch actions from inside callbacks. let dispatch; yield put((_dispatch) => { diff --git a/src/sagas/reown.js b/src/sagas/reown.js index 6b5f9ab6c..4aeac243c 100644 --- a/src/sagas/reown.js +++ b/src/sagas/reown.js @@ -124,6 +124,8 @@ import { reownReject, } from '../actions'; import { checkForFeatureFlag, getNetworkSettings, retryHandler, showPinScreenForResult } from './helpers'; +import { STORE } from '../store'; +import { consumePasskeySigningCancelled } from '../passkey/passkeySigner'; import { logger } from '../logger'; const log = logger('reown'); @@ -599,6 +601,30 @@ export function* processRequest(action) { chain: get(requestSession.namespaces, 'hathor.chains[0]', ''), }; + // Message/oracle signing calls wallet.signMessageWithAddress, which needs the stored + // private key and bypasses the external signer — impossible for xpub-only passkey + // wallets. Reject cleanly instead of crashing inside the rpc-handler. + const passkeyUnsupportedMethods = [ + AVAILABLE_METHODS.HATHOR_SIGN_MESSAGE, + AVAILABLE_METHODS.HATHOR_SIGN_ORACLE_DATA, + ]; + if (STORE.isPasskeyWallet() && passkeyUnsupportedMethods.includes(params.request.method)) { + log.debug(`Rejecting ${params.request.method}: not supported for passkey wallets.`); + yield call(() => walletKit.respondSessionRequest({ + topic: payload.topic, + response: { + id: payload.id, + jsonrpc: '2.0', + error: { + code: ERROR_CODES.USER_REJECTED_METHOD, + message: 'This operation is not yet supported for passkey wallets.', + }, + }, + })); + yield cancel(pendingPollTask); + return false; + } + // Track whether this flow shows a success screen that will dispatch the ready signal let successScreenWillSignal = false; @@ -652,6 +678,18 @@ export function* processRequest(action) { } catch (e) { log.error('Error in processRequest:', e); + // A cancelled passkey ceremony is "not now", not an error (the rpc-handler re-wraps our + // typed error, so we use a consumable flag — same pattern as pinWasCancelled). Retry the + // request: the dapp consent modal shows again and the user can accept or reject. + if (consumePasskeySigningCancelled()) { + // Cancel the poll fork from this attempt before recursing — the early return below skips the + // cleanup at the end of processRequest, so without this the fork leaks (and a new one is + // forked per retry). Mirrors the SendNanoContractTxError retry path. + yield cancel(pendingPollTask); + const result = yield* processRequest(action); + return result; // Recursive call handles waiting + } + let shouldAnswer = true; switch (e.constructor) { case SendNanoContractTxError: { @@ -1061,6 +1099,21 @@ const promptHandler = (dispatch) => (request, requestMetadata) => resolve(); break; case TriggerTypes.PinConfirmationPrompt: { + // Passkey wallets have no PIN: consent is the passkey ceremony (Face ID / fingerprint) + // fired by the external signer when the lib requests signatures. We resolve with no PIN + // — the lib's signing entry points are PIN-optional when an external tx-signing method + // is registered. + if (STORE.isPasskeyWallet()) { + resolve({ + type: TriggerResponseTypes.PinRequestResponse, + data: { + accepted: true, + pinCode: '', + } + }); + break; + } + const pinCode = await showPinScreenForResult(dispatch, true); if (pinCode === null) { diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index ee81ed8cd..158d63c3c 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -44,6 +44,7 @@ import { networkSettingsKeyMap, } from '../constants'; import { STORE } from '../store'; +import { makePasskeyTxSigner } from '../passkey/passkeySigner'; import { tokenFetchBalanceRequested, tokenFetchHistoryRequested, @@ -187,6 +188,18 @@ export function* startWallet(action) { pin, } = action.payload; + // Passkey (xpub-only) wallets carry no seed and no PIN: the saga re-reads the persisted + // metadata instead of trusting action payloads with sensitive data. + const walletMeta = STORE.getWalletMeta(); + const isPasskeyWallet = walletMeta?.walletType === 'passkey'; + + // A passkey wallet boots read-only from this xpub; if it is missing/corrupted, fail fast with a + // clear message instead of handing `{ xpub: undefined }` to HathorWallet and surfacing an opaque + // wallet-lib error later. (makePasskeyTxSigner guards the same case at sign time.) + if (isPasskeyWallet && !walletMeta?.xpub) { + throw new Error('Passkey wallet metadata is missing its xpub.'); + } + // clean memory storage and metadata before starting the wallet. // This should be cleaned when stopping the wallet, // but the wallet may be closed unexpectedly @@ -223,8 +236,11 @@ export function* startWallet(action) { yield call(monitorFeatureFlags, 0, false); const uniqueDeviceId = getUniqueId(); - const useWalletService = yield call(isWalletServiceEnabled); - const usePushNotification = yield call(isPushNotificationEnabled); + // Passkey wallets always run on the fullnode facade: the wallet-service flow requires + // PIN-decryptable secrets (auth xpriv, requestPassword) that an xpub-only wallet lacks. + // Push notifications are off for the same reason (registration re-derives the seed words). + const useWalletService = isPasskeyWallet ? false : yield call(isWalletServiceEnabled); + const usePushNotification = isPasskeyWallet ? false : yield call(isPushNotificationEnabled); yield put(setUseWalletService(useWalletService)); yield put(setAvailablePushNotification(usePushNotification)); @@ -277,7 +293,9 @@ export function* startWallet(action) { // We will save the access data on the persistent async storage // To allow starting the wallet again const walletConfig = { - seed: words, + // Passkey wallets start read-only from the stored xpub; the seed only ever exists + // ephemerally inside the passkey signer during a signature ceremony. + ...(isPasskeyWallet ? { xpub: walletMeta.xpub } : { seed: words }), storage, connection, beforeReloadCallback: () => { @@ -291,6 +309,11 @@ export function* startWallet(action) { }, }; wallet = new HathorWallet(walletConfig); + if (isPasskeyWallet) { + // Must happen BEFORE start(): flips isSignedExternally so the read-only guards allow + // sending, with signatures produced by the passkey ceremony instead of a stored key. + wallet.setExternalTxSigningMethod(makePasskeyTxSigner()); + } } // Extra wallet configuration based on customNetwork @@ -315,10 +338,11 @@ export function* startWallet(action) { try { // XXX: This comes as undefined when the facade is the wallet-service. // We need to update this when we start returning something there. - const serverInfo = yield call(wallet.start.bind(wallet), { - pinCode: pin, - password: pin, - }); + // The xpub-only (passkey) start takes no pinCode/password — there is nothing to decrypt. + const serverInfo = yield call( + wallet.start.bind(wallet), + isPasskeyWallet ? {} : { pinCode: pin, password: pin }, + ); yield put(setServerInfo(serverInfo)); diff --git a/src/screens/CreateTokenConfirm.js b/src/screens/CreateTokenConfirm.js index 28d32640c..da23d2c0a 100644 --- a/src/screens/CreateTokenConfirm.js +++ b/src/screens/CreateTokenConfirm.js @@ -23,6 +23,7 @@ import SendTransactionFeedbackModal from '../components/SendTransactionFeedbackM import TextFmt from '../components/TextFmt'; import { updateSelectedToken } from '../actions'; import { registerToken } from '../utils/tokens'; +import { STORE } from '../store'; import { InfoCircleIcon } from '../components/Icons/InfoCircle'; import { useNavigation, useParams } from '../hooks/navigation'; import { getCreateTokenTitle } from '../utils'; @@ -161,6 +162,13 @@ const CreateTokenConfirm = () => { // Disable the button before opening the PinScreen so it can't be tapped // again while we return from it and build the feedback modal. setIsSending(true); + if (STORE.isPasskeyWallet()) { + // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the + // external signer when the lib requests signatures. No PIN is passed — create-token is + // PIN-optional when an external tx-signing method is registered. + executeCreate(); + return; + } const pinParams = { cb: executeCreate, screenText: t`Enter your 6-digit pin to create your token`, @@ -210,7 +218,12 @@ const CreateTokenConfirm = () => { successText={{t`**${name}** created successfully`}} onTxSuccess={onTxSuccess} onDismissSuccess={exitScreen} - onDismissError={() => setSendTransactionModal(null)} + onDismissError={() => { + setSendTransactionModal(null); + // Re-enable Create: the passkey flow never leaves this screen, so the + // focus listener that resets it after PinScreen won't fire. + setIsSending(false); + }} hide={isShowingPinScreen} /> )} diff --git a/src/screens/InitWallet.js b/src/screens/InitWallet.js index 6d99c2885..0b1cd9ec0 100644 --- a/src/screens/InitWallet.js +++ b/src/screens/InitWallet.js @@ -26,6 +26,7 @@ import { t } from 'ttag'; import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import TextFmt from '../components/TextFmt'; +import PasskeyOnboardingButton from '../components/PasskeyOnboardingButton'; import baseStyle from '../styles/init'; import { Link, str2jsx } from '../utils'; @@ -123,16 +124,19 @@ class InitialScreen extends React.Component { {t`To import a wallet, you will need to provide your seed words.`} + this.props.navigation.navigate('NewWordsScreen')} + title={t`New Wallet`} + style={{ marginBottom: 16 }} + /> this.props.navigation.navigate('LoadWordsScreen')} title={t`Import Wallet`} style={{ marginBottom: 16 }} secondary /> - this.props.navigation.navigate('NewWordsScreen')} - title={t`New Wallet`} - /> + {/* Renders only when the passkey-onboarding feature toggle is on. */} + diff --git a/src/screens/PasskeyLockScreen.js b/src/screens/PasskeyLockScreen.js new file mode 100644 index 000000000..30a3fd365 --- /dev/null +++ b/src/screens/PasskeyLockScreen.js @@ -0,0 +1,154 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useEffect, useRef, useState } from 'react'; +import { Text, View } from 'react-native'; +import { useDispatch, useSelector } from 'react-redux'; +import { t } from 'ttag'; + +import SimpleButton from '../components/SimpleButton'; +import NewHathorButton from '../components/NewHathorButton'; +import Logo from '../components/Logo'; +import Spinner from '../components/Spinner'; +import { + resetOnLockScreen, + startWalletRequested, + unlockScreen, +} from '../actions'; +import { COLORS } from '../styles/themes'; +import { STORE } from '../store'; +import baseStyle from '../styles/init'; +import { PasskeyCancelledError, verifyPasskeyForUnlock } from '../passkey/passkeySigner'; +import { sanitizePasskeyLabel } from '../passkey/passkeyService'; + +/** + * Lock screen for passkey (PIN-less, xpub-only) wallets — the counterpart of PinScreen's + * lock-screen mode. There is no PIN and no stored secret: unlocking runs a passkey ceremony, + * re-derives the xpub in memory and compares it to the stored one. Verification failure is + * never fatal — the user can always retry or fall back to resetting the wallet. + */ +const PasskeyLockScreen = () => { + const dispatch = useDispatch(); + const wallet = useSelector((state) => state.wallet); + const [verifying, setVerifying] = useState(false); + const [error, setError] = useState(null); + // Ceremony must fire exactly once on mount even if the component re-renders (the OS shows + // a single credential sheet; a duplicate call would fail or stack sheets). + const startedRef = useRef(false); + + // Labels persisted from old-format credentials may be decode garbage; never show those. + const passkeyLabel = sanitizePasskeyLabel(STORE.getWalletMeta()?.passkeyLabel); + + const unlock = async () => { + if (verifying) { + return; + } + setVerifying(true); + setError(null); + try { + await verifyPasskeyForUnlock(); + if (!wallet) { + // App boot (or wallet stopped): start the xpub-only wallet. The saga re-reads the + // persisted walletMeta; no secret travels through redux. + dispatch(startWalletRequested({ walletType: 'passkey' })); + } + dispatch(unlockScreen()); + } catch (e) { + setError(e instanceof PasskeyCancelledError ? t`Unlock cancelled.` : e.message); + setVerifying(false); + } + }; + + useEffect(() => { + if (!startedRef.current) { + startedRef.current = true; + unlock(); + } + }, []); + + return ( + + + + + + + {passkeyLabel + ? t`Unlock with the passkey "${passkeyLabel}"` + : t`Unlock with your passkey`} + + {verifying ? ( + + ) : ( + <> + {error && ( + + {error} + + )} + + + )} + + + dispatch(resetOnLockScreen())} + title={t`Reset wallet`} + textStyle={{ + textAlign: 'center', + textTransform: 'uppercase', + color: COLORS.textColorShadow, + letterSpacing: 1, + padding: 4, + }} + containerStyle={{ marginTop: 16, marginBottom: 32 }} + /> + + + ); +}; + +export default PasskeyLockScreen; diff --git a/src/screens/Security.js b/src/screens/Security.js index 445b16c5c..097f507ce 100644 --- a/src/screens/Security.js +++ b/src/screens/Security.js @@ -185,6 +185,10 @@ export class Security extends React.Component { const useSafeBiometryFeature = STORE.getItem(SAFE_BIOMETRY_FEATURE_FLAG_KEY); const safeBiometryActive = this.state.biometryEnabled && useSafeBiometryFeature; + // Passkey wallets have no PIN and no stored secret: both the biometry-instead-of-PIN + // switch and Change PIN are meaningless (authorization is always the passkey ceremony). + const isPasskeyWallet = STORE.isPasskeyWallet(); + return ( this.props.navigation.goBack()} /> - + )} + isFirst /> )} - isFirst - /> - { safeBiometryActive + { (safeBiometryActive || isPasskeyWallet) ? null : ( diff --git a/src/screens/SendConfirmScreen.js b/src/screens/SendConfirmScreen.js index fc84a2969..e4884d50d 100644 --- a/src/screens/SendConfirmScreen.js +++ b/src/screens/SendConfirmScreen.js @@ -27,6 +27,7 @@ import { COLORS } from '../styles/themes'; import { InfoCircleIcon } from '../components/Icons/InfoCircle'; import { CheckIcon } from '../components/Icons/Check.icon'; import { TOKEN_DEPOSIT_URL, TOKEN_FEES_URL } from '../constants'; +import { STORE } from '../store'; import errorIcon from '../assets/images/icErrorBig.png'; const PHASE = Object.freeze({ @@ -188,6 +189,13 @@ const SendConfirmScreen = () => { // Disable the button before opening the PinScreen so it can't be tapped // again while we return from it and build the feedback modal. setIsSending(true); + if (STORE.isPasskeyWallet()) { + // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the + // external signer when the lib requests signatures. No PIN is passed — signTx is + // PIN-optional when an external tx-signing method is registered. + executeSend(); + return; + } const pinParams = { cb: executeSend, canCancel: true, @@ -310,7 +318,12 @@ const SendConfirmScreen = () => { promise={modal.promise} successText={{t`Your transfer of **${amountAndToken}** has been confirmed`}} onDismissSuccess={exitScreen} - onDismissError={() => setModal(null)} + onDismissError={() => { + setModal(null); + // Re-enable Send: the passkey flow never leaves this screen, so the + // focus listener that resets it after PinScreen won't fire. + setIsSending(false); + }} hide={isShowingPinScreen} /> )} diff --git a/src/screens/TokenSwapReview.js b/src/screens/TokenSwapReview.js index 5824658d5..819a9b5c6 100644 --- a/src/screens/TokenSwapReview.js +++ b/src/screens/TokenSwapReview.js @@ -29,6 +29,7 @@ import { selectTokenSwapContractId, } from '../utils/tokenSwap'; import { renderValue } from '../utils'; +import { STORE } from '../store'; import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import OfflineBar from '../components/OfflineBar'; @@ -68,6 +69,10 @@ const TokenSwapReview = () => { const [buildError, setBuildError] = useState(null); const [sendTx, setSendTx] = useState(null); const [modal, setModal] = useState(null); + // Disables the SWAP button from the moment it is tapped. On the passkey path executeSend runs + // the signing ceremony inline (no PinScreen to cover the button), so without this a second tap + // during the multi-second ceremony would fire a concurrent signTx. + const [isSending, setIsSending] = useState(false); const registrationPromiseRef = useRef(null); const sentSuccessfullyRef = useRef(false); @@ -164,6 +169,7 @@ const TokenSwapReview = () => { await registrationPromiseRef.current; } setModal(null); + setIsSending(false); dispatch(tokenSwapResetSwapData()); NavigationService.resetToMain(); }; @@ -183,6 +189,7 @@ const TokenSwapReview = () => { // listener releases reserved UTXOs along the way. const exitOnError = () => { setModal(null); + setIsSending(false); refreshQuote(); navigation.goBack(); }; @@ -213,6 +220,15 @@ const TokenSwapReview = () => { }; const onSwapButtonPress = () => { + if (STORE.isPasskeyWallet()) { + // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the + // external signer when the lib requests signatures. No PIN is passed — signTx is + // PIN-optional when an external tx-signing method is registered. + // Disable the button before the inline ceremony starts (executeSend is async). + setIsSending(true); + executeSend(); + return; + } const pinParams = { cb: executeSend, canCancel: true, @@ -327,7 +343,7 @@ const TokenSwapReview = () => { From 624b6a3f21c0e8de317e6fc1e7194827acc4d72e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 27 Jul 2026 12:46:32 -0300 Subject: [PATCH 2/3] refactor(passkey): extract a shared authorizeTransaction helper The passkey shortcut -- if (STORE.isPasskeyWallet()) run the operation directly (no PIN), else open the PinScreen -- plus its multi-line rationale was duplicated verbatim across SendConfirmScreen, CreateTokenConfirm and TokenSwapReview. The copies had already started to drift (the comment said "signTx" in two screens and "create-token" in the third), which is exactly the maintenance hazard that duplication invites. Extract src/passkey/authorizeTransaction.js so the PIN-less contract and its rationale live in one place, and rewire all three screens to call it. The refactor is behavior-preserving: - SendConfirmScreen / CreateTokenConfirm keep setIsSending(true) unconditionally before the call (button disabled on both the passkey and PIN paths, as before). - TokenSwapReview keeps setIsSending(true) ONLY on the passkey path (the PIN path never set it), by wrapping it inside the `execute` callback the helper runs for passkey wallets. Each screen still owns its own PinScreen params (screen text differs per operation); the helper only centralizes the wallet-type branch. The now-unused STORE import is dropped from all three. --- src/passkey/authorizeTransaction.js | 35 +++++++++++++++++++++++++++++ src/screens/CreateTokenConfirm.js | 28 ++++++++++------------- src/screens/SendConfirmScreen.js | 28 ++++++++++------------- src/screens/TokenSwapReview.js | 35 ++++++++++++++--------------- 4 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 src/passkey/authorizeTransaction.js diff --git a/src/passkey/authorizeTransaction.js b/src/passkey/authorizeTransaction.js new file mode 100644 index 000000000..1e59d4642 --- /dev/null +++ b/src/passkey/authorizeTransaction.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { STORE } from '../store'; + +/** + * Authorize a transaction, choosing the mechanism by wallet type. Centralizes the PIN-less passkey + * contract so its rationale lives in ONE place instead of being duplicated (and drifting) across + * every confirm screen. + * + * Passkey wallets have no PIN: authorization IS the passkey ceremony, fired by the external signer + * when the lib requests signatures. No PIN is passed — signing is PIN-optional once an external + * tx-signing method is registered — so `execute` is called directly. Every other wallet collects + * the PIN through the PinScreen, which invokes `pinParams.cb` (typically the same `execute`) with + * the entered PIN. + * + * @param {object} args + * @param {() => void} args.execute - The passkey-path action, run directly (no PIN) for passkey + * wallets. NOT called for PIN wallets — those authorize through pinParams.cb instead. It is + * often the same function as pinParams.cb, but need not be (a caller may wrap it). + * @param {{ navigate: Function }} args.navigation - Navigation used to open the PinScreen. + * @param {object} args.pinParams - Params forwarded to the PinScreen (cb, screenText, ...); cb is + * invoked with the entered PIN. Ignored for passkey wallets. + */ +export function authorizeTransaction({ execute, navigation, pinParams }) { + if (STORE.isPasskeyWallet()) { + execute(); + return; + } + navigation.navigate('PinScreen', pinParams); +} diff --git a/src/screens/CreateTokenConfirm.js b/src/screens/CreateTokenConfirm.js index da23d2c0a..896844453 100644 --- a/src/screens/CreateTokenConfirm.js +++ b/src/screens/CreateTokenConfirm.js @@ -23,7 +23,7 @@ import SendTransactionFeedbackModal from '../components/SendTransactionFeedbackM import TextFmt from '../components/TextFmt'; import { updateSelectedToken } from '../actions'; import { registerToken } from '../utils/tokens'; -import { STORE } from '../store'; +import { authorizeTransaction } from '../passkey/authorizeTransaction'; import { InfoCircleIcon } from '../components/Icons/InfoCircle'; import { useNavigation, useParams } from '../hooks/navigation'; import { getCreateTokenTitle } from '../utils'; @@ -162,21 +162,17 @@ const CreateTokenConfirm = () => { // Disable the button before opening the PinScreen so it can't be tapped // again while we return from it and build the feedback modal. setIsSending(true); - if (STORE.isPasskeyWallet()) { - // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the - // external signer when the lib requests signatures. No PIN is passed — create-token is - // PIN-optional when an external tx-signing method is registered. - executeCreate(); - return; - } - const pinParams = { - cb: executeCreate, - screenText: t`Enter your 6-digit pin to create your token`, - biometryText: t`Authorize token creation`, - canCancel: true, - biometryLoadingText: t`Building transaction`, - }; - navigation.navigate('PinScreen', pinParams); + authorizeTransaction({ + execute: executeCreate, + navigation, + pinParams: { + cb: executeCreate, + screenText: t`Enter your 6-digit pin to create your token`, + biometryText: t`Authorize token creation`, + canCancel: true, + biometryLoadingText: t`Building transaction`, + }, + }); }; /** diff --git a/src/screens/SendConfirmScreen.js b/src/screens/SendConfirmScreen.js index e4884d50d..15b923005 100644 --- a/src/screens/SendConfirmScreen.js +++ b/src/screens/SendConfirmScreen.js @@ -27,7 +27,7 @@ import { COLORS } from '../styles/themes'; import { InfoCircleIcon } from '../components/Icons/InfoCircle'; import { CheckIcon } from '../components/Icons/Check.icon'; import { TOKEN_DEPOSIT_URL, TOKEN_FEES_URL } from '../constants'; -import { STORE } from '../store'; +import { authorizeTransaction } from '../passkey/authorizeTransaction'; import errorIcon from '../assets/images/icErrorBig.png'; const PHASE = Object.freeze({ @@ -189,21 +189,17 @@ const SendConfirmScreen = () => { // Disable the button before opening the PinScreen so it can't be tapped // again while we return from it and build the feedback modal. setIsSending(true); - if (STORE.isPasskeyWallet()) { - // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the - // external signer when the lib requests signatures. No PIN is passed — signTx is - // PIN-optional when an external tx-signing method is registered. - executeSend(); - return; - } - const pinParams = { - cb: executeSend, - canCancel: true, - screenText: t`Enter your 6-digit pin to authorize operation`, - biometryText: t`Authorize operation`, - biometryLoadingText: t`Building transaction`, - }; - navigation.navigate('PinScreen', pinParams); + authorizeTransaction({ + execute: executeSend, + navigation, + pinParams: { + cb: executeSend, + canCancel: true, + screenText: t`Enter your 6-digit pin to authorize operation`, + biometryText: t`Authorize operation`, + biometryLoadingText: t`Building transaction`, + }, + }); }; /** diff --git a/src/screens/TokenSwapReview.js b/src/screens/TokenSwapReview.js index 819a9b5c6..0b39f4783 100644 --- a/src/screens/TokenSwapReview.js +++ b/src/screens/TokenSwapReview.js @@ -29,7 +29,7 @@ import { selectTokenSwapContractId, } from '../utils/tokenSwap'; import { renderValue } from '../utils'; -import { STORE } from '../store'; +import { authorizeTransaction } from '../passkey/authorizeTransaction'; import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import OfflineBar from '../components/OfflineBar'; @@ -220,23 +220,22 @@ const TokenSwapReview = () => { }; const onSwapButtonPress = () => { - if (STORE.isPasskeyWallet()) { - // Passkey wallets have no PIN: authorization is the passkey ceremony, fired by the - // external signer when the lib requests signatures. No PIN is passed — signTx is - // PIN-optional when an external tx-signing method is registered. - // Disable the button before the inline ceremony starts (executeSend is async). - setIsSending(true); - executeSend(); - return; - } - const pinParams = { - cb: executeSend, - canCancel: true, - screenText: t`Enter your 6-digit pin to authorize operation`, - biometryText: t`Authorize operation`, - biometryLoadingText: t`Building transaction`, - }; - navigation.navigate('PinScreen', pinParams); + authorizeTransaction({ + // Passkey-only path: disable the button before the inline ceremony starts (executeSend is + // async). The PIN path doesn't set this here — the PinScreen navigation covers the button. + execute: () => { + setIsSending(true); + executeSend(); + }, + navigation, + pinParams: { + cb: executeSend, + canCancel: true, + screenText: t`Enter your 6-digit pin to authorize operation`, + biometryText: t`Authorize operation`, + biometryLoadingText: t`Building transaction`, + }, + }); }; return ( From 0e63b48d3edb629f4bfac5e1c5bd9a0dd66304b4 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 28 Jul 2026 13:02:56 -0300 Subject: [PATCH 3/3] =?UTF-8?q?fix(passkey):=20address=20PR=20#897=20revie?= =?UTF-8?q?w=20=E2=80=94=20saga=20retries,=20error=20handling,=20UI=20gati?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batched fixes for the round-2 review on the passkey integration: reown saga: - reset the per-method tx status to READY before the passkey-cancel retry, so the retry no longer lands on a full-screen "Sending transaction" spinner the user can't dismiss (READY, not *StatusFailure, which would pop an error modal and contradict the retry); - clear the stale module-global signingCancelled flag at the top of processRequest so an earlier in-app cancel can't hijack a later unrelated dapp failure; - reject unsupported passkey methods with UNAUTHORIZED_METHODS (3001), not USER_REJECTED; - correct the gated-methods comment. Screens / components: - TokenSwapReview: a cancelled/busy passkey ceremony keeps the reviewed swap intact; real failures (incl. the actionable xpub-mismatch message) surface through the error modal + onExceptionCaptured instead of a silent goBack that destroyed the reviewed transaction; - PasskeyLockScreen: log + report non-cancel unlock failures and always show a translated message; - PasskeyOnboardingButton: a dismissed OS sheet is a silent cancel, not "Passkey unavailable"; use the shared logger; narrow the try so post-persist nav failures aren't mislabeled; generic backup copy (the storing password manager isn't reliably detectable); - InitWallet: gate the New/Import button reorder behind the passkey feature flag; - drop the unread walletType dispatch payload; wrap default wallet names in ttag; authorizeTransaction defaults onPasskey to pinParams.cb. Storage / signer / saga (core-owned, mirrored from feat/passkey-core): - initPasskeyStorage persists walletMeta before the access data (crash-atomic in the safe direction); - a missing stored xpub throws a distinct PasskeyMetadataMissingError (vs xpub mismatch) and the start saga reports it via onExceptionCaptured; softened the before-start() comments; - pushNotification logs the defense-in-depth guard when it fires. i18n: new strings extracted; pt-br translated, da/ru-ru English fallback. --- __tests__/passkey/passkeySigner.test.js | 8 +- locale/da/texts.po | 210 ++++++++++++--------- locale/pt-br/texts.po | 219 ++++++++++++---------- locale/ru-ru/texts.po | 210 ++++++++++++--------- locale/texts.pot | 212 ++++++++++++--------- src/components/PasskeyOnboardingButton.js | 40 ++-- src/passkey/authorizeTransaction.js | 18 +- src/passkey/passkeySigner.js | 27 ++- src/sagas/pushNotification.js | 4 + src/sagas/reown.js | 30 ++- src/sagas/wallet.js | 16 +- src/screens/CreateTokenConfirm.js | 2 +- src/screens/InitWallet.js | 67 +++++-- src/screens/PasskeyLockScreen.js | 30 ++- src/screens/SendConfirmScreen.js | 2 +- src/screens/TokenSwapReview.js | 25 ++- src/store.js | 12 +- 17 files changed, 690 insertions(+), 442 deletions(-) diff --git a/__tests__/passkey/passkeySigner.test.js b/__tests__/passkey/passkeySigner.test.js index 75ec0a171..a46b9395e 100644 --- a/__tests__/passkey/passkeySigner.test.js +++ b/__tests__/passkey/passkeySigner.test.js @@ -58,6 +58,7 @@ import { consumePasskeySigningCancelled, PasskeyCancelledError, PasskeyXpubMismatchError, + PasskeyMetadataMissingError, PasskeyBusyError, } from '../../src/passkey/passkeySigner'; /* eslint-enable import/first, import/order */ @@ -121,13 +122,16 @@ describe('makePasskeyTxSigner', () => { expect(err.storedLabel).toBe(STORED_LABEL); }); - test('throws PasskeyXpubMismatchError when no xpub is stored at all', async () => { + test('throws PasskeyMetadataMissingError (not mismatch) when no xpub is stored at all', async () => { + // A missing stored xpub is corrupted/incomplete metadata, NOT a wrong passkey — it must be + // diagnosed distinctly (reset, not "use the other passkey"). STORE.getWalletMeta.mockReturnValue(makeMeta({ xpub: undefined })); const signer = makePasskeyTxSigner(); const err = await signer(fakeTx, fakeStorage).catch((e) => e); - expect(err).toBeInstanceOf(PasskeyXpubMismatchError); + expect(err).toBeInstanceOf(PasskeyMetadataMissingError); + expect(err).not.toBeInstanceOf(PasskeyXpubMismatchError); expect(transactionUtils.signTxInputs).not.toHaveBeenCalled(); }); diff --git a/locale/da/texts.po b/locale/da/texts.po index d3a1e6d8c..2f14040fe 100644 --- a/locale/da/texts.po +++ b/locale/da/texts.po @@ -141,11 +141,11 @@ msgstr "OM" msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Denne app er udviklet af Hathor Labs og distribueres gratis." -#: src/screens/About.js:99 src/screens/InitWallet.js:66 +#: src/screens/About.js:99 src/screens/InitWallet.js:68 msgid "This wallet is connected to the **mainnet**." msgstr "Denne wallet er tilsluttet til ** mainnet **." -#: src/screens/About.js:102 src/screens/InitWallet.js:69 +#: src/screens/About.js:102 src/screens/InitWallet.js:71 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -319,7 +319,7 @@ msgstr "" msgid "Import tokens" msgstr "" -#: src/components/PasskeyOnboardingButton.js:176 +#: src/components/PasskeyOnboardingButton.js:188 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -354,8 +354,8 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 -#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:268 +#: src/screens/InitWallet.js:421 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Næste" @@ -380,47 +380,50 @@ msgstr "Opretter token" msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:174 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:170 msgid "Enter your 6-digit pin to create your token" msgstr "Indtast din 6-cifrede pin for at oprette din token" -#: src/screens/CreateTokenConfirm.js:175 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:171 msgid "Authorize token creation" msgstr "Autoriser oprettelse af token" -#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 -#: src/screens/TokenSwapReview.js:237 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:173 src/screens/SendConfirmScreen.js:200 +#: src/screens/TokenSwapReview.js:251 msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:218 +#: src/screens/CreateTokenConfirm.js:214 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:235 +#: src/screens/CreateTokenConfirm.js:231 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:245 msgid "Token name" msgstr "Token navn" -#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:251 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Token symbol" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:262 +#: src/screens/CreateTokenConfirm.js:258 msgid "Deposit" msgstr "Indsæt" -#: src/screens/CreateTokenConfirm.js:270 +#: src/screens/CreateTokenConfirm.js:266 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:279 +#: src/screens/CreateTokenConfirm.js:275 msgid "Create token" msgstr "Opret token" @@ -593,36 +596,44 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:63 +#: src/screens/InitWallet.js:65 msgid "Welcome to Hathor Wallet!" msgstr "Velkommen til Hathor Wallet!" -#: src/screens/InitWallet.js:74 +#: src/screens/InitWallet.js:76 msgid "" "For further information, check out our website |link:https://hathor." "network/|." msgstr "" "For yderligere information, se vores websted |link:https://hathor.network/|." -#: src/screens/InitWallet.js:87 +#: src/screens/InitWallet.js:89 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:99 +#: src/screens/InitWallet.js:101 msgid "Start" msgstr "Start" -#: src/screens/InitWallet.js:116 +#: src/screens/InitWallet.js:123 +msgid "New Wallet" +msgstr "Ny wallet" + +#: src/screens/InitWallet.js:130 +msgid "Import Wallet" +msgstr "Importer wallet" + +#: src/screens/InitWallet.js:162 msgid "To start," msgstr "For at begynde," -#: src/screens/InitWallet.js:118 +#: src/screens/InitWallet.js:164 msgid "You need to **initialize your wallet**." msgstr "Skal du **initialisere din wallet**." -#: src/screens/InitWallet.js:121 +#: src/screens/InitWallet.js:167 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -630,23 +641,15 @@ msgstr "" "Du kan enten **oprette en ny wallet** eller **importere en wallet**, der " "allerede findes." -#: src/screens/InitWallet.js:124 +#: src/screens/InitWallet.js:170 msgid "To import a wallet, you will need to provide your seed words." msgstr "For at importere en wallet skal du angive dine seed-ord." -#: src/screens/InitWallet.js:129 -msgid "New Wallet" -msgstr "Ny wallet" - -#: src/screens/InitWallet.js:134 -msgid "Import Wallet" -msgstr "Importer wallet" - -#: src/screens/InitWallet.js:224 +#: src/screens/InitWallet.js:259 msgid "Your wallet has been created!" msgstr "Din wallet er oprettet!" -#: src/screens/InitWallet.js:226 +#: src/screens/InitWallet.js:261 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -654,11 +657,11 @@ msgstr "" "Du skal **lave en sikkerhedskopi** og gemme nedenstående ord **i samme " "rækkefølge, som de vises**." -#: src/screens/InitWallet.js:347 +#: src/screens/InitWallet.js:382 msgid "To import a wallet," msgstr "Hvis du vil importere en wallet," -#: src/screens/InitWallet.js:349 +#: src/screens/InitWallet.js:384 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -667,11 +670,11 @@ msgstr "" "Skal du **skrive ${ this.numberOfWords } seed-ord** i din wallet adskilt med " "mellemrum." -#: src/screens/InitWallet.js:352 +#: src/screens/InitWallet.js:387 msgid "Words" msgstr "Ord" -#: src/screens/InitWallet.js:357 +#: src/screens/InitWallet.js:392 msgid "Enter your seed words separated by space" msgstr "Indtast dine seed-ord adskilt med mellemrum" @@ -704,7 +707,7 @@ msgstr "**${ props.loadedData.addresses } adresser** fundet" msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:157 #: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Nulstil wallet" @@ -759,20 +762,24 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PasskeyLockScreen.js:61 +#: src/screens/PasskeyLockScreen.js:69 msgid "Unlock cancelled." msgstr "" -#: src/screens/PasskeyLockScreen.js:103 +#: src/screens/PasskeyLockScreen.js:78 +msgid "Could not verify your passkey. Please try again." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:121 #, javascript-format msgid "Unlock with the passkey \"${ passkeyLabel }\"" msgstr "" -#: src/screens/PasskeyLockScreen.js:104 +#: src/screens/PasskeyLockScreen.js:122 msgid "Unlock with your passkey" msgstr "" -#: src/screens/PasskeyLockScreen.js:124 +#: src/screens/PasskeyLockScreen.js:142 msgid "Unlock" msgstr "" @@ -995,11 +1002,11 @@ msgid_plural "${ amountAndToken } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:291 msgid "SEND ${ tokenNameUpperCase }" msgstr "SEND ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:350 msgid "No fee" msgstr "" @@ -1015,79 +1022,81 @@ msgstr "" msgid "Failed to process transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:214 msgid "Your transfer is being processed" msgstr "Din overførsel behandles" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 -#: src/screens/TokenSwapReview.js:235 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:198 +#: src/screens/TokenSwapReview.js:249 msgid "Enter your 6-digit pin to authorize operation" msgstr "Indtast din 6-cifrede pin for at godkende overførslen" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 -#: src/screens/TokenSwapReview.js:236 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:199 +#: src/screens/TokenSwapReview.js:250 msgid "Authorize operation" msgstr "Autoriserer overførslen" -#: src/screens/SendConfirmScreen.js:243 +#: src/screens/SendConfirmScreen.js:239 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:262 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:269 +#: src/screens/SendConfirmScreen.js:265 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:272 +#: src/screens/SendConfirmScreen.js:268 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:274 +#: src/screens/SendConfirmScreen.js:270 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:279 +#: src/screens/SendConfirmScreen.js:275 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:301 +#: src/screens/SendConfirmScreen.js:297 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:319 +#: src/screens/SendConfirmScreen.js:315 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:335 +#: src/screens/SendConfirmScreen.js:331 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:357 +#: src/screens/SendConfirmScreen.js:353 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:356 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:365 +#: src/screens/SendConfirmScreen.js:361 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:373 +#: src/screens/SendConfirmScreen.js:369 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:380 +#: src/screens/SendConfirmScreen.js:376 msgid "Send" msgstr "Send" @@ -1174,25 +1183,29 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:247 +#: src/screens/TokenSwapReview.js:230 +msgid "Could not complete the swap. Please try again." +msgstr "" + +#: src/screens/TokenSwapReview.js:261 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:253 +#: src/screens/TokenSwapReview.js:267 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:271 +#: src/screens/TokenSwapReview.js:285 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:332 +#: src/screens/TokenSwapReview.js:346 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:344 +#: src/screens/TokenSwapReview.js:358 msgid "SWAP" msgstr "" @@ -1638,44 +1651,50 @@ msgstr "Transaktion" msgid "Open" msgstr "Åben" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:940 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:946 +#: src/sagas/wallet.js:954 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:956 +#: src/sagas/wallet.js:964 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:972 +#: src/sagas/wallet.js:980 msgid "There was an error while loading first wallet address. Try again." msgstr "" -#: src/passkey/passkeySigner.js:35 +#: src/passkey/passkeySigner.js:37 msgid "Passkey authorization was cancelled. Nothing was sent." msgstr "" -#: src/passkey/passkeySigner.js:45 +#: src/passkey/passkeySigner.js:47 #, javascript-format msgid "" "This passkey opens a different wallet. Use the passkey named \"${ label }\"." msgstr "" -#: src/passkey/passkeySigner.js:46 +#: src/passkey/passkeySigner.js:48 msgid "" "This passkey opens a different wallet. Use the passkey that created this " "wallet." msgstr "" -#: src/passkey/passkeySigner.js:54 +#: src/passkey/passkeySigner.js:56 msgid "Another passkey authorization is already in progress." msgstr "" +#: src/passkey/passkeySigner.js:67 +msgid "" +"Passkey wallet data is missing or corrupted. Reset the wallet and sign in " +"again with your passkey." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1796,51 +1815,58 @@ msgstr "" msgid "No internet connection" msgstr "Ingen internetforbindelse" -#: src/components/PasskeyOnboardingButton.js:98 +#: src/components/PasskeyOnboardingButton.js:91 +msgid "Passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:104 msgid "Passkey unavailable" msgstr "" -#: src/components/PasskeyOnboardingButton.js:121 +#: src/components/PasskeyOnboardingButton.js:118 +msgid "Hathor Wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:130 msgid "Passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:128 +#: src/components/PasskeyOnboardingButton.js:137 msgid "Continue with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:142 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Working…" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:142 msgid "Sign in with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:139 +#: src/components/PasskeyOnboardingButton.js:148 msgid "Create passkey wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:144 -#, javascript-format +#: src/components/PasskeyOnboardingButton.js:156 msgid "" -"Your wallet is protected by this passkey. Keep it backed up by syncing it to " -"${ passwordManager } so you don't lose access." +"Your wallet is protected by this passkey. Keep it backed up in your password " +"manager so you don't lose access." msgstr "" -#: src/components/PasskeyOnboardingButton.js:149 +#: src/components/PasskeyOnboardingButton.js:161 msgid "Name your wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:151 +#: src/components/PasskeyOnboardingButton.js:163 msgid "This name identifies the passkey when you sign in later." msgstr "" -#: src/components/PasskeyOnboardingButton.js:158 +#: src/components/PasskeyOnboardingButton.js:170 msgid "e.g. Savings" msgstr "" -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Create" msgstr "" diff --git a/locale/pt-br/texts.po b/locale/pt-br/texts.po index 24e47de09..9fffa8cd3 100644 --- a/locale/pt-br/texts.po +++ b/locale/pt-br/texts.po @@ -148,11 +148,11 @@ msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" "Este aplicativo foi desenvolvido pela Hathor Labs e é distribuído de graça." -#: src/screens/About.js:99 src/screens/InitWallet.js:66 +#: src/screens/About.js:99 src/screens/InitWallet.js:68 msgid "This wallet is connected to the **mainnet**." msgstr "Esta wallet está conectada à **mainnet**." -#: src/screens/About.js:102 src/screens/InitWallet.js:69 +#: src/screens/About.js:102 src/screens/InitWallet.js:71 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -332,7 +332,7 @@ msgstr "Você está prestes a adicionar esses tokens à sua carteira:" msgid "Import tokens" msgstr "Importar tokens" -#: src/components/PasskeyOnboardingButton.js:176 +#: src/components/PasskeyOnboardingButton.js:188 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -367,8 +367,8 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "Quantidade de ${ name } (${ symbol })" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 -#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:268 +#: src/screens/InitWallet.js:421 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Próximo" @@ -397,47 +397,50 @@ msgstr "Criando um token" msgid "Building the transaction" msgstr "Criando a transação" -#: src/screens/CreateTokenConfirm.js:174 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:170 msgid "Enter your 6-digit pin to create your token" msgstr "Digite seu PIN de 6 dígitos para criar o seu token" -#: src/screens/CreateTokenConfirm.js:175 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:171 msgid "Authorize token creation" msgstr "Autorizar criação de token" -#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 -#: src/screens/TokenSwapReview.js:237 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:173 src/screens/SendConfirmScreen.js:200 +#: src/screens/TokenSwapReview.js:251 msgid "Building transaction" msgstr "Criando transação" -#: src/screens/CreateTokenConfirm.js:218 +#: src/screens/CreateTokenConfirm.js:214 #, javascript-format msgid "**${ name }** created successfully" msgstr "**${ name }** criado com sucesso" -#: src/screens/CreateTokenConfirm.js:235 +#: src/screens/CreateTokenConfirm.js:231 msgid "Amount of ${ name }" msgstr "Quantidade de ${ name }" -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:245 msgid "Token name" msgstr "Nome do token" -#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:251 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Símbolo do token" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:262 +#: src/screens/CreateTokenConfirm.js:258 msgid "Deposit" msgstr "Depósito" -#: src/screens/CreateTokenConfirm.js:270 +#: src/screens/CreateTokenConfirm.js:266 msgid "Network fee" msgstr "Taxa de Rede" -#: src/screens/CreateTokenConfirm.js:279 +#: src/screens/CreateTokenConfirm.js:275 msgid "Create token" msgstr "Criar um token" @@ -615,18 +618,18 @@ msgstr "Tokens encontrados" msgid "Continue" msgstr "Continuar" -#: src/screens/InitWallet.js:63 +#: src/screens/InitWallet.js:65 msgid "Welcome to Hathor Wallet!" msgstr "Bem vindo à Hathor Wallet!" -#: src/screens/InitWallet.js:74 +#: src/screens/InitWallet.js:76 msgid "" "For further information, check out our website |link:https://hathor." "network/|." msgstr "" "Para mais informações, visite nosso site |link:https://hathor.network/|." -#: src/screens/InitWallet.js:87 +#: src/screens/InitWallet.js:89 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" @@ -634,19 +637,27 @@ msgstr "" "Eu concordo com os |link1:Termos de Serviço| e |link2:Política de " "Privacidade| e entendo os riscos de usar uma wallet de celular" -#: src/screens/InitWallet.js:99 +#: src/screens/InitWallet.js:101 msgid "Start" msgstr "Iniciar" -#: src/screens/InitWallet.js:116 +#: src/screens/InitWallet.js:123 +msgid "New Wallet" +msgstr "Nova Wallet" + +#: src/screens/InitWallet.js:130 +msgid "Import Wallet" +msgstr "Importar Wallet" + +#: src/screens/InitWallet.js:162 msgid "To start," msgstr "Para começar," -#: src/screens/InitWallet.js:118 +#: src/screens/InitWallet.js:164 msgid "You need to **initialize your wallet**." msgstr "Você precisa **inicializar sua wallet**." -#: src/screens/InitWallet.js:121 +#: src/screens/InitWallet.js:167 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -654,23 +665,15 @@ msgstr "" "Você pode tanto **iniciar uma nova wallet** ou **importar uma wallet** que " "já existe." -#: src/screens/InitWallet.js:124 +#: src/screens/InitWallet.js:170 msgid "To import a wallet, you will need to provide your seed words." msgstr "Para importar uma wallet, você deve entrar com as suas palavras." -#: src/screens/InitWallet.js:129 -msgid "New Wallet" -msgstr "Nova Wallet" - -#: src/screens/InitWallet.js:134 -msgid "Import Wallet" -msgstr "Importar Wallet" - -#: src/screens/InitWallet.js:224 +#: src/screens/InitWallet.js:259 msgid "Your wallet has been created!" msgstr "Sua wallet foi criada!" -#: src/screens/InitWallet.js:226 +#: src/screens/InitWallet.js:261 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -678,11 +681,11 @@ msgstr "" "Você deve **realizar o backup** e salvar as palavras abaixo **na mesma ordem " "em que elas aparecem**." -#: src/screens/InitWallet.js:347 +#: src/screens/InitWallet.js:382 msgid "To import a wallet," msgstr "Para importar sua wallet," -#: src/screens/InitWallet.js:349 +#: src/screens/InitWallet.js:384 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -691,11 +694,11 @@ msgstr "" "Você precisa **digitar as ${ this.numberOfWords } palavras** da sua wallet, " "separadas por espaço." -#: src/screens/InitWallet.js:352 +#: src/screens/InitWallet.js:387 msgid "Words" msgstr "Palavras" -#: src/screens/InitWallet.js:357 +#: src/screens/InitWallet.js:392 msgid "Enter your seed words separated by space" msgstr "Digite suas palavras separadas por espaços" @@ -728,7 +731,7 @@ msgstr "**${ loadedData.addresses } endereços** encontrados" msgid "There's been an error connecting to the server." msgstr "Ocorreu um erro ao conectar com o servidor." -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:157 #: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Resetar Wallet" @@ -785,20 +788,24 @@ msgstr "Registrar Nano Contract" msgid "Scan the nano contract ID QR code" msgstr "Escaneie o QR code do ID do Nano Contract" -#: src/screens/PasskeyLockScreen.js:61 +#: src/screens/PasskeyLockScreen.js:69 msgid "Unlock cancelled." msgstr "Desbloqueamento cancelado." -#: src/screens/PasskeyLockScreen.js:103 +#: src/screens/PasskeyLockScreen.js:78 +msgid "Could not verify your passkey. Please try again." +msgstr "Não foi possível verificar sua passkey. Tente novamente." + +#: src/screens/PasskeyLockScreen.js:121 #, javascript-format msgid "Unlock with the passkey \"${ passkeyLabel }\"" msgstr "Desbloquear com passkey \"${ passkeyLabel }\"" -#: src/screens/PasskeyLockScreen.js:104 +#: src/screens/PasskeyLockScreen.js:122 msgid "Unlock with your passkey" msgstr "Desbloquear com sua passkey" -#: src/screens/PasskeyLockScreen.js:124 +#: src/screens/PasskeyLockScreen.js:142 msgid "Unlock" msgstr "Desbloquear" @@ -1024,11 +1031,11 @@ msgid_plural "${ amountAndToken } available" msgstr[0] "${ amountAndToken } disponível" msgstr[1] "${ amountAndToken } disponíveis" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:291 msgid "SEND ${ tokenNameUpperCase }" msgstr "ENVIAR ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:350 msgid "No fee" msgstr "Sem taxa" @@ -1044,79 +1051,81 @@ msgstr "Saldo insuficiente para completar a transação." msgid "Failed to process transaction." msgstr "Falha ao processar a transação" -#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:214 msgid "Your transfer is being processed" msgstr "Sua transferência está sendo processada" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 -#: src/screens/TokenSwapReview.js:235 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:198 +#: src/screens/TokenSwapReview.js:249 msgid "Enter your 6-digit pin to authorize operation" msgstr "Digite seu PIN de 6 dígitos para autorizar a operação" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 -#: src/screens/TokenSwapReview.js:236 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:199 +#: src/screens/TokenSwapReview.js:250 msgid "Authorize operation" msgstr "Autorizar operação" -#: src/screens/SendConfirmScreen.js:243 +#: src/screens/SendConfirmScreen.js:239 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "Você tem ${ availablePretty } disponível" msgstr[1] "Você tem ${ availablePretty } disponíveis" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:262 msgid "Loading fee information..." msgstr "Carregando informações da taxa..." -#: src/screens/SendConfirmScreen.js:269 +#: src/screens/SendConfirmScreen.js:265 msgid "This is the native token, no network fees are charged." msgstr "Token nativo, nenhuma taxa aplicada." -#: src/screens/SendConfirmScreen.js:272 +#: src/screens/SendConfirmScreen.js:268 msgid "This token is Deposit Based, no network fee will be charged." msgstr "Este token é de Depósito, nenhuma taxa de rede será cobrada." -#: src/screens/SendConfirmScreen.js:274 +#: src/screens/SendConfirmScreen.js:270 msgid "This fee is fixed and required for every transfer of this token." msgstr "Esta taxa é fixa e obrigatória para cada transação deste token." #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:279 +#: src/screens/SendConfirmScreen.js:275 msgid "Loading..." msgstr "Carregando..." -#: src/screens/SendConfirmScreen.js:301 +#: src/screens/SendConfirmScreen.js:297 msgid "Building your transaction" msgstr "Criando sua transação" -#: src/screens/SendConfirmScreen.js:319 +#: src/screens/SendConfirmScreen.js:315 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Sua transferência de **${ amountAndToken }** foi confirmada" -#: src/screens/SendConfirmScreen.js:335 +#: src/screens/SendConfirmScreen.js:331 msgid "Read more." msgstr "Ler mais." -#: src/screens/SendConfirmScreen.js:357 +#: src/screens/SendConfirmScreen.js:353 msgid "**Transaction summary**" msgstr "**Resumo da transação**" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:356 msgid "**To**" msgstr "**Para**" -#: src/screens/SendConfirmScreen.js:365 +#: src/screens/SendConfirmScreen.js:361 msgid "**Network Fee**" msgstr "**Taxa de Rede**" -#: src/screens/SendConfirmScreen.js:373 +#: src/screens/SendConfirmScreen.js:369 msgid "**Total**" msgstr "**Total**" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:380 +#: src/screens/SendConfirmScreen.js:376 msgid "Send" msgstr "Enviar" @@ -1203,25 +1212,29 @@ msgstr "Ocorreu um erro ao carregar o protocolo de troca de tokens." msgid "Loading Token Swap" msgstr "Carregando troca de tokens" -#: src/screens/TokenSwapReview.js:247 +#: src/screens/TokenSwapReview.js:230 +msgid "Could not complete the swap. Please try again." +msgstr "Não foi possível completar a troca. Tente novamente." + +#: src/screens/TokenSwapReview.js:261 msgid "REVIEW TOKEN SWAP" msgstr "REVISAR TROCA DE TOKENS" -#: src/screens/TokenSwapReview.js:253 +#: src/screens/TokenSwapReview.js:267 msgid "Building the swap transaction for your review" msgstr "Criando transação de swap para ser revisada" -#: src/screens/TokenSwapReview.js:271 +#: src/screens/TokenSwapReview.js:285 msgid "Your swap was successful" msgstr "Sua troca foi bem-sucedida" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:332 +#: src/screens/TokenSwapReview.js:346 msgid "Network Fee" msgstr "Taxa de Rede" -#: src/screens/TokenSwapReview.js:344 +#: src/screens/TokenSwapReview.js:358 msgid "SWAP" msgstr "TROCAR" @@ -1681,47 +1694,56 @@ msgstr "Transação" msgid "Open" msgstr "Abrir" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:940 msgid "Wallet is not ready to load addresses." msgstr "A wallet não está pronta para carregar os endereços." #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:946 +#: src/sagas/wallet.js:954 msgid "There was an error while loading wallet addresses. Try again." msgstr "Ocorreu um erro ao carregar os endereços da wallet. Tente novamente." -#: src/sagas/wallet.js:956 +#: src/sagas/wallet.js:964 msgid "Wallet is not ready to load the first address." msgstr "A wallet não está pronta para carregar o primeiro endereço." #. This will show the message in the feedback content -#: src/sagas/wallet.js:972 +#: src/sagas/wallet.js:980 msgid "There was an error while loading first wallet address. Try again." msgstr "" "Ocorreu um erro ao carregar o primeiro endereço da wallet. Tente novamente." -#: src/passkey/passkeySigner.js:35 +#: src/passkey/passkeySigner.js:37 msgid "Passkey authorization was cancelled. Nothing was sent." msgstr "Autorização da passkey cancelada. Nada foi enviado." -#: src/passkey/passkeySigner.js:45 +#: src/passkey/passkeySigner.js:47 #, javascript-format msgid "" "This passkey opens a different wallet. Use the passkey named \"${ label }\"." msgstr "" "Essa passkey é de uma carteira diferente. Use a passkey \"${ label }\"." -#: src/passkey/passkeySigner.js:46 +#: src/passkey/passkeySigner.js:48 msgid "" "This passkey opens a different wallet. Use the passkey that created this " "wallet." msgstr "" -"Essa passkey é de uma carteira diferente. Use a passkey que criou essa wallet" +"Essa passkey é de uma carteira diferente. Use a passkey que criou essa " +"carteira" -#: src/passkey/passkeySigner.js:54 +#: src/passkey/passkeySigner.js:56 msgid "Another passkey authorization is already in progress." msgstr "Outra autorização de passkey em progresso." +#: src/passkey/passkeySigner.js:67 +msgid "" +"Passkey wallet data is missing or corrupted. Reset the wallet and sign in " +"again with your passkey." +msgstr "" +"Os dados da passkey da carteira estão ausentes ou corrompidos. Redefina a " +"carteira e entre novamente com sua passkey." + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "Máx" @@ -1845,53 +1867,60 @@ msgstr "Adicionar tokens falhou" msgid "No internet connection" msgstr "Sem conexão com a internet" -#: src/components/PasskeyOnboardingButton.js:98 +#: src/components/PasskeyOnboardingButton.js:91 +msgid "Passkey wallet" +msgstr "Carteira passkey" + +#: src/components/PasskeyOnboardingButton.js:104 msgid "Passkey unavailable" msgstr "Passkey indisponível." -#: src/components/PasskeyOnboardingButton.js:121 +#: src/components/PasskeyOnboardingButton.js:118 +msgid "Hathor Wallet" +msgstr "Hathor Wallet" + +#: src/components/PasskeyOnboardingButton.js:130 msgid "Passkey" msgstr "Passkey" -#: src/components/PasskeyOnboardingButton.js:128 +#: src/components/PasskeyOnboardingButton.js:137 msgid "Continue with passkey" msgstr "Continuar com passkey" -#: src/components/PasskeyOnboardingButton.js:133 -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:142 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Working…" msgstr "Processando..." -#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:142 msgid "Sign in with passkey" msgstr "Entrar com passkey" -#: src/components/PasskeyOnboardingButton.js:139 +#: src/components/PasskeyOnboardingButton.js:148 msgid "Create passkey wallet" msgstr "Criar uma carteira com passkey" -#: src/components/PasskeyOnboardingButton.js:144 -#, javascript-format +#: src/components/PasskeyOnboardingButton.js:156 msgid "" -"Your wallet is protected by this passkey. Keep it backed up by syncing it to " -"${ passwordManager } so you don't lose access." +"Your wallet is protected by this passkey. Keep it backed up in your password " +"manager so you don't lose access." msgstr "" -"Sua carteira está protegida por esse passkey. Mantenha um backup dele " -"sincronizando o ${ passwordManager } para não perder acesso." +"Sua carteira está protegida por essa passkey. Mantenha um backup no seu " +"gerenciador de senhas para não perder o acesso." -#: src/components/PasskeyOnboardingButton.js:149 +#: src/components/PasskeyOnboardingButton.js:161 msgid "Name your wallet" msgstr "Nome da sua carteira" -#: src/components/PasskeyOnboardingButton.js:151 +#: src/components/PasskeyOnboardingButton.js:163 msgid "This name identifies the passkey when you sign in later." msgstr "Esse nome identifica a passkey quando você entrar depois." -#: src/components/PasskeyOnboardingButton.js:158 +#: src/components/PasskeyOnboardingButton.js:170 msgid "e.g. Savings" msgstr "e.g. Poupança" -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Create" msgstr "Criar" diff --git a/locale/ru-ru/texts.po b/locale/ru-ru/texts.po index 20e6a24c3..321a18abc 100644 --- a/locale/ru-ru/texts.po +++ b/locale/ru-ru/texts.po @@ -143,11 +143,11 @@ msgstr "О НАС" msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Это приложение разработано Hathor Labs и распространяется бесплатно." -#: src/screens/About.js:99 src/screens/InitWallet.js:66 +#: src/screens/About.js:99 src/screens/InitWallet.js:68 msgid "This wallet is connected to the **mainnet**." msgstr "Этот кошелек подключен к **mainnet**." -#: src/screens/About.js:102 src/screens/InitWallet.js:69 +#: src/screens/About.js:102 src/screens/InitWallet.js:71 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -320,7 +320,7 @@ msgstr "" msgid "Import tokens" msgstr "" -#: src/components/PasskeyOnboardingButton.js:176 +#: src/components/PasskeyOnboardingButton.js:188 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -355,8 +355,8 @@ msgid "Amount of ${ name } (${ symbol })" msgstr "" #: src/screens/CreateTokenAmount.js:211 src/screens/CreateTokenName.js:67 -#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:233 -#: src/screens/InitWallet.js:386 src/screens/SendAddressInput.js:66 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:268 +#: src/screens/InitWallet.js:421 src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" msgstr "Далее" @@ -381,47 +381,50 @@ msgstr "Создать токен" msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:174 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:170 msgid "Enter your 6-digit pin to create your token" msgstr "Введите 6-значный PIN-код для создания токена" -#: src/screens/CreateTokenConfirm.js:175 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:171 msgid "Authorize token creation" msgstr "Авторизовать создание токена" -#: src/screens/CreateTokenConfirm.js:177 src/screens/SendConfirmScreen.js:204 -#: src/screens/TokenSwapReview.js:237 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. +#: src/screens/CreateTokenConfirm.js:173 src/screens/SendConfirmScreen.js:200 +#: src/screens/TokenSwapReview.js:251 msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:218 +#: src/screens/CreateTokenConfirm.js:214 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:235 +#: src/screens/CreateTokenConfirm.js:231 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:245 msgid "Token name" msgstr "Имя токена" -#: src/screens/CreateTokenConfirm.js:255 src/screens/CreateTokenSymbol.js:71 +#: src/screens/CreateTokenConfirm.js:251 src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "Символ токена" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:262 +#: src/screens/CreateTokenConfirm.js:258 msgid "Deposit" msgstr "Депозит" -#: src/screens/CreateTokenConfirm.js:270 +#: src/screens/CreateTokenConfirm.js:266 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:279 +#: src/screens/CreateTokenConfirm.js:275 msgid "Create token" msgstr "Создать токен" @@ -594,11 +597,11 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:63 +#: src/screens/InitWallet.js:65 msgid "Welcome to Hathor Wallet!" msgstr "Добро пожаловать в Hathor Wallet!" -#: src/screens/InitWallet.js:74 +#: src/screens/InitWallet.js:76 msgid "" "For further information, check out our website |link:https://hathor." "network/|." @@ -606,25 +609,33 @@ msgstr "" "Для получения дополнительной информации, посетите наш веб-сайт |link:https://" "hathor.network/|." -#: src/screens/InitWallet.js:87 +#: src/screens/InitWallet.js:89 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:99 +#: src/screens/InitWallet.js:101 msgid "Start" msgstr "Начать" -#: src/screens/InitWallet.js:116 +#: src/screens/InitWallet.js:123 +msgid "New Wallet" +msgstr "Новый Кошелек" + +#: src/screens/InitWallet.js:130 +msgid "Import Wallet" +msgstr "Импортировать Кошелек" + +#: src/screens/InitWallet.js:162 msgid "To start," msgstr "Начать," -#: src/screens/InitWallet.js:118 +#: src/screens/InitWallet.js:164 msgid "You need to **initialize your wallet**." msgstr "Вам нужно **инициализировать свой кошелек**." -#: src/screens/InitWallet.js:121 +#: src/screens/InitWallet.js:167 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -632,23 +643,15 @@ msgstr "" "Вы можете **создать новый кошелек**, либо **импортировать кошелек**, который " "уже существует." -#: src/screens/InitWallet.js:124 +#: src/screens/InitWallet.js:170 msgid "To import a wallet, you will need to provide your seed words." msgstr "Чтобы импортировать кошелек, необходимо ввести seed-фразу." -#: src/screens/InitWallet.js:129 -msgid "New Wallet" -msgstr "Новый Кошелек" - -#: src/screens/InitWallet.js:134 -msgid "Import Wallet" -msgstr "Импортировать Кошелек" - -#: src/screens/InitWallet.js:224 +#: src/screens/InitWallet.js:259 msgid "Your wallet has been created!" msgstr "Ваш кошелек создан!" -#: src/screens/InitWallet.js:226 +#: src/screens/InitWallet.js:261 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -656,11 +659,11 @@ msgstr "" "Вы должны **сделать резервную копию** и сохранить слова ниже **в том же " "порядке, в котором они появились**." -#: src/screens/InitWallet.js:347 +#: src/screens/InitWallet.js:382 msgid "To import a wallet," msgstr "Чтобы импортировать кошелек," -#: src/screens/InitWallet.js:349 +#: src/screens/InitWallet.js:384 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -668,11 +671,11 @@ msgid "" msgstr "" "Вам нужно **записать ${ this.numberOfWords } seed-фразу** вашего кошелька." -#: src/screens/InitWallet.js:352 +#: src/screens/InitWallet.js:387 msgid "Words" msgstr "Слова" -#: src/screens/InitWallet.js:357 +#: src/screens/InitWallet.js:392 msgid "Enter your seed words separated by space" msgstr "Введите seed-фразу" @@ -705,7 +708,7 @@ msgstr "**${ loadedData.addresses } адресов** найдено" msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:139 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PasskeyLockScreen.js:157 #: src/screens/PinScreen.js:318 src/screens/Settings.js:177 msgid "Reset wallet" msgstr "Сбросить кошелек" @@ -761,20 +764,24 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PasskeyLockScreen.js:61 +#: src/screens/PasskeyLockScreen.js:69 msgid "Unlock cancelled." msgstr "" -#: src/screens/PasskeyLockScreen.js:103 +#: src/screens/PasskeyLockScreen.js:78 +msgid "Could not verify your passkey. Please try again." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:121 #, javascript-format msgid "Unlock with the passkey \"${ passkeyLabel }\"" msgstr "" -#: src/screens/PasskeyLockScreen.js:104 +#: src/screens/PasskeyLockScreen.js:122 msgid "Unlock with your passkey" msgstr "" -#: src/screens/PasskeyLockScreen.js:124 +#: src/screens/PasskeyLockScreen.js:142 msgid "Unlock" msgstr "" @@ -997,11 +1004,11 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:295 +#: src/screens/SendAmountInput.js:137 src/screens/SendConfirmScreen.js:291 msgid "SEND ${ tokenNameUpperCase }" msgstr "ОТПРАВИТЬ ${ tokenNameUpperCase }" -#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:336 +#: src/screens/SendConfirmScreen.js:43 src/screens/TokenSwapReview.js:350 msgid "No fee" msgstr "" @@ -1017,21 +1024,23 @@ msgstr "" msgid "Failed to process transaction." msgstr "" -#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:212 +#: src/screens/SendConfirmScreen.js:179 src/screens/TokenSwapReview.js:214 msgid "Your transfer is being processed" msgstr "Ваш перевод обрабатывается" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:202 -#: src/screens/TokenSwapReview.js:235 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:198 +#: src/screens/TokenSwapReview.js:249 msgid "Enter your 6-digit pin to authorize operation" msgstr "Введите 6-значный PIN-код для авторизации операции" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:203 -#: src/screens/TokenSwapReview.js:236 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:199 +#: src/screens/TokenSwapReview.js:250 msgid "Authorize operation" msgstr "Авторизовать операцию" -#: src/screens/SendConfirmScreen.js:243 +#: src/screens/SendConfirmScreen.js:239 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" @@ -1039,58 +1048,58 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:262 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:269 +#: src/screens/SendConfirmScreen.js:265 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:272 +#: src/screens/SendConfirmScreen.js:268 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:274 +#: src/screens/SendConfirmScreen.js:270 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:279 +#: src/screens/SendConfirmScreen.js:275 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:301 +#: src/screens/SendConfirmScreen.js:297 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:319 +#: src/screens/SendConfirmScreen.js:315 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Ваш перевод **${ amountAndToken }** был подтвержден" -#: src/screens/SendConfirmScreen.js:335 +#: src/screens/SendConfirmScreen.js:331 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:357 +#: src/screens/SendConfirmScreen.js:353 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:356 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:365 +#: src/screens/SendConfirmScreen.js:361 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:373 +#: src/screens/SendConfirmScreen.js:369 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:380 +#: src/screens/SendConfirmScreen.js:376 msgid "Send" msgstr "Отправить" @@ -1177,25 +1186,29 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:247 +#: src/screens/TokenSwapReview.js:230 +msgid "Could not complete the swap. Please try again." +msgstr "" + +#: src/screens/TokenSwapReview.js:261 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:253 +#: src/screens/TokenSwapReview.js:267 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:271 +#: src/screens/TokenSwapReview.js:285 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:332 +#: src/screens/TokenSwapReview.js:346 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:344 +#: src/screens/TokenSwapReview.js:358 msgid "SWAP" msgstr "" @@ -1643,44 +1656,50 @@ msgstr "" msgid "Open" msgstr "Открыть" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:940 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:946 +#: src/sagas/wallet.js:954 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:956 +#: src/sagas/wallet.js:964 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:972 +#: src/sagas/wallet.js:980 msgid "There was an error while loading first wallet address. Try again." msgstr "" -#: src/passkey/passkeySigner.js:35 +#: src/passkey/passkeySigner.js:37 msgid "Passkey authorization was cancelled. Nothing was sent." msgstr "" -#: src/passkey/passkeySigner.js:45 +#: src/passkey/passkeySigner.js:47 #, javascript-format msgid "" "This passkey opens a different wallet. Use the passkey named \"${ label }\"." msgstr "" -#: src/passkey/passkeySigner.js:46 +#: src/passkey/passkeySigner.js:48 msgid "" "This passkey opens a different wallet. Use the passkey that created this " "wallet." msgstr "" -#: src/passkey/passkeySigner.js:54 +#: src/passkey/passkeySigner.js:56 msgid "Another passkey authorization is already in progress." msgstr "" +#: src/passkey/passkeySigner.js:67 +msgid "" +"Passkey wallet data is missing or corrupted. Reset the wallet and sign in " +"again with your passkey." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1787,51 +1806,58 @@ msgstr "" msgid "No internet connection" msgstr "Нет соединения с интернетом" -#: src/components/PasskeyOnboardingButton.js:98 +#: src/components/PasskeyOnboardingButton.js:91 +msgid "Passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:104 msgid "Passkey unavailable" msgstr "" -#: src/components/PasskeyOnboardingButton.js:121 +#: src/components/PasskeyOnboardingButton.js:118 +msgid "Hathor Wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:130 msgid "Passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:128 +#: src/components/PasskeyOnboardingButton.js:137 msgid "Continue with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:142 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Working…" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:142 msgid "Sign in with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:139 +#: src/components/PasskeyOnboardingButton.js:148 msgid "Create passkey wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:144 -#, javascript-format +#: src/components/PasskeyOnboardingButton.js:156 msgid "" -"Your wallet is protected by this passkey. Keep it backed up by syncing it to " -"${ passwordManager } so you don't lose access." +"Your wallet is protected by this passkey. Keep it backed up in your password " +"manager so you don't lose access." msgstr "" -#: src/components/PasskeyOnboardingButton.js:149 +#: src/components/PasskeyOnboardingButton.js:161 msgid "Name your wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:151 +#: src/components/PasskeyOnboardingButton.js:163 msgid "This name identifies the passkey when you sign in later." msgstr "" -#: src/components/PasskeyOnboardingButton.js:158 +#: src/components/PasskeyOnboardingButton.js:170 msgid "e.g. Savings" msgstr "" -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Create" msgstr "" diff --git a/locale/texts.pot b/locale/texts.pot index 0eccf91ba..28448b659 100644 --- a/locale/texts.pot +++ b/locale/texts.pot @@ -137,12 +137,12 @@ msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" #: src/screens/About.js:99 -#: src/screens/InitWallet.js:66 +#: src/screens/InitWallet.js:68 msgid "This wallet is connected to the **mainnet**." msgstr "" #: src/screens/About.js:102 -#: src/screens/InitWallet.js:69 +#: src/screens/InitWallet.js:71 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as " @@ -316,7 +316,7 @@ msgstr "" msgid "Import tokens" msgstr "" -#: src/components/PasskeyOnboardingButton.js:176 +#: src/components/PasskeyOnboardingButton.js:188 #: src/screens/ConfirmImportScreen.js:129 #: src/screens/Reown/RegisterTokenAfterSuccess.js:80 #: src/screens/Reown/SuccessFeedbackScreen.js:39 @@ -354,8 +354,8 @@ msgstr "" #: src/screens/CreateTokenAmount.js:211 #: src/screens/CreateTokenName.js:67 #: src/screens/CreateTokenSymbol.js:86 -#: src/screens/InitWallet.js:233 -#: src/screens/InitWallet.js:386 +#: src/screens/InitWallet.js:268 +#: src/screens/InitWallet.js:421 #: src/screens/SendAddressInput.js:66 #: src/screens/SendAmountInput.js:169 msgid "Next" @@ -381,49 +381,52 @@ msgstr "" msgid "Building the transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:174 +#: src/screens/CreateTokenConfirm.js:170 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. msgid "Enter your 6-digit pin to create your token" msgstr "" -#: src/screens/CreateTokenConfirm.js:175 +#: src/screens/CreateTokenConfirm.js:171 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. msgid "Authorize token creation" msgstr "" -#: src/screens/CreateTokenConfirm.js:177 -#: src/screens/SendConfirmScreen.js:204 -#: src/screens/TokenSwapReview.js:237 +#: src/screens/CreateTokenConfirm.js:173 +#: src/screens/SendConfirmScreen.js:200 +#: src/screens/TokenSwapReview.js:251 +#. onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. msgid "Building transaction" msgstr "" -#: src/screens/CreateTokenConfirm.js:218 +#: src/screens/CreateTokenConfirm.js:214 #, javascript-format msgid "**${ name }** created successfully" msgstr "" -#: src/screens/CreateTokenConfirm.js:235 +#: src/screens/CreateTokenConfirm.js:231 msgid "Amount of ${ name }" msgstr "" -#: src/screens/CreateTokenConfirm.js:249 +#: src/screens/CreateTokenConfirm.js:245 msgid "Token name" msgstr "" -#: src/screens/CreateTokenConfirm.js:255 +#: src/screens/CreateTokenConfirm.js:251 #: src/screens/CreateTokenSymbol.js:71 msgid "Token symbol" msgstr "" #: src/components/Reown/CreateTokenRequest.js:87 #: src/components/Reown/CreateTokenRequest.js:129 -#: src/screens/CreateTokenConfirm.js:262 +#: src/screens/CreateTokenConfirm.js:258 msgid "Deposit" msgstr "" -#: src/screens/CreateTokenConfirm.js:270 +#: src/screens/CreateTokenConfirm.js:266 msgid "Network fee" msgstr "" -#: src/screens/CreateTokenConfirm.js:279 +#: src/screens/CreateTokenConfirm.js:275 msgid "Create token" msgstr "" @@ -594,78 +597,78 @@ msgstr "" msgid "Continue" msgstr "" -#: src/screens/InitWallet.js:63 +#: src/screens/InitWallet.js:65 msgid "Welcome to Hathor Wallet!" msgstr "" -#: src/screens/InitWallet.js:74 +#: src/screens/InitWallet.js:76 msgid "" "For further information, check out our website " "|link:https://hathor.network/|." msgstr "" -#: src/screens/InitWallet.js:87 +#: src/screens/InitWallet.js:89 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" msgstr "" -#: src/screens/InitWallet.js:99 +#: src/screens/InitWallet.js:101 msgid "Start" msgstr "" -#: src/screens/InitWallet.js:116 +#: src/screens/InitWallet.js:123 +msgid "New Wallet" +msgstr "" + +#: src/screens/InitWallet.js:130 +msgid "Import Wallet" +msgstr "" + +#: src/screens/InitWallet.js:162 msgid "To start," msgstr "" -#: src/screens/InitWallet.js:118 +#: src/screens/InitWallet.js:164 msgid "You need to **initialize your wallet**." msgstr "" -#: src/screens/InitWallet.js:121 +#: src/screens/InitWallet.js:167 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." msgstr "" -#: src/screens/InitWallet.js:124 +#: src/screens/InitWallet.js:170 msgid "To import a wallet, you will need to provide your seed words." msgstr "" -#: src/screens/InitWallet.js:129 -msgid "New Wallet" -msgstr "" - -#: src/screens/InitWallet.js:134 -msgid "Import Wallet" -msgstr "" - -#: src/screens/InitWallet.js:224 +#: src/screens/InitWallet.js:259 msgid "Your wallet has been created!" msgstr "" -#: src/screens/InitWallet.js:226 +#: src/screens/InitWallet.js:261 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." msgstr "" -#: src/screens/InitWallet.js:347 +#: src/screens/InitWallet.js:382 msgid "To import a wallet," msgstr "" -#: src/screens/InitWallet.js:349 +#: src/screens/InitWallet.js:384 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " "wallet, separated by space." msgstr "" -#: src/screens/InitWallet.js:352 +#: src/screens/InitWallet.js:387 msgid "Words" msgstr "" -#: src/screens/InitWallet.js:357 +#: src/screens/InitWallet.js:392 msgid "Enter your seed words separated by space" msgstr "" @@ -701,7 +704,7 @@ msgid "There's been an error connecting to the server." msgstr "" #: src/screens/LoadWalletErrorScreen.js:28 -#: src/screens/PasskeyLockScreen.js:139 +#: src/screens/PasskeyLockScreen.js:157 #: src/screens/PinScreen.js:318 #: src/screens/Settings.js:177 msgid "Reset wallet" @@ -759,20 +762,24 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PasskeyLockScreen.js:61 +#: src/screens/PasskeyLockScreen.js:69 msgid "Unlock cancelled." msgstr "" -#: src/screens/PasskeyLockScreen.js:103 +#: src/screens/PasskeyLockScreen.js:78 +msgid "Could not verify your passkey. Please try again." +msgstr "" + +#: src/screens/PasskeyLockScreen.js:121 #, javascript-format msgid "Unlock with the passkey \"${ passkeyLabel }\"" msgstr "" -#: src/screens/PasskeyLockScreen.js:104 +#: src/screens/PasskeyLockScreen.js:122 msgid "Unlock with your passkey" msgstr "" -#: src/screens/PasskeyLockScreen.js:124 +#: src/screens/PasskeyLockScreen.js:142 msgid "Unlock" msgstr "" @@ -1002,12 +1009,12 @@ msgstr[0] "" msgstr[1] "" #: src/screens/SendAmountInput.js:137 -#: src/screens/SendConfirmScreen.js:295 +#: src/screens/SendConfirmScreen.js:291 msgid "SEND ${ tokenNameUpperCase }" msgstr "" #: src/screens/SendConfirmScreen.js:43 -#: src/screens/TokenSwapReview.js:336 +#: src/screens/TokenSwapReview.js:350 msgid "No fee" msgstr "" @@ -1024,81 +1031,83 @@ msgid "Failed to process transaction." msgstr "" #: src/screens/SendConfirmScreen.js:179 -#: src/screens/TokenSwapReview.js:212 +#: src/screens/TokenSwapReview.js:214 msgid "Your transfer is being processed" msgstr "" #: src/sagas/helpers.js:147 -#: src/screens/SendConfirmScreen.js:202 -#: src/screens/TokenSwapReview.js:235 +#: src/screens/SendConfirmScreen.js:198 +#: src/screens/TokenSwapReview.js:249 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. msgid "Enter your 6-digit pin to authorize operation" msgstr "" #: src/sagas/helpers.js:148 -#: src/screens/SendConfirmScreen.js:203 -#: src/screens/TokenSwapReview.js:236 +#: src/screens/SendConfirmScreen.js:199 +#: src/screens/TokenSwapReview.js:250 +#. onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. msgid "Authorize operation" msgstr "" -#: src/screens/SendConfirmScreen.js:243 +#: src/screens/SendConfirmScreen.js:239 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:266 +#: src/screens/SendConfirmScreen.js:262 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:269 +#: src/screens/SendConfirmScreen.js:265 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:272 +#: src/screens/SendConfirmScreen.js:268 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:274 +#: src/screens/SendConfirmScreen.js:270 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:108 -#: src/screens/SendConfirmScreen.js:279 +#: src/screens/SendConfirmScreen.js:275 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:301 +#: src/screens/SendConfirmScreen.js:297 msgid "Building your transaction" msgstr "" -#: src/screens/SendConfirmScreen.js:319 +#: src/screens/SendConfirmScreen.js:315 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:335 +#: src/screens/SendConfirmScreen.js:331 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:357 +#: src/screens/SendConfirmScreen.js:353 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:360 +#: src/screens/SendConfirmScreen.js:356 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:365 +#: src/screens/SendConfirmScreen.js:361 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:373 +#: src/screens/SendConfirmScreen.js:369 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:380 +#: src/screens/SendConfirmScreen.js:376 msgid "Send" msgstr "" @@ -1186,25 +1195,29 @@ msgstr "" msgid "Loading Token Swap" msgstr "" -#: src/screens/TokenSwapReview.js:247 +#: src/screens/TokenSwapReview.js:230 +msgid "Could not complete the swap. Please try again." +msgstr "" + +#: src/screens/TokenSwapReview.js:261 msgid "REVIEW TOKEN SWAP" msgstr "" -#: src/screens/TokenSwapReview.js:253 +#: src/screens/TokenSwapReview.js:267 msgid "Building the swap transaction for your review" msgstr "" -#: src/screens/TokenSwapReview.js:271 +#: src/screens/TokenSwapReview.js:285 msgid "Your swap was successful" msgstr "" #: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 -#: src/screens/TokenSwapReview.js:332 +#: src/screens/TokenSwapReview.js:346 msgid "Network Fee" msgstr "" -#: src/screens/TokenSwapReview.js:344 +#: src/screens/TokenSwapReview.js:358 msgid "SWAP" msgstr "" @@ -1652,43 +1665,49 @@ msgstr "" msgid "Open" msgstr "" -#: src/sagas/wallet.js:932 +#: src/sagas/wallet.js:940 msgid "Wallet is not ready to load addresses." msgstr "" -#: src/sagas/wallet.js:946 +#: src/sagas/wallet.js:954 #. This will show the message in the feedback content at SelectAddressModal msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:956 +#: src/sagas/wallet.js:964 msgid "Wallet is not ready to load the first address." msgstr "" -#: src/sagas/wallet.js:972 +#: src/sagas/wallet.js:980 #. This will show the message in the feedback content msgid "There was an error while loading first wallet address. Try again." msgstr "" -#: src/passkey/passkeySigner.js:35 +#: src/passkey/passkeySigner.js:37 msgid "Passkey authorization was cancelled. Nothing was sent." msgstr "" -#: src/passkey/passkeySigner.js:45 +#: src/passkey/passkeySigner.js:47 #, javascript-format msgid "This passkey opens a different wallet. Use the passkey named \"${ label }\"." msgstr "" -#: src/passkey/passkeySigner.js:46 +#: src/passkey/passkeySigner.js:48 msgid "" "This passkey opens a different wallet. Use the passkey that created this " "wallet." msgstr "" -#: src/passkey/passkeySigner.js:54 +#: src/passkey/passkeySigner.js:56 msgid "Another passkey authorization is already in progress." msgstr "" +#: src/passkey/passkeySigner.js:67 +msgid "" +"Passkey wallet data is missing or corrupted. Reset the wallet and sign in " +"again with your passkey." +msgstr "" + #: src/components/AmountInputAccessory.js:98 msgid "Max" msgstr "" @@ -1795,51 +1814,58 @@ msgstr "" msgid "No internet connection" msgstr "" -#: src/components/PasskeyOnboardingButton.js:98 +#: src/components/PasskeyOnboardingButton.js:91 +msgid "Passkey wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:104 msgid "Passkey unavailable" msgstr "" -#: src/components/PasskeyOnboardingButton.js:121 +#: src/components/PasskeyOnboardingButton.js:118 +msgid "Hathor Wallet" +msgstr "" + +#: src/components/PasskeyOnboardingButton.js:130 msgid "Passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:128 +#: src/components/PasskeyOnboardingButton.js:137 msgid "Continue with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:142 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Working…" msgstr "" -#: src/components/PasskeyOnboardingButton.js:133 +#: src/components/PasskeyOnboardingButton.js:142 msgid "Sign in with passkey" msgstr "" -#: src/components/PasskeyOnboardingButton.js:139 +#: src/components/PasskeyOnboardingButton.js:148 msgid "Create passkey wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:144 -#, javascript-format +#: src/components/PasskeyOnboardingButton.js:156 msgid "" -"Your wallet is protected by this passkey. Keep it backed up by syncing it " -"to ${ passwordManager } so you don't lose access." +"Your wallet is protected by this passkey. Keep it backed up in your " +"password manager so you don't lose access." msgstr "" -#: src/components/PasskeyOnboardingButton.js:149 +#: src/components/PasskeyOnboardingButton.js:161 msgid "Name your wallet" msgstr "" -#: src/components/PasskeyOnboardingButton.js:151 +#: src/components/PasskeyOnboardingButton.js:163 msgid "This name identifies the passkey when you sign in later." msgstr "" -#: src/components/PasskeyOnboardingButton.js:158 +#: src/components/PasskeyOnboardingButton.js:170 msgid "e.g. Savings" msgstr "" -#: src/components/PasskeyOnboardingButton.js:170 +#: src/components/PasskeyOnboardingButton.js:182 msgid "Create" msgstr "" diff --git a/src/components/PasskeyOnboardingButton.js b/src/components/PasskeyOnboardingButton.js index 561831ad7..91a66159b 100644 --- a/src/components/PasskeyOnboardingButton.js +++ b/src/components/PasskeyOnboardingButton.js @@ -19,11 +19,15 @@ import { PASSKEY_ONBOARDING_FEATURE_TOGGLE } from '../constants'; import { createWalletWordsFromPasskey, signInWalletWordsFromPasskey, + isPasskeyCancel, } from '../passkey/passkeyService'; import { derivePasskeyXpub } from '../passkey/passkeySigner'; import { startWalletRequested, unlockScreen } from '../actions'; import { STORE } from '../store'; import NavigationService from '../NavigationService'; +import { logger } from '../logger'; + +const log = logger('passkey'); /** * Passkey onboarding (passkey PRF -> seed). @@ -75,41 +79,46 @@ const PasskeyOnboardingButton = () => { const run = async (deriveWords) => { setBusy(true); + // Keep ONLY the passkey ceremony + storage inside the try. Everything after the wallet is + // persisted (navigation/dispatch) must not be reported as "Passkey unavailable" — the wallet + // WAS created, and a nav failure there is a different problem. try { const { words, label, credentialId } = await deriveWords(); // The words exist only in this scope: derive the account xpub, persist it with the // wallet metadata, and start the wallet read-only. No PIN, no stored secret. const xpub = derivePasskeyXpub(words); await STORE.initPasskeyStorage(xpub, { - passkeyLabel: label || 'Passkey wallet', + passkeyLabel: label || t`Passkey wallet`, credentialId: credentialId ?? null, }); - setModalVisible(false); - // Same sequence ChoosePinScreen uses: unlock before entering the main stack, - // then request the wallet start (the saga re-reads the persisted walletMeta). - dispatch(unlockScreen()); - dispatch(startWalletRequested({ walletType: 'passkey' })); - NavigationService.resetToMain(); } catch (e) { + // Dismissing the OS passkey sheet is a cancel, not a failure — stay silent and let the user + // retry (the modal stays open). Only real failures raise the alert. + if (isPasskeyCancel(e)) { + return; + } // react-native-passkey throws a PasskeyError with a `.error` code — surface it so a // generic "unknown error" becomes actionable (e.g. BadConfiguration = domain not verified). - console.warn('[passkey] failed:', e?.error, e?.message, e); + log.error('passkey onboarding failed', e); const code = e?.error ? `[${e.error}] ` : ''; Alert.alert(t`Passkey unavailable`, `${code}${e?.message ?? String(e)}`); + return; } finally { setBusy(false); } + setModalVisible(false); + // Same sequence ChoosePinScreen uses: unlock before entering the main stack, then request the + // wallet start. The saga derives isPasskeyWallet from the persisted walletMeta, so no payload. + dispatch(unlockScreen()); + dispatch(startWalletRequested()); + NavigationService.resetToMain(); }; const onCreate = () => { - const name = walletName.trim() || 'Hathor Wallet'; + const name = walletName.trim() || t`Hathor Wallet`; run(() => createWalletWordsFromPasskey(name)); }; - // A passkey wallet has no seed phrase and no PIN — its recovery is the OS syncing the passkey to - // the user's password manager. Name it per platform so the disclosure below is concrete. - const passwordManager = Platform.OS === 'ios' ? 'iCloud Keychain' : 'Google Password Manager'; - const onDismiss = () => { if (!busy) setModalVisible(false); }; @@ -140,8 +149,11 @@ const PasskeyOnboardingButton = () => { secondary /> + {/* We can't reliably detect WHICH manager stores the passkey — the credential is + discoverable and users may route passkeys to iCloud Keychain, Google Password + Manager, 1Password, a hardware key, etc. — so keep the disclosure generic. */} - {t`Your wallet is protected by this passkey. Keep it backed up by syncing it to ${passwordManager} so you don't lose access.`} + {t`Your wallet is protected by this passkey. Keep it backed up in your password manager so you don't lose access.`} ) : ( diff --git a/src/passkey/authorizeTransaction.js b/src/passkey/authorizeTransaction.js index 1e59d4642..290c96ad9 100644 --- a/src/passkey/authorizeTransaction.js +++ b/src/passkey/authorizeTransaction.js @@ -14,21 +14,23 @@ import { STORE } from '../store'; * * Passkey wallets have no PIN: authorization IS the passkey ceremony, fired by the external signer * when the lib requests signatures. No PIN is passed — signing is PIN-optional once an external - * tx-signing method is registered — so `execute` is called directly. Every other wallet collects - * the PIN through the PinScreen, which invokes `pinParams.cb` (typically the same `execute`) with - * the entered PIN. + * tx-signing method is registered — so the passkey action runs directly. Other wallets collect the + * PIN through the PinScreen, which invokes `pinParams.cb` with the entered PIN. + * + * The passkey action defaults to `pinParams.cb` — the common case, where both paths run the same + * function — so a caller only passes `onPasskey` when it must differ (e.g. wrapping it to flip a + * sending flag only on the inline passkey path). This keeps the two from silently diverging. * * @param {object} args - * @param {() => void} args.execute - The passkey-path action, run directly (no PIN) for passkey - * wallets. NOT called for PIN wallets — those authorize through pinParams.cb instead. It is - * often the same function as pinParams.cb, but need not be (a caller may wrap it). * @param {{ navigate: Function }} args.navigation - Navigation used to open the PinScreen. * @param {object} args.pinParams - Params forwarded to the PinScreen (cb, screenText, ...); cb is * invoked with the entered PIN. Ignored for passkey wallets. + * @param {() => void} [args.onPasskey] - The passkey-path action, run directly (no PIN) for passkey + * wallets. Defaults to `pinParams.cb`; pass it only when the passkey path must differ. */ -export function authorizeTransaction({ execute, navigation, pinParams }) { +export function authorizeTransaction({ navigation, pinParams, onPasskey = pinParams.cb }) { if (STORE.isPasskeyWallet()) { - execute(); + onPasskey(); return; } navigation.navigate('PinScreen', pinParams); diff --git a/src/passkey/passkeySigner.js b/src/passkey/passkeySigner.js index d03b4ba07..4a5c21484 100644 --- a/src/passkey/passkeySigner.js +++ b/src/passkey/passkeySigner.js @@ -13,8 +13,10 @@ * makePasskeyTxSigner() runs the WebAuthn PRF ceremony, re-derives the BIP39 seed in memory, * verifies it belongs to THIS wallet (derived xpub === stored xpub), then delegates the actual * signing to wallet-lib's transactionUtils.signTxInputs (supplying the ceremony-derived chain - * xprivs), and discards all key material before returning. Register it with - * wallet.setExternalTxSigningMethod() BEFORE wallet.start(). + * xprivs), and discards all key material before returning. Register it via + * wallet.setExternalTxSigningMethod() before the first send/sign; wallet-lib re-checks isReadonly() + * per operation, so ordering relative to wallet.start() is not enforced (we do it before start() + * as a safe convention). */ import { t } from 'ttag'; @@ -56,6 +58,17 @@ export class PasskeyBusyError extends Error { } } +// Distinct from PasskeyXpubMismatchError: a MISSING stored xpub means the wallet metadata is +// corrupted or was never fully written (the xpub is the wallet's only persisted identity), NOT that +// the user presented the wrong passkey. The two need different guidance — reset here vs "use the +// other passkey" there — and this case should be reported as corruption, not shown as a mismatch. +export class PasskeyMetadataMissingError extends Error { + constructor() { + super(t`Passkey wallet data is missing or corrupted. Reset the wallet and sign in again with your passkey.`); + this.name = 'PasskeyMetadataMissingError'; + } +} + /** * The ONE xpub derivation used everywhere (onboarding, unlock verification, signing): * account-path xpub (m/44'/280'/0') — exactly what generateAccessDataFromXpub expects, @@ -138,7 +151,10 @@ export function makePasskeyTxSigner() { throw e; } - if (!meta?.xpub || derivePasskeyXpub(words) !== meta.xpub) { + if (!meta?.xpub) { + throw new PasskeyMetadataMissingError(); + } + if (derivePasskeyXpub(words) !== meta.xpub) { throw new PasskeyXpubMismatchError(meta?.passkeyLabel); } @@ -200,7 +216,10 @@ export function verifyPasskeyForUnlock() { throw e; } try { - if (!meta?.xpub || derivePasskeyXpub(words) !== meta.xpub) { + if (!meta?.xpub) { + throw new PasskeyMetadataMissingError(); + } + if (derivePasskeyXpub(words) !== meta.xpub) { throw new PasskeyXpubMismatchError(meta?.passkeyLabel); } // Best-effort backfill (see makePasskeyTxSigner): fire-and-forget, log on failure. diff --git a/src/sagas/pushNotification.js b/src/sagas/pushNotification.js index 5a2a57d86..2b05a6198 100644 --- a/src/sagas/pushNotification.js +++ b/src/sagas/pushNotification.js @@ -381,6 +381,10 @@ export function* loadWallet() { // requires. Push notifications are already gated off for them at startWallet; this guard // only protects against unexpected entry paths. if (STORE.isPasskeyWallet()) { + // Defense-in-depth: this should never fire (push is already gated off for passkey wallets at + // startWallet). The PUSH_WALLET_LOAD_FAILED consumer only reads it as a signal and discards the + // error, so log here too, otherwise hitting this unexpected path leaves no trace of why. + log.error('loadWallet reached for a passkey wallet — push is unsupported (unexpected entry path).'); yield put(pushLoadWalletFailed({ error: new Error('Push notifications are not supported for passkey wallets.'), })); diff --git a/src/sagas/reown.js b/src/sagas/reown.js index 4aeac243c..7bc15e0f8 100644 --- a/src/sagas/reown.js +++ b/src/sagas/reown.js @@ -558,6 +558,12 @@ export function* processRequest(action) { const { payload } = action; const { params } = payload; + // Clear any stale passkey-cancel flag left by an EARLIER in-app ceremony (a cancel on the + // Send/Create/Swap screens sets this module-global flag, which is consumed only in the catch + // below). Without this reset, the next dapp request that fails for an UNRELATED reason would read + // that stale `true` and spuriously retry instead of reporting the error (consume = read+reset). + consumePasskeySigningCancelled(); + const reownClient = yield call(getReownClient); if (!reownClient) { log.debug('Tried to get reown client in processRequest but it is undefined.'); @@ -601,9 +607,10 @@ export function* processRequest(action) { chain: get(requestSession.namespaces, 'hathor.chains[0]', ''), }; - // Message/oracle signing calls wallet.signMessageWithAddress, which needs the stored - // private key and bypasses the external signer — impossible for xpub-only passkey - // wallets. Reject cleanly instead of crashing inside the rpc-handler. + // These two methods each need the stored private key at one of our addresses, which bypasses the + // external tx signer: htr_signWithAddress signs via wallet.signMessageWithAddress, and + // htr_signOracleData signs via nanoUtils.getOracleInputData. Neither is possible for an xpub-only + // passkey wallet, so reject cleanly instead of crashing inside the rpc-handler. const passkeyUnsupportedMethods = [ AVAILABLE_METHODS.HATHOR_SIGN_MESSAGE, AVAILABLE_METHODS.HATHOR_SIGN_ORACLE_DATA, @@ -616,8 +623,11 @@ export function* processRequest(action) { id: payload.id, jsonrpc: '2.0', error: { - code: ERROR_CODES.USER_REJECTED_METHOD, - message: 'This operation is not yet supported for passkey wallets.', + // The wallet structurally cannot service this method — it's not a user rejection. 3001 + // (UNAUTHORIZED_METHODS) lets the dapp disable the action rather than offer a retry that + // can never succeed, unlike USER_REJECTED_METHOD (5002). + code: ERROR_CODES.UNAUTHORIZED_METHODS, + message: 'This operation is not supported for passkey wallets.', }, }, })); @@ -682,6 +692,16 @@ export function* processRequest(action) { // typed error, so we use a consumable flag — same pattern as pinWasCancelled). Retry the // request: the dapp consent modal shows again and the user can accept or reject. if (consumePasskeySigningCancelled()) { + // The rpc-handler dispatches a *StatusLoading BEFORE the passkey ceremony runs, and this + // branch returns before the switch below that would otherwise reset it. Without clearing it, + // the retry's fresh consent modal renders on top of a full-screen "Sending transaction" + // spinner the user can't dismiss. Reset the per-method status to READY (NOT *StatusFailure, + // which pops an "Error / Try again" modal, contradicting the retry). Dispatching all four + // is safe — only the in-flight one is non-idle — and mirrors the timeout cleanup above. + yield put(setNewNanoContractStatusReady()); + yield put(setCreateTokenStatusReady()); + yield put(setSendTxStatusReady()); + yield put(setCreateNanoContractCreateTokenTxStatusReady()); // Cancel the poll fork from this attempt before recursing — the early return below skips the // cleanup at the end of processRequest, so without this the fork leaks (and a new one is // forked per retry). Mirrors the SendNanoContractTxError retry path. diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index 158d63c3c..9af0a2244 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -195,9 +195,15 @@ export function* startWallet(action) { // A passkey wallet boots read-only from this xpub; if it is missing/corrupted, fail fast with a // clear message instead of handing `{ xpub: undefined }` to HathorWallet and surfacing an opaque - // wallet-lib error later. (makePasskeyTxSigner guards the same case at sign time.) + // wallet-lib error later. (makePasskeyTxSigner guards the same case at sign time via + // PasskeyMetadataMissingError.) The xpub is this wallet's ONLY persisted identity, so a missing + // one is storage corruption, not a network error — report it to Sentry explicitly, since this + // throw otherwise lands in the global saga errorHandler which never captures and only renders the + // generic "error connecting to the server" screen. if (isPasskeyWallet && !walletMeta?.xpub) { - throw new Error('Passkey wallet metadata is missing its xpub.'); + const xpubError = new Error('Passkey wallet metadata is missing its xpub.'); + yield put(onExceptionCaptured(xpubError, false)); + throw xpubError; } // clean memory storage and metadata before starting the wallet. @@ -310,8 +316,10 @@ export function* startWallet(action) { }; wallet = new HathorWallet(walletConfig); if (isPasskeyWallet) { - // Must happen BEFORE start(): flips isSignedExternally so the read-only guards allow - // sending, with signatures produced by the passkey ceremony instead of a stored key. + // Register the external signer before the first send/sign: it flips isSignedExternally so the + // read-only guards allow sending, with signatures produced by the passkey ceremony instead of + // a stored key. wallet-lib re-checks isReadonly() per operation, so ordering relative to + // start() is not enforced — we do it here, before start(), as a safe convention. wallet.setExternalTxSigningMethod(makePasskeyTxSigner()); } } diff --git a/src/screens/CreateTokenConfirm.js b/src/screens/CreateTokenConfirm.js index 896844453..74586bc3a 100644 --- a/src/screens/CreateTokenConfirm.js +++ b/src/screens/CreateTokenConfirm.js @@ -163,8 +163,8 @@ const CreateTokenConfirm = () => { // again while we return from it and build the feedback modal. setIsSending(true); authorizeTransaction({ - execute: executeCreate, navigation, + // onPasskey defaults to pinParams.cb (executeCreate) — both paths run the same function here. pinParams: { cb: executeCreate, screenText: t`Enter your 6-digit pin to create your token`, diff --git a/src/screens/InitWallet.js b/src/screens/InitWallet.js index 0b1cd9ec0..f19a3df96 100644 --- a/src/screens/InitWallet.js +++ b/src/screens/InitWallet.js @@ -23,6 +23,8 @@ import { View, } from 'react-native'; import { t } from 'ttag'; +import { useSelector } from 'react-redux'; +import { get } from 'lodash'; import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import TextFmt from '../components/TextFmt'; @@ -31,7 +33,7 @@ import PasskeyOnboardingButton from '../components/PasskeyOnboardingButton'; import baseStyle from '../styles/init'; import { Link, str2jsx } from '../utils'; -import { TERMS_OF_SERVICE_URL, PRIVACY_POLICY_URL } from '../constants'; +import { TERMS_OF_SERVICE_URL, PRIVACY_POLICY_URL, PASSKEY_ONBOARDING_FEATURE_TOGGLE } from '../constants'; import { COLORS } from '../styles/themes'; import { SKIP_SEED_CONFIRMATION } from '../config'; @@ -105,6 +107,50 @@ class WelcomeScreen extends React.Component { } } +/** + * Onboarding buttons on the initial screen. The New Wallet / Import Wallet order is gated on the + * passkey feature toggle: only when it is ON do we lead with the primary "New Wallet" (and append + * the passkey button). With the flag OFF, the original order — Import first (secondary), New Wallet + * last (primary) — is preserved, so the default affordance is unchanged outside the rollout. + */ +const OnboardingButtons = ({ navigation, buttonViewStyle }) => { + const featureToggles = useSelector((state) => state.featureToggles); + const passkeyEnabled = get(featureToggles, PASSKEY_ONBOARDING_FEATURE_TOGGLE, false); + + const newWalletButton = (marginBottom) => ( + navigation.navigate('NewWordsScreen')} + title={t`New Wallet`} + style={marginBottom ? { marginBottom: 16 } : undefined} + /> + ); + const importWalletButton = ( + navigation.navigate('LoadWordsScreen')} + title={t`Import Wallet`} + style={{ marginBottom: 16 }} + secondary + /> + ); + + if (!passkeyEnabled) { + // Original order: Import first (secondary), New Wallet last (primary, no bottom margin). + return ( + + {importWalletButton} + {newWalletButton(false)} + + ); + } + return ( + + {newWalletButton(true)} + {importWalletButton} + + + ); +}; + class InitialScreen extends React.Component { style = ({ ...baseStyle }); @@ -123,21 +169,10 @@ class InitialScreen extends React.Component { {t`To import a wallet, you will need to provide your seed words.`} - - this.props.navigation.navigate('NewWordsScreen')} - title={t`New Wallet`} - style={{ marginBottom: 16 }} - /> - this.props.navigation.navigate('LoadWordsScreen')} - title={t`Import Wallet`} - style={{ marginBottom: 16 }} - secondary - /> - {/* Renders only when the passkey-onboarding feature toggle is on. */} - - + ); diff --git a/src/screens/PasskeyLockScreen.js b/src/screens/PasskeyLockScreen.js index 30a3fd365..2fa349889 100644 --- a/src/screens/PasskeyLockScreen.js +++ b/src/screens/PasskeyLockScreen.js @@ -15,6 +15,7 @@ import NewHathorButton from '../components/NewHathorButton'; import Logo from '../components/Logo'; import Spinner from '../components/Spinner'; import { + onExceptionCaptured, resetOnLockScreen, startWalletRequested, unlockScreen, @@ -24,6 +25,9 @@ import { STORE } from '../store'; import baseStyle from '../styles/init'; import { PasskeyCancelledError, verifyPasskeyForUnlock } from '../passkey/passkeySigner'; import { sanitizePasskeyLabel } from '../passkey/passkeyService'; +import { logger } from '../logger'; + +const log = logger('passkey'); /** * Lock screen for passkey (PIN-less, xpub-only) wallets — the counterpart of PinScreen's @@ -36,8 +40,11 @@ const PasskeyLockScreen = () => { const wallet = useSelector((state) => state.wallet); const [verifying, setVerifying] = useState(false); const [error, setError] = useState(null); - // Ceremony must fire exactly once on mount even if the component re-renders (the OS shows - // a single credential sheet; a duplicate call would fail or stack sheets). + // Synchronous idempotency guard: the mount effect must fire the ceremony at most once even if it + // is invoked more than once in the same tick (e.g. a Fast Refresh remount). setVerifying is + // async, + // so the `verifying` check alone can't cover a same-tick double-fire; the OS shows a single + // credential sheet and a duplicate call would fail or stack sheets. const startedRef = useRef(false); // Labels persisted from old-format credentials may be decode garbage; never show those. @@ -52,13 +59,24 @@ const PasskeyLockScreen = () => { try { await verifyPasskeyForUnlock(); if (!wallet) { - // App boot (or wallet stopped): start the xpub-only wallet. The saga re-reads the - // persisted walletMeta; no secret travels through redux. - dispatch(startWalletRequested({ walletType: 'passkey' })); + // App boot (or wallet stopped): start the xpub-only wallet. The saga derives + // isPasskeyWallet from the persisted walletMeta; no secret (and no payload flag) via redux. + dispatch(startWalletRequested()); } dispatch(unlockScreen()); } catch (e) { - setError(e instanceof PasskeyCancelledError ? t`Unlock cancelled.` : e.message); + if (e instanceof PasskeyCancelledError) { + setError(t`Unlock cancelled.`); + } else { + // A non-cancel failure on the only entry point for these wallets: log + report to Sentry + // (fatal=false — the user can always retry or reset, this isn't a crash), and ALWAYS show + // text, since a non-Error rejection or empty native message would otherwise + // leave the error banner blank on an Unlock button that looks like it did nothing. + log.error('passkey unlock failed', e); + dispatch(onExceptionCaptured(e, false)); + const code = e?.error ? `[${e.error}] ` : ''; + setError(`${code}${e?.message || t`Could not verify your passkey. Please try again.`}`); + } setVerifying(false); } }; diff --git a/src/screens/SendConfirmScreen.js b/src/screens/SendConfirmScreen.js index 15b923005..68dcf3296 100644 --- a/src/screens/SendConfirmScreen.js +++ b/src/screens/SendConfirmScreen.js @@ -190,8 +190,8 @@ const SendConfirmScreen = () => { // again while we return from it and build the feedback modal. setIsSending(true); authorizeTransaction({ - execute: executeSend, navigation, + // onPasskey defaults to pinParams.cb (executeSend) — both paths run the same function here. pinParams: { cb: executeSend, canCancel: true, diff --git a/src/screens/TokenSwapReview.js b/src/screens/TokenSwapReview.js index 0b39f4783..fa0107a2d 100644 --- a/src/screens/TokenSwapReview.js +++ b/src/screens/TokenSwapReview.js @@ -40,9 +40,11 @@ import NavigationService from '../NavigationService'; import { ArrowDownIcon } from '../components/Icons/ArrowDown.icon'; import TextFmt from '../components/TextFmt'; import { + onExceptionCaptured, tokenSwapFetchSwapQuote, tokenSwapResetSwapData, } from '../actions'; +import { PasskeyCancelledError, PasskeyBusyError } from '../passkey/passkeySigner'; import { registerToken, updateTokensMetadata } from '../utils/tokens'; import { TOKEN_SWAP_SLIPPAGE } from '../constants'; import Spinner from '../components/Spinner'; @@ -214,20 +216,33 @@ const TokenSwapReview = () => { promise, }); } catch (err) { + // A cancelled or busy passkey ceremony is NOT a failure: keep the reviewed swap intact and + // let the user retry. Do NOT release the reserved UTXOs, refetch the quote, or navigate away. + if (err instanceof PasskeyCancelledError || err instanceof PasskeyBusyError) { + setIsSending(false); + return; + } + // A real failure — e.g. PasskeyXpubMismatchError, whose message names the passkey to use. + // Report it and SHOW it (reusing the error FeedbackModal below) instead of a silent goBack; + // dismissing it navigates back, releasing the reserved UTXOs via the beforeRemove listener. console.error(err); - exitOnError(); + dispatch(onExceptionCaptured(err, false)); + setBuildError({ message: err?.message || t`Could not complete the swap. Please try again.` }); + setPhase(PHASE.ERROR); + setIsSending(false); } }; const onSwapButtonPress = () => { authorizeTransaction({ - // Passkey-only path: disable the button before the inline ceremony starts (executeSend is - // async). The PIN path doesn't set this here — the PinScreen navigation covers the button. - execute: () => { + navigation, + // The passkey path differs from the PIN path here, so onPasskey is explicit: disable the + // button before the inline (async) ceremony starts. The PIN path doesn't set this — the + // PinScreen navigation covers the button. + onPasskey: () => { setIsSending(true); executeSend(); }, - navigation, pinParams: { cb: executeSend, canCancel: true, diff --git a/src/store.js b/src/store.js index e565e15c3..d5fae5d52 100644 --- a/src/store.js +++ b/src/store.js @@ -359,16 +359,20 @@ class AsyncStorageStore { async initPasskeyStorage(xpub, meta) { const accessData = walletUtils.generateAccessDataFromXpub(xpub); const storage = this.getStorage(); - await storage.saveAccessData(accessData); - // Await the meta write: for a passkey wallet this record's xpub is the only persisted identity - // (no seed, no PIN), so an app-kill in the flush window would lose it (recoverable only via a - // fresh discoverable sign-in). + // Persist the wallet metadata BEFORE the access data, so this write is crash-atomic in the + // right direction. The app gates "is a wallet loaded?" on the access data (walletIsLoaded()), + // so if an app-kill interrupts between the two flushes the surviving state is + // meta-without-access-data => walletIsLoaded() is false => a clean re-onboarding. The reverse + // order would leave access-data-without-meta, stranding this PIN-less wallet on the PinScreen + // (walletIsLoaded() true but isPasskeyWallet() false) with no PIN that could ever unlock it. + // The meta's xpub is this wallet's ONLY persisted identity (no seed, no PIN), so we await both. await this.setItem(WALLET_META_KEY, { walletType: 'passkey', xpub, createdAt: Date.now(), ...meta, }); + await storage.saveAccessData(accessData); // A passkey wallet is born on the current storage version — the PIN-based data // migration path (handleDataMigration) must never run for it. this.updateStorageVersion();