diff --git a/explicacao-hacky-web3-auth.md b/explicacao-hacky-web3-auth.md new file mode 100644 index 000000000..9cca72384 --- /dev/null +++ b/explicacao-hacky-web3-auth.md @@ -0,0 +1,77 @@ +# Explicação — o `--hack` do rn-nodeify e por que ele quebra o Web3Auth + +## O que é `rn-nodeify` + +`rn-nodeify` é uma ferramenta que adapta projetos React Native para usar módulos core do Node.js (`crypto`, `stream`, `buffer`, etc.), que não existem no runtime do RN. Como o `bitcore-lib` e outras libs da Hathor foram escritas para Node, sem isso a wallet nem inicializa. + +No script `setup` do projeto: + +``` +rn-nodeify --install stream,process,path,events,crypto,console,buffer,zlib --hack +``` + +Tem duas partes. + +--- + +## Parte 1 — `--install ` + +Instala polyfills "browserify-style" para cada módulo Node listado: + +- `crypto` → `react-native-crypto` + `react-native-randombytes` +- `stream` → `stream-browserify` +- `buffer` → `buffer` +- etc. + +Também gera/atualiza o `shim.js` na raiz do projeto (o mesmo que está em uso) que faz `require('crypto')`, `require('buffer')` etc. globalmente, antes do app rodar. + +--- + +## Parte 2 — `--hack` (a parte problemática) + +Esse é o "modo agressivo". Para cada `package.json` dentro de `node_modules/` (de **todos** os pacotes, recursivamente), o rn-nodeify injeta dois campos: + +```json +{ + "react-native": { + "crypto": "react-native-crypto", + "stream": "stream-browserify", + "buffer": "buffer", + ... + }, + "browser": { + "crypto": "react-native-crypto", + ... + } +} +``` + +O Metro bundler do React Native lê esses campos no resolve de módulos. Então quando **qualquer** código dentro daquele pacote faz `require('crypto')`, o Metro redireciona para `react-native-crypto` em vez de falhar com "module not found". + +É força bruta: ao invés de adicionar um alias global de uma vez no `metro.config.js`, ele "tatua" todo o `node_modules`. + +--- + +## Por que isso quebra o Web3Auth + +Os pacotes do Web3Auth (`@toruslabs/*`, `@web3auth/*`) **já trazem implementações próprias de crypto** (via `@noble/curves`, `@toruslabs/eccrypto`, `elliptic`, etc.) e **não querem** o polyfill do browserify — eles esperam o `globalThis.crypto` da Web Crypto API ou suas próprias libs puras-JS. + +Mas com o `--hack`, qualquer `require('crypto')` dentro desses pacotes vai parar no `react-native-crypto`, que: + +- Tem uma API incompleta (sem `subtle`, sem `createHash` em alguns casos) +- Suas dependências (`brorand`, `hash.js`, `hmac-drbg`) também recebem o mesmo redirect, e algumas têm fallbacks frágeis +- O barrel export do `@web3auth/auth` (que carrega submódulos tipo `starkey`) crasha durante o load do módulo → `LOGIN_PROVIDER` vira `undefined` + +Por isso o fix aplicado foi remover o campo `crypto` do `react-native`/`browser` dos 20 pacotes específicos do Web3Auth, deixando o `--hack` agir só no resto do `node_modules` (que **precisa** do polyfill, como o `bitcore-lib`, hathor lib, etc.). + +--- + +## TL;DR + +`--hack` = monkey-patch global em todos os `package.json` do `node_modules` para redirecionar `require('crypto')` (e outros core modules Node) para polyfills browserify. Necessário para o ecossistema antigo Hathor/bitcore, **fatal** para o ecossistema novo Web3Auth, que traz crypto próprio. + +--- + +## Pacotes que precisam do crypto hack removido (20) + +`@toruslabs/base-controllers`, `@toruslabs/broadcast-channel`, `@toruslabs/constants`, `@toruslabs/eccrypto`, `@toruslabs/ffjavascript`, `@toruslabs/http-helpers`, `@toruslabs/metadata-helpers`, `@toruslabs/react-native-web-browser`, `@toruslabs/secure-pub-sub`, `@toruslabs/session-manager`, `@toruslabs/starkware-crypto`, `@toruslabs/tweetnacl-js`, `@web3auth/auth`, `@web3auth/base`, `@web3auth/base-provider`, `@web3auth/react-native-sdk`, `elliptic`, `brorand`, `hash.js`, `hmac-drbg`. diff --git a/locale/da/texts.po b/locale/da/texts.po index 4d2711193..c8ffade66 100644 --- a/locale/da/texts.po +++ b/locale/da/texts.po @@ -129,11 +129,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:69 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:72 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 " @@ -145,8 +145,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/BackupWords.js:184 @@ -202,32 +202,32 @@ msgstr "Ny PIN gemt" msgid "CHANGE PIN" msgstr "SKIFT PIN" -#: src/screens/ChoosePinScreen.js:140 +#: src/screens/ChoosePinScreen.js:167 msgid "Enter your new PIN code" msgstr "Indtast din nye PIN-kode" -#: src/screens/ChoosePinScreen.js:152 +#: src/screens/ChoosePinScreen.js:179 msgid "Enter your new PIN code again" msgstr "Indtast din nye PIN-kode igen" -#: src/screens/ChoosePinScreen.js:74 +#: src/screens/ChoosePinScreen.js:80 msgid "Create a new PIN code," msgstr "Opret en ny PIN-kode," -#: src/screens/ChoosePinScreen.js:77 +#: src/screens/ChoosePinScreen.js:83 msgid "To confirm the PIN," msgstr "For at bekræfte pinkoden," -#: src/screens/ChoosePinScreen.js:166 +#: src/screens/ChoosePinScreen.js:193 msgid "PIN codes don't match. Try again." msgstr "PIN-koder stemmer ikke overens. Prøv igen." -#: src/screens/ChoosePinScreen.js:189 +#: src/screens/ChoosePinScreen.js:216 msgid "Start the Wallet" msgstr "Start Wallet" #: src/screens/CreateTokenAmount.js:96 src/screens/CreateTokenAmount.js:108 -#: src/screens/SendAmountInput.js:138 +#: src/screens/SendAmountInput.js:142 msgid "Invalid amount" msgstr "" @@ -254,9 +254,9 @@ 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/SendAmountInput.js:235 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:317 +#: src/screens/InitWallet.js:470 src/screens/SendAddressInput.js:66 +#: src/screens/SendAmountInput.js:239 msgid "Next" msgstr "Næste" @@ -284,7 +284,7 @@ msgstr "Indtast din 6-cifrede pin for at oprette din token" msgid "Authorize token creation" msgstr "Autoriser oprettelse af token" -#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:101 +#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:105 #: src/screens/TokenSwapReview.js:163 msgid "Building transaction" msgstr "" @@ -307,7 +307,7 @@ msgid "Token symbol" msgstr "Token symbol" #: src/components/Reown/CreateTokenRequest.js:87 -#: src/components/Reown/CreateTokenRequest.js:128 +#: src/components/Reown/CreateTokenRequest.js:129 #: src/screens/CreateTokenConfirm.js:251 msgid "Deposit" msgstr "Indsæt" @@ -326,9 +326,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 "" @@ -452,36 +452,36 @@ msgstr "Tokens" msgid "Nano Contracts" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:66 msgid "Welcome to Hathor Wallet!" msgstr "Velkommen til Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:77 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:90 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:102 msgid "Start" msgstr "Start" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:168 msgid "To start," msgstr "For at begynde," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:170 msgid "You need to **initialize your wallet**." msgstr "Skal du **initialisere din wallet**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:173 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -489,23 +489,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:176 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 +#: src/screens/InitWallet.js:204 msgid "Import Wallet" msgstr "Importer wallet" -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:210 msgid "New Wallet" msgstr "Ny wallet" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:308 msgid "Your wallet has been created!" msgstr "Din wallet er oprettet!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:310 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -513,11 +513,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:431 msgid "To import a wallet," msgstr "Hvis du vil importere en wallet," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:433 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -526,21 +526,24 @@ msgstr "" "Skal du **skrive ${ this.numberOfWords } seed-ord** i din wallet adskilt med " "mellemrum." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:436 msgid "Words" msgstr "Ord" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:441 msgid "Enter your seed words separated by space" msgstr "Indtast dine seed-ord adskilt med mellemrum" #: src/components/NanoContract/NanoContractDetails.js:252 -#: src/components/Reown/CreateTokenRequest.js:248 +#: src/components/Reown/CreateTokenRequest.js:249 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:626 #: src/components/Reown/SendTransactionRequest.js:550 -#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:27 +#: src/components/Web3AuthErrorDialog.js:26 +#: src/components/Web3AuthErrorDialog.js:34 +#: src/components/Web3AuthErrorDialog.js:52 src/screens/LoadHistoryScreen.js:51 +#: src/screens/LoadWalletErrorScreen.js:27 #: src/screens/NanoContract/NanoContractRegisterScreen.js:174 -#: src/screens/PinScreen.js:283 src/screens/TokenSwapLoadingScreen.js:90 +#: src/screens/PinScreen.js:288 src/screens/TokenSwapLoadingScreen.js:90 msgid "Try again" msgstr "" @@ -562,8 +565,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:162 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:323 +#: src/screens/Settings.js:170 msgid "Reset wallet" msgstr "Nulstil wallet" @@ -617,28 +620,32 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PinScreen.js:272 +#: src/screens/PinScreen.js:277 msgid "Incorrect PIN Code. Try again." msgstr "Forkert PIN-kode. Prøv igen." -#: src/screens/PinScreen.js:76 +#: src/screens/PinScreen.js:77 msgid "Enter your PIN Code " msgstr "Indtast din pinkode " -#: src/screens/PinScreen.js:77 +#: src/screens/PinScreen.js:78 msgid "Unlock Hathor Wallet" msgstr "Lås Hathor-wallet op" #: src/components/Reown/RequestConfirmationModal.js:110 -#: src/screens/PinScreen.js:309 src/screens/Reown/ReownList.js:127 +#: src/components/Web3AuthErrorDialog.js:27 +#: src/components/Web3AuthErrorDialog.js:35 +#: src/components/Web3AuthErrorDialog.js:43 +#: src/components/Web3AuthErrorDialog.js:53 src/screens/PinScreen.js:314 +#: src/screens/Reown/ReownList.js:127 msgid "Cancel" msgstr "Annuller" -#: src/screens/PinScreen.js:349 src/screens/PinScreen.js:353 +#: src/screens/PinScreen.js:354 src/screens/PinScreen.js:358 msgid "Biometry failed or canceled." msgstr "Biometri mislykkedes eller blev annulleret." -#: src/screens/PushNotification.js:58 src/screens/Settings.js:136 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:138 msgid "Push Notification" msgstr "" @@ -784,36 +791,36 @@ msgstr "" msgid "Reset Wallet" msgstr "Nulstil wallet" -#: src/screens/Security.js:145 +#: src/screens/Security.js:147 msgid "Disable biometry" msgstr "Deaktiver biometri" -#: src/screens/Security.js:146 +#: src/screens/Security.js:148 msgid "Disabling biometry" msgstr "Deaktiverer biometri" -#: src/screens/Security.js:158 src/screens/Security.js:170 +#: src/screens/Security.js:160 src/screens/Security.js:172 msgid "Enter your 6-digit pin to enable biometry" msgstr "Indtast din 6-cifrede pin for at aktivere biometri" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 msgid "No biometry supported" msgstr "Ingen biometri understøttet" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 #, javascript-format msgid "Use ${ this.supportedBiometry }" msgstr "Brug ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:193 msgid "SECURITY" msgstr "SIKKERHED" -#: src/screens/Security.js:213 +#: src/screens/Security.js:215 msgid "Change PIN" msgstr "Skift pinkode" -#: src/screens/Security.js:218 +#: src/screens/Security.js:220 msgid "Lock wallet" msgstr "Lås wallet" @@ -825,26 +832,26 @@ msgstr "SEND" msgid "Address to send" msgstr "Adresse, der skal sendes til" -#: src/screens/SendAmountInput.js:143 +#: src/screens/SendAmountInput.js:147 msgid "Insufficient funds" msgstr "Utilstrækkelige midler" -#: src/screens/SendAmountInput.js:150 +#: src/screens/SendAmountInput.js:154 msgid "Calculating network fee..." msgstr "" -#: src/screens/SendAmountInput.js:157 src/screens/SendAmountInput.js:162 +#: src/screens/SendAmountInput.js:161 src/screens/SendAmountInput.js:166 msgid "Insufficient balance of HTR to cover the network fee." msgstr "" -#: src/screens/SendAmountInput.js:189 +#: src/screens/SendAmountInput.js:193 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendAmountInput.js:203 src/screens/SendConfirmScreen.js:188 +#: src/screens/SendAmountInput.js:207 src/screens/SendConfirmScreen.js:193 msgid "SEND ${ tokenNameUpperCase }" msgstr "SEND ${ tokenNameUpperCase }" @@ -853,75 +860,75 @@ msgid "No fee" msgstr "" #. show loading modal -#: src/screens/SendConfirmScreen.js:86 src/screens/TokenSwapReview.js:145 +#: src/screens/SendConfirmScreen.js:90 src/screens/TokenSwapReview.js:145 msgid "Your transfer is being processed" msgstr "Din overførsel behandles" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:99 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:103 #: src/screens/TokenSwapReview.js:161 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:100 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:104 #: src/screens/TokenSwapReview.js:162 msgid "Authorize operation" msgstr "Autoriserer overførslen" -#: src/screens/SendConfirmScreen.js:136 +#: src/screens/SendConfirmScreen.js:140 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:159 +#: src/screens/SendConfirmScreen.js:164 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:162 +#: src/screens/SendConfirmScreen.js:167 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:165 +#: src/screens/SendConfirmScreen.js:170 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:167 +#: src/screens/SendConfirmScreen.js:172 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:106 -#: src/screens/SendConfirmScreen.js:172 +#: src/screens/SendConfirmScreen.js:177 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:197 +#: src/screens/SendConfirmScreen.js:202 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:208 +#: src/screens/SendConfirmScreen.js:213 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:224 +#: src/screens/SendConfirmScreen.js:229 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:227 +#: src/screens/SendConfirmScreen.js:232 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:232 +#: src/screens/SendConfirmScreen.js:237 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:240 +#: src/screens/SendConfirmScreen.js:245 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:247 +#: src/screens/SendConfirmScreen.js:252 msgid "Send" msgstr "Send" @@ -934,43 +941,47 @@ msgstr "Ugyldig QR-kode" msgid "Scan the QR code" msgstr "Scan QR-koden" -#: src/screens/Settings.js:105 +#: src/screens/Settings.js:107 msgid "You are connected to" msgstr "Du er tilsluttet til" -#: src/screens/Settings.js:113 +#: src/screens/Settings.js:115 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:117 +#: src/screens/Settings.js:119 msgid "Connected to" msgstr "Forbundet til" -#: src/screens/Settings.js:130 +#: src/screens/Settings.js:132 msgid "Security" msgstr "Sikkerhed" -#: src/screens/Settings.js:143 +#: src/screens/Settings.js:145 msgid "Create a new token" msgstr "Opret en ny token" -#: src/screens/Settings.js:150 +#: src/screens/Settings.js:152 msgid "Register a token" msgstr "Registrer en token" -#: src/screens/Settings.js:166 +#: src/screens/Settings.js:165 +msgid "Sign out" +msgstr "" + +#: src/screens/Settings.js:174 msgid "About" msgstr "Om" -#: src/screens/Settings.js:173 +#: src/screens/Settings.js:181 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:187 +#: src/screens/Settings.js:195 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:189 +#: src/screens/Settings.js:197 msgid "Network Settings" msgstr "" @@ -1055,6 +1066,20 @@ msgstr "Jeg vil afregistrere token **${ tokenLabel }**" msgid "Unregister token" msgstr "Afregistrer token" +#: src/screens/Web3AuthRecoveryScreen.js:33 +msgid "Set up recovery" +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:35 +msgid "" +"To protect your wallet, you need to set up a recovery method. This ensures " +"you can access your funds even if you lose this device." +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:42 +msgid "Continue" +msgstr "" + #: src/screens/Reown/CreateNanoContractCreateTokenTxScreen.js:25 msgid "Create Nano Contract & Token" msgstr "" @@ -1363,31 +1388,31 @@ msgstr "" msgid "Error while trying to download Nano Contract transactions history." msgstr "" -#: src/sagas/networkSettings.js:86 +#: src/sagas/networkSettings.js:87 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:93 +#: src/sagas/networkSettings.js:94 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:100 +#: src/sagas/networkSettings.js:101 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:107 +#: src/sagas/networkSettings.js:108 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:114 +#: src/sagas/networkSettings.js:115 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:121 +#: src/sagas/networkSettings.js:122 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:128 +#: src/sagas/networkSettings.js:129 msgid "walletServiceWsUrl should be a valid URL." msgstr "" @@ -1399,21 +1424,21 @@ msgstr "Transaktion" msgid "Open" msgstr "Åben" -#: src/sagas/wallet.js:811 +#: src/sagas/wallet.js:881 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:825 +#: src/sagas/wallet.js:895 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:835 +#: src/sagas/wallet.js:905 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:851 +#: src/sagas/wallet.js:921 msgid "There was an error while loading first wallet address. Try again." msgstr "" @@ -1540,16 +1565,16 @@ msgstr "ID" msgid "New Transaction" msgstr "" -#: src/components/ReceiveMyAddress.js:34 +#: src/components/ReceiveMyAddress.js:36 #, javascript-format msgid "Here is my address: ${ lastSharedAddress }" msgstr "Her er min adresse: ${ lastSharedAddress }" -#: src/components/ReceiveMyAddress.js:57 +#: src/components/ReceiveMyAddress.js:60 msgid "New address" msgstr "Ny adresse" -#: src/components/ReceiveMyAddress.js:63 src/components/TokenDetails.js:102 +#: src/components/ReceiveMyAddress.js:67 src/components/TokenDetails.js:102 msgid "Share" msgstr "Del" @@ -1665,6 +1690,51 @@ msgstr "" msgid "Amount" msgstr "Antal" +#: src/components/Web3AuthErrorDialog.js:24 +msgid "Connection issue" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:25 +msgid "" +"We couldn't reach Web3Auth. Check your internet connection and try again." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:32 +#, fuzzy +msgid "Configuration error" +msgstr "Konfigurationsstreng" + +#: src/components/Web3AuthErrorDialog.js:33 +msgid "" +"There was an issue with our authentication setup. Please try again in a few " +"minutes. If the problem persists, contact support." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:40 +msgid "Recovery factor required" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:41 +msgid "" +"To protect your wallet, you must configure at least one recovery factor. " +"Would you like to set it up now?" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:42 +#, fuzzy +msgid "Configure now" +msgstr "Konfigurationsstreng" + +#: src/components/Web3AuthErrorDialog.js:50 +msgid "Something went wrong" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:51 +msgid "" +"We couldn't complete the sign-in. Please try again. If the issue persists, " +"contact support." +msgstr "" + #: src/components/Reown/AdvancedErrorOptions.js:41 msgid "Advanced options" msgstr "" @@ -1711,14 +1781,14 @@ msgid "Create Token Data" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:70 -#: src/components/Reown/CreateTokenRequest.js:201 +#: src/components/Reown/CreateTokenRequest.js:202 #: src/components/Reown/SignMessageRequest.js:81 #: src/components/Reown/SignOracleDataRequest.js:146 msgid "Accept Request" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:71 -#: src/components/Reown/CreateTokenRequest.js:205 +#: src/components/Reown/CreateTokenRequest.js:206 #: src/components/Reown/SignMessageRequest.js:85 #: src/components/Reown/SignOracleDataRequest.js:150 msgid "Decline Request" @@ -1814,32 +1884,32 @@ msgstr "" msgid "Token data" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:129 +#: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 msgid "Network Fee" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:215 +#: src/components/Reown/CreateTokenRequest.js:216 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:515 #: src/components/Reown/SendTransactionRequest.js:487 msgid "Sending transaction" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:216 +#: src/components/Reown/CreateTokenRequest.js:217 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:516 #: src/components/Reown/SendTransactionRequest.js:488 msgid "Please wait." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:235 +#: src/components/Reown/CreateTokenRequest.js:236 msgid "Create Token Transaction successfully sent." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:237 +#: src/components/Reown/CreateTokenRequest.js:238 msgid "Ok, close" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:244 +#: src/components/Reown/CreateTokenRequest.js:245 msgid "Error while sending create token transaction." msgstr "" diff --git a/locale/pt-br/texts.po b/locale/pt-br/texts.po index 160cff0ff..c2aa9653b 100644 --- a/locale/pt-br/texts.po +++ b/locale/pt-br/texts.po @@ -135,11 +135,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:69 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:72 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 " @@ -151,8 +151,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/|." @@ -210,32 +210,32 @@ msgstr "Novo PIN salvo" msgid "CHANGE PIN" msgstr "MUDAR PIN" -#: src/screens/ChoosePinScreen.js:140 +#: src/screens/ChoosePinScreen.js:167 msgid "Enter your new PIN code" msgstr "Digite seu novo PIN" -#: src/screens/ChoosePinScreen.js:152 +#: src/screens/ChoosePinScreen.js:179 msgid "Enter your new PIN code again" msgstr "Digite seu novo PIN novamente" -#: src/screens/ChoosePinScreen.js:74 +#: src/screens/ChoosePinScreen.js:80 msgid "Create a new PIN code," msgstr "Criar um novo PIN," -#: src/screens/ChoosePinScreen.js:77 +#: src/screens/ChoosePinScreen.js:83 msgid "To confirm the PIN," msgstr "Para confirmar o PIN," -#: src/screens/ChoosePinScreen.js:166 +#: src/screens/ChoosePinScreen.js:193 msgid "PIN codes don't match. Try again." msgstr "Os PINs estão diferentes. Tente novamente." -#: src/screens/ChoosePinScreen.js:189 +#: src/screens/ChoosePinScreen.js:216 msgid "Start the Wallet" msgstr "Iniciar a Wallet" #: src/screens/CreateTokenAmount.js:96 src/screens/CreateTokenAmount.js:108 -#: src/screens/SendAmountInput.js:138 +#: src/screens/SendAmountInput.js:142 msgid "Invalid amount" msgstr "Quantidade inválida" @@ -262,9 +262,9 @@ 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/SendAmountInput.js:235 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:317 +#: src/screens/InitWallet.js:470 src/screens/SendAddressInput.js:66 +#: src/screens/SendAmountInput.js:239 msgid "Next" msgstr "Próximo" @@ -296,7 +296,7 @@ msgstr "Digite seu PIN de 6 dígitos para criar o seu token" msgid "Authorize token creation" msgstr "Autorizar criação de token" -#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:101 +#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:105 #: src/screens/TokenSwapReview.js:163 msgid "Building transaction" msgstr "Criando transação" @@ -319,7 +319,7 @@ msgid "Token symbol" msgstr "Símbolo do token" #: src/components/Reown/CreateTokenRequest.js:87 -#: src/components/Reown/CreateTokenRequest.js:128 +#: src/components/Reown/CreateTokenRequest.js:129 #: src/screens/CreateTokenConfirm.js:251 msgid "Deposit" msgstr "Depósito" @@ -445,8 +445,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" @@ -467,18 +467,18 @@ msgstr "Tokens" msgid "Nano Contracts" msgstr "Nano Contracts" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:66 msgid "Welcome to Hathor Wallet!" msgstr "Bem vindo à Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:77 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:90 msgid "" "I agree with the |link1:Terms of Service| and |link2:Privacy Policy| and " "understand the risks of using a mobile wallet" @@ -486,19 +486,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:102 msgid "Start" msgstr "Iniciar" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:168 msgid "To start," msgstr "Para começar," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:170 msgid "You need to **initialize your wallet**." msgstr "Você precisa **inicializar sua wallet**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:173 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -506,23 +506,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:176 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 +#: src/screens/InitWallet.js:204 msgid "Import Wallet" msgstr "Importar Wallet" -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:210 msgid "New Wallet" msgstr "Nova Wallet" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:308 msgid "Your wallet has been created!" msgstr "Sua wallet foi criada!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:310 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -530,11 +530,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:431 msgid "To import a wallet," msgstr "Para importar sua wallet," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:433 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -543,21 +543,24 @@ msgstr "" "Você precisa **digitar as ${ this.numberOfWords } palavras** da sua wallet, " "separadas por espaço." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:436 msgid "Words" msgstr "Palavras" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:441 msgid "Enter your seed words separated by space" msgstr "Digite suas palavras separadas por espaços" #: src/components/NanoContract/NanoContractDetails.js:252 -#: src/components/Reown/CreateTokenRequest.js:248 +#: src/components/Reown/CreateTokenRequest.js:249 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:626 #: src/components/Reown/SendTransactionRequest.js:550 -#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:27 +#: src/components/Web3AuthErrorDialog.js:26 +#: src/components/Web3AuthErrorDialog.js:34 +#: src/components/Web3AuthErrorDialog.js:52 src/screens/LoadHistoryScreen.js:51 +#: src/screens/LoadWalletErrorScreen.js:27 #: src/screens/NanoContract/NanoContractRegisterScreen.js:174 -#: src/screens/PinScreen.js:283 src/screens/TokenSwapLoadingScreen.js:90 +#: src/screens/PinScreen.js:288 src/screens/TokenSwapLoadingScreen.js:90 msgid "Try again" msgstr "Tente novamente" @@ -579,8 +582,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:162 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:323 +#: src/screens/Settings.js:170 msgid "Reset wallet" msgstr "Resetar Wallet" @@ -636,28 +639,32 @@ msgstr "Registrar Nano Contract" msgid "Scan the nano contract ID QR code" msgstr "Escaneie o QR code do ID do Nano Contract" -#: src/screens/PinScreen.js:272 +#: src/screens/PinScreen.js:277 msgid "Incorrect PIN Code. Try again." msgstr "PIN incorreto. Tente novamente." -#: src/screens/PinScreen.js:76 +#: src/screens/PinScreen.js:77 msgid "Enter your PIN Code " msgstr "Digite seu PIN " -#: src/screens/PinScreen.js:77 +#: src/screens/PinScreen.js:78 msgid "Unlock Hathor Wallet" msgstr "Desbloqueie sua Hathor Wallet" #: src/components/Reown/RequestConfirmationModal.js:110 -#: src/screens/PinScreen.js:309 src/screens/Reown/ReownList.js:127 +#: src/components/Web3AuthErrorDialog.js:27 +#: src/components/Web3AuthErrorDialog.js:35 +#: src/components/Web3AuthErrorDialog.js:43 +#: src/components/Web3AuthErrorDialog.js:53 src/screens/PinScreen.js:314 +#: src/screens/Reown/ReownList.js:127 msgid "Cancel" msgstr "Cancelar" -#: src/screens/PinScreen.js:349 src/screens/PinScreen.js:353 +#: src/screens/PinScreen.js:354 src/screens/PinScreen.js:358 msgid "Biometry failed or canceled." msgstr "Biometria falhou ou foi cancelada." -#: src/screens/PushNotification.js:58 src/screens/Settings.js:136 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:138 msgid "Push Notification" msgstr "Notificação" @@ -806,36 +813,36 @@ msgstr "" msgid "Reset Wallet" msgstr "Resetar Wallet" -#: src/screens/Security.js:145 +#: src/screens/Security.js:147 msgid "Disable biometry" msgstr "Desativar biometria" -#: src/screens/Security.js:146 +#: src/screens/Security.js:148 msgid "Disabling biometry" msgstr "Desativando biometria" -#: src/screens/Security.js:158 src/screens/Security.js:170 +#: src/screens/Security.js:160 src/screens/Security.js:172 msgid "Enter your 6-digit pin to enable biometry" msgstr "Digite seu PIN de 6 dígitos para habilitar biometria" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 msgid "No biometry supported" msgstr "Nenhuma biometria suportada" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 #, javascript-format msgid "Use ${ this.supportedBiometry }" msgstr "Usar ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:193 msgid "SECURITY" msgstr "SEGURANÇA" -#: src/screens/Security.js:213 +#: src/screens/Security.js:215 msgid "Change PIN" msgstr "Mudar PIN" -#: src/screens/Security.js:218 +#: src/screens/Security.js:220 msgid "Lock wallet" msgstr "Bloquear a wallet" @@ -847,26 +854,26 @@ msgstr "ENVIAR" msgid "Address to send" msgstr "Endereço para enviar" -#: src/screens/SendAmountInput.js:143 +#: src/screens/SendAmountInput.js:147 msgid "Insufficient funds" msgstr "Saldo insuficiente" -#: src/screens/SendAmountInput.js:150 +#: src/screens/SendAmountInput.js:154 msgid "Calculating network fee..." msgstr "Calculando taxa da rede..." -#: src/screens/SendAmountInput.js:157 src/screens/SendAmountInput.js:162 +#: src/screens/SendAmountInput.js:161 src/screens/SendAmountInput.js:166 msgid "Insufficient balance of HTR to cover the network fee." msgstr "Saldo insuficiente para cobrir a taxa da rede." -#: src/screens/SendAmountInput.js:189 +#: src/screens/SendAmountInput.js:193 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" msgstr[0] "${ amountAndToken } disponível" msgstr[1] "${ amountAndToken } disponíveis" -#: src/screens/SendAmountInput.js:203 src/screens/SendConfirmScreen.js:188 +#: src/screens/SendAmountInput.js:207 src/screens/SendConfirmScreen.js:193 msgid "SEND ${ tokenNameUpperCase }" msgstr "ENVIAR ${ tokenNameUpperCase }" @@ -875,75 +882,75 @@ msgid "No fee" msgstr "Sem taxa" #. show loading modal -#: src/screens/SendConfirmScreen.js:86 src/screens/TokenSwapReview.js:145 +#: src/screens/SendConfirmScreen.js:90 src/screens/TokenSwapReview.js:145 msgid "Your transfer is being processed" msgstr "Sua transferência está sendo processada" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:99 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:103 #: src/screens/TokenSwapReview.js:161 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:100 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:104 #: src/screens/TokenSwapReview.js:162 msgid "Authorize operation" msgstr "Autorizar operação" -#: src/screens/SendConfirmScreen.js:136 +#: src/screens/SendConfirmScreen.js:140 #, 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:159 +#: src/screens/SendConfirmScreen.js:164 msgid "Loading fee information..." msgstr "Carregando informações da taxa..." -#: src/screens/SendConfirmScreen.js:162 +#: src/screens/SendConfirmScreen.js:167 msgid "This is the native token, no network fees are charged." msgstr "Token nativo, nenhuma taxa aplicada." -#: src/screens/SendConfirmScreen.js:165 +#: src/screens/SendConfirmScreen.js:170 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:167 +#: src/screens/SendConfirmScreen.js:172 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:106 -#: src/screens/SendConfirmScreen.js:172 +#: src/screens/SendConfirmScreen.js:177 msgid "Loading..." msgstr "Carregando..." -#: src/screens/SendConfirmScreen.js:197 +#: src/screens/SendConfirmScreen.js:202 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Sua transferência de **${ amountAndToken }** foi confirmada" -#: src/screens/SendConfirmScreen.js:208 +#: src/screens/SendConfirmScreen.js:213 msgid "Read more." msgstr "Ler mais." -#: src/screens/SendConfirmScreen.js:224 +#: src/screens/SendConfirmScreen.js:229 msgid "**Transaction summary**" msgstr "**Resumo da transação**" -#: src/screens/SendConfirmScreen.js:227 +#: src/screens/SendConfirmScreen.js:232 msgid "**To**" msgstr "**Para**" -#: src/screens/SendConfirmScreen.js:232 +#: src/screens/SendConfirmScreen.js:237 msgid "**Network Fee**" msgstr "**Taxa de Rede**" -#: src/screens/SendConfirmScreen.js:240 +#: src/screens/SendConfirmScreen.js:245 msgid "**Total**" msgstr "**Total**" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:247 +#: src/screens/SendConfirmScreen.js:252 msgid "Send" msgstr "Enviar" @@ -956,43 +963,47 @@ msgstr "QR code inválido" msgid "Scan the QR code" msgstr "Leia o QR code" -#: src/screens/Settings.js:105 +#: src/screens/Settings.js:107 msgid "You are connected to" msgstr "Você está conectado à" -#: src/screens/Settings.js:113 +#: src/screens/Settings.js:115 msgid "General Settings" msgstr "Configurações Gerais" -#: src/screens/Settings.js:117 +#: src/screens/Settings.js:119 msgid "Connected to" msgstr "Conectado ao servidor" -#: src/screens/Settings.js:130 +#: src/screens/Settings.js:132 msgid "Security" msgstr "Segurança" -#: src/screens/Settings.js:143 +#: src/screens/Settings.js:145 msgid "Create a new token" msgstr "Criar um novo token" -#: src/screens/Settings.js:150 +#: src/screens/Settings.js:152 msgid "Register a token" msgstr "Registrar um token" -#: src/screens/Settings.js:166 +#: src/screens/Settings.js:165 +msgid "Sign out" +msgstr "" + +#: src/screens/Settings.js:174 msgid "About" msgstr "Sobre" -#: src/screens/Settings.js:173 +#: src/screens/Settings.js:181 msgid "Unique app identifier" msgstr "Identificador único do aplicativo" -#: src/screens/Settings.js:187 +#: src/screens/Settings.js:195 msgid "Developer Settings" msgstr "Configurações do Desenvolvedor" -#: src/screens/Settings.js:189 +#: src/screens/Settings.js:197 msgid "Network Settings" msgstr "Configurações de Rede" @@ -1079,6 +1090,20 @@ msgstr "Eu quero desregistrar o token **${ tokenLabel }**" msgid "Unregister token" msgstr "Desregistrar token" +#: src/screens/Web3AuthRecoveryScreen.js:33 +msgid "Set up recovery" +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:35 +msgid "" +"To protect your wallet, you need to set up a recovery method. This ensures " +"you can access your funds even if you lose this device." +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:42 +msgid "Continue" +msgstr "" + #: src/screens/Reown/CreateNanoContractCreateTokenTxScreen.js:25 msgid "Create Nano Contract & Token" msgstr "Criar Nano Contract e Token" @@ -1399,31 +1424,31 @@ msgstr "Nano Contract não registrado." msgid "Error while trying to download Nano Contract transactions history." msgstr "Error ao fazer download do histórico de transações do Nano Contract." -#: src/sagas/networkSettings.js:86 +#: src/sagas/networkSettings.js:87 msgid "Custom Network Settings cannot be empty." msgstr "As Configurações de Rede não podem estar vazias." -#: src/sagas/networkSettings.js:93 +#: src/sagas/networkSettings.js:94 msgid "explorerUrl should be a valid URL." msgstr "explorerUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:100 +#: src/sagas/networkSettings.js:101 msgid "explorerServiceUrl should be a valid URL." msgstr "explorerServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:107 +#: src/sagas/networkSettings.js:108 msgid "txMiningServiceUrl should be a valid URL." msgstr "txMiningServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:114 +#: src/sagas/networkSettings.js:115 msgid "nodeUrl should be a valid URL." msgstr "nodeUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:121 +#: src/sagas/networkSettings.js:122 msgid "walletServiceUrl should be a valid URL." msgstr "walletServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:128 +#: src/sagas/networkSettings.js:129 msgid "walletServiceWsUrl should be a valid URL." msgstr "walletServiceWsUrl deve ser uma URL válida." @@ -1435,21 +1460,21 @@ msgstr "Transação" msgid "Open" msgstr "Abrir" -#: src/sagas/wallet.js:811 +#: src/sagas/wallet.js:881 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:825 +#: src/sagas/wallet.js:895 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:835 +#: src/sagas/wallet.js:905 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:851 +#: src/sagas/wallet.js:921 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." @@ -1580,16 +1605,16 @@ msgstr "ID" msgid "New Transaction" msgstr "Transação" -#: src/components/ReceiveMyAddress.js:34 +#: src/components/ReceiveMyAddress.js:36 #, javascript-format msgid "Here is my address: ${ lastSharedAddress }" msgstr "Esse é o meu endereço: ${ lastSharedAddress }" -#: src/components/ReceiveMyAddress.js:57 +#: src/components/ReceiveMyAddress.js:60 msgid "New address" msgstr "Novo endereço" -#: src/components/ReceiveMyAddress.js:63 src/components/TokenDetails.js:102 +#: src/components/ReceiveMyAddress.js:67 src/components/TokenDetails.js:102 msgid "Share" msgstr "Compartilhar" @@ -1705,6 +1730,52 @@ msgstr "Nano Contract" msgid "Amount" msgstr "Quantidade" +#: src/components/Web3AuthErrorDialog.js:24 +#, fuzzy +msgid "Connection issue" +msgstr "Conectar à mainnet" + +#: src/components/Web3AuthErrorDialog.js:25 +msgid "" +"We couldn't reach Web3Auth. Check your internet connection and try again." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:32 +#, fuzzy +msgid "Configuration error" +msgstr "Configuração" + +#: src/components/Web3AuthErrorDialog.js:33 +msgid "" +"There was an issue with our authentication setup. Please try again in a few " +"minutes. If the problem persists, contact support." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:40 +msgid "Recovery factor required" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:41 +msgid "" +"To protect your wallet, you must configure at least one recovery factor. " +"Would you like to set it up now?" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:42 +#, fuzzy +msgid "Configure now" +msgstr "Configuração" + +#: src/components/Web3AuthErrorDialog.js:50 +msgid "Something went wrong" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:51 +msgid "" +"We couldn't complete the sign-in. Please try again. If the issue persists, " +"contact support." +msgstr "" + #: src/components/Reown/AdvancedErrorOptions.js:41 msgid "Advanced options" msgstr "Opções avançadas" @@ -1758,14 +1829,14 @@ msgid "Create Token Data" msgstr "Dados da criaçao de token" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:70 -#: src/components/Reown/CreateTokenRequest.js:201 +#: src/components/Reown/CreateTokenRequest.js:202 #: src/components/Reown/SignMessageRequest.js:81 #: src/components/Reown/SignOracleDataRequest.js:146 msgid "Accept Request" msgstr "Aceitar Solicitação" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:71 -#: src/components/Reown/CreateTokenRequest.js:205 +#: src/components/Reown/CreateTokenRequest.js:206 #: src/components/Reown/SignMessageRequest.js:85 #: src/components/Reown/SignOracleDataRequest.js:150 msgid "Decline Request" @@ -1863,32 +1934,32 @@ msgstr "Contrato paga taxas da rede?" msgid "Token data" msgstr "Dados do Token" -#: src/components/Reown/CreateTokenRequest.js:129 +#: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 msgid "Network Fee" msgstr "Taxa de Rede" -#: src/components/Reown/CreateTokenRequest.js:215 +#: src/components/Reown/CreateTokenRequest.js:216 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:515 #: src/components/Reown/SendTransactionRequest.js:487 msgid "Sending transaction" msgstr "Enviando transação" -#: src/components/Reown/CreateTokenRequest.js:216 +#: src/components/Reown/CreateTokenRequest.js:217 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:516 #: src/components/Reown/SendTransactionRequest.js:488 msgid "Please wait." msgstr "Por favor, espere." -#: src/components/Reown/CreateTokenRequest.js:235 +#: src/components/Reown/CreateTokenRequest.js:236 msgid "Create Token Transaction successfully sent." msgstr "Transação de Criação de Token enviada com sucesso." -#: src/components/Reown/CreateTokenRequest.js:237 +#: src/components/Reown/CreateTokenRequest.js:238 msgid "Ok, close" msgstr "Ok, fechar" -#: src/components/Reown/CreateTokenRequest.js:244 +#: src/components/Reown/CreateTokenRequest.js:245 msgid "Error while sending create token transaction." msgstr "Erro ao enviar transação de criação de token." diff --git a/locale/ru-ru/texts.po b/locale/ru-ru/texts.po index 805b479ad..a49df9ac4 100644 --- a/locale/ru-ru/texts.po +++ b/locale/ru-ru/texts.po @@ -131,11 +131,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:69 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:72 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 " @@ -146,8 +146,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/BackupWords.js:184 @@ -203,32 +203,32 @@ msgstr "Новый PIN-код сохранен" msgid "CHANGE PIN" msgstr "ИЗМЕНИТЬ PIN-код" -#: src/screens/ChoosePinScreen.js:140 +#: src/screens/ChoosePinScreen.js:167 msgid "Enter your new PIN code" msgstr "Введите новый PIN-код" -#: src/screens/ChoosePinScreen.js:152 +#: src/screens/ChoosePinScreen.js:179 msgid "Enter your new PIN code again" msgstr "Введите новый PIN-код еще раз" -#: src/screens/ChoosePinScreen.js:74 +#: src/screens/ChoosePinScreen.js:80 msgid "Create a new PIN code," msgstr "Создать новый PIN-код," -#: src/screens/ChoosePinScreen.js:77 +#: src/screens/ChoosePinScreen.js:83 msgid "To confirm the PIN," msgstr "Для подтверждения PIN-кода," -#: src/screens/ChoosePinScreen.js:166 +#: src/screens/ChoosePinScreen.js:193 msgid "PIN codes don't match. Try again." msgstr "PIN-коды не совпадают. Попробуйте еще раз." -#: src/screens/ChoosePinScreen.js:189 +#: src/screens/ChoosePinScreen.js:216 msgid "Start the Wallet" msgstr "Запустить кошелек" #: src/screens/CreateTokenAmount.js:96 src/screens/CreateTokenAmount.js:108 -#: src/screens/SendAmountInput.js:138 +#: src/screens/SendAmountInput.js:142 msgid "Invalid amount" msgstr "" @@ -255,9 +255,9 @@ 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/SendAmountInput.js:235 +#: src/screens/CreateTokenSymbol.js:86 src/screens/InitWallet.js:317 +#: src/screens/InitWallet.js:470 src/screens/SendAddressInput.js:66 +#: src/screens/SendAmountInput.js:239 msgid "Next" msgstr "Далее" @@ -285,7 +285,7 @@ msgstr "Введите 6-значный PIN-код для создания то msgid "Authorize token creation" msgstr "Авторизовать создание токена" -#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:101 +#: src/screens/CreateTokenConfirm.js:158 src/screens/SendConfirmScreen.js:105 #: src/screens/TokenSwapReview.js:163 msgid "Building transaction" msgstr "" @@ -308,7 +308,7 @@ msgid "Token symbol" msgstr "Символ токена" #: src/components/Reown/CreateTokenRequest.js:87 -#: src/components/Reown/CreateTokenRequest.js:128 +#: src/components/Reown/CreateTokenRequest.js:129 #: src/screens/CreateTokenConfirm.js:251 msgid "Deposit" msgstr "Депозит" @@ -453,37 +453,37 @@ msgstr "ТокенЫ" msgid "Nano Contracts" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:66 msgid "Welcome to Hathor Wallet!" msgstr "Добро пожаловать в Hathor Wallet!" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:77 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:90 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:102 msgid "Start" msgstr "Начать" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:168 msgid "To start," msgstr "Начать," -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:170 msgid "You need to **initialize your wallet**." msgstr "Вам нужно **инициализировать свой кошелек**." -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:173 msgid "" "You can either **start a new wallet** or **import a wallet** that already " "exists." @@ -491,23 +491,23 @@ msgstr "" "Вы можете **создать новый кошелек**, либо **импортировать кошелек**, который " "уже существует." -#: src/screens/InitWallet.js:123 +#: src/screens/InitWallet.js:176 msgid "To import a wallet, you will need to provide your seed words." msgstr "Чтобы импортировать кошелек, необходимо ввести seed-фразу." -#: src/screens/InitWallet.js:128 +#: src/screens/InitWallet.js:204 msgid "Import Wallet" msgstr "Импортировать Кошелек" -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:210 msgid "New Wallet" msgstr "Новый Кошелек" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:308 msgid "Your wallet has been created!" msgstr "Ваш кошелек создан!" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:310 msgid "" "You must **do a backup** and save the words below **in the same order they " "appear**." @@ -515,11 +515,11 @@ msgstr "" "Вы должны **сделать резервную копию** и сохранить слова ниже **в том же " "порядке, в котором они появились**." -#: src/screens/InitWallet.js:343 +#: src/screens/InitWallet.js:431 msgid "To import a wallet," msgstr "Чтобы импортировать кошелек," -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:433 #, javascript-format msgid "" "You need to **write down the ${ this.numberOfWords } seed words** of your " @@ -527,21 +527,24 @@ msgid "" msgstr "" "Вам нужно **записать ${ this.numberOfWords } seed-фразу** вашего кошелька." -#: src/screens/InitWallet.js:348 +#: src/screens/InitWallet.js:436 msgid "Words" msgstr "Слова" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:441 msgid "Enter your seed words separated by space" msgstr "Введите seed-фразу" #: src/components/NanoContract/NanoContractDetails.js:252 -#: src/components/Reown/CreateTokenRequest.js:248 +#: src/components/Reown/CreateTokenRequest.js:249 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:626 #: src/components/Reown/SendTransactionRequest.js:550 -#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:27 +#: src/components/Web3AuthErrorDialog.js:26 +#: src/components/Web3AuthErrorDialog.js:34 +#: src/components/Web3AuthErrorDialog.js:52 src/screens/LoadHistoryScreen.js:51 +#: src/screens/LoadWalletErrorScreen.js:27 #: src/screens/NanoContract/NanoContractRegisterScreen.js:174 -#: src/screens/PinScreen.js:283 src/screens/TokenSwapLoadingScreen.js:90 +#: src/screens/PinScreen.js:288 src/screens/TokenSwapLoadingScreen.js:90 msgid "Try again" msgstr "" @@ -563,8 +566,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:162 +#: src/screens/LoadWalletErrorScreen.js:28 src/screens/PinScreen.js:323 +#: src/screens/Settings.js:170 msgid "Reset wallet" msgstr "Сбросить кошелек" @@ -619,28 +622,32 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PinScreen.js:272 +#: src/screens/PinScreen.js:277 msgid "Incorrect PIN Code. Try again." msgstr "Неверный PIN-код. Попробуйте еще раз." -#: src/screens/PinScreen.js:76 +#: src/screens/PinScreen.js:77 msgid "Enter your PIN Code " msgstr "Введите свой PIN-код " -#: src/screens/PinScreen.js:77 +#: src/screens/PinScreen.js:78 msgid "Unlock Hathor Wallet" msgstr "Разблокировать Hathor Wallet" #: src/components/Reown/RequestConfirmationModal.js:110 -#: src/screens/PinScreen.js:309 src/screens/Reown/ReownList.js:127 +#: src/components/Web3AuthErrorDialog.js:27 +#: src/components/Web3AuthErrorDialog.js:35 +#: src/components/Web3AuthErrorDialog.js:43 +#: src/components/Web3AuthErrorDialog.js:53 src/screens/PinScreen.js:314 +#: src/screens/Reown/ReownList.js:127 msgid "Cancel" msgstr "Отмена" -#: src/screens/PinScreen.js:349 src/screens/PinScreen.js:353 +#: src/screens/PinScreen.js:354 src/screens/PinScreen.js:358 msgid "Biometry failed or canceled." msgstr "Биометрия не удалась или была отменена." -#: src/screens/PushNotification.js:58 src/screens/Settings.js:136 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:138 msgid "Push Notification" msgstr "" @@ -785,36 +792,36 @@ msgstr "" msgid "Reset Wallet" msgstr "Сбросить Кошелек" -#: src/screens/Security.js:145 +#: src/screens/Security.js:147 msgid "Disable biometry" msgstr "Отключить биометрию" -#: src/screens/Security.js:146 +#: src/screens/Security.js:148 msgid "Disabling biometry" msgstr "Отключение биометрии" -#: src/screens/Security.js:158 src/screens/Security.js:170 +#: src/screens/Security.js:160 src/screens/Security.js:172 msgid "Enter your 6-digit pin to enable biometry" msgstr "Введите 6-значный PIN-код, чтобы включить биометрию" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 msgid "No biometry supported" msgstr "Биометрия не поддерживается" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 #, javascript-format msgid "Use ${ this.supportedBiometry }" msgstr "Использовать ${ this.supportedBiometry }" -#: src/screens/Security.js:191 +#: src/screens/Security.js:193 msgid "SECURITY" msgstr "БЕЗОПАСНОСТЬ" -#: src/screens/Security.js:213 +#: src/screens/Security.js:215 msgid "Change PIN" msgstr "Изменить PIN-код" -#: src/screens/Security.js:218 +#: src/screens/Security.js:220 msgid "Lock wallet" msgstr "Заблокировать кошелек" @@ -826,19 +833,19 @@ msgstr "ОТПРАВИТЬ" msgid "Address to send" msgstr "Адрес отправки" -#: src/screens/SendAmountInput.js:143 +#: src/screens/SendAmountInput.js:147 msgid "Insufficient funds" msgstr "Недостаточно средств" -#: src/screens/SendAmountInput.js:150 +#: src/screens/SendAmountInput.js:154 msgid "Calculating network fee..." msgstr "" -#: src/screens/SendAmountInput.js:157 src/screens/SendAmountInput.js:162 +#: src/screens/SendAmountInput.js:161 src/screens/SendAmountInput.js:166 msgid "Insufficient balance of HTR to cover the network fee." msgstr "" -#: src/screens/SendAmountInput.js:189 +#: src/screens/SendAmountInput.js:193 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" @@ -846,7 +853,7 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendAmountInput.js:203 src/screens/SendConfirmScreen.js:188 +#: src/screens/SendAmountInput.js:207 src/screens/SendConfirmScreen.js:193 msgid "SEND ${ tokenNameUpperCase }" msgstr "ОТПРАВИТЬ ${ tokenNameUpperCase }" @@ -855,21 +862,21 @@ msgid "No fee" msgstr "" #. show loading modal -#: src/screens/SendConfirmScreen.js:86 src/screens/TokenSwapReview.js:145 +#: src/screens/SendConfirmScreen.js:90 src/screens/TokenSwapReview.js:145 msgid "Your transfer is being processed" msgstr "Ваш перевод обрабатывается" -#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:99 +#: src/sagas/helpers.js:147 src/screens/SendConfirmScreen.js:103 #: src/screens/TokenSwapReview.js:161 msgid "Enter your 6-digit pin to authorize operation" msgstr "Введите 6-значный PIN-код для авторизации операции" -#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:100 +#: src/sagas/helpers.js:148 src/screens/SendConfirmScreen.js:104 #: src/screens/TokenSwapReview.js:162 msgid "Authorize operation" msgstr "Авторизовать операцию" -#: src/screens/SendConfirmScreen.js:136 +#: src/screens/SendConfirmScreen.js:140 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" @@ -877,54 +884,54 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: src/screens/SendConfirmScreen.js:159 +#: src/screens/SendConfirmScreen.js:164 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:162 +#: src/screens/SendConfirmScreen.js:167 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:165 +#: src/screens/SendConfirmScreen.js:170 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:167 +#: src/screens/SendConfirmScreen.js:172 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:106 -#: src/screens/SendConfirmScreen.js:172 +#: src/screens/SendConfirmScreen.js:177 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:197 +#: src/screens/SendConfirmScreen.js:202 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "Ваш перевод **${ amountAndToken }** был подтвержден" -#: src/screens/SendConfirmScreen.js:208 +#: src/screens/SendConfirmScreen.js:213 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:224 +#: src/screens/SendConfirmScreen.js:229 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:227 +#: src/screens/SendConfirmScreen.js:232 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:232 +#: src/screens/SendConfirmScreen.js:237 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:240 +#: src/screens/SendConfirmScreen.js:245 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:247 +#: src/screens/SendConfirmScreen.js:252 msgid "Send" msgstr "Отправить" @@ -937,43 +944,47 @@ msgstr "Неверный QR-код" msgid "Scan the QR code" msgstr "Сканировать QR-код" -#: src/screens/Settings.js:105 +#: src/screens/Settings.js:107 msgid "You are connected to" msgstr "Вы подключены к" -#: src/screens/Settings.js:113 +#: src/screens/Settings.js:115 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:117 +#: src/screens/Settings.js:119 msgid "Connected to" msgstr "Подключены к" -#: src/screens/Settings.js:130 +#: src/screens/Settings.js:132 msgid "Security" msgstr "Безопасность" -#: src/screens/Settings.js:143 +#: src/screens/Settings.js:145 msgid "Create a new token" msgstr "Создать новый токен" -#: src/screens/Settings.js:150 +#: src/screens/Settings.js:152 msgid "Register a token" msgstr "Зарегистрировать токен" -#: src/screens/Settings.js:166 +#: src/screens/Settings.js:165 +msgid "Sign out" +msgstr "" + +#: src/screens/Settings.js:174 msgid "About" msgstr "О нас" -#: src/screens/Settings.js:173 +#: src/screens/Settings.js:181 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:187 +#: src/screens/Settings.js:195 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:189 +#: src/screens/Settings.js:197 msgid "Network Settings" msgstr "" @@ -1060,6 +1071,20 @@ msgstr "Я хочу отменить регистрацию токена **${ to msgid "Unregister token" msgstr "Отменить регистрацию токена" +#: src/screens/Web3AuthRecoveryScreen.js:33 +msgid "Set up recovery" +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:35 +msgid "" +"To protect your wallet, you need to set up a recovery method. This ensures " +"you can access your funds even if you lose this device." +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:42 +msgid "Continue" +msgstr "" + #: src/screens/Reown/CreateNanoContractCreateTokenTxScreen.js:25 msgid "Create Nano Contract & Token" msgstr "" @@ -1368,31 +1393,31 @@ msgstr "" msgid "Error while trying to download Nano Contract transactions history." msgstr "" -#: src/sagas/networkSettings.js:86 +#: src/sagas/networkSettings.js:87 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:93 +#: src/sagas/networkSettings.js:94 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:100 +#: src/sagas/networkSettings.js:101 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:107 +#: src/sagas/networkSettings.js:108 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:114 +#: src/sagas/networkSettings.js:115 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:121 +#: src/sagas/networkSettings.js:122 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:128 +#: src/sagas/networkSettings.js:129 msgid "walletServiceWsUrl should be a valid URL." msgstr "" @@ -1404,21 +1429,21 @@ msgstr "" msgid "Open" msgstr "Открыть" -#: src/sagas/wallet.js:811 +#: src/sagas/wallet.js:881 msgid "Wallet is not ready to load addresses." msgstr "" #. This will show the message in the feedback content at SelectAddressModal -#: src/sagas/wallet.js:825 +#: src/sagas/wallet.js:895 msgid "There was an error while loading wallet addresses. Try again." msgstr "" -#: src/sagas/wallet.js:835 +#: src/sagas/wallet.js:905 msgid "Wallet is not ready to load the first address." msgstr "" #. This will show the message in the feedback content -#: src/sagas/wallet.js:851 +#: src/sagas/wallet.js:921 msgid "There was an error while loading first wallet address. Try again." msgstr "" @@ -1531,16 +1556,16 @@ msgstr "ID" msgid "New Transaction" msgstr "" -#: src/components/ReceiveMyAddress.js:34 +#: src/components/ReceiveMyAddress.js:36 #, javascript-format msgid "Here is my address: ${ lastSharedAddress }" msgstr "Это мой адрес: ${ lastSharedAddress }" -#: src/components/ReceiveMyAddress.js:57 +#: src/components/ReceiveMyAddress.js:60 msgid "New address" msgstr "Новый адрес" -#: src/components/ReceiveMyAddress.js:63 src/components/TokenDetails.js:102 +#: src/components/ReceiveMyAddress.js:67 src/components/TokenDetails.js:102 msgid "Share" msgstr "Поделиться" @@ -1655,6 +1680,51 @@ msgstr "" msgid "Amount" msgstr "Количество" +#: src/components/Web3AuthErrorDialog.js:24 +msgid "Connection issue" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:25 +msgid "" +"We couldn't reach Web3Auth. Check your internet connection and try again." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:32 +#, fuzzy +msgid "Configuration error" +msgstr "Конфигурация" + +#: src/components/Web3AuthErrorDialog.js:33 +msgid "" +"There was an issue with our authentication setup. Please try again in a few " +"minutes. If the problem persists, contact support." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:40 +msgid "Recovery factor required" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:41 +msgid "" +"To protect your wallet, you must configure at least one recovery factor. " +"Would you like to set it up now?" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:42 +#, fuzzy +msgid "Configure now" +msgstr "Конфигурация" + +#: src/components/Web3AuthErrorDialog.js:50 +msgid "Something went wrong" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:51 +msgid "" +"We couldn't complete the sign-in. Please try again. If the issue persists, " +"contact support." +msgstr "" + #: src/components/Reown/AdvancedErrorOptions.js:41 msgid "Advanced options" msgstr "" @@ -1701,14 +1771,14 @@ msgid "Create Token Data" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:70 -#: src/components/Reown/CreateTokenRequest.js:201 +#: src/components/Reown/CreateTokenRequest.js:202 #: src/components/Reown/SignMessageRequest.js:81 #: src/components/Reown/SignOracleDataRequest.js:146 msgid "Accept Request" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:71 -#: src/components/Reown/CreateTokenRequest.js:205 +#: src/components/Reown/CreateTokenRequest.js:206 #: src/components/Reown/SignMessageRequest.js:85 #: src/components/Reown/SignOracleDataRequest.js:150 msgid "Decline Request" @@ -1804,32 +1874,32 @@ msgstr "" msgid "Token data" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:129 +#: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 msgid "Network Fee" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:215 +#: src/components/Reown/CreateTokenRequest.js:216 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:515 #: src/components/Reown/SendTransactionRequest.js:487 msgid "Sending transaction" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:216 +#: src/components/Reown/CreateTokenRequest.js:217 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:516 #: src/components/Reown/SendTransactionRequest.js:488 msgid "Please wait." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:235 +#: src/components/Reown/CreateTokenRequest.js:236 msgid "Create Token Transaction successfully sent." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:237 +#: src/components/Reown/CreateTokenRequest.js:238 msgid "Ok, close" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:244 +#: src/components/Reown/CreateTokenRequest.js:245 msgid "Error while sending create token transaction." msgstr "" diff --git a/locale/texts.pot b/locale/texts.pot index 865de5848..8558f3694 100644 --- a/locale/texts.pot +++ b/locale/texts.pot @@ -125,12 +125,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:69 msgid "This wallet is connected to the **mainnet**." msgstr "" #: src/screens/About.js:102 -#: src/screens/InitWallet.js:68 +#: src/screens/InitWallet.js:72 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 " @@ -196,33 +196,33 @@ msgstr "" msgid "CHANGE PIN" msgstr "" -#: src/screens/ChoosePinScreen.js:140 +#: src/screens/ChoosePinScreen.js:167 msgid "Enter your new PIN code" msgstr "" -#: src/screens/ChoosePinScreen.js:152 +#: src/screens/ChoosePinScreen.js:179 msgid "Enter your new PIN code again" msgstr "" -#: src/screens/ChoosePinScreen.js:74 +#: src/screens/ChoosePinScreen.js:80 msgid "Create a new PIN code," msgstr "" -#: src/screens/ChoosePinScreen.js:77 +#: src/screens/ChoosePinScreen.js:83 msgid "To confirm the PIN," msgstr "" -#: src/screens/ChoosePinScreen.js:166 +#: src/screens/ChoosePinScreen.js:193 msgid "PIN codes don't match. Try again." msgstr "" -#: src/screens/ChoosePinScreen.js:189 +#: src/screens/ChoosePinScreen.js:216 msgid "Start the Wallet" msgstr "" #: src/screens/CreateTokenAmount.js:96 #: src/screens/CreateTokenAmount.js:108 -#: src/screens/SendAmountInput.js:138 +#: src/screens/SendAmountInput.js:142 msgid "Invalid amount" msgstr "" @@ -251,10 +251,10 @@ 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:317 +#: src/screens/InitWallet.js:470 #: src/screens/SendAddressInput.js:66 -#: src/screens/SendAmountInput.js:235 +#: src/screens/SendAmountInput.js:239 msgid "Next" msgstr "" @@ -283,7 +283,7 @@ msgid "Authorize token creation" msgstr "" #: src/screens/CreateTokenConfirm.js:158 -#: src/screens/SendConfirmScreen.js:101 +#: src/screens/SendConfirmScreen.js:105 #: src/screens/TokenSwapReview.js:163 msgid "Building transaction" msgstr "" @@ -307,7 +307,7 @@ msgid "Token symbol" msgstr "" #: src/components/Reown/CreateTokenRequest.js:87 -#: src/components/Reown/CreateTokenRequest.js:128 +#: src/components/Reown/CreateTokenRequest.js:129 #: src/screens/CreateTokenConfirm.js:251 msgid "Deposit" msgstr "" @@ -450,89 +450,92 @@ msgstr "" msgid "Nano Contracts" msgstr "" -#: src/screens/InitWallet.js:62 +#: src/screens/InitWallet.js:66 msgid "Welcome to Hathor Wallet!" msgstr "" -#: src/screens/InitWallet.js:73 +#: src/screens/InitWallet.js:77 msgid "" "For further information, check out our website " "|link:https://hathor.network/|." msgstr "" -#: src/screens/InitWallet.js:86 +#: src/screens/InitWallet.js:90 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:102 msgid "Start" msgstr "" -#: src/screens/InitWallet.js:115 +#: src/screens/InitWallet.js:168 msgid "To start," msgstr "" -#: src/screens/InitWallet.js:117 +#: src/screens/InitWallet.js:170 msgid "You need to **initialize your wallet**." msgstr "" -#: src/screens/InitWallet.js:120 +#: src/screens/InitWallet.js:173 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:176 msgid "To import a wallet, you will need to provide your seed words." msgstr "" -#: src/screens/InitWallet.js:128 +#: src/screens/InitWallet.js:204 msgid "Import Wallet" msgstr "" -#: src/screens/InitWallet.js:134 +#: src/screens/InitWallet.js:210 msgid "New Wallet" msgstr "" -#: src/screens/InitWallet.js:220 +#: src/screens/InitWallet.js:308 msgid "Your wallet has been created!" msgstr "" -#: src/screens/InitWallet.js:222 +#: src/screens/InitWallet.js:310 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:431 msgid "To import a wallet," msgstr "" -#: src/screens/InitWallet.js:345 +#: src/screens/InitWallet.js:433 #, 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:436 msgid "Words" msgstr "" -#: src/screens/InitWallet.js:353 +#: src/screens/InitWallet.js:441 msgid "Enter your seed words separated by space" msgstr "" #: src/components/NanoContract/NanoContractDetails.js:252 -#: src/components/Reown/CreateTokenRequest.js:248 +#: src/components/Reown/CreateTokenRequest.js:249 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:626 #: src/components/Reown/SendTransactionRequest.js:550 +#: src/components/Web3AuthErrorDialog.js:26 +#: src/components/Web3AuthErrorDialog.js:34 +#: src/components/Web3AuthErrorDialog.js:52 #: src/screens/LoadHistoryScreen.js:51 #: src/screens/LoadWalletErrorScreen.js:27 #: src/screens/NanoContract/NanoContractRegisterScreen.js:174 -#: src/screens/PinScreen.js:283 +#: src/screens/PinScreen.js:288 #: src/screens/TokenSwapLoadingScreen.js:90 msgid "Try again" msgstr "" @@ -556,8 +559,8 @@ 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:162 +#: src/screens/PinScreen.js:323 +#: src/screens/Settings.js:170 msgid "Reset wallet" msgstr "" @@ -613,31 +616,35 @@ msgstr "" msgid "Scan the nano contract ID QR code" msgstr "" -#: src/screens/PinScreen.js:272 +#: src/screens/PinScreen.js:277 msgid "Incorrect PIN Code. Try again." msgstr "" -#: src/screens/PinScreen.js:76 +#: src/screens/PinScreen.js:77 msgid "Enter your PIN Code " msgstr "" -#: src/screens/PinScreen.js:77 +#: src/screens/PinScreen.js:78 msgid "Unlock Hathor Wallet" msgstr "" #: src/components/Reown/RequestConfirmationModal.js:110 -#: src/screens/PinScreen.js:309 +#: src/components/Web3AuthErrorDialog.js:27 +#: src/components/Web3AuthErrorDialog.js:35 +#: src/components/Web3AuthErrorDialog.js:43 +#: src/components/Web3AuthErrorDialog.js:53 +#: src/screens/PinScreen.js:314 #: src/screens/Reown/ReownList.js:127 msgid "Cancel" msgstr "" -#: src/screens/PinScreen.js:349 -#: src/screens/PinScreen.js:353 +#: src/screens/PinScreen.js:354 +#: src/screens/PinScreen.js:358 msgid "Biometry failed or canceled." msgstr "" #: src/screens/PushNotification.js:58 -#: src/screens/Settings.js:136 +#: src/screens/Settings.js:138 msgid "Push Notification" msgstr "" @@ -784,37 +791,37 @@ msgstr "" msgid "Reset Wallet" msgstr "" -#: src/screens/Security.js:145 +#: src/screens/Security.js:147 msgid "Disable biometry" msgstr "" -#: src/screens/Security.js:146 +#: src/screens/Security.js:148 msgid "Disabling biometry" msgstr "" -#: src/screens/Security.js:158 -#: src/screens/Security.js:170 +#: src/screens/Security.js:160 +#: src/screens/Security.js:172 msgid "Enter your 6-digit pin to enable biometry" msgstr "" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 msgid "No biometry supported" msgstr "" -#: src/screens/Security.js:183 +#: src/screens/Security.js:185 #, javascript-format msgid "Use ${ this.supportedBiometry }" msgstr "" -#: src/screens/Security.js:191 +#: src/screens/Security.js:193 msgid "SECURITY" msgstr "" -#: src/screens/Security.js:213 +#: src/screens/Security.js:215 msgid "Change PIN" msgstr "" -#: src/screens/Security.js:218 +#: src/screens/Security.js:220 msgid "Lock wallet" msgstr "" @@ -827,28 +834,28 @@ msgstr "" msgid "Address to send" msgstr "" -#: src/screens/SendAmountInput.js:143 +#: src/screens/SendAmountInput.js:147 msgid "Insufficient funds" msgstr "" -#: src/screens/SendAmountInput.js:150 +#: src/screens/SendAmountInput.js:154 msgid "Calculating network fee..." msgstr "" -#: src/screens/SendAmountInput.js:157 -#: src/screens/SendAmountInput.js:162 +#: src/screens/SendAmountInput.js:161 +#: src/screens/SendAmountInput.js:166 msgid "Insufficient balance of HTR to cover the network fee." msgstr "" -#: src/screens/SendAmountInput.js:189 +#: src/screens/SendAmountInput.js:193 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendAmountInput.js:203 -#: src/screens/SendConfirmScreen.js:188 +#: src/screens/SendAmountInput.js:207 +#: src/screens/SendConfirmScreen.js:193 msgid "SEND ${ tokenNameUpperCase }" msgstr "" @@ -856,79 +863,79 @@ msgstr "" msgid "No fee" msgstr "" -#: src/screens/SendConfirmScreen.js:86 +#: src/screens/SendConfirmScreen.js:90 #: src/screens/TokenSwapReview.js:145 #. show loading modal msgid "Your transfer is being processed" msgstr "" #: src/sagas/helpers.js:147 -#: src/screens/SendConfirmScreen.js:99 +#: src/screens/SendConfirmScreen.js:103 #: src/screens/TokenSwapReview.js:161 msgid "Enter your 6-digit pin to authorize operation" msgstr "" #: src/sagas/helpers.js:148 -#: src/screens/SendConfirmScreen.js:100 +#: src/screens/SendConfirmScreen.js:104 #: src/screens/TokenSwapReview.js:162 msgid "Authorize operation" msgstr "" -#: src/screens/SendConfirmScreen.js:136 +#: src/screens/SendConfirmScreen.js:140 #, javascript-format msgid "${ availablePretty } available" msgid_plural "${ availablePretty } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendConfirmScreen.js:159 +#: src/screens/SendConfirmScreen.js:164 msgid "Loading fee information..." msgstr "" -#: src/screens/SendConfirmScreen.js:162 +#: src/screens/SendConfirmScreen.js:167 msgid "This is the native token, no network fees are charged." msgstr "" -#: src/screens/SendConfirmScreen.js:165 +#: src/screens/SendConfirmScreen.js:170 msgid "This token is Deposit Based, no network fee will be charged." msgstr "" -#: src/screens/SendConfirmScreen.js:167 +#: src/screens/SendConfirmScreen.js:172 msgid "This fee is fixed and required for every transfer of this token." msgstr "" #: src/components/Reown/NanoContract/NanoContractExecInfo.js:106 -#: src/screens/SendConfirmScreen.js:172 +#: src/screens/SendConfirmScreen.js:177 msgid "Loading..." msgstr "" -#: src/screens/SendConfirmScreen.js:197 +#: src/screens/SendConfirmScreen.js:202 #, javascript-format msgid "Your transfer of **${ amountAndToken }** has been confirmed" msgstr "" -#: src/screens/SendConfirmScreen.js:208 +#: src/screens/SendConfirmScreen.js:213 msgid "Read more." msgstr "" -#: src/screens/SendConfirmScreen.js:224 +#: src/screens/SendConfirmScreen.js:229 msgid "**Transaction summary**" msgstr "" -#: src/screens/SendConfirmScreen.js:227 +#: src/screens/SendConfirmScreen.js:232 msgid "**To**" msgstr "" -#: src/screens/SendConfirmScreen.js:232 +#: src/screens/SendConfirmScreen.js:237 msgid "**Network Fee**" msgstr "" -#: src/screens/SendConfirmScreen.js:240 +#: src/screens/SendConfirmScreen.js:245 msgid "**Total**" msgstr "" #: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:295 -#: src/screens/SendConfirmScreen.js:247 +#: src/screens/SendConfirmScreen.js:252 msgid "Send" msgstr "" @@ -942,43 +949,47 @@ msgstr "" msgid "Scan the QR code" msgstr "" -#: src/screens/Settings.js:105 +#: src/screens/Settings.js:107 msgid "You are connected to" msgstr "" -#: src/screens/Settings.js:113 +#: src/screens/Settings.js:115 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:117 +#: src/screens/Settings.js:119 msgid "Connected to" msgstr "" -#: src/screens/Settings.js:130 +#: src/screens/Settings.js:132 msgid "Security" msgstr "" -#: src/screens/Settings.js:143 +#: src/screens/Settings.js:145 msgid "Create a new token" msgstr "" -#: src/screens/Settings.js:150 +#: src/screens/Settings.js:152 msgid "Register a token" msgstr "" -#: src/screens/Settings.js:166 +#: src/screens/Settings.js:165 +msgid "Sign out" +msgstr "" + +#: src/screens/Settings.js:174 msgid "About" msgstr "" -#: src/screens/Settings.js:173 +#: src/screens/Settings.js:181 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:187 +#: src/screens/Settings.js:195 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:189 +#: src/screens/Settings.js:197 msgid "Network Settings" msgstr "" @@ -1062,6 +1073,20 @@ msgstr "" msgid "Unregister token" msgstr "" +#: src/screens/Web3AuthRecoveryScreen.js:33 +msgid "Set up recovery" +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:35 +msgid "" +"To protect your wallet, you need to set up a recovery method. This ensures " +"you can access your funds even if you lose this device." +msgstr "" + +#: src/screens/Web3AuthRecoveryScreen.js:42 +msgid "Continue" +msgstr "" + #: src/screens/Reown/CreateNanoContractCreateTokenTxScreen.js:25 msgid "Create Nano Contract & Token" msgstr "" @@ -1371,31 +1396,31 @@ msgstr "" msgid "Error while trying to download Nano Contract transactions history." msgstr "" -#: src/sagas/networkSettings.js:86 +#: src/sagas/networkSettings.js:87 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:93 +#: src/sagas/networkSettings.js:94 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:100 +#: src/sagas/networkSettings.js:101 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:107 +#: src/sagas/networkSettings.js:108 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:114 +#: src/sagas/networkSettings.js:115 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:121 +#: src/sagas/networkSettings.js:122 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:128 +#: src/sagas/networkSettings.js:129 msgid "walletServiceWsUrl should be a valid URL." msgstr "" @@ -1407,20 +1432,20 @@ msgstr "" msgid "Open" msgstr "" -#: src/sagas/wallet.js:811 +#: src/sagas/wallet.js:881 msgid "Wallet is not ready to load addresses." msgstr "" -#: src/sagas/wallet.js:825 +#: src/sagas/wallet.js:895 #. 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:835 +#: src/sagas/wallet.js:905 msgid "Wallet is not ready to load the first address." msgstr "" -#: src/sagas/wallet.js:851 +#: src/sagas/wallet.js:921 #. This will show the message in the feedback content msgid "There was an error while loading first wallet address. Try again." msgstr "" @@ -1535,16 +1560,16 @@ msgstr "" msgid "New Transaction" msgstr "" -#: src/components/ReceiveMyAddress.js:34 +#: src/components/ReceiveMyAddress.js:36 #, javascript-format msgid "Here is my address: ${ lastSharedAddress }" msgstr "" -#: src/components/ReceiveMyAddress.js:57 +#: src/components/ReceiveMyAddress.js:60 msgid "New address" msgstr "" -#: src/components/ReceiveMyAddress.js:63 +#: src/components/ReceiveMyAddress.js:67 #: src/components/TokenDetails.js:102 msgid "Share" msgstr "" @@ -1659,6 +1684,48 @@ msgstr "" msgid "Amount" msgstr "" +#: src/components/Web3AuthErrorDialog.js:24 +msgid "Connection issue" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:25 +msgid "We couldn't reach Web3Auth. Check your internet connection and try again." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:32 +msgid "Configuration error" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:33 +msgid "" +"There was an issue with our authentication setup. Please try again in a few " +"minutes. If the problem persists, contact support." +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:40 +msgid "Recovery factor required" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:41 +msgid "" +"To protect your wallet, you must configure at least one recovery factor. " +"Would you like to set it up now?" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:42 +msgid "Configure now" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:50 +msgid "Something went wrong" +msgstr "" + +#: src/components/Web3AuthErrorDialog.js:51 +msgid "" +"We couldn't complete the sign-in. Please try again. If the issue persists, " +"contact support." +msgstr "" + #: src/components/Reown/AdvancedErrorOptions.js:41 msgid "Advanced options" msgstr "" @@ -1705,14 +1772,14 @@ msgid "Create Token Data" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:70 -#: src/components/Reown/CreateTokenRequest.js:201 +#: src/components/Reown/CreateTokenRequest.js:202 #: src/components/Reown/SignMessageRequest.js:81 #: src/components/Reown/SignOracleDataRequest.js:146 msgid "Accept Request" msgstr "" #: src/components/Reown/CreateNanoContractCreateTokenTxRequest.js:71 -#: src/components/Reown/CreateTokenRequest.js:205 +#: src/components/Reown/CreateTokenRequest.js:206 #: src/components/Reown/SignMessageRequest.js:85 #: src/components/Reown/SignOracleDataRequest.js:150 msgid "Decline Request" @@ -1808,32 +1875,32 @@ msgstr "" msgid "Token data" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:129 +#: src/components/Reown/CreateTokenRequest.js:130 #: src/components/Reown/TransactionFees.js:55 msgid "Network Fee" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:215 +#: src/components/Reown/CreateTokenRequest.js:216 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:515 #: src/components/Reown/SendTransactionRequest.js:487 msgid "Sending transaction" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:216 +#: src/components/Reown/CreateTokenRequest.js:217 #: src/components/Reown/NanoContract/BaseNanoContractRequest.js:516 #: src/components/Reown/SendTransactionRequest.js:488 msgid "Please wait." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:235 +#: src/components/Reown/CreateTokenRequest.js:236 msgid "Create Token Transaction successfully sent." msgstr "" -#: src/components/Reown/CreateTokenRequest.js:237 +#: src/components/Reown/CreateTokenRequest.js:238 msgid "Ok, close" msgstr "" -#: src/components/Reown/CreateTokenRequest.js:244 +#: src/components/Reown/CreateTokenRequest.js:245 msgid "Error while sending create token transaction." msgstr "" diff --git a/package.json b/package.json index 2f7c16c63..e21d25231 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "start:clean": "react-native start --reset-cache", "lint": "eslint .", "test": "jest", - "setup": "npm install && ./node_modules/.bin/allow-scripts && rn-nodeify --install stream,process,path,events,crypto,console,buffer,zlib --hack && npx patch-package", - "setup:release": "npm ci && ./node_modules/.bin/allow-scripts && rn-nodeify --install stream,process,path,events,crypto,console,buffer,zlib --hack && npx patch-package", + "setup": "npm install && ./node_modules/.bin/allow-scripts && rn-nodeify --install stream,process,path,events,crypto,console,buffer,zlib --hack && node scripts/fix-web3auth-crypto-hack.js && npx patch-package", + "setup:release": "npm ci && ./node_modules/.bin/allow-scripts && rn-nodeify --install stream,process,path,events,crypto,console,buffer,zlib --hack && node scripts/fix-web3auth-crypto-hack.js && npx patch-package", "i18n": "make i18n" }, "dependencies": { @@ -25,7 +25,7 @@ "@fortawesome/react-native-fontawesome": "0.3.2", "@hathor/hathor-rpc-handler": "4.3.0", "@hathor/unleash-client": "0.1.0", - "@hathor/wallet-lib": "2.17.0", + "@hathor/wallet-lib": "file:.yalc/@hathor/wallet-lib", "@json-rpc-tools/utils": "1.7.6", "@lavamoat/preinstall-always-fail": "2.1.0", "@lavamoat/react-native-lockdown": "0.1.0", @@ -39,9 +39,15 @@ "@react-navigation/stack": "7.3.4", "@reown/walletkit": "1.4.1", "@sentry/react-native": "6.10.0", + "@toruslabs/react-native-web-browser": "^1.1.0", "@walletconnect/core": "2.23.0", "@walletconnect/react-native-compat": "2.23.0", + "@web3auth/auth": "9.6.0", + "@web3auth/base": "9.7.0", + "@web3auth/base-provider": "9.7.0", + "@web3auth/react-native-sdk": "8.1.0", "assert": "2.0.0", + "bitcore-lib": "8.25.10", "browserify-zlib": "0.1.4", "buffer": "4.9.2", "console-browserify": "1.2.0", @@ -62,6 +68,7 @@ "react-native-camera-kit": "15.1.0", "react-native-crypto": "2.2.0", "react-native-device-info": "8.7.1", + "react-native-encrypted-storage": "4.0.3", "react-native-exception-handler": "2.10.10", "react-native-gesture-handler": "2.25.0", "react-native-get-random-values": "1.11.0", diff --git a/patches/@hathor+wallet-lib+2.17.0.patch b/patches/@hathor+wallet-lib+3.1.0.patch similarity index 100% rename from patches/@hathor+wallet-lib+2.17.0.patch rename to patches/@hathor+wallet-lib+3.1.0.patch diff --git a/patches/bitcore-lib+8.25.10.patch b/patches/bitcore-lib+8.25.10.patch new file mode 100644 index 000000000..007f7a476 --- /dev/null +++ b/patches/bitcore-lib+8.25.10.patch @@ -0,0 +1,51 @@ +diff --git a/node_modules/bitcore-lib/lib/crypto/point.js b/node_modules/bitcore-lib/lib/crypto/point.js +index 6046ed3..1b24ccd 100644 +--- a/node_modules/bitcore-lib/lib/crypto/point.js ++++ b/node_modules/bitcore-lib/lib/crypto/point.js +@@ -26,7 +26,13 @@ var Point = function Point(x, y, isRed) { + } catch (e) { + throw new Error('Invalid Point'); + } +- point.validate(); ++ // Use bitcore's strict validate if available (saved from prototype before restore) ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(point); ++ } else { ++ point.validate(); ++ } + return point; + }; + +@@ -47,7 +53,12 @@ Point.fromX = function fromX(odd, x){ + } catch (e) { + throw new Error('Invalid X'); + } +- point.validate(); ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(point); ++ } else { ++ point.validate(); ++ } + return point; + }; + +diff --git a/node_modules/bitcore-lib/lib/publickey.js b/node_modules/bitcore-lib/lib/publickey.js +index f9c48a1..53b84c6 100644 +--- a/node_modules/bitcore-lib/lib/publickey.js ++++ b/node_modules/bitcore-lib/lib/publickey.js +@@ -50,7 +50,12 @@ function PublicKey(data, extra) { + var info = this._classifyArgs(data, extra); + + // validation +- info.point.validate(); ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(info.point); ++ } else { ++ info.point.validate(); ++ } + + JSUtil.defineImmutable(this, { + point: info.point, diff --git a/scripts/fix-web3auth-crypto-hack.js b/scripts/fix-web3auth-crypto-hack.js new file mode 100644 index 000000000..38ab77151 --- /dev/null +++ b/scripts/fix-web3auth-crypto-hack.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node +/** + * Removes the `"crypto": "react-native-crypto"` mapping injected by + * `rn-nodeify --hack` from packages that ship their own crypto + * implementations. + * + * Why this is needed: + * `rn-nodeify --hack` (run by `npm run setup`) writes a `react-native` + * and `browser` field into every package.json under node_modules, + * redirecting `require('crypto')` to the browserify polyfill. That is + * correct for legacy consumers like bitcore-lib but fatal for the + * Web3Auth ecosystem, which brings its own EC crypto via + * `@noble/curves` / `@toruslabs/eccrypto`. The polyfill is incomplete + * (missing `subtle`, partial `createHash`) and causes the + * `@web3auth/auth` barrel export to crash on load + * (`LOGIN_PROVIDER` becomes undefined). + * + * See `explicacao-hacky-web3-auth.md` for the full story. + * + * Order in the `setup` script: + * npm install + * -> allow-scripts + * -> rn-nodeify --hack (writes the bad mapping) + * -> node scripts/fix-web3auth-crypto-hack.js (THIS script) + * -> npx patch-package (applies bitcore-lib patches) + * + * The script is idempotent and safe to re-run. + */ + +const fs = require('fs'); +const path = require('path'); + +const PACKAGES = [ + '@toruslabs/base-controllers', + '@toruslabs/broadcast-channel', + '@toruslabs/constants', + '@toruslabs/eccrypto', + '@toruslabs/ffjavascript', + '@toruslabs/http-helpers', + '@toruslabs/metadata-helpers', + '@toruslabs/react-native-web-browser', + '@toruslabs/secure-pub-sub', + '@toruslabs/session-manager', + '@toruslabs/starkware-crypto', + '@toruslabs/tweetnacl-js', + '@web3auth/auth', + '@web3auth/base', + '@web3auth/base-provider', + '@web3auth/react-native-sdk', + 'elliptic', + 'brorand', + 'hash.js', + 'hmac-drbg', +]; + +const FIELDS = ['react-native', 'browser']; +const KEY_TO_REMOVE = 'crypto'; + +let touched = 0; +const missing = []; + +for (const pkg of PACKAGES) { + const pjsonPath = path.join('node_modules', pkg, 'package.json'); + if (!fs.existsSync(pjsonPath)) { + missing.push(pkg); + continue; + } + + const raw = fs.readFileSync(pjsonPath, 'utf8'); + const data = JSON.parse(raw); + + let changed = false; + for (const field of FIELDS) { + const value = data[field]; + if (value && typeof value === 'object' && KEY_TO_REMOVE in value) { + delete value[KEY_TO_REMOVE]; + changed = true; + } + } + + if (changed) { + fs.writeFileSync(pjsonPath, `${JSON.stringify(data, null, 2)}\n`); + touched += 1; + console.log(` fixed ${pkg}`); + } +} + +console.log(`\nfix-web3auth-crypto-hack: cleaned ${touched}/${PACKAGES.length} packages`); + +if (missing.length) { + console.warn( + `\nWARNING: ${missing.length} expected packages are missing from node_modules:`, + ); + for (const pkg of missing) console.warn(` - ${pkg}`); + console.warn( + 'This may indicate that the Web3Auth dependency tree has changed. ' + + 'Update the PACKAGES list in this script.', + ); +} diff --git a/shim.js b/shim.js index 27480bb04..d4cab5d4f 100644 --- a/shim.js +++ b/shim.js @@ -25,7 +25,30 @@ if (typeof localStorage !== 'undefined') { // If using the crypto shim, uncomment the following line to ensure // crypto is loaded first, so it can populate global.crypto -require('crypto') +const cryptoPolyfill = require('crypto') + +// Expose createHash/createHmac on globalThis.crypto so that +// @toruslabs/eccrypto (which checks globalThis.crypto.createHash) can use them. +// Without this, eccrypto falls back to subtle.digest() which doesn't exist in RN. +if (typeof globalThis.crypto === 'undefined') { + globalThis.crypto = {}; +} +// Expose all crypto polyfill methods on globalThis.crypto so that +// @toruslabs/eccrypto can use createHash, createCipheriv, createDecipheriv, etc. +const methodsToExpose = [ + 'createHash', 'createHmac', 'createCipheriv', 'createDecipheriv', + 'randomBytes', 'publicEncrypt', 'publicDecrypt', +]; +for (const method of methodsToExpose) { + if (cryptoPolyfill[method] && !globalThis.crypto[method]) { + globalThis.crypto[method] = cryptoPolyfill[method]; + } +} +if (!globalThis.crypto.getRandomValues && cryptoPolyfill.randomFillSync) { + globalThis.crypto.getRandomValues = function(arr) { + return cryptoPolyfill.randomFillSync(arr); + }; +} const { TextEncoder, TextDecoder } = require('text-encoding'); @@ -47,3 +70,34 @@ if (typeof atob === 'undefined') { const { URLSearchParams } = require('react-native-url-polyfill'); global.URLSearchParams = URLSearchParams; + +// Fix bitcore-lib prototype pollution on elliptic points. +// bitcore-lib replaces elliptic's BasePoint.prototype.validate (returns boolean) +// with a version that THROWS on invalid points. This breaks Web3Auth SDK +// because elliptic's ec/key.js calls pub.validate() expecting a boolean. +// Fix: save native validate, let bitcore-lib load, then restore it. +// bitcore-lib's own Point constructor calls .validate() but we patch it +// to use its own strict validation function directly. +try { + const EC = require('elliptic').ec; + const ec = new EC('secp256k1'); + const pointProto = Object.getPrototypeOf(ec.curve.point()); + + // Save elliptic's native validate (returns boolean) + const nativeValidate = pointProto.validate; + + // Load bitcore-lib which will monkey-patch pointProto.validate + require('bitcore-lib'); + + // Save bitcore-lib's strict validate (throws on invalid) + const bitcoreValidate = pointProto.validate; + + // Restore elliptic's native validate on the shared prototype + pointProto.validate = nativeValidate; + + // Store bitcore's validate so it can still be called explicitly + // by bitcore-lib code that needs it (via the Point constructor) + global.__bitcorePointValidate = bitcoreValidate; +} catch (e) { + // bitcore-lib or elliptic not available — no action needed +} diff --git a/src/App.js b/src/App.js index c5cbe71c4..fecea3f4f 100644 --- a/src/App.js +++ b/src/App.js @@ -25,8 +25,16 @@ import { useRoute } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import IconTabBar from './icon-font'; -import { IS_MULTI_TOKEN, LOCK_TIMEOUT, PUSH_ACTION, INITIAL_TOKENS } from './constants'; +import { + IS_MULTI_TOKEN, + LOCK_TIMEOUT, + PUSH_ACTION, + INITIAL_TOKENS, + WEB3AUTH_WALLET_TYPE_KEY, + WEB3AUTH_EMAIL_KEY, +} from './constants'; import { setSupportedBiometry, isTokenSwapEnabled, isFeeBasedTokensEnabled } from './utils'; import { appStateUpdate, @@ -36,6 +44,8 @@ import { requestCameraPermission, resetData, setTokens, + setWalletType, + setWeb3authEmail, } from './actions'; import { HathorDeeplinkProvider } from './contexts/HathorDeeplinkContext'; import { store } from './reducers/reducer.init'; @@ -48,6 +58,7 @@ import { WelcomeScreen, } from './screens/InitWallet'; import ChoosePinScreen from './screens/ChoosePinScreen'; +import Web3AuthRecoveryScreen from './screens/Web3AuthRecoveryScreen'; import BackupWords from './screens/BackupWords'; import PinScreen from './screens/PinScreen'; import ResetWallet from './screens/ResetWallet'; @@ -137,6 +148,7 @@ const InitStack = () => { + ); @@ -787,6 +799,20 @@ const RootStack = () => { useEffect(() => { STORE.preStart() + .then(async () => { + // Restore Web3Auth state into Redux. Values are JSON-serialized to stay + // compatible with STORE.preStart, which JSON.parse's every value. + const rawWalletType = await AsyncStorage.getItem(WEB3AUTH_WALLET_TYPE_KEY); + const rawWeb3authEmail = await AsyncStorage.getItem(WEB3AUTH_EMAIL_KEY); + const walletType = rawWalletType ? JSON.parse(rawWalletType) : null; + const web3authEmail = rawWeb3authEmail ? JSON.parse(rawWeb3authEmail) : null; + if (walletType) { + dispatch(setWalletType(walletType)); + } + if (web3authEmail) { + dispatch(setWeb3authEmail(web3authEmail)); + } + }) .then(() => STORE.walletIsLoaded()) .then((_isLoaded) => { setAppStatus(_isLoaded ? 'isLoaded' : 'notLoaded'); diff --git a/src/actions.js b/src/actions.js index 09a91c6bf..15eb74289 100644 --- a/src/actions.js +++ b/src/actions.js @@ -254,6 +254,9 @@ export const types = { // Clean swap data when user does not confirm the swap TOKEN_SWAP_RESET_SWAP_DATA: 'TOKEN_SWAP_RESET_SWAP_DATA', TOKEN_SWAP_START_SWAP: 'TOKEN_SWAP_START_SWAP', + // Web3Auth actions + SET_WALLET_TYPE: 'SET_WALLET_TYPE', + SET_WEB3AUTH_EMAIL: 'SET_WEB3AUTH_EMAIL', }; export const featureToggleInitialized = () => ({ @@ -1741,3 +1744,19 @@ export const tokenSwapResetSwapData = () => ({ export const tokenSwapSwitchTokens = () => ({ type: types.TOKEN_SWAP_SWITCH_TOKENS, }); + +/** + * walletType {'hd' | 'web3auth'} The type of wallet being used + */ +export const setWalletType = (walletType) => ({ + type: types.SET_WALLET_TYPE, + payload: walletType, +}); + +/** + * email {string} The email from Web3Auth social login + */ +export const setWeb3authEmail = (email) => ({ + type: types.SET_WEB3AUTH_EMAIL, + payload: email, +}); diff --git a/src/assets/web3auth-providers/apple-logo.svg b/src/assets/web3auth-providers/apple-logo.svg new file mode 100644 index 000000000..b2f290c21 --- /dev/null +++ b/src/assets/web3auth-providers/apple-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/web3auth-providers/email-icon.svg b/src/assets/web3auth-providers/email-icon.svg new file mode 100644 index 000000000..f8f9cec06 --- /dev/null +++ b/src/assets/web3auth-providers/email-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/web3auth-providers/google-logo.svg b/src/assets/web3auth-providers/google-logo.svg new file mode 100644 index 000000000..8e0558efa --- /dev/null +++ b/src/assets/web3auth-providers/google-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/Icons/AppleProvider.icon.js b/src/components/Icons/AppleProvider.icon.js new file mode 100644 index 000000000..2823b3ac5 --- /dev/null +++ b/src/components/Icons/AppleProvider.icon.js @@ -0,0 +1,21 @@ +/** + * 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 from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { COLORS } from '../../styles/themes'; + +const AppleProviderIcon = ({ size = 24, color = COLORS.black }) => ( + + + +); + +export default AppleProviderIcon; diff --git a/src/components/Icons/EmailProvider.icon.js b/src/components/Icons/EmailProvider.icon.js new file mode 100644 index 000000000..8acd5f8a7 --- /dev/null +++ b/src/components/Icons/EmailProvider.icon.js @@ -0,0 +1,21 @@ +/** + * 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 from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { COLORS } from '../../styles/themes'; + +const EmailProviderIcon = ({ size = 24, color = COLORS.black }) => ( + + + +); + +export default EmailProviderIcon; diff --git a/src/components/Icons/GoogleProvider.icon.js b/src/components/Icons/GoogleProvider.icon.js new file mode 100644 index 000000000..2c3b5be7a --- /dev/null +++ b/src/components/Icons/GoogleProvider.icon.js @@ -0,0 +1,32 @@ +/** + * 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 from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const GoogleProviderIcon = ({ size = 24 }) => ( + + + + + + +); + +export default GoogleProviderIcon; diff --git a/src/components/ReceiveMyAddress.js b/src/components/ReceiveMyAddress.js index 91d22a404..92fc11990 100644 --- a/src/components/ReceiveMyAddress.js +++ b/src/components/ReceiveMyAddress.js @@ -16,12 +16,14 @@ import { useDispatch, useSelector } from 'react-redux'; import SimpleButton from './SimpleButton'; import CopyClipboard from './CopyClipboard'; import { sharedAddressUpdate } from '../actions'; +import { isSingleKeyWallet } from '../selectors'; import { COLORS } from '../styles/themes'; export default function ReceiveMyAddress() { const dispatch = useDispatch(); const wallet = useSelector((state) => state.wallet); const lastSharedAddress = useSelector((state) => state.lastSharedAddress); + const singleKey = useSelector(isSingleKeyWallet); const getNextAddress = async () => { const { address, index } = await wallet.getNextAddress(); @@ -53,11 +55,13 @@ export default function ReceiveMyAddress() { /> - + {!singleKey && ( + + )} { + switch (errorType) { + case WEB3AUTH_ERROR_TYPES.NETWORK: + return { + title: t`Connection issue`, + body: t`We couldn't reach Web3Auth. Check your internet connection and try again.`, + primary: t`Try again`, + secondary: t`Cancel`, + sentryCategory: 'network', + }; + case WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG: + return { + title: t`Configuration error`, + body: t`There was an issue with our authentication setup. Please try again in a few minutes. If the problem persists, contact support.`, + primary: t`Try again`, + secondary: t`Cancel`, + sentryCategory: 'config', + }; + case WEB3AUTH_ERROR_TYPES.MFA_REQUIRED: + return { + title: t`Recovery factor required`, + body: t`To protect your wallet, you must configure at least one recovery factor. Would you like to set it up now?`, + primary: t`Configure now`, + secondary: t`Cancel`, + sentryCategory: 'mfa', + }; + case WEB3AUTH_ERROR_TYPES.KEY_DERIVATION: + case WEB3AUTH_ERROR_TYPES.UNKNOWN: + default: + return { + title: t`Something went wrong`, + body: t`We couldn't complete the sign-in. Please try again. If the issue persists, contact support.`, + primary: t`Try again`, + secondary: t`Cancel`, + sentryCategory: 'unknown', + }; + } +}; + +const Web3AuthErrorDialog = ({ errorType, onRetry, onCancel, originalError }) => { + useEffect(() => { + if (!errorType) return; + const { sentryCategory } = getDialogContent(errorType); + log.error('web3auth_error_dialog_shown', { + errorType, + category: sentryCategory, + originalError: originalError ? String(originalError.message || originalError) : null, + }); + }, [errorType, originalError]); + + if (!errorType) return null; + + const content = getDialogContent(errorType); + + return ( + + {content.title} + + {content.body} + + + + + ); +}; + +const styles = StyleSheet.create({ + body: { + paddingBottom: 20, + }, + text: { + fontSize: 14, + lineHeight: 20, + }, +}); + +export default Web3AuthErrorDialog; diff --git a/src/constants.js b/src/constants.js index 15f079929..12c91f897 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,7 @@ import 'intl'; import 'intl/locale-data/jsonp/en'; +import { WEB3AUTH_NETWORK } from '@web3auth/react-native-sdk'; import { _IS_MULTI_TOKEN as IS_MULTI_TOKEN, _DEFAULT_TOKEN as DEFAULT_TOKEN, @@ -182,6 +183,7 @@ export const NANO_CONTRACT_FEATURE_TOGGLE = 'nano-contract.rollout'; export const SAFE_BIOMETRY_MODE_FEATURE_TOGGLE = 'safe-biometry-mode.rollout' export const TOKEN_SWAP_FEATURE_TOGGLE = 'token-swap.rollout'; export const FBT_FEATURE_TOGGLE = 'fee-based-tokens.rollout'; +export const WEB3AUTH_FEATURE_TOGGLE = 'web3auth.rollout'; /** * Default feature toggle values. @@ -200,8 +202,48 @@ export const FEATURE_TOGGLE_DEFAULTS = { [SAFE_BIOMETRY_MODE_FEATURE_TOGGLE]: false, [TOKEN_SWAP_FEATURE_TOGGLE]: false, [FBT_FEATURE_TOGGLE]: false, + [WEB3AUTH_FEATURE_TOGGLE]: true, }; +export const WEB3AUTH_WALLET_TYPE_KEY = 'web3auth:walletType'; +export const WEB3AUTH_EMAIL_KEY = 'web3auth:email'; + +// Web3Auth redirect scheme +export const WEB3AUTH_REDIRECT_URL = 'hathorwallet://openlogin'; + +// Custom verifier name in the MetaMask Embedded Wallets dashboard. +// Same name is used across both networks but each network has its own +// verifier deployment. +const HATHOR_GOOGLE_VERIFIER = 'hathor-google'; + +/** + * Web3Auth configuration per Hathor network. + * + * Hathor testnet maps to Web3Auth Sapphire Devnet. + * Hathor mainnet maps to Web3Auth Sapphire Mainnet. + * + * Each entry contains the Web3Auth project Client ID, the Hathor-owned Google + * OAuth Client ID configured on the verifier, and the Web3Auth Auth Network. + * + * Mainnet entries are placeholders until the team creates the mainnet project + * in the dashboard. A user trying to log in on Hathor mainnet before that point + * will hit a controlled error in getWeb3AuthConfig(). + */ +export const WEB3AUTH_CONFIG = Object.freeze({ + testnet: { + clientId: 'BLQbTFHmFa4TpQwAKEnBsf9ZArKB8R_hP3gKjBdSrF48fSmzo3ES-KpoaAvX7JMaa1PvefbD5yEXgRrgsiQiauQ', + googleClientId: '206408356798-lmqb7i1n1vr6e761479q146sfqgnvue8.apps.googleusercontent.com', + verifier: HATHOR_GOOGLE_VERIFIER, + network: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET, + }, + mainnet: { + clientId: null, // TODO: create mainnet project in MetaMask dashboard + googleClientId: null, // TODO: create Google OAuth client for mainnet (or reuse) + verifier: HATHOR_GOOGLE_VERIFIER, + network: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET, + }, +}); + // Project id configured in https://walletconnect.com export const REOWN_PROJECT_ID = '8264fff563181da658ce64ee80e80458'; diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js index 460685709..0a799941f 100644 --- a/src/reducers/reducer.js +++ b/src/reducers/reducer.js @@ -557,6 +557,16 @@ const initialState = { swapPathQuote: null, loadSwapPathQuoteStatus: TOKEN_SWAP_QUOTE_STATUS.READY, }, + /** + * walletType {'hd' | 'web3auth' | null} The type of wallet being used. + * null when uninitialized. + */ + walletType: null, + /** + * web3authEmail {string | null} Email from Web3Auth social login. + * null when not using Web3Auth. + */ + web3authEmail: null, }; export const reducer = (state = initialState, action) => { @@ -595,6 +605,16 @@ export const reducer = (state = initialState, action) => { return onUpdateLoadedData(state, action); case types.SET_USE_WALLET_SERVICE: return onSetUseWalletService(state, action); + case types.SET_WALLET_TYPE: + return { + ...state, + walletType: action.payload, + }; + case types.SET_WEB3AUTH_EMAIL: + return { + ...state, + web3authEmail: action.payload, + }; case types.TOKEN_METADATA_UPDATED: return onTokenMetadataUpdated(state, action); case types.TOKEN_METADATA_REMOVED: diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index 8d97e04af..70ec296ef 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -13,8 +13,10 @@ import { constants as hathorLibConstants, config, errors, + transactionUtils, } from '@hathor/wallet-lib'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { PrivateKey } from 'bitcore-lib'; import { takeLatest, takeEvery, @@ -39,6 +41,7 @@ import { PUSH_NOTIFICATION_FEATURE_TOGGLE, networkSettingsKeyMap, } from '../constants'; +import { web3authLogout, cleanWeb3AuthState } from './web3auth'; import { STORE } from '../store'; import { tokenFetchBalanceRequested, @@ -71,6 +74,7 @@ import { firstAddressSuccess, firstAddressRequest, setFullNodeNetworkName, + setWalletType, } from '../actions'; import { fetchTokenData } from './tokens'; import { @@ -154,6 +158,9 @@ export function* startWallet(action) { const { words, pin, + privateKey, + publicKey, + walletType, } = action.payload; // clean memory storage and metadata before starting the wallet. @@ -192,7 +199,10 @@ export function* startWallet(action) { const useWalletService = yield call(isWalletServiceEnabled); const usePushNotification = yield call(isPushNotificationEnabled); - yield put(setUseWalletService(useWalletService)); + // Force-disable wallet-service for single-key (web3auth) wallets + const effectiveUseWalletService = walletType === 'web3auth' ? false : useWalletService; + + yield put(setUseWalletService(effectiveUseWalletService)); yield put(setAvailablePushNotification(usePushNotification)); // This is a work-around so we can dispatch actions from inside callbacks. @@ -202,7 +212,7 @@ export function* startWallet(action) { }); let wallet; - if (useWalletService && !isEmpty(networkSettings.walletServiceUrl)) { + if (effectiveUseWalletService && !isEmpty(networkSettings.walletServiceUrl)) { const network = new Network(networkSettings.network); // Set urls for wallet service @@ -221,17 +231,21 @@ export function* startWallet(action) { servers: [networkSettings.nodeUrl], }); - // The default configuration will use a memory store - // We will save the access data on the persistent async storage - // To allow starting the wallet again const walletConfig = { - seed: words, storage, connection, beforeReloadCallback: () => { dispatch(onWalletReload()); }, }; + + if (walletType === 'web3auth') { + walletConfig.privateKey = privateKey; + walletConfig.publicKey = publicKey; + } else { + walletConfig.seed = words; + } + wallet = new HathorWallet(walletConfig); } @@ -241,6 +255,52 @@ export function* startWallet(action) { yield put(setWallet(wallet)); + if (walletType === 'web3auth') { + yield put(setWalletType('web3auth')); + wallet.setExternalTxSigningMethod(async (tx, walletStorage, pinCode) => { + const privKeyHex = await walletStorage.getSingleKeyPrivateKey(pinCode); + const privKey = new PrivateKey(privKeyHex); + const dataToSignHash = tx.getDataToSignHash(); + const inputSignatures = []; + + const spentTxs = walletStorage.getSpentTxs(tx.inputs); + for await (const { tx: spentTx, input, index: inputIndex } of spentTxs) { + if (input.data) { + // This input is already signed + continue; + } + const spentOut = spentTx.outputs[input.index]; + if (!spentOut.decoded.address) { + continue; + } + const addressInfo = await walletStorage.getAddressInfo(spentOut.decoded.address); + if (!addressInfo) { + continue; + } + inputSignatures.push({ + inputIndex, + addressIndex: addressInfo.bip32AddressIndex, + signature: transactionUtils.getSignature(dataToSignHash, privKey), + pubkey: privKey.publicKey.toDER(), + }); + } + + let ncCallerSignature = null; + if (tx.isNanoContract()) { + const ncAddress = transactionUtils.getNanoContractCaller(tx); + if (ncAddress) { + const ncAddrInfo = await walletStorage.getAddressInfo(ncAddress.base58); + if (ncAddrInfo) { + const sig = transactionUtils.getSignature(dataToSignHash, privKey); + ncCallerSignature = transactionUtils.createInputData(sig, privKey.publicKey.toDER()); + } + } + } + + return { inputSignatures, ncCallerSignature }; + }); + } + // Setup listeners before starting the wallet so we don't lose messages yield fork(setupWalletListeners, wallet); @@ -265,7 +325,7 @@ export function* startWallet(action) { yield put(setServerInfo(serverInfo)); let network = get(serverInfo, 'network'); - if (useWalletService) { + if (effectiveUseWalletService) { // In the wallet-service facade, serverInfo is null, so we need to get // version data and convert it to what serverInfo expects: const versionData = yield call([wallet, wallet.getVersionData]); @@ -304,7 +364,7 @@ export function* startWallet(action) { yield put(onExceptionCaptured(e, false)); } - if (useWalletService) { + if (effectiveUseWalletService) { // Wallet Service start wallet will fail if the status returned from // the service is 'error' or if the start wallet request failed. // @@ -760,6 +820,7 @@ export function* onWalletReloadData() { export function* onResetWallet() { const wallet = yield select((state) => state.wallet); + const walletType = yield select((state) => state.walletType); // We need to clear the ignore flag so that new wallet starts can load in the // wallet-service after a start error: @@ -770,6 +831,12 @@ export function* onResetWallet() { yield setWallet(null); } + // Clean up Web3Auth state if this was a web3auth wallet + if (walletType === 'web3auth') { + yield call(web3authLogout); + yield* cleanWeb3AuthState(); + } + yield call(() => STORE.resetWallet()); yield put(resetWalletSuccess()); diff --git a/src/sagas/web3auth.js b/src/sagas/web3auth.js new file mode 100644 index 000000000..64da5b9eb --- /dev/null +++ b/src/sagas/web3auth.js @@ -0,0 +1,240 @@ +/** + * 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 { put, call } from 'redux-saga/effects'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import Web3Auth from '@web3auth/react-native-sdk'; +import { CHAIN_NAMESPACES } from '@web3auth/base'; +import { CommonPrivateKeyProvider } from '@web3auth/base-provider'; +import * as WebBrowser from '@toruslabs/react-native-web-browser'; +import EncryptedStorage from 'react-native-encrypted-storage'; +import { PrivateKey } from 'bitcore-lib'; +import { + WEB3AUTH_CONFIG, + WEB3AUTH_REDIRECT_URL, + WEB3AUTH_WALLET_TYPE_KEY, + WEB3AUTH_EMAIL_KEY, +} from '../constants'; +import { setWalletType, setWeb3authEmail } from '../actions'; +import { logger } from '../logger'; + +const log = logger('web3auth'); + +let web3authInstance = null; + +export const WEB3AUTH_ERROR_TYPES = Object.freeze({ + USER_CANCELLED: 'user_cancelled', + NETWORK: 'network', + VERIFIER_CONFIG: 'verifier_config', + MFA_REQUIRED: 'mfa_required', + KEY_DERIVATION: 'key_derivation', + UNKNOWN: 'unknown', +}); + +/** + * Classifies a Web3Auth SDK error into a known type so the UI can pick the + * right dialog copy and the observability layer can tag the event correctly. + * + * The SDK does not expose a stable error enum, so we string-match the messages + * we have seen in practice. Unknown errors fall back to UNKNOWN, which the UI + * treats like a network error but Sentry tags differently so the team can + * triage new patterns. + */ +export function classifyWeb3AuthError(err) { + if (!err) return WEB3AUTH_ERROR_TYPES.UNKNOWN; + + const msg = String(err.message || err).toLowerCase(); + + if (msg.includes('user closed') || msg.includes('user_cancelled') || msg.includes('user cancelled')) { + return WEB3AUTH_ERROR_TYPES.USER_CANCELLED; + } + if (msg.includes('network') || msg.includes('timeout') || msg.includes('fetch')) { + return WEB3AUTH_ERROR_TYPES.NETWORK; + } + if (msg.includes('verifier') || msg.includes('jwt') || msg.includes('invalid_token')) { + return WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG; + } + if (msg.includes('mfa') || msg.includes('factor')) { + return WEB3AUTH_ERROR_TYPES.MFA_REQUIRED; + } + if (msg.includes('invalid') && msg.includes('key')) { + return WEB3AUTH_ERROR_TYPES.KEY_DERIVATION; + } + return WEB3AUTH_ERROR_TYPES.UNKNOWN; +} + +/** + * Returns the Web3Auth config entry matching the current Hathor network. + * + * @throws {Error} if the matching entry is incomplete (e.g., mainnet not yet + * configured in the dashboard). The error is intentional — + * it surfaces as a controlled failure to the user instead of + * silently logging into the wrong Web3Auth project. + */ +// eslint-disable-next-line no-unused-vars +function getWeb3AuthConfig(hathorNetwork) { + // TEMP: force testnet (Sapphire Devnet) Web3Auth config while the mainnet + // project is not provisioned yet. All development testing runs against + // devnet — restore the block below once WEB3AUTH_CONFIG.mainnet has + // clientId/googleClientId filled in. + return WEB3AUTH_CONFIG.testnet; + + // const cfg = WEB3AUTH_CONFIG[hathorNetwork]; + // if (!cfg) { + // throw new Error(`Unknown Hathor network: ${hathorNetwork}`); + // } + // if (!cfg.clientId || !cfg.googleClientId) { + // throw new Error( + // `Web3Auth is not configured for ${hathorNetwork} yet. ` + // + `Please contact support.`, + // ); + // } + // return cfg; +} + +/** + * Initialize the Web3Auth SDK singleton. + * All SDK objects are created lazily to avoid top-level crypto operations + * that conflict with the react-native-crypto polyfill. + * Must be awaited — calls init() on first use. + * @returns {Promise} + */ +async function getWeb3AuthInstance(hathorNetwork) { + if (!web3authInstance) { + console.log('[W3A-DEBUG] Creating Web3Auth instance...'); + const cfg = getWeb3AuthConfig(hathorNetwork); + const pkProvider = new CommonPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: CHAIN_NAMESPACES.OTHER, + chainId: '0x1', + rpcTarget: 'https://node1.mainnet.hathor.network/v1a/', + displayName: 'Hathor Network', + ticker: 'HTR', + tickerName: 'Hathor', + }, + }, + }); + console.log('[W3A-DEBUG] CommonPrivateKeyProvider created'); + + web3authInstance = new Web3Auth(WebBrowser, EncryptedStorage, { + clientId: cfg.clientId, + redirectUrl: WEB3AUTH_REDIRECT_URL, + network: cfg.network, + privateKeyProvider: pkProvider, + loginConfig: { + google: { + verifier: cfg.verifier, + typeOfLogin: 'google', + clientId: cfg.googleClientId, + }, + }, + }); + console.log('[W3A-DEBUG] Web3Auth constructor done, calling init()...'); + await web3authInstance.init(); + console.log('[W3A-DEBUG] Web3Auth init() complete'); + } + return web3authInstance; +} + +/** + * Perform social login via Web3Auth. + * @param {string} loginProvider - One of LOGIN_PROVIDER.GOOGLE, APPLE, EMAIL_PASSWORDLESS + * @returns {Promise<{privateKey: string, email: string}>} + */ +export async function web3authLogin(loginProvider, hathorNetwork, extraLoginOptions = {}) { + const web3auth = await getWeb3AuthInstance(hathorNetwork); + const provider = await web3auth.login({ + loginProvider, + curve: 'secp256k1', + mfaLevel: 'mandatory', + extraLoginOptions, + }); + + // Get user info from the web3auth instance (v8 API) + const userInfo = web3auth.userInfo(); + const email = userInfo?.email || userInfo?.name || ''; + + // Get raw private key from provider (CommonPrivateKeyProvider) + const privateKey = await provider.request({ method: 'private_key' }); + + // TEMP: print private key to compare custom verifier vs shared verifier. + // Remove before merging. + console.log('[W3A-DEBUG] privateKey:', privateKey, 'email:', email); + + return { privateKey, email }; +} + +/** + * Derive the compressed public key (hex) from a raw secp256k1 private key. + * + * The single-key wallet only needs the public key at construction time; + * the address is derived inside the wallet-lib using the connection network + * (see HathorWallet.start in @hathor/wallet-lib), so we don't need to know + * the network here. + * + * @param {string} privateKeyHex - 32-byte hex private key + * @returns {string} Compressed public key hex + */ +export function derivePublicKey(privateKeyHex) { + return new PrivateKey(privateKeyHex).toPublicKey().toString(); +} + +/** + * Persist walletType and email to AsyncStorage and Redux. + */ +export function* persistWeb3AuthState(walletType, email) { + // Values are JSON-serialized to remain compatible with STORE.preStart, + // which iterates AsyncStorage on startup and JSON.parse's every value. + yield call(() => AsyncStorage.setItem(WEB3AUTH_WALLET_TYPE_KEY, JSON.stringify(walletType))); + if (email) { + yield call(() => AsyncStorage.setItem(WEB3AUTH_EMAIL_KEY, JSON.stringify(email))); + } + yield put(setWalletType(walletType)); + yield put(setWeb3authEmail(email)); +} + +/** + * Restore walletType and email from AsyncStorage into Redux on app start. + */ +export function* restoreWeb3AuthState() { + const rawWalletType = yield call(() => AsyncStorage.getItem(WEB3AUTH_WALLET_TYPE_KEY)); + const rawEmail = yield call(() => AsyncStorage.getItem(WEB3AUTH_EMAIL_KEY)); + const walletType = rawWalletType ? JSON.parse(rawWalletType) : null; + const email = rawEmail ? JSON.parse(rawEmail) : null; + + if (walletType) { + yield put(setWalletType(walletType)); + } + if (email) { + yield put(setWeb3authEmail(email)); + } +} + +/** + * Perform Web3Auth logout (clear session). + */ +export async function web3authLogout() { + try { + if (web3authInstance) { + await web3authInstance.logout(); + } + } catch (e) { + log.error('Error during web3auth logout:', e); + } + web3authInstance = null; +} + +/** + * Clean up Web3Auth state from AsyncStorage. + */ +export function* cleanWeb3AuthState() { + yield call(() => AsyncStorage.removeItem(WEB3AUTH_WALLET_TYPE_KEY)); + yield call(() => AsyncStorage.removeItem(WEB3AUTH_EMAIL_KEY)); + yield put(setWalletType(null)); + yield put(setWeb3authEmail(null)); +} diff --git a/src/screens/ChoosePinScreen.js b/src/screens/ChoosePinScreen.js index 4917c1157..c897bdf94 100644 --- a/src/screens/ChoosePinScreen.js +++ b/src/screens/ChoosePinScreen.js @@ -14,11 +14,13 @@ import { import { connect } from 'react-redux'; import { t } from 'ttag'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import PinInput from '../components/PinInput'; -import { startWalletRequested, unlockScreen } from '../actions'; -import { PIN_SIZE } from '../constants'; +import { setWeb3authEmail, startWalletRequested, unlockScreen } from '../actions'; +import { PIN_SIZE, WEB3AUTH_WALLET_TYPE_KEY, WEB3AUTH_EMAIL_KEY } from '../constants'; import { COLORS } from '../styles/themes'; import baseStyle from '../styles/init'; @@ -27,10 +29,8 @@ import NavigationService from '../NavigationService'; const mapDispatchToProps = (dispatch) => ({ unlockScreen: () => dispatch(unlockScreen()), - startWalletRequested: (words, pin) => dispatch(startWalletRequested({ - words, - pin - })), + startWalletRequested: (payload) => dispatch(startWalletRequested(payload)), + setWeb3authEmail: (email) => dispatch(setWeb3authEmail(email)), }); class ChoosePinScreen extends React.Component { @@ -50,6 +50,12 @@ class ChoosePinScreen extends React.Component { super(props); this.words = this.props.route.params?.words; // Mandatory parameter + // Web3Auth params (optional — only set when coming from social login) + this.privateKey = this.props.route.params?.privateKey; + this.publicKey = this.props.route.params?.publicKey; + this.web3authEmail = this.props.route.params?.web3authEmail; + this.walletType = this.props.route.params?.walletType || 'hd'; + /** * pin1 {string} Input value for pin * pin2 {string} Input value for pin confirmation @@ -81,10 +87,33 @@ class ChoosePinScreen extends React.Component { } goToNextScreen = () => { - STORE.initStorage(this.words, this.state.pin1).then(() => { - // we are just initializing the wallet, so make sure it's not locked when going to AppStack + const pin = this.state.pin1; + + const initPromise = this.walletType === 'web3auth' + ? STORE.initWeb3AuthStorage(this.privateKey, this.publicKey, pin) + : STORE.initStorage(this.words, pin); + + initPromise.then(() => { this.props.unlockScreen(); - this.props.startWalletRequested(this.words, this.state.pin1); + + if (this.walletType === 'web3auth') { + // Values are JSON-serialized to remain compatible with STORE.preStart, + // which iterates AsyncStorage on startup and JSON.parse's every value. + AsyncStorage.setItem(WEB3AUTH_WALLET_TYPE_KEY, JSON.stringify('web3auth')); + if (this.web3authEmail) { + AsyncStorage.setItem(WEB3AUTH_EMAIL_KEY, JSON.stringify(this.web3authEmail)); + this.props.setWeb3authEmail(this.web3authEmail); + } + this.props.startWalletRequested({ + privateKey: this.privateKey, + publicKey: this.publicKey, + pin, + walletType: 'web3auth', + }); + } else { + this.props.startWalletRequested({ words: this.words, pin }); + } + NavigationService.resetToMain(); }); } diff --git a/src/screens/InitWallet.js b/src/screens/InitWallet.js index 6d99c2885..0be4d8e75 100644 --- a/src/screens/InitWallet.js +++ b/src/screens/InitWallet.js @@ -19,18 +19,26 @@ import { Switch, Text, TextInput, + TouchableOpacity, TouchableWithoutFeedback, View, } from 'react-native'; +import { connect } from 'react-redux'; +import { LOGIN_PROVIDER } from '@web3auth/auth'; import { t } from 'ttag'; import NewHathorButton from '../components/NewHathorButton'; import HathorHeader from '../components/HathorHeader'; import TextFmt from '../components/TextFmt'; +import GoogleProviderIcon from '../components/Icons/GoogleProvider.icon'; +import EmailProviderIcon from '../components/Icons/EmailProvider.icon'; +import AppleProviderIcon from '../components/Icons/AppleProvider.icon'; 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, WEB3AUTH_FEATURE_TOGGLE } from '../constants'; +import { web3authLogin, derivePublicKey, classifyWeb3AuthError, WEB3AUTH_ERROR_TYPES } from '../sagas/web3auth'; +import Web3AuthErrorDialog from '../components/Web3AuthErrorDialog'; import { COLORS } from '../styles/themes'; import { SKIP_SEED_CONFIRMATION } from '../config'; @@ -105,7 +113,91 @@ class WelcomeScreen extends React.Component { } class InitialScreen extends React.Component { - style = ({ ...baseStyle }); + state = { + web3authErrorType: null, + web3authOriginalError: null, + }; + + style = ({ ...baseStyle, + ...StyleSheet.create({ + socialRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginVertical: 16, + gap: 8, + }, + providerCard: { + flex: 1, + height: 56, + borderWidth: 1, + borderColor: '#ece5f8', + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.white, + }, + orRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 0, + marginBottom: 16, + }, + orLine: { + flex: 1, + height: 1, + backgroundColor: COLORS.borderColorMid, + }, + orText: { + marginHorizontal: 12, + fontSize: 14, + color: COLORS.midContrastDetail, + fontWeight: '600', + textTransform: 'uppercase', + }, + }) }); + + handleSocialLogin = async (loginProvider) => { + try { + let extraLoginOptions = {}; + if (loginProvider === LOGIN_PROVIDER.EMAIL_PASSWORDLESS) { + // For email passwordless, Web3Auth requires login_hint (the email) + const testEmail = 'test_account_5041@example.com'; // Web3Auth test account (OTP: 973012) + extraLoginOptions = { login_hint: testEmail }; + } + const { privateKey, email } = await web3authLogin( + loginProvider, + this.props.hathorNetwork, + extraLoginOptions, + ); + const publicKey = derivePublicKey(privateKey); + + this.props.navigation.navigate('Web3AuthRecoveryScreen', { + privateKey, + publicKey, + web3authEmail: email, + walletType: 'web3auth', + }); + } catch (err) { + const errorType = classifyWeb3AuthError(err); + if (errorType === WEB3AUTH_ERROR_TYPES.USER_CANCELLED) { + // Silent — user intentionally backed out of the OAuth flow. + return; + } + this.setState({ web3authErrorType: errorType, web3authOriginalError: err }); + } + }; + + dismissWeb3AuthError = () => { + this.setState({ web3authErrorType: null, web3authOriginalError: null }); + }; + + retryWeb3AuthLogin = () => { + this.dismissWeb3AuthError(); + // Only Google is currently wired up; Email and Apple cards are inert. + // Retrying defaults to Google because that is the only provider that + // could have produced the failure we are recovering from. + this.handleSocialLogin(LOGIN_PROVIDER.GOOGLE); + }; render() { return ( @@ -123,6 +215,31 @@ class InitialScreen extends React.Component { {t`To import a wallet, you will need to provide your seed words.`} + {this.props.web3authEnabled && ( + <> + + this.handleSocialLogin(LOGIN_PROVIDER.GOOGLE)} + > + + + {/* TODO: WAITING OAUTH CLIENT TO BE DEFINED */} + + + + {/* TODO: WAITING OAUTH CLIENT TO BE DEFINED */} + + + + + + + {t`OR`} + + + + )} this.props.navigation.navigate('LoadWordsScreen')} title={t`Import Wallet`} @@ -135,11 +252,24 @@ class InitialScreen extends React.Component { /> + ); } } +const mapInitialStateToProps = (state) => ({ + web3authEnabled: !!state.featureToggles[WEB3AUTH_FEATURE_TOGGLE], + hathorNetwork: state.networkSettings.network, +}); + +const ConnectedInitialScreen = connect(mapInitialStateToProps)(InitialScreen); + class NewWordsScreen extends React.Component { state = { words: walletUtils.generateWalletWords(hathorConstants.HD_WALLET_ENTROPY), @@ -392,5 +522,5 @@ class LoadWordsScreen extends React.Component { } export { - WelcomeScreen, InitialScreen, LoadWordsScreen, NewWordsScreen, + WelcomeScreen, ConnectedInitialScreen as InitialScreen, LoadWordsScreen, NewWordsScreen, }; diff --git a/src/screens/PinScreen.js b/src/screens/PinScreen.js index 8260dc72b..98ef00b3d 100644 --- a/src/screens/PinScreen.js +++ b/src/screens/PinScreen.js @@ -43,6 +43,7 @@ const log = logger('PIN_SCREEN'); const mapStateToProps = (state) => ({ loadHistoryActive: state.loadHistoryStatus.active, wallet: state.wallet, + walletType: state.walletType, }); const mapDispatchToProps = (dispatch) => ({ @@ -156,8 +157,19 @@ class PinScreen extends React.Component { // The handleDataMigration method ensures we have already migrated if necessary // This means the wallet is loaded and the access data is ready to be used. - const words = await STORE.getWalletWords(actualPin); - this.props.startWalletRequested({ words, pin: actualPin }); + if (this.props.walletType === 'web3auth') { + const privateKey = await STORE.getWeb3AuthPrivateKey(actualPin); + const publicKey = await STORE.getWeb3AuthPublicKey(); + this.props.startWalletRequested({ + privateKey, + publicKey, + pin: actualPin, + walletType: 'web3auth', + }); + } else { + const words = await STORE.getWalletWords(actualPin); + this.props.startWalletRequested({ words, pin: actualPin }); + } } this.props.unlockScreen(); } catch (e) { @@ -192,61 +204,51 @@ class PinScreen extends React.Component { validatePin = async (pin) => { try { - // Validate if we are able to decrypt the seed using this PIN - // this will throw if the words are not able to be decoded with this - // pin. - - // This will return either the old or the new access data. - // We can ignore which one it is since we will only use the words which is present on both. const { accessData } = await STORE.getAvailableAccessData(); if (!accessData) { - // The wallet does not have an access data, we can't unlock it - // This should not happen since we check if the wallet is initialized - // before showing the unlock screen, but we will handle it anyway this.props.onExceptionCaptured( - new Error( - "Attempted to unlock wallet but it wasn't initialized.", - ), - true, // Fatal since we can't start the wallet + new Error("Attempted to unlock wallet but it wasn't initialized."), + true, ); return; } - let wordsEncryptedData = accessData.words; - if (!accessData.words.data) { - // This is from a previous version - // We need aditional data to check pin - wordsEncryptedData = { - data: accessData.words, - hash: accessData.hashPasswd, - salt: accessData.saltPasswd, - iterations: accessData.hashIterations, - pbkdf2Hasher: accessData.pbkdf2Hasher, - }; - } - const pinCorrect = cryptoUtils.checkPassword(wordsEncryptedData, pin); - - if (!pinCorrect) { - this.removeOneChar(); - return; + if (accessData.singleKeyMode) { + // Web3Auth wallet: validate PIN against encrypted private key + const pinCorrect = cryptoUtils.checkPassword(accessData.singleKeyPrivateKey, pin); + if (!pinCorrect) { + this.removeOneChar(); + return; + } + } else { + // HD wallet: validate PIN against encrypted words (existing logic) + let wordsEncryptedData = accessData.words; + if (!accessData.words.data) { + wordsEncryptedData = { + data: accessData.words, + hash: accessData.hashPasswd, + salt: accessData.saltPasswd, + iterations: accessData.hashIterations, + pbkdf2Hasher: accessData.pbkdf2Hasher, + }; + } + const pinCorrect = cryptoUtils.checkPassword(wordsEncryptedData, pin); + if (!pinCorrect) { + this.removeOneChar(); + return; + } + const words = cryptoUtils.decryptData(wordsEncryptedData, pin); + walletUtils.wordsValid(words); } - - const words = cryptoUtils.decryptData(wordsEncryptedData, pin); - // Will throw InvalidWords if the seed is invalid - walletUtils.wordsValid(words); } catch (e) { this.props.onExceptionCaptured( - new Error( - "User inserted a valid PIN but the app wasn't able to decrypt the words", - ), - true, // Fatal since we can't start the wallet + new Error("User inserted a valid PIN but the app wasn't able to decrypt the data"), + true, ); - return; } - // Inserted PIN was able to decrypt the words successfully this.dismiss(pin); }; diff --git a/src/screens/ResetWallet.js b/src/screens/ResetWallet.js index c7de11462..0ccbd0152 100644 --- a/src/screens/ResetWallet.js +++ b/src/screens/ResetWallet.js @@ -22,13 +22,19 @@ import NewHathorButton from '../components/NewHathorButton'; import TextFmt from '../components/TextFmt'; import baseStyle from '../styles/init'; import { PRIMARY_COLOR } from '../constants'; +import { COLORS } from '../styles/themes'; import { dropResetOnLockScreen, resetWallet } from '../actions'; +import { isSingleKeyWallet } from '../selectors'; /** * isScreenLocked {bool} check if is in lock screen state + * isWeb3Auth {bool} check if wallet is web3auth type + * email {string} web3auth email address */ const mapStateToProps = (state) => ({ isScreenLocked: state.lockScreen, + isWeb3Auth: isSingleKeyWallet(state), + email: state.web3authEmail, }); const mapDispatchToProps = (dispatch) => ({ @@ -48,6 +54,40 @@ class ResetWallet extends React.Component { lineHeight: 18, flex: 1, }, + accountCard: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 8, + backgroundColor: COLORS.lowContrastDetail, + borderRadius: 8, + marginVertical: 12, + }, + avatar: { + width: 26, + height: 26, + borderRadius: 13, + backgroundColor: COLORS.primary, + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + avatarText: { + color: COLORS.white, + fontWeight: '700', + fontSize: 12, + }, + accountInfo: { + flex: 1, + }, + accountEmail: { + fontWeight: '700', + fontSize: 14, + }, + accountProvider: { + color: COLORS.midContrastDetail, + fontSize: 12, + }, }) }); willReset: false; @@ -104,34 +144,85 @@ class ResetWallet extends React.Component { title={t`RESET WALLET`} onBackPress={this.hideBackButton ? null : () => this.onBackPress()} /> - - {t`Are you sure?`} - - - {t`If you reset your wallet, **all data will be deleted**, and you will **lose access to your tokens**.`} - - {' '}{t`To recover access to your tokens, you will need to import your seed words again.`} - - - - - {t`I want to reset my wallet, and I acknowledge that **all data will be wiped out**.`} - + {this.props.isWeb3Auth ? this.renderWeb3Auth() : this.renderHd()} + + ); + } + + renderHd() { + return ( + + {t`Are you sure?`} + + + {t`If you reset your wallet, **all data will be deleted**, and you will **lose access to your tokens**.`} + + {' '}{t`To recover access to your tokens, you will need to import your seed words again.`} + + + + + {t`I want to reset my wallet, and I acknowledge that **all data will be wiped out**.`} + + + + + + + ); + } + + renderWeb3Auth() { + const initial = (this.props.email || '?')[0].toUpperCase(); + return ( + + {t`Sign out of your wallet?`} + {t`You're signed in as:`} + + + {initial} - - + + {this.props.email} + {t`via Google`} + + {t`Signing out will disconnect your account and erase local wallet data from this device.`} + + + + {t`You'll be able to sign back in with the same social account to restore access **as long as you still have your recovery factors**.`} + + + + + + {t`I understand local data will be erased, and I have my recovery factors saved.`} + + + + + ); } diff --git a/src/screens/Security.js b/src/screens/Security.js index 445b16c5c..c64ef1b6c 100644 --- a/src/screens/Security.js +++ b/src/screens/Security.js @@ -28,9 +28,11 @@ import { HathorList, ListItem, ListMenu } from '../components/HathorList'; import { lockScreen, onExceptionCaptured } from '../actions'; import { COLORS } from '../styles/themes'; import { SAFE_BIOMETRY_FEATURE_FLAG_KEY, STORE } from '../store'; +import { isSingleKeyWallet } from '../selectors'; const mapStateToProps = (state) => ({ wallet: state.wallet, + singleKey: isSingleKeyWallet(state), }); const mapDispatchToProps = (dispatch) => ({ diff --git a/src/screens/Settings.js b/src/screens/Settings.js index 43f35f97c..cbd10452d 100644 --- a/src/screens/Settings.js +++ b/src/screens/Settings.js @@ -28,6 +28,7 @@ import { COLORS } from '../styles/themes'; import { NetworkSettingsFlowNav } from './NetworkSettings'; import { isNanoContractsEnabled, isPushNotificationAvailableForUser } from '../utils'; import { getNetworkSettings } from '../sagas/helpers'; +import { isSingleKeyWallet } from '../selectors'; /** * selectedToken {Object} Select token config {name, symbol, uid} @@ -53,6 +54,7 @@ const mapStateToProps = (state) => { isPushNotificationAvailable: isPushNotificationAvailableForUser(state), reownEnabled: state.featureToggles[REOWN_FEATURE_TOGGLE] && isNanoContractsEnabled(state), networkSettingsEnabled: state.featureToggles[NETWORK_SETTINGS_FEATURE_TOGGLE], + singleKey: isSingleKeyWallet(state), }; }; @@ -151,7 +153,7 @@ export class Settings extends React.Component { onPress={() => this.props.navigation.navigate('RegisterToken')} /> )} - {this.props.reownEnabled + {this.props.reownEnabled && !this.props.singleKey && ( )} this.props.navigation.navigate('ResetWallet')} /> + this.props.navigation.goBack()} + /> + + {t`Set up recovery`} + + {t`To protect your wallet, you need to set up a recovery method. This ensures you can access your funds even if you lose this device.`} + + + { + this.props.navigation.navigate('ChoosePinScreen', this.props.route.params); + }} + title={t`Continue`} + /> + + + + ); + } +} diff --git a/src/selectors.js b/src/selectors.js new file mode 100644 index 000000000..3ba213351 --- /dev/null +++ b/src/selectors.js @@ -0,0 +1,16 @@ +/** + * 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. + */ + +/** + * Returns true if the current wallet is a Web3Auth single-key wallet. + * Use this selector everywhere you need to conditionally hide/show UI + * or branch logic for single-key vs HD wallets. + * + * @param {Object} state - Redux state + * @returns {boolean} + */ +export const isSingleKeyWallet = (state) => state.walletType === 'web3auth'; diff --git a/src/store.js b/src/store.js index 3c6051d3d..7ed6408d5 100644 --- a/src/store.js +++ b/src/store.js @@ -40,6 +40,8 @@ export const cleanOnWalletReset = [ PIN_BACKUP_KEY, IS_BIOMETRY_ENABLED_KEY, IS_OLD_BIOMETRY_ENABLED_KEY, + 'web3auth:walletType', + 'web3auth:email', ]; /* eslint-disable class-methods-use-this */ @@ -337,6 +339,22 @@ class AsyncStorageStore { await storage.saveAccessData(accessData); } + /** + * Generate accessData for a Web3Auth single-key wallet and prepare storage. + * @param {string} privateKey - 32-byte hex raw secp256k1 private key + * @param {string} publicKey - 33-byte compressed public key hex + * @param {string} pin - User PIN for encryption + */ + async initWeb3AuthStorage(privateKey, publicKey, pin) { + const accessData = walletUtils.generateAccessDataFromPrivateKey( + privateKey, + publicKey, + { pin }, + ); + const storage = this.getStorage(); + await storage.saveAccessData(accessData); + } + /** * Get access data of loaded wallet from async storage. * @@ -395,6 +413,32 @@ class AsyncStorageStore { return cryptoUtils.decryptData(accessData.words, pin); } + /** + * Get the decrypted private key for a Web3Auth single-key wallet. + * @throws {Error} If the private key cannot be decrypted. + * @param {string} pin + * @returns {Promise} Hex-encoded raw private key. + */ + async getWeb3AuthPrivateKey(pin) { + const accessData = await this._getAccessData(); + if (!accessData || !accessData.singleKeyPrivateKey) { + return null; + } + return cryptoUtils.decryptData(accessData.singleKeyPrivateKey, pin); + } + + /** + * Get the public key for a Web3Auth single-key wallet. + * @returns {Promise} Hex-encoded compressed public key. + */ + async getWeb3AuthPublicKey() { + const accessData = await this._getAccessData(); + if (!accessData || !accessData.singleKeyPublicKey) { + return null; + } + return accessData.singleKeyPublicKey; + } + /** * Get old wallet words if possible. * diff --git a/web3_auth_status.md b/web3_auth_status.md new file mode 100644 index 000000000..9092a0f80 --- /dev/null +++ b/web3_auth_status.md @@ -0,0 +1,175 @@ +--- +name: Web3Auth Mobile Implementation +description: Status tracker for Web3Auth single-key wallet implementation on wallet-mobile +last_updated: 2026-04-17T17:30 +--- + +# Web3Auth Mobile Implementation Status + +## RFCs de referencia + +| # | Tipo | Link | Escopo | +|---|------|------|--------| +| 0 | Foundation RFC | HathorNetwork/rfcs#106 | O que e Web3Auth + setup operacional | +| 1 | Design RFC | HathorNetwork/internal-rfcs#46 | wallet-lib single-key mode + mobile onboarding UI | +| 2 | PoC | HathorNetwork/hathor-wallet-lib#1062 | Implementacao na lib + testes | +| 3 | Design RFC | HathorNetwork/internal-rfcs#47 | Wallet-service support (stacked on #46) | + +## Setup + +- **Worktree:** `/Users/rauloliveira/git/hathor/wallet-mobile-web3auth` (branch `feat/web3auth`) +- **Wallet-lib PoC:** `/Users/rauloliveira/git/hathor/hathor-wallet-lib` (branch `feat/web3auth-single-key-poc`, yalc linked) +- **Design doc:** `docs/plans/2026-04-17-web3auth-mobile-design.md` +- **Implementation plan:** `docs/plans/2026-04-17-web3auth-mobile-implementation.md` + +## Implementacao concluida (15 tasks) + +Todas as tasks do plano de implementacao foram executadas por subagents: + +| Task | Arquivo(s) | Status | +|------|-----------|--------| +| 1. Install deps | package.json | Done - `@web3auth/react-native-sdk@8.1.0`, `@web3auth/base@9.7.0`, `@toruslabs/react-native-web-browser@1.1.0` | +| 2. Constants/toggle | src/constants.js | Done - `WEB3AUTH_FEATURE_TOGGLE`, client ID/secret, storage keys, redirect URL | +| 3. Redux state | src/actions.js, src/reducers/reducer.js, src/selectors.js | Done - `walletType`, `web3authEmail`, `isSingleKeyWallet()` | +| 4. Store methods | src/store.js | Done - `initWeb3AuthStorage`, `getWeb3AuthPrivateKey`, `getWeb3AuthPublicKey` | +| 5. Web3Auth saga | src/sagas/web3auth.js | Done - login, key derivation, state mgmt | +| 6. startWallet saga | src/sagas/wallet.js | Done - web3auth branch, force-disable wallet-service, external signer | +| 7. Reset wallet | src/sagas/wallet.js | Done - `web3auth.logout()` + cleanup | +| 8. PinScreen | src/screens/PinScreen.js | Done - unlock/validatePin web3auth branch | +| 9. ChoosePinScreen | src/screens/ChoosePinScreen.js | Done - web3auth params, `initWeb3AuthStorage` | +| 10. InitialScreen | src/screens/InitWallet.js | Done - social login icons (G, @, A) | +| 11. ReceiveMyAddress | src/components/ReceiveMyAddress.js | Done - hide "New address" | +| 12. Settings/Security | src/screens/Settings.js, Security.js | Done - hide Reown, add "Sign out" | +| 13. App startup | src/App.js | Done - restore walletType/email from AsyncStorage | +| 14. Recovery screen | src/screens/Web3AuthRecoveryScreen.js, App.js | Done - placeholder | +| 15. URL schemes | android/AndroidManifest.xml, ios/Info.plist | Done - already existed | + +## Dependencias adicionadas durante debugging + +```bash +npm install react-native-encrypted-storage @web3auth/base-provider +``` + +- `react-native-encrypted-storage` — required como 2o argumento do constructor Web3Auth v8 +- `@web3auth/base-provider` — `CommonPrivateKeyProvider` para chains non-EVM + +## Correcoes feitas durante debugging (v7 -> v8 API) + +| Problema | Correcao | +|----------|----------| +| `OPENLOGIN_NETWORK` nao existe na v8 | Trocado por `WEB3AUTH_NETWORK` de `@web3auth/react-native-sdk` | +| `LOGIN_PROVIDER` undefined de `@web3auth/react-native-sdk` | Import de `@web3auth/auth` (nao `@web3auth/base`, que nao exporta) | +| Constructor com 2 args | Corrigido para 3 args: `new Web3Auth(WebBrowser, EncryptedStorage, options)` | +| Falta `privateKeyProvider` | Adicionado `CommonPrivateKeyProvider` com `CHAIN_NAMESPACES.OTHER` | +| Falta `init()` antes de `login()` | Adicionado `await web3authInstance.init()` | +| `login()` retorna `{ privKey }` (v7) | Corrigido: v8 retorna `IProvider`, usar `provider.request({ method: 'private_key' })` | +| `userInfo` no resultado do login | Corrigido: v8 usa `web3auth.userInfo()` (metodo da instancia) | +| `redirectUrl` so no login | Movido para as options do constructor (required em v8) | +| Provider criado no top-level | Movido para lazy init dentro de `getWeb3AuthInstance()` | + +## BLOCKER RESOLVIDO: "Invalid y value for curve" + +### Sintoma +Ao clicar no icone Google no InitialScreen, o Web3Auth SDK crashava com: +``` +Error: Invalid y value for curve. +TypeError: Cannot read property 'LOGIN_PROVIDER' of undefined +``` + +### Causa raiz (atualizada 2026-04-17) + +**Duas causas independentes, ambas resolvidas:** + +#### Causa 1: rn-nodeify crypto hack (LOGIN_PROVIDER) +O `rn-nodeify --hack` injeta `"crypto": "react-native-crypto"` nos `package.json` de TODOS os pacotes. Isso corrompe o barrel export de `@web3auth/auth` — submodulos como `starkey` dependem de `@toruslabs/starkware-crypto` → `elliptic` → `brorand`, que recebe o polyfill quebrado. O modulo inteiro falha ao carregar, resultando em `LOGIN_PROVIDER === undefined`. + +**Fix:** Remover o hack de 19 pacotes (`@toruslabs/*`, `@web3auth/*`, `elliptic`, `brorand`, `hash.js`, `hmac-drbg`). + +#### Causa 2: bitcore-lib prototype pollution (Invalid y value) +`bitcore-lib/lib/crypto/point.js` faz prototype pollution no `elliptic`: +```js +Point.prototype = Object.getPrototypeOf(ec.curve.point()); // shared prototype! +Point.prototype.validate = function() { /* THROWS instead of returning boolean */ }; +``` +Isso substitui `elliptic`'s `BasePoint.prototype.validate` (que retorna boolean) por uma versao que **throws**. Quando o Web3Auth SDK cria EC points internamente via `@toruslabs/session-manager` → `@toruslabs/eccrypto` → `elliptic`, o `elliptic/ec/key.js:45` chama `pub.validate()` esperando boolean, mas recebe a versao do bitcore-lib que throws. + +**Fix:** Em `shim.js`, salvar o `validate` nativo do `elliptic`, deixar `bitcore-lib` carregar (poluindo o prototype), depois restaurar o nativo. O `validate` do bitcore e salvo em `global.__bitcorePointValidate` e chamado explicitamente nos 3 callsites internos do bitcore-lib. + +### Detalhes da investigacao +Ver `debug_investigation.md` para o passo-a-passo completo da investigacao, incluindo como o prototype pollution foi identificado usando variaveis globais de debug. + +### Fixes aplicados + +1. **Remocao do crypto hack** — script Python que remove `"crypto": "react-native-crypto"` dos campos `react-native`/`browser` de 19 pacotes +2. **Restauracao do validate nativo** — em `shim.js`, apos `require('bitcore-lib')`, restaura `BasePoint.prototype.validate` do `elliptic` +3. **Validate explicito no bitcore-lib** — `point.js` e `publickey.js` chamam `global.__bitcorePointValidate` diretamente + +### Permanencia dos fixes +**Automatizada via `npm run setup`.** A ordem do pipeline agora e: + +``` +npm install -> allow-scripts -> rn-nodeify --hack + -> node scripts/fix-web3auth-crypto-hack.js (limpa crypto hack dos 20 pacotes Web3Auth) + -> npx patch-package (aplica bitcore-lib + outros 5 patches) +``` + +Arquivos: +- `scripts/fix-web3auth-crypto-hack.js` - cleanup data-driven dos pacotes Web3Auth +- `patches/bitcore-lib+8.25.10.patch` - patch dos 3 callsites de `validate()` + +Ver `explicacao-hacky-web3-auth.md` para o motivo do hack e o impacto no Web3Auth. + +### Proximo blocker +`RNEncryptedStorage is undefined` — o modulo nativo `react-native-encrypted-storage` nao esta linkado. Precisa de `pod install` e rebuild. + +## Mudancas temporarias (reverter antes de merge) + +| Arquivo | Mudanca | Reverter | +|---------|---------|----------| +| src/constants.js:204 | `WEB3AUTH_FEATURE_TOGGLE: true` | Mudar para `false` | +| src/screens/InitWallet.js:169-170 | `{true && (` hardcoded | Restaurar `{this.props.web3authEnabled && (` | +| src/screens/InitWallet.js:148 | `alert()` no catch | Remover ou trocar por log | +| src/screens/InitWallet.js:207-211 | `console.log` no mapStateToProps | Remover | +| index.js:10-11 | `LogBox.ignoreAllLogs(true)` | Remover | +| src/sagas/web3auth.js:55 | `SAPPHIRE_DEVNET` | Trocar para `SAPPHIRE_MAINNET` em prod | + +## Skills criadas + +| Skill | Path | Escopo | +|-------|------|--------| +| worktree-env-replication | `~/.claude/skills/worktree-env-replication/` | Global - replica .claude/settings do repo para worktrees | +| worktree-preparation | `hathor-wallet-mobile/.claude/skills/worktree-preparation/` | Projeto - steps para preparar worktree da wallet-mobile | + +## Comandos uteis + +```bash +# Atualizar wallet-lib +cd /Users/rauloliveira/git/hathor/hathor-wallet-lib && npm run build && yalc push + +# Reiniciar Metro +cd /Users/rauloliveira/git/hathor/wallet-mobile-web3auth +lsof -ti:8081 | xargs kill -9; npx react-native start --reset-cache + +# Verificar logs do simulador +xcrun simctl spawn booted log show --predicate 'process == "HathorMobile"' --last 30s --style compact | grep -iE "error|javascript" + +# Limpar hacks do rn-nodeify nos pacotes web3auth +for pkg in $(find node_modules/@toruslabs node_modules/@web3auth -name "package.json" -maxdepth 2); do + python3 -c " +import json +with open('$pkg', 'r') as f: + d = json.load(f) +rn = d.get('react-native', {}) +if isinstance(rn, dict) and 'crypto' in rn: + del rn['crypto'] + d['react-native'] = rn + with open('$pkg', 'w') as f: + json.dump(d, f, indent=2) + print('Fixed: $pkg') +" +done + +# MCP mobile - device ID +# iPhone 16: D49EDFFA-658B-4023-9EC5-840AF31ACA7D +# App bundle: network.hathor.wallet +``` diff --git a/web3auth_report.md b/web3auth_report.md new file mode 100644 index 000000000..273fa2919 --- /dev/null +++ b/web3auth_report.md @@ -0,0 +1,302 @@ +# Web3Auth — Mudancas em node_modules + +Este documento lista TODAS as mudancas feitas em `node_modules/` durante o debugging do Web3Auth. + +**Status: AUTOMATIZADO.** As mudancas sao reaplicadas automaticamente pelo `npm run setup` via: +- `scripts/fix-web3auth-crypto-hack.js` (cleanup do crypto hack nos 20 pacotes Web3Auth) +- `patches/bitcore-lib+8.25.10.patch` (patches dos 3 callsites de `validate()`) + +Este documento permanece como referencia historica do debugging. Para o motivo do `rn-nodeify --hack` e seu impacto no Web3Auth, ver `explicacao-hacky-web3-auth.md`. + +--- + +## 1. Remocao do crypto hack do rn-nodeify (19 pacotes) + +O `rn-nodeify --hack` (executado por `npm run setup`) injeta `"crypto": "react-native-crypto"` nos campos `react-native` e `browser` do `package.json` de TODOS os pacotes em `node_modules/`. Isso precisa ser removido dos pacotes do ecosistema Web3Auth para que o barrel export de `@web3auth/auth` nao crashe ao carregar. + +### Pacotes afetados + +| # | Pacote | Campo `react-native` | Campo `browser` | +|---|--------|---------------------|-----------------| +| 1 | `@toruslabs/base-controllers` | remover `"crypto"` | remover `"crypto"` | +| 2 | `@toruslabs/broadcast-channel` | remover `"crypto"` | remover `"crypto"` | +| 3 | `@toruslabs/constants` | remover `"crypto"` | remover `"crypto"` | +| 4 | `@toruslabs/eccrypto` | remover `"crypto"` | remover `"crypto"` | +| 5 | `@toruslabs/ffjavascript` | remover `"crypto"` | remover `"crypto"` | +| 6 | `@toruslabs/http-helpers` | remover `"crypto"` | remover `"crypto"` | +| 7 | `@toruslabs/metadata-helpers` | remover `"crypto"` | remover `"crypto"` | +| 8 | `@toruslabs/react-native-web-browser` | remover `"crypto"` | remover `"crypto"` | +| 9 | `@toruslabs/secure-pub-sub` | remover `"crypto"` | remover `"crypto"` | +| 10 | `@toruslabs/session-manager` | remover `"crypto"` | remover `"crypto"` | +| 11 | `@toruslabs/starkware-crypto` | remover `"crypto"` | remover `"crypto"` | +| 12 | `@toruslabs/tweetnacl-js` | remover `"crypto"` | remover `"crypto"` | +| 13 | `@web3auth/auth` | remover `"crypto"` | remover `"crypto"` | +| 14 | `@web3auth/base` | remover `"crypto"` | remover `"crypto"` | +| 15 | `@web3auth/react-native-sdk` | remover `"crypto"` | remover `"crypto"` | +| 16 | `elliptic` | remover `"crypto"` | remover `"crypto"` | +| 17 | `brorand` | remover `"crypto"` | remover `"crypto"` | +| 18 | `hash.js` | remover `"crypto"` | remover `"crypto"` | +| 19 | `hmac-drbg` | remover `"crypto"` | remover `"crypto"` | + +### Script para reaplicar + +```python +import json, os + +targets_scoped = ['@toruslabs', '@web3auth'] +targets_flat = ['elliptic', 'brorand', 'hash.js', 'hmac-drbg'] + +for scope in targets_scoped: + base = f'node_modules/{scope}' + if not os.path.isdir(base): + continue + for pkg in os.listdir(base): + pjson = os.path.join(base, pkg, 'package.json') + if not os.path.exists(pjson): + continue + with open(pjson) as f: + d = json.load(f) + changed = False + for field in ['react-native', 'browser']: + v = d.get(field, {}) + if isinstance(v, dict) and 'crypto' in v: + del v['crypto'] + d[field] = v + changed = True + if changed: + with open(pjson, 'w') as f: + json.dump(d, f, indent=2) + f.write('\n') + print(f'Fixed: {scope}/{pkg}') + +for target in targets_flat: + pjson = f'node_modules/{target}/package.json' + if not os.path.exists(pjson): + continue + with open(pjson) as f: + d = json.load(f) + changed = False + for field in ['react-native', 'browser']: + v = d.get(field, {}) + if isinstance(v, dict) and 'crypto' in v: + del v['crypto'] + d[field] = v + changed = True + if changed: + with open(pjson, 'w') as f: + json.dump(d, f, indent=2) + f.write('\n') + print(f'Fixed: {target}') +``` + +--- + +## 2. bitcore-lib/lib/crypto/point.js + +### Motivo +`bitcore-lib` faz prototype pollution no `elliptic`: substitui `BasePoint.prototype.validate` (que retorna boolean) por uma versao que **throws**. Isso quebra o Web3Auth SDK porque `elliptic/ec/key.js:45` chama `pub.validate()` esperando boolean. + +### Mudanca +Nos 2 callsites que chamam `point.validate()`, substituir por chamada explicita a `global.__bitcorePointValidate` (salvo pelo `shim.js`). + +### Diff + +```diff +--- a/node_modules/bitcore-lib/lib/crypto/point.js ++++ b/node_modules/bitcore-lib/lib/crypto/point.js +@@ -23,11 +23,15 @@ var Point = function Point(x, y, isRed) { + } catch (e) { + throw new Error('Invalid Point'); + } +- point.validate(); ++ // Use bitcore's strict validate if available (saved from prototype before restore) ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(point); ++ } else { ++ point.validate(); ++ } + return point; + }; + +@@ -50,7 +54,13 @@ Point.fromX = function fromX(odd, x){ + } catch (e) { + throw new Error('Invalid X'); + } +- point.validate(); ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(point); ++ } else { ++ point.validate(); ++ } + return point; + }; +``` + +--- + +## 3. bitcore-lib/lib/publickey.js + +### Motivo +Mesmo que acima — `publickey.js` tambem chama `point.validate()` diretamente. + +### Diff + +```diff +--- a/node_modules/bitcore-lib/lib/publickey.js ++++ b/node_modules/bitcore-lib/lib/publickey.js +@@ -50,7 +50,13 @@ PublicKey = function PublicKey(data, extra) { + var info = this._classifyArgs(data, extra); + + // validation +- info.point.validate(); ++ var strictValidate = global.__bitcorePointValidate; ++ if (strictValidate) { ++ strictValidate.call(info.point); ++ } else { ++ info.point.validate(); ++ } + + JSUtil.defineImmutable(this, { +``` + +--- + +## 4. @toruslabs/eccrypto/dist/lib.esm/index.js (APENAS DEBUG — remover) + +### Mudanca +Adicionado `console.log` na linha 6 para debug. **Deve ser removido** — nao e necessario para o fix. + +### Diff + +```diff +--- a/node_modules/@toruslabs/eccrypto/dist/lib.esm/index.js ++++ b/node_modules/@toruslabs/eccrypto/dist/lib.esm/index.js +@@ -3,6 +3,7 @@ + const ec = new ec$1("secp256k1"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins + const browserCrypto = globalThis.crypto || globalThis.msCrypto || {}; ++console.log('[ECCRYPTO] globalThis.crypto exists:', !!globalThis.crypto, 'createHash:', typeof browserCrypto.createHash, 'subtle:', typeof (browserCrypto.subtle || browserCrypto.webkitSubtle)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; +``` + +--- + +## 5. bitcore-lib/bitcore-lib.js (REVERTER) + +### Mudanca +Tinha um patch de debug (Alert) na linha 27445. Existe um `.bak` do original. + +### Como reverter + +```bash +mv node_modules/bitcore-lib/bitcore-lib.js.bak node_modules/bitcore-lib/bitcore-lib.js +``` + +--- + +## Resumo de acoes — TODAS AUTOMATIZADAS + +| Acao | Mecanismo | Localizacao | +|------|-----------|-------------| +| Remocao do crypto hack dos 20 pacotes | Script Node | `scripts/fix-web3auth-crypto-hack.js` | +| Patches do bitcore-lib (3 callsites) | patch-package | `patches/bitcore-lib+8.25.10.patch` | +| Debug do eccrypto (secao 4) | Removido (nao automatizado, era so debug) | n/a | +| Reverter bitcore-lib.js bundle (secao 5) | Restaurado para pristine; rn-nodeify nao toca o bundle | n/a | + +Tudo encadeado no `npm run setup` apos o `rn-nodeify --hack`. + +### Mudancas no projeto (ja aplicadas, persistentes): + +| Arquivo | Mudanca | +|---------|---------| +| `shim.js` | Prototype pollution fix + exposicao de `globalThis.crypto` methods | +| `metro.config.js` | Removido `resolveRequest` que bloqueava crypto para web3auth | +| `index.js` | Debug handler removido (limpo) | + +--- + +## Como auditar este documento + +### 1. Verificar os 19 pacotes com crypto hack + +Apos um `npm install` + `npm run setup` limpo (ANTES de aplicar o fix), rodar: + +```bash +python3 -c " +import json, os +for scope in ['@toruslabs', '@web3auth']: + base = f'node_modules/{scope}' + if not os.path.isdir(base): continue + for pkg in sorted(os.listdir(base)): + pjson = os.path.join(base, pkg, 'package.json') + if not os.path.exists(pjson): continue + with open(pjson) as f: + d = json.load(f) + for field in ['react-native', 'browser']: + v = d.get(field, {}) + if isinstance(v, dict) and 'crypto' in v: + print(f'{scope}/{pkg} [{field}]') +for t in ['elliptic','brorand','hash.js','hmac-drbg']: + pjson = f'node_modules/{t}/package.json' + if os.path.exists(pjson): + with open(pjson) as f: + d = json.load(f) + for field in ['react-native', 'browser']: + v = d.get(field, {}) + if isinstance(v, dict) and 'crypto' in v: + print(f'{t} [{field}]') +" +``` + +A lista de saida deve bater exatamente com os 19 pacotes da secao 1. + +### 2. Verificar os diffs do bitcore-lib + +Comparar com a versao original do npm: + +```bash +cd /tmp +npm pack bitcore-lib 2>/dev/null +tar xzf bitcore-lib-*.tgz +echo "=== point.js ===" +diff package/lib/crypto/point.js /Users/rauloliveira/git/hathor/wallet-mobile-web3auth/node_modules/bitcore-lib/lib/crypto/point.js +echo "=== publickey.js ===" +diff package/lib/publickey.js /Users/rauloliveira/git/hathor/wallet-mobile-web3auth/node_modules/bitcore-lib/lib/publickey.js +rm -rf package bitcore-lib-*.tgz +``` + +As unicas diferencas devem ser as substituicoes de `point.validate()` por `global.__bitcorePointValidate` — conforme os diffs das secoes 2 e 3. + +### 3. Verificar que o shim.js complementa os patches + +O `shim.js` e responsavel por salvar/restaurar o validate e expor crypto methods. Verificar: + +```bash +grep -n "bitcorePointValidate\|nativeValidate\|bitcoreValidate\|globalThis.crypto" shim.js +``` + +Deve mostrar: +- Salvar validate nativo do elliptic +- Carregar bitcore-lib (que polui o prototype) +- Salvar validate do bitcore-lib em `global.__bitcorePointValidate` +- Restaurar validate nativo no prototype +- Expor methods do crypto polyfill em `globalThis.crypto` + +### 4. Verificar debug residual (deve ser removido) + +```bash +# Nao deve existir console.log nosso no eccrypto: +grep "ECCRYPTO" node_modules/@toruslabs/eccrypto/dist/lib.esm/index.js + +# Nao deve existir .bak: +ls node_modules/bitcore-lib/bitcore-lib.js.bak 2>/dev/null && echo "REVERTER: mv .bak para .js" +``` + +### 5. Teste funcional + +Apos aplicar todas as mudancas: +1. App deve carregar sem erros na tela de login (sem "Invalid y value", sem "LOGIN_PROVIDER undefined") +2. Clicar no Google deve avancar para o fluxo de login (ou mostrar erro esperado de autenticacao, NAO erro de crypto)