Skip to content

fix(controllers): scope one-click auth chains to the connector's namespace - #5729

Closed
svenvoskamp wants to merge 1 commit into
mainfrom
fix/wc-one-click-auth-namespace-scope
Closed

fix(controllers): scope one-click auth chains to the connector's namespace#5729
svenvoskamp wants to merge 1 commit into
mainfrom
fix/wc-one-click-auth-namespace-scope

Conversation

@svenvoskamp

Copy link
Copy Markdown
Contributor

Description

WalletConnect one-click auth was silently skipped for every wallet as soon as a second chain namespace was registered.

WalletConnectConnector.authenticate() passed this.chains to SIWXUtil.universalProviderAuthenticate. That getter resolves to ChainController.getCaipNetworks() with no namespace argument — i.e. getAllRequestedCaipNetworks(), every registered namespace — even though the connector is constructed per namespace and already stores it as this.chain:

constructor({ provider, namespace }: WalletConnectConnector.Options<Namespace>) {
  this.caipNetworks = this.getCaipNetworks()
  this.provider = provider
  this.chain = namespace          // ← the namespace is right here
}

get chains() {
  return this.getCaipNetworks()   // ← but this returns all of them
}

universalProviderAuthenticate then bails early (SIWXUtil.ts:391):

const namespaces = new Set(chains.map(chain => chain.split(':')[0] as ChainNamespace))

if (!siwx || namespaces.size !== 1 || !namespaces.has('eip155')) {
  return false
}

So an app registering, say, Solana alongside EVM produced {eip155, solana} → size 2 → authenticate() returned false, and connectWalletConnect() fell back to provider.connect() plus a separate personal_sign round trip on every login. That fallback is markedly less reliable on mobile wallets — it reproduces as "Error signing message" on Trust Wallet mobile, where one-click auth would otherwise be used and works.

getCaipNetworks() already accepts a namespace, so this passes this.chain.

Single-namespace apps are unaffected — the set was already size 1, so the value is identical.

Why authenticate() and not the chains getter

chains is part of the public ChainAdapterConnector surface, and the bitcoin, tron and ton adapters read connector.chains[0], so narrowing it deserves its own change. Two things worth noting for a follow-up:

  • TonWalletConnectConnector and TronWalletConnectConnector already override get chains() to scope by namespace, which suggests namespace-scoping is the intended semantics of the base getter and the eip155/solana connectors simply never got it.
  • With the unscoped getter, connector.chains[0] on a non-EVM connector can currently return a network from a different namespace.

Also unused but not touched here: WalletConnectConnector.Options.caipNetworks is accepted by the constructor and then discarded in favour of this.getCaipNetworks().

Type of change

  • Chore (non-breaking change that addresses non-functional tasks, maintenance, or code quality improvements)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Associated Issues

None filed — found while debugging a multi-namespace (EVM + Solana) app with SIWX, and independently reproduced on SIWX in the laboratory app.

Showcase (Optional)

Effect on a hub registering 11 EVM chains plus Solana with siwx and required: true:

connector chains passed to universalProviderAuthenticate one-click auth
eip155 — before 12 chains, {eip155, solana} not offered
eip155 — after 11 chains, {eip155} offered
solana — after 1 chain, {solana} correctly declines (EIP-4361 is EVM-only)

Checklist

  • Code in this PR is covered by automated tests (Unit tests, E2E tests)
  • My changes generate no new warnings
  • I have reviewed my own code
  • I have filled out all required sections
  • I have tested my changes on the preview link
  • Approver of this PR confirms that the changes are tested on the preview link

Test notes

New packages/controllers/tests/controllers/WalletConnectConnector.test.ts covers an eip155 and a non-eip155 connector while two namespaces are registered. Confirmed it is a real regression test — both cases fail on main and pass with the fix.

packages/controllers: 49/50 test files pass. The one failure, tests/features/ReownAuthentication.test.ts (25 tests), fails identically on unmodified main in my environment — it expects a populated projectId in request URLs — so it is pre-existing and unrelated.

Not run locally: eslint crashes in a node:fs callback on my machine regardless of heap size, and tsc --noEmit in packages/controllers reports 7 pre-existing ChainNamespace/tron errors from a stale local appkit-common build. Neither implicates the changed files (0 errors reference them); leaving both to CI.

I have not been able to verify the Trust Wallet mobile flow end-to-end against a preview build — happy to if someone can point me at one.

🤖 Generated with Claude Code

…space

`WalletConnectConnector.authenticate()` passed `this.chains` to
`SIWXUtil.universalProviderAuthenticate`. That getter resolves to
`ChainController.getCaipNetworks()` with no namespace argument, i.e.
`getAllRequestedCaipNetworks()` — every registered namespace rather than
the connector's own, even though the connector is constructed per
namespace and already stores it as `this.chain`.

`universalProviderAuthenticate` then bails early:

    const namespaces = new Set(chains.map(chain => chain.split(':')[0]))
    if (!siwx || namespaces.size !== 1 || !namespaces.has('eip155')) {
      return false
    }

So registering any second namespace alongside EVM — Solana, for example —
made that set `{eip155, solana}` and silently disabled one-click auth for
every wallet. `connectWalletConnect()` fell back to `provider.connect()`
followed by a separate `personal_sign` round trip on every login, which is
markedly less reliable on mobile wallets; it reproduces as "Error signing
message" on Trust Wallet.

`getCaipNetworks()` already accepts a namespace, so pass `this.chain`.
Single-namespace apps are unaffected: the set was already of size one.
Scoped in `authenticate()` rather than in the `chains` getter because
`chains` is part of the public `ChainAdapterConnector` surface — the
bitcoin, tron and ton adapters read `connector.chains[0]` — so narrowing it
deserves its own change.

Added a regression test covering both an eip155 and a non-eip155 connector
while two namespaces are registered.

Co-Authored-By: Claude <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 246f056

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@reown/appkit-controllers Patch
@reown/appkit-adapter-bitcoin Patch
@reown/appkit-adapter-ethers Patch
@reown/appkit-adapter-ethers5 Patch
@reown/appkit-adapter-solana Patch
@reown/appkit-adapter-ton Patch
@reown/appkit-adapter-tron Patch
@reown/appkit-adapter-wagmi Patch
@reown/appkit Patch
@reown/appkit-ui Patch
@reown/appkit-core Patch
@reown/appkit-utils Patch
@reown/appkit-scaffold-ui Patch
@reown/appkit-siwe Patch
@reown/appkit-siwx Patch
@reown/appkit-wallet-button Patch
@reown/appkit-experimental Patch
@reown/appkit-pay Patch
@reown/appkit-cdn Patch
@reown/appkit-universal-connector Patch
@reown/appkit-testing Patch
@reown/appkit-common Patch
@reown/appkit-polyfills Patch
@reown/appkit-wallet Patch
@reown/appkit-cli Patch
@reown/appkit-codemod Patch

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

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
appkit-demo Ready Ready Preview Jul 30, 2026 4:25pm
appkit-gallery Ready Ready Preview Jul 30, 2026 4:25pm
appkit-headless-sample-app Ready Ready Preview Jul 30, 2026 4:25pm
appkit-laboratory Ready Ready Preview Jul 30, 2026 4:25pm
9 Skipped Deployments
Project Deployment Actions Updated (UTC)
appkit-basic-example Ignored Ignored Jul 30, 2026 4:25pm
appkit-basic-sign-client-example Ignored Ignored Jul 30, 2026 4:25pm
appkit-basic-up-example Ignored Ignored Jul 30, 2026 4:25pm
appkit-ethers5-bera Ignored Ignored Jul 30, 2026 4:25pm
appkit-nansen-demo Ignored Ignored Jul 30, 2026 4:25pm
appkit-wagmi-cdn-example Ignored Ignored Jul 30, 2026 4:25pm
ethereum-provider-wagmi-example Ignored Ignored Jul 30, 2026 4:25pm
next-wagmi-solana-bitcoin-example Ignored Ignored Jul 30, 2026 4:25pm
vue-wagmi-example Ignored Ignored Jul 30, 2026 4:25pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Visual Regression Test Results ✅ Passed

⚠️ 20 visual change(s) detected

Chromatic Build: https://www.chromatic.com/build?appId=6493191bf4b10fed8ca7353f&number=996
Storybook Preview: https://6493191bf4b10fed8ca7353f-stnrchyvbt.chromatic.com/

👉 Please review the visual changes in Chromatic and accept or reject them.

@github-actions

Copy link
Copy Markdown
Contributor
Warnings
⚠️

🔑 Potential UUID detected in packages/controllers/src/controllers/AdapterController/WalletConnectConnector.ts (line 18): ef1a1fcf-7fe8-4d69-b...

Generated by 🚫 dangerJS against 246f056

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Check

All bundles are within size limits

📊 View detailed bundle sizes

> @reown/appkit-monorepo@1.7.1 size /home/runner/work/appkit/appkit


> size-limit

@reown/appkit - Main Entry
Size limit:   80 kB
Size:         75.87 kB with all dependencies, minified and gzipped
Loading time: 1.5 s    on slow 3G
Running time: 781 ms   on Snapdragon 410
Total time:   2.3 s
@reown/appkit/react
Size limit:   236 kB
Size:         235.11 kB with all dependencies, minified and gzipped
Loading time: 4.6 s     on slow 3G
Running time: 1.6 s     on Snapdragon 410
Total time:   6.2 s
@reown/appkit/vue
Size limit:   80 kB
Size:         75.87 kB with all dependencies, minified and gzipped
Loading time: 1.5 s    on slow 3G
Running time: 431 ms   on Snapdragon 410
Total time:   2 s
@reown/appkit-scaffold-ui
Size limit:   220 kB
Size:         214.41 kB with all dependencies, minified and gzipped
Loading time: 4.2 s     on slow 3G
Running time: 1.6 s     on Snapdragon 410
Total time:   5.7 s
@reown/appkit-ui
Size limit:   500 kB
Size:         13.16 kB with all dependencies, minified and gzipped
Loading time: 258 ms   on slow 3G
Running time: 106 ms   on Snapdragon 410
Total time:   363 ms

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.63% 40224 / 51156
🔵 Statements 78.63% 40224 / 51156
🔵 Functions 76.1% 4294 / 5642
🔵 Branches 86.72% 9848 / 11355
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/controllers/src/controllers/AdapterController/WalletConnectConnector.ts 100% 100% 100% 100%
Generated in workflow #17215 for commit 246f056 by the Vitest Coverage Report Action

@svenvoskamp

Copy link
Copy Markdown
Contributor Author

Closing this — the fix is wrong, and the failing E2E test is correct to reject it.

multi-wallet-siwx.spec.ts:62 › should connect multiple wallets with SIWX fails with Siwe prompt sign button should be visible, alongside this in the console:

An error occurred while connecting an inactive namespace solana: "Failed to connect to the wallet"

I had read the namespaces.size !== 1 guard as an accident. It isn't. chains is passed straight into universalProvider.authenticate({ ..., chains }), so it defines the session's namespaces, not just the chain list for the SIWE message — and the code immediately after assumes a single namespace:

ChainController.setAccountProp(
  'connectedWalletInfo',
  walletInfo,
  Array.from(namespaces)[0] as ChainNamespace
)

So wc_sessionAuthenticate can only establish a single-EVM-namespace authenticated session, and declining one-click auth for multi-namespace apps is deliberate: it lets connectWalletConnect() fall back to provider.connect({ optionalNamespaces }), which establishes a session covering every namespace, after which SIWX prompts a separate personal_sign. Scoping chains to this.chain made one-click auth succeed for multichain apps and silently dropped the non-EVM namespaces from the session — exactly what the test caught.

For the record, the problem that sent me here is real but lives elsewhere: on a multi-namespace app (EVM + Solana) with SIWX, mobile wallets always take the connect-then-personal_sign fallback, and that path fails on Trust Wallet mobile with "Error signing message". I'll chase the actual cause in the fallback path rather than the one-click-auth gate.

Two things I noticed that may still be worth separate issues:

  1. WagmiAdapter.signMessage (packages/adapters/wagmi/src/client.ts) discards the underlying error and rethrows a bare new Error('WagmiAdapter:signMessage - Sign message failed'), so the real cause of a failed sign never reaches the console. That made this considerably harder to diagnose than it needed to be.
  2. WalletConnectConnector.Options.caipNetworks is accepted by the constructor and then discarded in favour of this.getCaipNetworks().

Sorry for the noise.

@svenvoskamp
svenvoskamp deleted the fix/wc-one-click-auth-namespace-scope branch July 30, 2026 16:51
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant