logo

Implementing “Connect Wallet” via WalletConnect v2: Getting to a Stable Session

3D rendering of a sleek, futuristic technology design featuring glowing elements and advanced shapes.

The first “Connect wallet” button you ship is usually a small celebration.

The second one is where reality shows up: a user connects on mobile, returns to your app, and the session is gone. Another user approves Ethereum and Polygon, then taps a feature that requires a third chain, and everything stalls. Someone else keeps seeing “Proposal expired” even though they scanned the QR code right away.

WalletConnect v2 fixes a lot of what made v1 brittle, yet it also forces you to be more explicit. Pairing, permissions, and sessions each have a job, and your integration has to respect that separation. When you do, “connect” stops being a UI action and becomes a dependable capability your app can build on.

Why WalletConnect v2 behaves differently from injected wallets

If you’ve built around an injected provider, you’re used to a single object that represents everything: identity, network, and the ability to sign. WalletConnect v2 is closer to a remote-control channel. Your app proposes what it wants, the wallet approves a scope, and then you operate within that scope until the session changes.

Pairing and session are separate on purpose

In v2, the URI you display (QR code or deep link) creates a pairing relationship. Only after pairing is established does your app propose a session with permissions. The WalletConnect docs describe this decoupling directly: a URI is used to construct a pairing proposal, and a session is proposed after pairing settles.

This matters because it changes the mental model of “connect.” Your UI can treat pairing as the handshake and session approval as the moment you obtain usable permissions. Your code should treat them the same way. If you collapse them, you end up with reconnection edge cases that feel random, especially on mobile, where app switching is the norm.

The relay is part of your dependency surface

WalletConnect v2 is a messaging protocol, and its network relies on relaying encrypted messages between peers. The WalletConnect specs outline server components like a relay server that routes messages using pub-sub with topic-based routing.

WalletConnect’s own v2 network announcement also highlights a major design shift: v2 uses Waku (libp2p-based) by default to relay messages and removes the old requirement that both clients remain connected to the same server. This is good news for reliability across devices and networks. It also means your “connect” quality depends on how well you handle message timing, app foregrounding, and session state.

Permissions are negotiated up front, and that changes UX

WalletConnect v2 sessions are permissioned using namespaces. Wallets must satisfy all “required” namespaces, and they may satisfy some or all “optional” namespaces. This design keeps capabilities explicit, which is great for security. It also introduces a key UX nuance: chains and methods should be requested intentionally, because a wallet can only approve what you ask for.

Even common integration toolkits call this out. wagmi’s WalletConnect connector documentation notes that v2 requires the user to pre-approve a set of chains up front, which affects what happens when the user later tries to switch to an unapproved chain.

Namespaces and session permissions: What to request, and why it matters

A v2 “Connect wallet” flow is only as solid as the session proposal you send. Many “it connects on my device” bugs are really “our requiredNamespaces are too strict” or “our requiredNamespaces are missing something we actually need.”

Required and optional namespaces have strict rules

The namespaces spec is blunt about the contract: all required namespaces must be satisfied by the wallet. Optional namespaces can be satisfied fully, partially, or not at all.

A useful way to structure your proposal is to make the required set minimal and stable. Then use optional namespaces to express “nice to have” capabilities, such as additional chains or extra methods that unlock advanced flows. This keeps your connect rate high while still letting your app discover what the wallet is willing to support.

CAIP-2 chain IDs are the boundary

WalletConnect v2 expresses chain support using CAIP-2 identifiers in namespaces. In the EVM world, you’ll typically operate under an eip155 namespace with chain IDs formatted like eip155:1 for Ethereum mainnet. The Trust Wallet developer docs explicitly mention that WalletConnect v2 follows CAIP-25 for session establishment and points developers to the namespaces spec for connecting to different networks.

This becomes practical when you look at real wallet behavior. For example, there are reported cases where a wallet rejects a session proposal if the requested chains don’t match the wallet’s current active chain. You don’t need to design around every wallet quirk, yet you do need a strategy: request only what you’ll use, and avoid over-scoping required chains “just in case.”

Methods and events should match actual product features

WalletConnect v2 is often introduced as “connect and sign,” but in v2, those are separate requests with explicit permissions. You’re declaring which JSON-RPC methods you’ll call and which session events you expect to receive, then the wallet decides whether to grant them.

This is the point where many teams either over-request (hurting conversion) or under-request (creating feature failures later). The healthiest posture is to map methods to user-facing features. If the product has a “Sign message” feature, request the method for that. If it supports sending transactions, request the relevant method. If the product has no reason to sign typed data, don’t request it yet.

Implementation flow that stays stable across mobile and desktop

WalletConnect integrations get a reputation for being “fussy” when the flow is implemented as one big connect function. They become much calmer when you treat them as a session lifecycle with clear phases and state.

Project setup: Client initialization and identity

WalletConnect v2 requires a project identity, commonly a project ID from the WalletConnect Cloud. Trust Wallet’s guide for WalletConnect v2 highlights initializing the client with your project ID and notes that the protocol uses the Sign API for connectivity.

At the code level, the important decision is where session state lives. You want a storage layer that survives refreshes and mobile app switching. If you rely on in-memory state, you’ll end up re-pairing users who already approved you yesterday, and it will feel like a bug.

Pairing UX: QR code, deep links, and returning to the app

Pairing begins when your app generates a URI. The v2 model makes this explicit: you share the URI for pairing, and only after pairing settles do you propose a session.

For web apps, QR codes are the baseline. For mobile apps, deep linking and “return to dapp” behavior are the difference between a clean flow and a confusing one. WalletConnect’s mobile linking documentation describes mobile linking as a way for the wallet to redirect back to the dapp to reduce user interactions.

In practice, the smoothest integrations treat pairing as a UI state machine: “waiting for scan,” then “pairing established,” then “session pending approval.” Each state should have its own timeout and recovery message. Users shouldn’t need to guess whether they should rescan, reopen the wallet, or return to your app.

Session proposal: requiredNamespaces, optionalNamespaces, and product intent

Once a pairing exists, you propose a session under the Sign protocol. The WalletConnect spec for session proposals frames pairing as a prerequisite before proceeding to Sign execution.

This is where you translate product needs into permissioning. A wallet that supports your required namespaces can approve, and you receive a session with namespaces describing what was granted. A wallet that can’t satisfy the required portion should be rejected quickly. The client API spec shows that the responder can approve a session proposal by providing namespaces or reject it with a reason.

Session requests: Signing, sending, and handling responses

After the session is established, “connect wallet” becomes meaningful because you can request actions. This is the stage where many apps accidentally leak complexity into UI components. A better approach is to centralize request construction in one module that understands chain IDs, method names, and per-chain RPC parameters.

When a user triggers an on-chain action, your module creates the request payload and sends it via the WalletConnect Sign client. The wallet surfaces it to the user, the user approves or rejects, and your app receives a response. If you keep the request format consistent, your error handling becomes consistent too, and the UX improves without redesign.

Session lifecycle: Updates, disconnects, and chain coverage changes

A v2 session is not static. Wallets can update session namespaces. Users can disconnect. Your app may need an additional chain later, and the original session might not cover it.

This is where the v2 “pre-approval” nuance becomes real product behavior. If a user approved Ethereum and Polygon, then later opens a BSC-only feature, you either prompt for a new session that includes BSC or you keep that feature unavailable until the user reconnects. wagmi’s docs call out that v2’s up-front chain approvals have UX consequences when switching to chains that were never approved.

The clean product approach is to make the permission boundary visible. If a feature requires an unapproved chain, don’t fail at the last second. Detect it early, explain what needs approval, and propose an updated session.

Benefits you get when WalletConnect v2 is wired correctly

Multi-chain sessions become a real capability

WalletConnect v2’s namespace model lets a single session cover multiple chains and accounts, which turns “supports Polygon and Ethereum” into an actual permissioned state rather than a UI toggle that can drift. The practical effect is fewer mid-flow interruptions. A user can connect once, approve the scope, then move between features that target different EVM networks without re-pairing. This maps well to how v2 sessions are negotiated: required namespaces must be satisfied, and optional namespaces can expand capability when the wallet supports them.

Mobile UX gets less fragile because pairing and session are separate stages

On mobile, the biggest enemy of “connect” is app switching. v2’s explicit split between pairing and session gives you clearer UI states: you can show “waiting for scan,” then “connected,” then “approval pending,” and recover gracefully when the user returns from the wallet. WalletConnect’s documentation describes pairing as the URI-based handshake and session proposal as the permissions step, which is exactly the separation that helps you avoid “it connected, then vanished” behavior. Mobile linking support adds to this by making the “return to dapp” path more reliable, which reduces abandoned flows.

Permission boundaries become clearer, and that reduces approval fatigue

In v1-style flows, apps often asked for broad access because there was no clean way to express scope. In v2, you declare chains, methods, and events up front in namespaces, and wallets evaluate that request. That changes the tone of the approval screen. Users see permissions that are closer to what they’re actually doing, and your product team has a reason to keep the required scope minimal while still supporting advanced features through optional namespaces. Over time, this tends to improve conversion because fewer users bounce when the request looks oversized.

Reliability improves under real-world connectivity and backgrounding

WalletConnect v2 is built around a relay-based messaging network with topic routing, and WalletConnect’s own v2 network announcement highlights that the design reduces reliance on both clients staying connected to the same server. That matters in the messy conditions where users actually connect wallets: flaky Wi-Fi, mobile data handoffs, backgrounded apps, and wallet deep links. When your integration also handles timeouts and retries cleanly, the protocol’s asynchronous model becomes a real resilience layer rather than a complexity tax.

Your codebase gets calmer because requests and errors can be centralized

A v2 integration nudges you into having one “wallet transport” module that builds requests and parses results. Once you centralize chain ID handling, method naming, and payload shapes, you stop duplicating wallet logic across UI components. The Sign client API spec supports this model by defining predictable request/response handling, and it pairs well with a single error taxonomy in your app. The benefit shows up months later: adding a new signing flow becomes a small change in one module rather than a brittle UI refactor.

Conclusion

A WalletConnect v2 “Connect wallet” integration works best when you treat it as a session lifecycle, not a single API call. Pairing creates the communication channel, session proposals define permissions, and namespaces make those permissions explicit.

Once your app respects those boundaries, the feature becomes easier to evolve. Adding chains becomes a permissioned extension rather than a brittle switch. Mobile flows become clearer because you can guide users through pairing, approval, and return. And the support tickets shift from “connect is broken” to concrete, diagnosable cases where a session scope needs to be updated.