feat: Web3Auth social login (Google + Apple) - #872
Conversation
Adds a third option on Welcome → WalletType ("Sign in with social") that
bootstraps a single-key Hathor wallet from a Web3Auth-derived private key,
delegating OAuth + mandatory MFA to the Web3Auth-hosted webview opened in
an Electron BrowserWindow popup.
Scope (PR1 of three):
- Web3Auth no-modal SDK + auth-adapter, lazy singleton in sagas/web3auth.js.
- Network-aware config (testnet Sapphire Devnet today; mainnet placeholder).
- Reuses the Hathor custom verifier (hathor-google) so the derived key is
the same across desktop and mobile. Apple gated on appleClientId.
- New Web3AuthLogin screen (Google + Apple + Email cards; Email inert until
the JWT verifier ships in PR3).
- ChoosePin (Signin) Web3Auth branch persists encrypted privateKey via
LOCAL_STORE.initWeb3AuthStorage; subsequent unlocks are PIN-only.
- startWallet saga branch constructs HathorWallet({privateKey, scanPolicy:
SINGLE_ADDRESS}), forces useWalletService=false, registers external
signer.
- LockedWallet, reset, Reown, Navigation, Settings, WalletAddress gated on
isSingleKeyWallet selector so single-key UI invariants hold.
- Web3AuthErrorDialog classifies SDK errors into 7 buckets with localized
copy and a Try-again affordance.
- Electron main process whitelists Web3Auth + Google + Apple OAuth hosts via
setWindowOpenHandler with sandboxed contextIsolation partition.
- Webpack node:crypto + webcrypto polyfill (NodeProtocolUrlPlugin + custom
ESM crypto shim) — required by the Web3Auth SDK chain.
- Gated by web3auth-desktop.rollout Unleash flag, default false.
- QA scenarios documented in qa/QA.md (10 cases).
Wallet-lib: this PR currently consumes @hathor/wallet-lib via yalc from
HathorNetwork/hathor-wallet-lib#1093. It MUST switch to the published
@hathor/wallet-lib@^3.2.0 before merge.
Spec: docs/superpowers/specs/2026-05-22-web3auth-desktop-design.md
Plan: docs/superpowers/plans/2026-05-22-web3auth-pr1-google-apple-login.md
📝 WalkthroughWalkthroughThis PR integrates Web3Auth social login into the Hathor Desktop Wallet, enabling users to sign in via Google or Apple accounts instead of managing seed phrases. The implementation includes webpack configuration for Web3Auth SDK compatibility, a new authentication flow with error handling, Redux state management for wallet type and email, storage initialization for single-key wallets, login and recovery screens, app routing integration, feature gating of advanced features for single-key wallets, and hardened security policies for OAuth popups and bundled dependencies. ChangesWeb3Auth Integration
Sequence Diagram(s)sequenceDiagram
participant User
participant WalletType
participant Web3AuthLogin
participant Web3AuthSDK as Web3Auth SDK
participant Signin
participant HathorWallet as Wallet Storage
participant App
User->>WalletType: Click "Sign in with social"
WalletType->>Web3AuthLogin: Navigate to /web3auth_login/
User->>Web3AuthLogin: Select provider (Google/Apple)
Web3AuthLogin->>Web3AuthSDK: web3authLogin(provider)
Web3AuthSDK->>User: OAuth popup (isolated partition)
User->>Web3AuthSDK: Authenticate, approve MFA
Web3AuthSDK->>Web3AuthLogin: Return privateKey & email
Web3AuthLogin->>Web3AuthLogin: Derive publicKey, persistWeb3AuthState
Web3AuthLogin->>Signin: Navigate with privateKey, publicKey, email
Signin->>User: Request PIN & password
User->>Signin: Enter PIN & password
Signin->>HathorWallet: initWeb3AuthStorage(privateKey, publicKey, pin, password)
HathorWallet->>Signin: Storage initialized
Signin->>App: startWalletRequested({walletType: 'web3auth'})
App->>App: Wallet unlocked, address mode forced to SINGLE
App->>User: Show wallet home, hide nano/atomic-swap nav
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (4)
src/sagas/web3auth.js (2)
174-176: 💤 Low valueThe
The fallback chain
userInfo?.email || userInfo?.name || ''meansWEB3AUTH_EMAIL_KEYstorage andweb3authEmailRedux state, which could confuse users if displayed in the UI as an email address.Consider either:
- Renaming to
userIdentifierto reflect the actual semantics, or- Only storing a value when
userInfo?.emailis truthy♻️ Option 2: Only persist actual email
const userInfo = await web3auth.getUserInfo(); - const email = userInfo?.email || userInfo?.name || ''; + const email = userInfo?.email || ''; const privateKey = await web3auth.provider.request({ method: 'private_key' });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/sagas/web3auth.js` around lines 174 - 176, The current fallback assigns userInfo?.name into the email variable, causing non-email values to be stored and dispatched; change the logic so email is only set from userInfo?.email (remove the fallback to userInfo?.name) and only write to WEB3AUTH_EMAIL_KEY and dispatch the web3authEmail Redux action when that email value is truthy, or alternatively rename the variable and related storage/dispatch keys to userIdentifier/web3authIdentifier if you intend to keep the display name; update references around userInfo, email, WEB3AUTH_EMAIL_KEY, and web3authEmail accordingly.
23-68: 💤 Low valueError classification relies on string matching.
The approach is documented as necessary given SDK limitations, but new SDK versions may introduce different error message patterns. Consider adding structured logging when
UNKNOWNis returned to surface unrecognized patterns for future classification refinement.🔍 Suggested enhancement for observability
+ // Log unclassified errors to help identify new patterns for future SDK updates + if (result === WEB3AUTH_ERROR_TYPES.UNKNOWN && err) { + // eslint-disable-next-line no-console + console.warn('[web3auth] Unclassified error pattern:', msg); + } return WEB3AUTH_ERROR_TYPES.UNKNOWN; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/sagas/web3auth.js` around lines 23 - 68, classifyWeb3AuthError currently falls back to WEB3AUTH_ERROR_TYPES.UNKNOWN for unrecognized messages but doesn't surface the raw pattern; update classifyWeb3AuthError to emit structured observability data whenever it will return UNKNOWN: log the original error object, its stack (if present), and the normalized msg string so new patterns can be triaged and added later. Use the existing application logger (or console as a fallback) and include clear fields like errorType: 'web3auth_unclassified', classified: 'unknown', rawError, stack, and normalizedMessage before returning WEB3AUTH_ERROR_TYPES.UNKNOWN to make future classification straightforward.qa/QA.md (1)
65-74: 💤 Low valueConsider splitting the alternate verification into a separate note.
Scenario 7, step 4 embeds a conditional verification ("But if the user had NOT signed out...") within the main test flow. This makes the scenario harder to execute linearly. Consider moving this alternate case to a separate note or sub-scenario for clarity.
📝 Optional refactor
3. Open the app — the WalletType screen is reachable, and the `Sign in with social` button is NOT shown. -4. Sign in with social again is not possible. But if the user had NOT signed out, the - LockedWallet screen would still be reachable and the wallet could be unlocked with - PIN. (Verify by retesting with a wallet that was not signed out.) +4. Sign in with social again is not possible. + +**Note:** If the feature toggle is disabled while a Web3Auth wallet is locked (not signed out), the LockedWallet screen remains reachable and the wallet can still be unlocked with PIN. Verify this separately by locking without signing out before disabling the toggle.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@qa/QA.md` around lines 65 - 74, Split the conditional verification embedded in Scenario 7 step 4 into a separate note or sub-scenario: remove the parenthetical sentence starting "But if the user had NOT signed out..." from the main flow of "Scenario 7 — Feature toggle off (with existing wallet)" and add a new sub-scenario or note titled e.g. "Alternate: wallet not signed out" that describes retesting the LockedWallet screen and unlocking with PIN; reference the same artifacts (WalletType screen, LockedWallet screen, and the web3auth-desktop.rollout toggle) so the alternate path is executed independently and the main scenario remains linear.lavamoat/webpack/policy.json (1)
166-173: 💤 Low valueReassess
$root$for@noble/hashes—it’s scoped and likely generator-driven
- In
lavamoat/webpack/policy.json,@web3auth/...>@noble/curves>@noble/hashesis the only `@noble/hashes` entry that gets `"$root$": true`; the direct `@web3auth/...>`@noble/hashesentry only getsTextEncoder.config-overrides.jsenablesnew LavaMoatPlugin({ generatePolicy: true, ... })in production and doesn’t referencepolicy-override.json, so this$root$permission is very likely coming from LavaMoat’s policy generation rather than a manual override.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lavamoat/webpack/policy.json` around lines 166 - 173, The policy currently grants "$root$": true for the chain "`@web3auth/base`>`@web3auth/auth`>`@ethereumjs/util`>ethereum-cryptography>`@noble/curves`>`@noble/hashes`" in lavamoat/webpack/policy.json—reassess and remove or tighten this "$root$" package permission and instead enumerate only required package keys (or set more restrictive package scopes) for the `@noble/hashes` entry; if the "$root$" was introduced by LavaMoat’s generator, update config-overrides.js (LavaMoatPlugin generatePolicy) or add an explicit policy-override to prevent generator from adding global root access, then regenerate the policy via the LavaMoatPlugin build to verify the "`@web3auth/`...>`@noble/hashes`" entry only has the minimal "globals": { "TextEncoder": true } or the intended restricted packages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@locale/texts.pot`:
- Around line 3185-3188: The message for WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG in
Web3AuthErrorDialog.js currently implies the Hathor team was notified but only
issues console.warn; either remove/soften that claim in the i18n string
(texts.pot) so it doesn't promise notification, or emit an explicit Sentry event
when the dialog is shown (call Sentry.captureException or Sentry.captureMessage
with contextual info) from the Web3AuthErrorDialog component after logging the
console.warn; update the string and/or add the
Sentry.captureMessage(SOME_DESCRIPTIVE_TAGGED_MESSAGE, { extra: { errorType:
WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG, ... } }) call to ensure an event is
recorded when Sentry is initialized.
In `@package.json`:
- Around line 48-51: package.json currently mixes Web3Auth 9.x packages
("`@web3auth/auth-adapter`","`@web3auth/base`","`@web3auth/base-provider`") with an
8.x "`@web3auth/no-modal`", causing peer-dep/runtime conflicts; fix by aligning
major versions: update the "`@web3auth/no-modal`" dependency to a 9.x release that
matches the others (or alternatively downgrade the 9.x entries to 8.x if you
must stay on v8), then run install and verify no peer dependency warnings and
that the login flow using the auth adapter/base/base-provider works as expected;
target the same major (e.g., set "`@web3auth/no-modal`" to "^9.7.0" to match
"`@web3auth/base`" and "`@web3auth/auth-adapter`").
- Line 41: Replace the local yalc dependency for `@hathor/wallet-lib` in
package.json (currently "file:.yalc/@hathor/wallet-lib") with the published
semver version "^3.1.1", then run npm install (or npm ci after removing
node_modules) to regenerate package-lock.json so it no longer references .yalc;
also search package-lock.json and the repo for any remaining
".yalc/@hathor/wallet-lib" entries and remove/replace them with
"`@hathor/wallet-lib`": "^3.1.1" to ensure clean reproducible installs.
In `@qa/QA.md`:
- Line 98: Replace the HTML-encoded placeholder `<email>` in QA.md with a
proper Markdown-safe representation: either plain angle brackets `<email>` or,
preferably for inline code style, use backticks `` `<email>` ``; update the
occurrence of `<email>` so the Markdown renders correctly and search for
other HTML-encoded entities to fix similarly.
In `@src/index.module.scss`:
- Line 1323: Import statement "`@import` 'styles/web3auth';" is currently placed
in an invalid position; move that `@import` so it appears before any other rules
(variables, mixins, selectors, or declarations)—ideally grouped with the other
top-of-file imports at the start of the stylesheet. Remove the offending line at
its current location and reinsert the same "`@import` 'styles/web3auth';" at the
top of the file (or into a consolidated imports section) to satisfy the
stylesheet rule order (no-invalid-position-at-import-rule).
In `@src/screens/Signin.js`:
- Around line 85-101: pinSuccess currently performs async setup
(LOCAL_STORE.unlock, initWeb3AuthStorage, markBackupDone, LOCAL_STORE.open,
dispatch(startWalletRequested...), setPassword, navigate) with no error
handling; wrap the async sequence in a try/catch and only clear password,
dispatch startWalletRequested, and navigate after all steps succeed. On failure
in initWeb3AuthStorage or LOCAL_STORE.open, catch the error, log/report it,
undo/rollback any partial state (e.g. if LOCAL_STORE.unlock() succeeded ensure
you call LOCAL_STORE.lock() or another cleanup method and avoid calling
markBackupDone/open/dispatch), and surface a user-visible error instead of
navigating. Ensure sensitive values (password/privateKey) are only cleared after
successful completion or securely handled on rollback.
- Around line 91-97: The dispatched startWalletRequested currently includes
secrets (privateKey, pin, password) which are saved into state.startWalletAction
by the START_WALLET_REQUESTED handler and replayed by LoadWalletFailed; change
the flow to avoid storing secrets in Redux: update the START_WALLET_REQUESTED
reducer to store only a sanitized summary (e.g., walletType, publicKey, flags)
not the full action, and ensure the reducer clears state.startWalletAction on
START_WALLET_FAILED as well as START_WALLET_SUCCESS; update LoadWalletFailed to
re-dispatch a reconstructed, non-sensitive retry (or prompt the UI to re-enter
secrets) instead of replaying the original action; alternatively, keep secrets
out of the dispatched payload by moving privateKey/pin/password into local
component state or passing them directly to the saga middleware
(startWalletRequested metadata or a secure channel) so startWalletRequested,
reducers, and LoadWalletFailed never persist secrets.
In `@src/screens/Web3AuthLogin.js`:
- Around line 43-46: The persisted network value from
LOCAL_STORE.getNetworkSettings() (persistedNetwork / network) must be normalized
against known keys in WEB3AUTH_CONFIG before being used in the login flow;
change the code that derives cfg to also produce a normalizedNetwork (e.g., pick
network if it exists in WEB3AUTH_CONFIG, otherwise use 'testnet' or 'mainnet'
fallback) and pass that normalizedNetwork to web3authLogin instead of the raw
network value so UI gating (appleEnabled / cfg) and runtime config remain
consistent; update references to persistedNetwork/network, cfg, and the
web3authLogin call to use this normalized value.
In `@src/storage.js`:
- Around line 385-389: getWeb3AuthPublicKey currently calls this.getStorage()
without guarding against a null return like getWeb3AuthPrivateKey does; update
getWeb3AuthPublicKey to first call const storage = this.getStorage() and if
storage is null/undefined return null (or the same fallback used in
getWeb3AuthPrivateKey), otherwise await storage.getAccessData() and return
accessData?.singleKeyPublicKey so it mirrors the null-safety of
getWeb3AuthPrivateKey.
- Around line 375-378: The getWeb3AuthPrivateKey function calls
this.getStorage() and directly invokes storage.getSingleKeyPrivateKey(pin) which
will throw if getStorage() returns null; add a null check for the result of
this.getStorage() (similar to the pattern used in _getAccessData) and handle the
missing storage case by returning null or throwing a clear error, e.g., if
(!storage) return null or throw new Error('wallet not loaded'), before calling
storage.getSingleKeyPrivateKey(pin).
---
Nitpick comments:
In `@lavamoat/webpack/policy.json`:
- Around line 166-173: The policy currently grants "$root$": true for the chain
"`@web3auth/base`>`@web3auth/auth`>`@ethereumjs/util`>ethereum-cryptography>`@noble/curves`>`@noble/hashes`"
in lavamoat/webpack/policy.json—reassess and remove or tighten this "$root$"
package permission and instead enumerate only required package keys (or set more
restrictive package scopes) for the `@noble/hashes` entry; if the "$root$" was
introduced by LavaMoat’s generator, update config-overrides.js (LavaMoatPlugin
generatePolicy) or add an explicit policy-override to prevent generator from
adding global root access, then regenerate the policy via the LavaMoatPlugin
build to verify the "`@web3auth/`...>`@noble/hashes`" entry only has the minimal
"globals": { "TextEncoder": true } or the intended restricted packages.
In `@qa/QA.md`:
- Around line 65-74: Split the conditional verification embedded in Scenario 7
step 4 into a separate note or sub-scenario: remove the parenthetical sentence
starting "But if the user had NOT signed out..." from the main flow of "Scenario
7 — Feature toggle off (with existing wallet)" and add a new sub-scenario or
note titled e.g. "Alternate: wallet not signed out" that describes retesting the
LockedWallet screen and unlocking with PIN; reference the same artifacts
(WalletType screen, LockedWallet screen, and the web3auth-desktop.rollout
toggle) so the alternate path is executed independently and the main scenario
remains linear.
In `@src/sagas/web3auth.js`:
- Around line 174-176: The current fallback assigns userInfo?.name into the
email variable, causing non-email values to be stored and dispatched; change the
logic so email is only set from userInfo?.email (remove the fallback to
userInfo?.name) and only write to WEB3AUTH_EMAIL_KEY and dispatch the
web3authEmail Redux action when that email value is truthy, or alternatively
rename the variable and related storage/dispatch keys to
userIdentifier/web3authIdentifier if you intend to keep the display name; update
references around userInfo, email, WEB3AUTH_EMAIL_KEY, and web3authEmail
accordingly.
- Around line 23-68: classifyWeb3AuthError currently falls back to
WEB3AUTH_ERROR_TYPES.UNKNOWN for unrecognized messages but doesn't surface the
raw pattern; update classifyWeb3AuthError to emit structured observability data
whenever it will return UNKNOWN: log the original error object, its stack (if
present), and the normalized msg string so new patterns can be triaged and added
later. Use the existing application logger (or console as a fallback) and
include clear fields like errorType: 'web3auth_unclassified', classified:
'unknown', rawError, stack, and normalizedMessage before returning
WEB3AUTH_ERROR_TYPES.UNKNOWN to make future classification straightforward.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2d1fe5e3-4af3-4b23-8dc4-f40022f6764f
⛔ Files ignored due to path filters (4)
package-lock.jsonis excluded by!**/package-lock.jsonsrc/assets/web3auth-providers/apple.svgis excluded by!**/*.svgsrc/assets/web3auth-providers/email.svgis excluded by!**/*.svgsrc/assets/web3auth-providers/google.svgis excluded by!**/*.svg
📒 Files selected for processing (26)
config-overrides.jslavamoat/webpack/policy.jsonlocale/texts.potpackage.jsonpublic/electron.jsqa/QA.mdsrc/App.jssrc/actions/index.jssrc/components/Navigation.jssrc/components/WalletAddress.jssrc/components/Web3AuthErrorDialog.jssrc/constants.jssrc/index.module.scsssrc/reducers/index.jssrc/sagas/reown.jssrc/sagas/wallet.jssrc/sagas/web3auth.jssrc/screens/LockedWallet.jssrc/screens/Settings.jssrc/screens/Signin.jssrc/screens/WalletType.jssrc/screens/Web3AuthLogin.jssrc/screens/Web3AuthRecovery.jssrc/storage.jssrc/styles/web3auth.scsssrc/web3auth-crypto-shim.js
| msgid "" | ||
| "There is a configuration issue with Web3Auth. The Hathor team has been " | ||
| "notified." | ||
| msgstr "" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check if CONFIG_ERROR triggers Sentry capture in the Web3Auth saga
# Search for Sentry.captureException or similar in web3auth saga error handling
rg -n -C5 'CONFIG_ERROR' --type=js src/sagas/web3auth.js
# Also check if this error type is captured in error boundary or middleware
rg -n 'CONFIG_ERROR.*Sentry|Sentry.*CONFIG_ERROR' --type=jsRepository: HathorNetwork/hathor-wallet
Length of output: 53
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find mapping to the notified copy =="
rg -n --hidden --no-ignore -S "The Hathor team has been notified" . || true
rg -n --hidden --no-ignore -S "\"There is a configuration issue with Web3Auth" . || true
echo
echo "== Locate CONFIG_ERROR definition/usages (repo-wide) =="
rg -n --hidden --no-ignore -S "CONFIG_ERROR" . || true
echo
echo "== Locate Web3Auth saga/error handling files =="
rg -n --hidden --no-ignore -S "web3auth" src . || true
rg -n --hidden --no-ignore -S "Web3Auth" src . || true
echo
echo "== Search for Sentry capture in Web3Auth-related code =="
rg -n --hidden --no-ignore -S "Sentry\.capture|captureException|captureMessage" src . || trueRepository: HathorNetwork/hathor-wallet
Length of output: 50386
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for CONFIG_ERROR only in src/ =="
rg -n "CONFIG_ERROR" src || true
echo
echo "== Search for Sentry only in src/ =="
rg -n -S "Sentry|sentry" src || true
echo
echo "== Inspect Web3AuthErrorDialog.js around the string =="
FILE="src/components/Web3AuthErrorDialog.js"
if [ -f "$FILE" ]; then
nl -ba "$FILE" | sed -n '1,120p'
else
echo "Missing $FILE"
fiRepository: HathorNetwork/hathor-wallet
Length of output: 5651
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/components/Web3AuthErrorDialog.js"
echo "== Web3AuthErrorDialog.js (key sections) =="
awk 'NR>=1 && NR<=140 {printf "%5d:%s\n", NR, $0}' "$FILE"
echo
echo "== Find where sentryTag / category is used =="
rg -n "sentryTag|category: copy\.sentryTag|breadcrumbs|breadcrumb|captureException|captureMessage" src/components src/utils src/screens || true
echo
echo "== Find any logger that handles category tags =="
rg -n "existing logger|observability hook|tagged log|category:" src || trueRepository: HathorNetwork/hathor-wallet
Length of output: 6298
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate Web3Auth saga files =="
ls -la src/sagas | head
rg -n "WEB3AUTH_ERROR_TYPES|VERIFIER_CONFIG|web3auth" src/sagas --type=js || true
echo
echo "== Search for Sentry capture / sentryWithScope in saga/util =="
rg -n -S "sentryWithScope|Sentry\.captureException|captureException\(" src/sagas src/utils || true
echo
echo "== Inspect initSentry in src/utils/wallet.js =="
rg -n "initSentry|Sentry\.init" src/utils/wallet.js
awk 'NR>=440 && NR<=520 {printf "%5d:%s\n", NR, $0}' src/utils/wallet.js
echo
echo "== Inspect src/sagas/web3auth around VERIFIER_CONFIG usage =="
FILE=$(fd -t f "web3auth.js" src/sagas || true | head -n 1)
if [ -n "$FILE" ]; then
echo "Using $FILE"
rg -n "VERIFIER_CONFIG|WEB3AUTH_ERROR_TYPES" "$FILE" || true
awk 'NR>=1 && NR<=220 {printf "%5d:%s\n", NR, $0}' "$FILE"
else
echo "Could not find src/sagas/web3auth.js"
fiRepository: HathorNetwork/hathor-wallet
Length of output: 14633
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find where Web3AuthErrorDialog is used =="
rg -n "Web3AuthErrorDialog" src --type=js || true
echo
echo "== Search for classifyWeb3AuthError usage =="
rg -n "classifyWeb3AuthError" src/sagas --type=js || true
echo
echo "== Search for VERIFIER_CONFIG usage in web3auth saga =="
rg -n "VERIFIER_CONFIG|verifier_config" src/sagas/web3auth.js --type=js || true
echo
echo "== Search for Sentry capture in web3auth saga =="
rg -n -S "Sentry\.capture|captureException|sentryWithScope" src/sagas/web3auth.js --type=js || true
echo
echo "== Inspect rest of src/sagas/web3auth.js for error handling/dialog dispatch =="
awk 'NR>=220 && NR<=520 {printf "%5d:%s\n", NR, $0}' src/sagas/web3auth.jsRepository: HathorNetwork/hathor-wallet
Length of output: 2237
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== web3authLogin call sites (src/) =="
rg -n "web3authLogin\\(" src --type=js || true
echo
echo "== classifyWeb3AuthError usage (src/) =="
rg -n "classifyWeb3AuthError" src --type=js || true
echo
echo "== Inspect src/screens/Web3AuthLogin.js around the error dialog =="
FILE="src/screens/Web3AuthLogin.js"
wc -l "$FILE"
awk 'NR>=90 && NR<=200 {printf "%5d:%s\n", NR, $0}' "$FILE"
echo
echo "== Search for verifier_config / VERIFIER_CONFIG in src/ =="
rg -n "VERIFIER_CONFIG|verifier_config" src --type=js || true
echo
echo "== Search for sentryWithScope mentions near web3auth =="
rg -n "sentryWithScope" src --type=js || true
rg -n -S "web3auth|web3authLogin|Web3Auth" src --type=js | head -n 50 || trueRepository: HathorNetwork/hathor-wallet
Length of output: 7227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Web3AuthLogin.js: inspect top + login/catch logic =="
FILE="src/screens/Web3AuthLogin.js"
wc -l "$FILE"
awk 'NR>=1 && NR<=120 {printf "%5d:%s\n", NR, $0}' "$FILE"
echo
echo "== Search for web3auth dialog console.warn message string =="
rg -n "web3auth\\] error dialog shown" src --type=js || true
echo
echo "== Search for sentryWithScope call sites in src (all) =="
rg -n "sentryWithScope\\(" src --type=js || true
echo
echo "== Search for Sentry breadcrumb setup / addBreadcrumb in src =="
rg -n -S "addBreadcrumb|breadcrumbs|beforeSend|enableConsole|console" src/utils src --type=js || trueRepository: HathorNetwork/hathor-wallet
Length of output: 12510
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Sentry permission gating (wallet.js) =="
awk 'NR>=390 && NR<=520 {printf "%5d:%s\n", NR, $0}' src/utils/wallet.js
echo
echo "== Where updateSentryState() is called =="
rg -n "updateSentryState\\(" src --type=js || trueRepository: HathorNetwork/hathor-wallet
Length of output: 4180
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Sentry init occurrences (src/) =="
rg -n "Sentry\\.init" src --type=js || true
echo
echo "== Sentry capture occurrences (src/) =="
rg -n -S "Sentry\\.(captureException|captureMessage|captureEvent)" src --type=js || true
echo
echo "== Sentry breadcrumb / console integration hints (src/) =="
rg -n -S "breadcrumb|breadcrumbs|beforeSend|integrations|Console|console\\.(warn|log)" src --type=js || trueRepository: HathorNetwork/hathor-wallet
Length of output: 1943
🌐 Web query:
Sentry browser SDK default Console integration captures console.warn as breadcrumbs? default integrations when calling Sentry.init({dsn, release}) without integrations option
💡 Result:
Yes. In the Sentry Browser JavaScript SDK, console.warn is captured as breadcrumbs by the default Breadcrumbs integration (enabled by default) when you call Sentry.init({ dsn, release }) without specifying integrations. 1) The Breadcrumbs integration is enabled by default and wraps the browser console API to add breadcrumbs. - The integration documentation states it “is enabled by default” and that “By default, the Sentry SDK wraps the console … browser APIs to add breadcrumbs.”[1] 2) Those console calls include warn (not only log). - The Breadcrumbs integration doc describes the console option as “Log calls to console.log, console.debug, and so on.”[1] - The SDK source for the Breadcrumbs integration shows _options.console defaults to true and, when enabled, it installs console breadcrumb instrumentation via addConsoleInstrumentationHandler(...). This is the default behavior when you don’t pass an integrations option.[2] 3) Therefore, calling Sentry.init({ dsn, release }) with no integrations option will include the default Breadcrumbs integration, which captures console API calls (including console.warn) as breadcrumbs. - The Breadcrumbs integration doc explicitly ties breadcrumb capture to the default wrapping of console.[1] 4) If you want to stop capturing console calls as breadcrumbs, you must disable/override the Breadcrumbs integration’s console option. - The integrations docs show you can turn off browser capturing console calls by setting console: false in the Breadcrumbs integration instance you pass to integrations.[3] If you tell me your exact SDK package name/version (e.g., @sentry/browser vs sentry-cordova) and how you’re calling Sentry.init, I can map this to the precise integration name you’ll see in your build output.
Citations:
- 1: https://docs.sentry.io/platforms/javascript/configuration/integrations/breadcrumbs/
- 2: https://github.com/getsentry/sentry-javascript/blob/develop/packages/browser/src/integrations/breadcrumbs.ts
- 3: https://docs.sentry.io/platforms/javascript/configuration/integrations/
Fix “Hathor team has been notified” wording for Web3Auth config errors
The “configuration issue” copy is for WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG in src/components/Web3AuthErrorDialog.js, but this dialog only logs a tagged console.warn and never calls Sentry.captureException/Sentry.captureMessage. While Sentry can record console.warn as breadcrumbs (via default Breadcrumbs), breadcrumbs don’t by themselves send a notification/event to Sentry; also Sentry is only initialized when the user allows it. Update the copy to avoid guaranteeing notification, or emit an explicit Sentry event when showing this dialog.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@locale/texts.pot` around lines 3185 - 3188, The message for
WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG in Web3AuthErrorDialog.js currently implies
the Hathor team was notified but only issues console.warn; either remove/soften
that claim in the i18n string (texts.pot) so it doesn't promise notification, or
emit an explicit Sentry event when the dialog is shown (call
Sentry.captureException or Sentry.captureMessage with contextual info) from the
Web3AuthErrorDialog component after logging the console.warn; update the string
and/or add the Sentry.captureMessage(SOME_DESCRIPTIVE_TAGGED_MESSAGE, { extra: {
errorType: WEB3AUTH_ERROR_TYPES.VERIFIER_CONFIG, ... } }) call to ensure an
event is recorded when Sentry is initialized.
| "dependencies": { | ||
| "@hathor/hathor-rpc-handler": "4.4.0", | ||
| "@hathor/wallet-lib": "3.1.1", | ||
| "@hathor/wallet-lib": "file:.yalc/@hathor/wallet-lib", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking current declaration in package.json..."
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("package.json").read_text())
print(pkg["dependencies"].get("`@hathor/wallet-lib`"))
PY
echo
echo "Searching repo for lingering yalc refs..."
rg -n "file:\.yalc/@hathor/wallet-lib|\.yalc/@hathor/wallet-lib" package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true
echo
echo "Verifying published target exists..."
npm view `@hathor/wallet-lib`@^3.2.0 versionRepository: HathorNetwork/hathor-wallet
Length of output: 1050
🏁 Script executed:
rg -n "file:\.yalc/@hathor/wallet-lib|\.yalc/@hathor/wallet-lib" package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true
npm view `@hathor/wallet-lib` versions --jsonRepository: HathorNetwork/hathor-wallet
Length of output: 2711
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking package.json declaration..."
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("package.json").read_text())
print(pkg["dependencies"].get("`@hathor/wallet-lib`"))
PY
echo
echo "Searching repo for lingering yalc refs..."
rg -n "file:\.yalc/@hathor/wallet-lib|\.yalc/@hathor/wallet-lib" package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true
echo
echo "Verifying published target exists..."
npm view `@hathor/wallet-lib`@3.1.1 versionRepository: HathorNetwork/hathor-wallet
Length of output: 580
🏁 Script executed:
rg -n "file:\.yalc/@hathor/wallet-lib|\.yalc/@hathor/wallet-lib" package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true
npm view `@hathor/wallet-lib`@3.1.1 versionRepository: HathorNetwork/hathor-wallet
Length of output: 432
Replace local Yalc dependency for @hathor/wallet-lib (blocker; ^3.2.0 not published, use ^3.1.1)
package.json (line 41) and package-lock.json still reference file:.yalc/@hathor/wallet-lib, which will fail clean/reproducible CI/release installs. npm does not provide @hathor/wallet-lib@^3.2.0, but @hathor/wallet-lib@3.1.1 exists.
Proposed fix
- "`@hathor/wallet-lib`": "file:.yalc/@hathor/wallet-lib",
+ "`@hathor/wallet-lib`": "^3.1.1",Also regenerate package-lock.json and remove any remaining .yalc/@hathor/wallet-lib entries.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "@hathor/wallet-lib": "file:.yalc/@hathor/wallet-lib", | |
| "`@hathor/wallet-lib`": "^3.1.1", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` at line 41, Replace the local yalc dependency for
`@hathor/wallet-lib` in package.json (currently "file:.yalc/@hathor/wallet-lib")
with the published semver version "^3.1.1", then run npm install (or npm ci
after removing node_modules) to regenerate package-lock.json so it no longer
references .yalc; also search package-lock.json and the repo for any remaining
".yalc/@hathor/wallet-lib" entries and remove/replace them with
"`@hathor/wallet-lib`": "^3.1.1" to ensure clean reproducible installs.
| "@web3auth/auth-adapter": "^9.7.0", | ||
| "@web3auth/base": "^9.7.0", | ||
| "@web3auth/base-provider": "^9.7.0", | ||
| "@web3auth/no-modal": "^8.12.4", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Declared Web3Auth versions in package.json:"
python - <<'PY'
import json, pathlib
deps = json.loads(pathlib.Path("package.json").read_text())["dependencies"]
for name in [
"`@web3auth/auth-adapter`",
"`@web3auth/base`",
"`@web3auth/base-provider`",
"`@web3auth/no-modal`",
]:
print(f"{name}: {deps.get(name)}")
PY
echo
echo "Inspecting npm metadata for compatibility-relevant ranges:"
for p in \
"`@web3auth/auth-adapter`@9.7.0" \
"`@web3auth/base`@9.7.0" \
"`@web3auth/base-provider`@9.7.0" \
"`@web3auth/no-modal`@8.12.4"
do
echo "---- $p ----"
npm view "$p" peerDependencies dependencies --json
doneRepository: HathorNetwork/hathor-wallet
Length of output: 1653
Align Web3Auth packages to one major release train.
@web3auth/no-modal@^8.12.4 depends on @web3auth/base@^8.12.4 (and Torus openlogin ^8.x) while @web3auth/auth-adapter/base/base-provider are on ^9.7.0 (and use @web3auth/auth@^9.6.4 with @web3auth/base@^9.7.0). This 8.x/9.x skew is a common source of peer dependency conflicts and runtime login issues.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 48 - 51, package.json currently mixes Web3Auth 9.x
packages ("`@web3auth/auth-adapter`","`@web3auth/base`","`@web3auth/base-provider`")
with an 8.x "`@web3auth/no-modal`", causing peer-dep/runtime conflicts; fix by
aligning major versions: update the "`@web3auth/no-modal`" dependency to a 9.x
release that matches the others (or alternatively downgrade the 9.x entries to
8.x if you must stay on v8), then run install and verify no peer dependency
warnings and that the login flow using the auth adapter/base/base-provider works
as expected; target the same major (e.g., set "`@web3auth/no-modal`" to "^9.7.0"
to match "`@web3auth/base`" and "`@web3auth/auth-adapter`").
| - Nano Contract (in top navigation) | ||
| 3. Open the `Receive` screen — verify the "Generate new address" button is NOT shown. | ||
| 4. Settings → reset row label reads "Sign out of your Hathor account <email>", | ||
| not "Reset wallet". |
There was a problem hiding this comment.
Fix HTML entity encoding in markdown.
The placeholder <email> uses HTML entities instead of plain angle brackets. In markdown, this should be <email> or the more conventional backtick format `<email>` for inline code.
✏️ Suggested fix
-4. Settings → reset row label reads "Sign out of your Hathor account <email>",
+4. Settings → reset row label reads "Sign out of your Hathor account `<email>`",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@qa/QA.md` at line 98, Replace the HTML-encoded placeholder `<email>` in
QA.md with a proper Markdown-safe representation: either plain angle brackets
`<email>` or, preferably for inline code style, use backticks `` `<email>` ``;
update the occurrence of `<email>` so the Markdown renders correctly and
search for other HTML-encoded entities to fix similarly.
| } | ||
| } | ||
|
|
||
| @import 'styles/web3auth'; |
There was a problem hiding this comment.
Move SCSS import to a valid position.
@import at Line 1323 violates the stylesheet rule order (no-invalid-position-at-import-rule) and can fail lint/pipeline.
Suggested fix
@@
:export {
purpleHathor: $purpleHathor;
}
+
+@import 'styles/web3auth';
@@
-@import 'styles/web3auth';🧰 Tools
🪛 Stylelint (17.11.1)
[error] 1323-1323: Invalid position for @import rule (no-invalid-position-at-import-rule)
(no-invalid-position-at-import-rule)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/index.module.scss` at line 1323, Import statement "`@import`
'styles/web3auth';" is currently placed in an invalid position; move that
`@import` so it appears before any other rules (variables, mixins, selectors, or
declarations)—ideally grouped with the other top-of-file imports at the start of
the stylesheet. Remove the offending line at its current location and reinsert
the same "`@import` 'styles/web3auth';" at the top of the file (or into a
consolidated imports section) to satisfy the stylesheet rule order
(no-invalid-position-at-import-rule).
| const pinSuccess = async (newPin) => { | ||
| const { privateKey, publicKey } = web3authState; | ||
| LOCAL_STORE.unlock(); | ||
| await LOCAL_STORE.initWeb3AuthStorage(privateKey, publicKey, newPin, password); | ||
| LOCAL_STORE.markBackupDone(); | ||
| LOCAL_STORE.open(); | ||
| dispatch(startWalletRequested({ | ||
| privateKey, | ||
| publicKey, | ||
| pin: newPin, | ||
| password, | ||
| walletType: 'web3auth', | ||
| })); | ||
| // Clear sensitive in-memory copy of the password. | ||
| setPassword(''); | ||
| navigate('/loading_addresses/', { replace: true }); | ||
| } |
There was a problem hiding this comment.
Handle bootstrap failures in pinSuccess to avoid partial wallet state.
The async chain on Line 85-101 has no error handling. If initWeb3AuthStorage/open fails, you can end with unlocked/partial state and no UX recovery path.
Suggested fix
const pinSuccess = async (newPin) => {
const { privateKey, publicKey } = web3authState;
- LOCAL_STORE.unlock();
- await LOCAL_STORE.initWeb3AuthStorage(privateKey, publicKey, newPin, password);
- LOCAL_STORE.markBackupDone();
- LOCAL_STORE.open();
- dispatch(startWalletRequested({
- privateKey,
- publicKey,
- pin: newPin,
- password,
- walletType: 'web3auth',
- }));
- // Clear sensitive in-memory copy of the password.
- setPassword('');
- navigate('/loading_addresses/', { replace: true });
+ try {
+ LOCAL_STORE.unlock();
+ await LOCAL_STORE.initWeb3AuthStorage(privateKey, publicKey, newPin, password);
+ LOCAL_STORE.markBackupDone();
+ LOCAL_STORE.open();
+ dispatch(startWalletRequested({
+ privateKey,
+ publicKey,
+ pin: newPin,
+ password,
+ walletType: 'web3auth',
+ }));
+ setPassword('');
+ navigate('/loading_addresses/', { replace: true });
+ } catch (e) {
+ LOCAL_STORE.lock();
+ setPassword('');
+ setAskPIN(false);
+ navigate('/web3auth_login/', { replace: true });
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const pinSuccess = async (newPin) => { | |
| const { privateKey, publicKey } = web3authState; | |
| LOCAL_STORE.unlock(); | |
| await LOCAL_STORE.initWeb3AuthStorage(privateKey, publicKey, newPin, password); | |
| LOCAL_STORE.markBackupDone(); | |
| LOCAL_STORE.open(); | |
| dispatch(startWalletRequested({ | |
| privateKey, | |
| publicKey, | |
| pin: newPin, | |
| password, | |
| walletType: 'web3auth', | |
| })); | |
| // Clear sensitive in-memory copy of the password. | |
| setPassword(''); | |
| navigate('/loading_addresses/', { replace: true }); | |
| } | |
| const pinSuccess = async (newPin) => { | |
| const { privateKey, publicKey } = web3authState; | |
| try { | |
| LOCAL_STORE.unlock(); | |
| await LOCAL_STORE.initWeb3AuthStorage(privateKey, publicKey, newPin, password); | |
| LOCAL_STORE.markBackupDone(); | |
| LOCAL_STORE.open(); | |
| dispatch(startWalletRequested({ | |
| privateKey, | |
| publicKey, | |
| pin: newPin, | |
| password, | |
| walletType: 'web3auth', | |
| })); | |
| setPassword(''); | |
| navigate('/loading_addresses/', { replace: true }); | |
| } catch (e) { | |
| LOCAL_STORE.lock(); | |
| setPassword(''); | |
| setAskPIN(false); | |
| navigate('/web3auth_login/', { replace: true }); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/screens/Signin.js` around lines 85 - 101, pinSuccess currently performs
async setup (LOCAL_STORE.unlock, initWeb3AuthStorage, markBackupDone,
LOCAL_STORE.open, dispatch(startWalletRequested...), setPassword, navigate) with
no error handling; wrap the async sequence in a try/catch and only clear
password, dispatch startWalletRequested, and navigate after all steps succeed.
On failure in initWeb3AuthStorage or LOCAL_STORE.open, catch the error,
log/report it, undo/rollback any partial state (e.g. if LOCAL_STORE.unlock()
succeeded ensure you call LOCAL_STORE.lock() or another cleanup method and avoid
calling markBackupDone/open/dispatch), and surface a user-visible error instead
of navigating. Ensure sensitive values (password/privateKey) are only cleared
after successful completion or securely handled on rollback.
| dispatch(startWalletRequested({ | ||
| privateKey, | ||
| publicKey, | ||
| pin: newPin, | ||
| password, | ||
| walletType: 'web3auth', | ||
| })); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how START_WALLET_REQUESTED payload fields are consumed and whether sensitive
# fields are persisted/logged in reducers, middleware, or sagas.
set -euo pipefail
echo "== Action type usage =="
rg -n -C3 'START_WALLET_REQUESTED|startWalletRequested\s*\(' --type js --type jsx --type ts --type tsx
echo
echo "== Sensitive field usage near start-wallet flow =="
rg -n -C3 '\b(privateKey|publicKey|pin|password)\b' --type js --type jsx --type ts --type tsx
echo
echo "== Potential persistence/logging touchpoints =="
rg -n -C3 'redux-logger|logger|persist|localStorage|sessionStorage|sentry|console\.(log|warn|error)' --type js --type jsx --type ts --type tsxRepository: HathorNetwork/hathor-wallet
Length of output: 141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate Signin dispatch payload =="
ls -la src/screens/Signin.js
sed -n '70,120p' src/screens/Signin.js | nl -ba | sed -n '1,120p'
echo
echo "== Find action type / action creator =="
rg -n --hidden -S --glob '*.js' --glob '*.jsx' --glob '*.ts' --glob '*.tsx' \
'START_WALLET_REQUESTED|startWalletRequested\s*\('
echo
echo "== Find sensitive field handling in start-wallet flow =="
rg -n --hidden -S --glob '*.js' --glob '*.jsx' --glob '*.ts' --glob '*.tsx' \
'\b(privateKey|publicKey|pin|password)\b'
echo
echo "== Check for common logging/persist middlewares =="
rg -n --hidden -S --glob '*.js' --glob '*.jsx' --glob '*.ts' --glob '*.tsx' \
'redux-logger|logger|persist|localStorage|sessionStorage|sentry|console\.(log|warn|error)'Repository: HathorNetwork/hathor-wallet
Length of output: 240
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Show Signin.js dispatch context =="
sed -n '70,115p' src/screens/Signin.js
echo
echo "== Find action creator / action type =="
rg -n -C2 'START_WALLET_REQUESTED|startWalletRequested\s*\(' -g'*.js' -g'*.jsx' -g'*.ts' -g'*.tsx'
echo
echo "== Find sensitive field identifiers =="
rg -n -C2 '\b(privateKey|publicKey|pin|password)\b' -g'*.js' -g'*.jsx' -g'*.ts' -g'*.tsx'
echo
echo "== Check for logging/persist/logging middlewares touchpoints =="
rg -n -C2 'redux-logger|persist|redux-persist|localStorage|sessionStorage|sentry|console\.(log|warn|error)|logger' -g'*.js' -g'*.jsx' -g'*.ts' -g'*.tsx'Repository: HathorNetwork/hathor-wallet
Length of output: 50384
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Logging/persist middleware touchpoints (src only) =="
rg -n --hidden -S \
'redux-logger|redux-persist|persistStore|persistReducer|PersistGate|localStorage|sessionStorage|sentry|console\.(log|warn|error)' \
src
echo
echo "== Does reducers store startWalletAction / START_WALLET_REQUESTED payload? =="
rg -n --hidden -S 'startWalletAction|START_WALLET_REQUESTED' src/reducers/index.js
sed -n '120,220p' src/reducers/index.js
sed -n '400,480p' src/reducers/index.js
echo
echo "== Inspect wallet saga for sensitive handling/logging around startWallet =="
rg -n --hidden -S 'function startWallet|startWallet\(|START_WALLET_REQUESTED|console\.(log|warn|error)|logger' src/sagas/wallet.js
# Show the relevant destructuring and web3auth branch
sed -n '120,240p' src/sagas/wallet.js
sed -n '240,360p' src/sagas/wallet.js
echo
echo "== Inspect startWalletRequested action definition =="
sed -n '430,520p' src/actions/index.jsRepository: HathorNetwork/hathor-wallet
Length of output: 29912
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find reducer logic for startWalletAction storage/clearing =="
rg -n --hidden -S 'startWalletAction' src/reducers/index.js
sed -n '1060,1180p' src/reducers/index.js
echo
echo "== Find where START_WALLET_REQUESTED action is dispatched/stored in reducers (handlers) =="
rg -n --hidden -S 'function onStartWalletRequested|onStartWalletRequested\(|onStartWalletReset\(|START_WALLET_RESET|START_WALLET_SUCCESS|START_WALLET_FAILED' src/reducers/index.js
echo
echo "== Inspect store/middleware setup for redux-logger/devtools/persist =="
rg -n --hidden -S 'applyMiddleware|redux-logger|devTools|composeWithDevTools|persistStore|persistReducer|redux-persist|createStore\(' srcRepository: HathorNetwork/hathor-wallet
Length of output: 4004
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find redux store configuration (createStore/applyMiddleware) =="
rg -n --hidden -S 'createStore\(|configureStore\(|applyMiddleware\(|composeWithDevTools|redux-logger|devTools|persistStore|persistReducer|redux-persist' src
echo
echo "== Find reducer persistence (if any) =="
rg -n --hidden -S 'persistReducer|persistStore|redux-persist|PersistGate' src
echo
echo "== Inspect entry where store is created (top candidates) =="
ls -ლა src | head
fd -t f 'store*.js|index.js|configureStore*.js|reducers*.js|rootReducer*.js|App*.js' src -i --max-results 50 || trueRepository: HathorNetwork/hathor-wallet
Length of output: 437
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' src/store/index.js
echo
echo "== Any store enhancers/devtools options? =="
rg -n --hidden -S 'devTools|redux-logger|logger|persist|redux-persist|PersistGate|applyMiddleware|middleware' src/store/index.js src/store -S || trueRepository: HathorNetwork/hathor-wallet
Length of output: 1389
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Where startWalletAction is used (beyond reducer) =="
rg -n --hidden -S 'startWalletAction' src
echo
echo "== Find errorHandler / startWallet retry logic =="
rg -n --hidden -S 'errorHandler\(|startWalletFailed\(|onStartWalletFailed\(|START_WALLET_FAILED|startWalletAction' src/sagas
echo
echo "== Inspect potential devtools configuration =="
rg -n --hidden -S 'devTools' src/store src
echo
echo "== Search for redux-persist in entire repo (includes non-src if any) =="
rg -n --hidden -S 'redux-persist|persistStore|persistReducer|PersistGate' .Repository: HathorNetwork/hathor-wallet
Length of output: 932
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== LoadWalletFailed usage of startWalletAction =="
sed -n '1,120p' src/screens/LoadWalletFailed.js
echo
echo "== errorHandler retry logic =="
sed -n '60,160p' src/sagas/helpers.js
echo
echo "== startWallet failure handling around where failures occur =="
sed -n '400,560p' src/sagas/wallet.js
sed -n '940,1030p' src/sagas/wallet.jsRepository: HathorNetwork/hathor-wallet
Length of output: 13523
Limit sensitive data exposure from startWalletRequested retry path.
src/screens/Signin.jsdispatchesstartWalletRequested({ privateKey, pin, password, walletType: 'web3auth' }).src/reducers/index.jsstores the entireactioninstate.startWalletActiononSTART_WALLET_REQUESTEDand only clears it onSTART_WALLET_SUCCESS(not onSTART_WALLET_FAILED);src/screens/LoadWalletFailed.jsretries by re-dispatchinglastAction, soprivateKey/pin/passwordremain in Redux state until success/reset.src/store/index.jsuses onlyredux-sagaandredux-thunk(noredux-logger/redux-persist), but keeping secrets in Redux still makes them available to Redux DevTools/state inspection.- In the Web3Auth first-login flow,
startWalletskips re-init and relies onprivateKey/publicKey;pinis used for Web3Auth restore whenprivateKeyis absent (andLockedWalletdispatches only{ pin }, indicatingpasswordisn’t needed for restore).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/screens/Signin.js` around lines 91 - 97, The dispatched
startWalletRequested currently includes secrets (privateKey, pin, password)
which are saved into state.startWalletAction by the START_WALLET_REQUESTED
handler and replayed by LoadWalletFailed; change the flow to avoid storing
secrets in Redux: update the START_WALLET_REQUESTED reducer to store only a
sanitized summary (e.g., walletType, publicKey, flags) not the full action, and
ensure the reducer clears state.startWalletAction on START_WALLET_FAILED as well
as START_WALLET_SUCCESS; update LoadWalletFailed to re-dispatch a reconstructed,
non-sensitive retry (or prompt the UI to re-enter secrets) instead of replaying
the original action; alternatively, keep secrets out of the dispatched payload
by moving privateKey/pin/password into local component state or passing them
directly to the saga middleware (startWalletRequested metadata or a secure
channel) so startWalletRequested, reducers, and LoadWalletFailed never persist
secrets.
| const persistedNetwork = LOCAL_STORE.getNetworkSettings()?.network || 'mainnet'; | ||
| const network = persistedNetwork; | ||
| const cfg = WEB3AUTH_CONFIG[network] || WEB3AUTH_CONFIG.testnet; | ||
| const appleEnabled = Boolean(cfg.appleClientId); |
There was a problem hiding this comment.
Normalize persisted network before using it in login flow.
On Line 45 you safely fall back cfg to testnet, but on Line 55 you still pass the raw network to web3authLogin. If local storage has an unexpected value, UI gating and runtime config can diverge.
Suggested fix
- const persistedNetwork = LOCAL_STORE.getNetworkSettings()?.network || 'mainnet';
- const network = persistedNetwork;
- const cfg = WEB3AUTH_CONFIG[network] || WEB3AUTH_CONFIG.testnet;
+ const persistedNetwork = LOCAL_STORE.getNetworkSettings()?.network || 'mainnet';
+ const network = WEB3AUTH_CONFIG[persistedNetwork] ? persistedNetwork : 'testnet';
+ const cfg = WEB3AUTH_CONFIG[network];Also applies to: 55-55
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/screens/Web3AuthLogin.js` around lines 43 - 46, The persisted network
value from LOCAL_STORE.getNetworkSettings() (persistedNetwork / network) must be
normalized against known keys in WEB3AUTH_CONFIG before being used in the login
flow; change the code that derives cfg to also produce a normalizedNetwork
(e.g., pick network if it exists in WEB3AUTH_CONFIG, otherwise use 'testnet' or
'mainnet' fallback) and pass that normalizedNetwork to web3authLogin instead of
the raw network value so UI gating (appleEnabled / cfg) and runtime config
remain consistent; update references to persistedNetwork/network, cfg, and the
web3authLogin call to use this normalized value.
| async getWeb3AuthPrivateKey(pin) { | ||
| const storage = this.getStorage(); | ||
| return storage.getSingleKeyPrivateKey(pin); | ||
| } |
There was a problem hiding this comment.
Add null check for storage to prevent potential runtime error.
getStorage() can return null when the wallet is not loaded (see lines 396-400). Calling storage.getSingleKeyPrivateKey(pin) on null would throw. Other accessors like _getAccessData (line 413-418) guard against this.
🛡️ Proposed fix
async getWeb3AuthPrivateKey(pin) {
const storage = this.getStorage();
+ if (!storage) {
+ throw new Error('Cannot get Web3Auth private key from uninitialized wallet');
+ }
return storage.getSingleKeyPrivateKey(pin);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/storage.js` around lines 375 - 378, The getWeb3AuthPrivateKey function
calls this.getStorage() and directly invokes storage.getSingleKeyPrivateKey(pin)
which will throw if getStorage() returns null; add a null check for the result
of this.getStorage() (similar to the pattern used in _getAccessData) and handle
the missing storage case by returning null or throwing a clear error, e.g., if
(!storage) return null or throw new Error('wallet not loaded'), before calling
storage.getSingleKeyPrivateKey(pin).
| async getWeb3AuthPublicKey() { | ||
| const storage = this.getStorage(); | ||
| const accessData = await storage.getAccessData(); | ||
| return accessData?.singleKeyPublicKey; | ||
| } |
There was a problem hiding this comment.
Add null check for storage consistency.
Same issue as getWeb3AuthPrivateKey — getStorage() may return null.
🛡️ Proposed fix
async getWeb3AuthPublicKey() {
const storage = this.getStorage();
+ if (!storage) {
+ return undefined;
+ }
const accessData = await storage.getAccessData();
return accessData?.singleKeyPublicKey;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async getWeb3AuthPublicKey() { | |
| const storage = this.getStorage(); | |
| const accessData = await storage.getAccessData(); | |
| return accessData?.singleKeyPublicKey; | |
| } | |
| async getWeb3AuthPublicKey() { | |
| const storage = this.getStorage(); | |
| if (!storage) { | |
| return undefined; | |
| } | |
| const accessData = await storage.getAccessData(); | |
| return accessData?.singleKeyPublicKey; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/storage.js` around lines 385 - 389, getWeb3AuthPublicKey currently calls
this.getStorage() without guarding against a null return like
getWeb3AuthPrivateKey does; update getWeb3AuthPublicKey to first call const
storage = this.getStorage() and if storage is null/undefined return null (or the
same fallback used in getWeb3AuthPrivateKey), otherwise await
storage.getAccessData() and return accessData?.singleKeyPublicKey so it mirrors
the null-safety of getWeb3AuthPrivateKey.
Summary
Adds a third option on Welcome → WalletType — "Sign in with social" — that bootstraps a single-key Hathor wallet from a Web3Auth-derived private key. OAuth and mandatory MFA are delegated to the Web3Auth-hosted webview opened in a sandboxed Electron
BrowserWindowpopup.This is the desktop sibling of the mobile Web3Auth integration on
wallet-mobile-web3authand reuses the same Hathor-owned custom verifier (hathor-google), so the derived address is identical across platforms for the same Google account.This is PR1 of three for the Web3Auth desktop rollout:
What's in
sagas/web3auth.js— lazy-singleton Web3Auth no-modal SDK,web3authLogin(provider, network),classifyWeb3AuthError, restore + logout flows.screens/Web3AuthLogin.js— three provider cards (Google active, Apple gated onappleClientId, Email inert until PR3).components/Web3AuthErrorDialog.js— classifies SDK errors into 7 buckets (USER_CANCELLED,NETWORK,MFA_REQUIRED,VERIFIER_CONFIG,RATE_LIMITED,SESSION_EXPIRED,UNKNOWN) with localized copy and a Try-again affordance.screens/Signin.js(Web3Auth branch) — ChoosePin variant that persists encryptedsingleKeyPrivateKeyviaLOCAL_STORE.initWeb3AuthStorage; subsequent unlocks become PIN-only.sagas/wallet.js(Web3Auth branch) — constructsHathorWallet({ privateKey, scanPolicy: SINGLE_ADDRESS, ... }), forcesuseWalletService=false, registerssetExternalTxSigningMethod.screens/LockedWallet.js— Web3Auth-aware unlock branch.WEB3AUTH_CONFIGinsrc/constants.js(testnet Sapphire Devnet today; mainnet placeholder with controlledVERIFIER_CONFIGerror).Settings,WalletAddress,sagas/reown.js,components/Navigation.jsgated onisSingleKeyWalletselector so Reown, Atomic Swap, Nano Contract, and "Generate new address" are hidden for Web3Auth wallets.public/electron.js—setWindowOpenHandlerwhitelists Web3Auth + Google + Apple OAuth hosts in a sandboxedcontextIsolationpartition.config-overrides.js+ newsrc/web3auth-crypto-shim.js— Webpack 5node:cryptoscheme resolver (NodeProtocolUrlPlugin) plus a custom ESM shim that wrapscrypto-browserifyand exposeswebcrypto(required by the Web3Auth SDK chain).reducers/index.js+actions/index.js— new Redux state:walletType,web3authEmail,isSingleKeyWallet.storage.js—initWeb3AuthStorage+ getters layered on top of the existingHybridStore.styles/web3auth.scss(BEM + nested&per repo SCSS conventions).web3auth-desktop.rollout(Unleash, default OFF inFEATURE_TOGGLE_DEFAULTS).qa/QA.md— 10 manual QA scenarios covering happy path, restart/unlock, sign-out/sign-in, OAuth cancel, offline, MFA abort, feature-flag off, cross-platform key portability, mainnet placeholder, and single-key UI invariants.locale/texts.pot— regenerated for the newt\`` strings.What's NOT in
useWalletServiceis forced tofalsefor Web3Auth wallets.WEB3AUTH_CONFIG.mainnet.clientIdisnulluntil the Web3Auth Mainnet project is provisioned; mainnet attempts surface the "Sign-in temporarily unavailable" dialog.appleClientIdisnulluntil access is granted; the Apple card auto-activates once the constant is populated (no other code change required).qa/QA.md.Acceptance criteria
All scenarios must pass on testnet with
web3auth-desktop.rolloutenabled. Mainnet items are gated on Web3Auth project provisioning.Happy path & lifecycle
LockedWalletwith PIN prompt (no seed words). PIN unlocks the wallet and the previous balance is preserved.Receiveaddress matches the previous session.Error / edge cases
Web3AuthErrorDialogwith "Connection problem" copy and a Try-again button.Web3AuthErrorDialogwith the "Set up a recovery factor" copy.Feature gating
web3auth-desktop.rolloutdisabled, the WalletType screen is reachable but the "Sign in with social" button is NOT shown. An already-loaded Web3Auth wallet can still be unlocked fromLockedWalletwith PIN.Cross-platform parity
Mainnet placeholder
Single-key UI invariants
Receive.Build / packaging
npm run build-css && npm run build-jsproduces a green production bundle.npm run locale-update-potsucceeds (nottagLogicalExpressionrejections).npm run electron-pack-mac) completes the Web3Auth happy path — confirms the deferred T0 spike (postMessageorigin behavior between the popupBrowserWindowand the parent renderer).Security checklist
@web3auth/no-modal@^8.12.4,@web3auth/base@^9.7.0,@web3auth/base-provider@^9.7.0,@web3auth/auth-adapter@^9.7.0. These are required for the OAuth + MFA + key-derivation flow; the same versions are used by the mobile wallet. The adapter is pinned to@9to match the renamedWALLET_ADAPTERS.AUTHconstant in@web3auth/base@9(using@web3auth/openlogin-adapter@8triggers a runtimeWallet is not found for undefinedcrash).@hathor/wallet-libis yalc-linked for now and MUST be switched to@hathor/wallet-lib@^3.2.0(published from HathorNetwork/hathor-wallet-lib#1093) before this PR is merged.setWindowOpenHandlerallow-list is scoped to Web3Auth + Google + Apple OAuth hosts; popup runs withcontextIsolation: true,sandbox: true, in thepersist:web3authpartition.singleKeyPrivateKeyis encrypted before being persisted viaLOCAL_STORE.initWeb3AuthStorage; PIN entry is required to decrypt on every unlock.WEB3AUTH_CONFIG(src/constants.js); mainnet entries are intentionallynulluntil provisioned.References
docs/superpowers/specs/2026-05-22-web3auth-desktop-design.mddocs/superpowers/plans/2026-05-22-web3auth-pr1-google-apple-login.mdSummary by CodeRabbit
Release Notes
New Features
Chores