fix(client): avoid interactive reconnect for remaining connectors - #70
Merged
Conversation
🦋 Changeset detectedLatest commit: d9359c6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Binance, Bitget, OKX and OneKey called provider.requestAccounts() unconditionally in connect(), so reconnect opened the wallet extension on every page load. Honour isReconnecting and fall back to passive getAccounts(), matching unisat, xverse and unhosted. Bitget and OKX also short-circuited isAuthorized() on the shimDisconnect storage flag without checking the extension still exposes an account, and all four getAccounts() implementations fed accounts[0] into getAddressInfo() unguarded, throwing when the wallet is locked. UniSat's empty-accounts case now throws ConnectorNotConnectedError instead of being laundered into UserRejectedRequestError by the catch-all — only requestAccounts() can actually be rejected by a user.
okx.getChainId() derives the network from the first account, and okx's connect() catch calls this.disconnect(). So a reconnect against a wallet exposing no accounts threw ConnectorChainIdDetectionError, which wiped the connected shim and set disconnected — permanently disabling auto-reconnect until the user connected by hand, with nothing shown to explain it. Read accounts before the chain id and bail with ConnectorNotConnectedError. Apply the same narrowing across binance, bitget and onekey so the UserRejectedRequestError catch wraps only requestAccounts(), matching unisat: it is the only step a user can actually reject. Adds the reconnect-with-no-accounts case to the shared connector spec, asserting the throw type and that neither shim is disturbed.
chybisov
force-pushed
the
fix/passive-reconnect-connectors
branch
from
July 27, 2026 12:08
96a65d0 to
d9359c6
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #67, which fixed this for UniSat only. Stacked on #69. GitHub retargets this to
mainautomatically when #69 merges, but because #69 lands as a squash commit this branch needs a rebase at that point so its diff does not re-show #69's changes:git rebase --onto origin/main c895e02 && git push --force-with-leaseProblem
reconnect()runs on app mount and callsconnector.connect({ isReconnecting: true }). Four connectors ignored that flag and calledprovider.requestAccounts()unconditionally, which opens the wallet extension. Result: an unprompted wallet popup on every page load.isAuthorized()trusted storage alonegetAccounts()threw when lockedbinancebitgetokxonekeyTwo secondary issues fell out of the same audit:
bitget/okxisAuthorized()returnedBoolean(storage['<id>.connected'])and never checked the extension — the exact weakness fix(client): avoid interactive UniSat reconnect #67 fixed for UniSat. A wallet that was uninstalled or locked still reported authorized.getAccounts()passedaccounts[0]intogetAddressInfo()without a guard, throwing on a locked wallet.binance/onekey'sisAuthorized()only returnedfalsethere by way of that throw hitting theircatch.Fix
Honour
isReconnectingand verify authorization passively, matching the pattern already used byunisat,xverse(xverse.ts:183) andunhosted(unhosted.ts:200):Plus
if (!address) return []in eachgetAccounts(), and the passive account check inbitget/okxisAuthorized(). That last one reads like a logic inversion but is not: the account check now runs in both shim modes, where previously it was skipped entirely whenevershimDisconnectwas set (the default). Behaviour is unchanged forshimDisconnect: false. OKX'sgetAccounts()fetched the public key before reading accounts, so that pair is reordered to avoid a wasted call on the empty path.OKX: a self-inflicted permanent disconnect
Caught while reviewing this PR, and the reason for the second commit.
okx.getChainId()is the only one of the five that derives the network from the first account, and OKX is one of two whoseconnect()catch callsawait this.disconnect(). Combined with thereturn []guard above, a reconnect against an account-less wallet did this:getChainId()->getAccounts()->[]->ConnectorChainIdDetectionError-> catch ->disconnect()persistsdisconnectedand deletesconnected.isAuthorized()then returnsfalseon every later page load, so OKX never auto-reconnects again until the user clicks Connect by hand — silently, from one transient empty read.The mechanism was pre-existing (dismissing the old prompt hit the same catch), but this PR turned a user-visible trigger into an invisible one. OKX now reads accounts before the chain id and bails early.
Error semantics
UniSat threw a bare
Errorfor an account-less extension inside thetrywhosecatchrewrites everything toUserRejectedRequestError— so a locked wallet surfaced to apps as "user rejected". All five connectors now wrap onlyrequestAccounts()in thattry, the one step a user can actually reject, and throwConnectorNotConnectedErrorfor the empty case. That narrowing is what keeps OKX's and Bitget'sdisconnect()cleanup scoped to real rejections instead of firing on any downstream failure.Tests
New
passiveReconnect.spec.tsruns the same four assertions against all four connectors viadescribe.each(16 tests). Verified against the unfixed code: 10 of the 16 fail, and exactly the expected ones — all four passive-reconnect and empty-accounts cases, plusis not authorizedforbitget/okxonly (binance/onekeyalready returnedfalse, via the accidental throw).A fifth shared case — reconnect against an account-less extension — asserts the throw type and that neither shim is disturbed. Verified against the previous commit: OKX fails it, the other three pass, which is exactly the blast radius described above. Two cases added to
unisat.spec.tspin the error semantics: account-less →ConnectorNotConnectedError, rejected prompt →UserRejectedRequestError.pnpm test: 28 passed (client), 32 passed / 1 skipped (core).Not included
reownalso connects unconditionally (reown.ts:157,provider.connector.connect()), which opens the WalletConnect modal on mount. It is not a copy-paste of this fix and can't be covered by these stubs, so it is tracked separately.Validation
pnpm check,pnpm check:types,pnpm check:circular-deps,pnpm knip:check,pnpm testall pass.