Skip to Content
On this page

This guide shows you how to let a user who is already connected prove they own another wallet, for example to link it to their account. You render a second WalletConnectProvider for the wallet being linked, configured so it doesn’t take over the page’s main connection. You need SDK 4.1.0 or later.

Add a Linking Provider#

  1. Build the second provider’s config from your main config, so it connects only when the user picks a wallet, saves nothing, and offers external wallets only:

    import type { WalletConnectKitConfig } from "@dogeos/dogeos-sdk" import { dogeConfig } from "./doge-config" // your main provider's config export function linkConfig(alreadyLinked: string[]): WalletConnectKitConfig { return { ...dogeConfig, login: { basicLogins: ["externalWallets"], socialLogins: [] }, autoRestore: false, persistConnection: false, forceAccountSelection: { blockedAddresses: alreadyLinked }, } }
    • Starting from your main config keeps its chains, walletConnectProjectId, metadata and theme. Without chains, the SDK would ask the linked wallet to switch to Ethereum mainnet after connecting, and the link fails if the user declines. Without walletConnectProjectId, phone users are sent to the wallet’s own browser and can’t finish linking.
    • autoRestore: false and persistConnection: false keep it from restoring, saving or replacing the main connection.
    • Leaving out email and social login keeps a signed-in user’s social wallet from connecting here on load.
    • forceAccountSelection asks EVM browser wallets to show their account picker, even for a site they already approved, so the user can choose a different account. blockedAddresses only applies to wallets that can’t be asked to show their picker: if such a wallet returns an address you’ve already linked, the connection fails and asks the user to disconnect the site in their wallet. Wallets that do show their picker can still return a linked address, so your server must reject addresses that are already linked.
  2. Render it around an inline wallet panel that stays on the wallet list after connecting:

    "use client" import { useMemo } from "react" import { WalletConnectEmbed, WalletConnectProvider } from "@dogeos/dogeos-sdk" import { linkConfig } from "./link-config" import { ProveOwnership } from "./prove-ownership" export function LinkWallet({ alreadyLinked }: { alreadyLinked: string[] }) { const config = useMemo(() => linkConfig(alreadyLinked), [alreadyLinked]) return ( <WalletConnectProvider config={config}> <h2>Link another wallet</h2> <WalletConnectEmbed initialView="walletList" hideHeader keepPickerAfterConnect /> <ProveOwnership /> </WalletConnectProvider> ) }

    SDK hooks inside this provider see the wallet being linked; SDK hooks outside it still see the main connection.

Prove Ownership#

When the user connects a wallet in the panel, ask it to sign a challenge from your server, and link the wallet only after your server verifies the signature:

"use client" import { useState } from "react" import { isWalletError, useAccount, WalletErrorCode } from "@dogeos/dogeos-sdk" export function ProveOwnership() { const { address, signMessage } = useAccount() const [status, setStatus] = useState("") if (!address || !signMessage) return null async function onLink() { if (!signMessage) return try { const { message } = await fetch("/api/link-challenge", { method: "POST" }).then((res) => res.json()) const signature = await signMessage({ message }) const res = await fetch("/api/link-wallet", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, message, signature }), }) setStatus(res.ok ? "Wallet linked." : "Linking failed.") } catch (error) { if (isWalletError(error, WalletErrorCode.UserRejected)) return setStatus("Linking failed.") } } return ( <div> <button type="button" onClick={onLink}> Link {address.slice(0, 6)}…{address.slice(-4)} </button> {status ? <p role="status">{status}</p> : null} </div> ) }

Your server issues a fresh, single-use challenge for the signed-in user, verifies the signature against address, rejects an address that’s already linked, and only then links the wallet. See Sign Users In With Their Wallet for what the server checks.

Things to Know#

  • Hide the linking UI inside the MyDoge app, when useWalletHost().isNativeHost is true. There the second provider can only connect the same MyDoge account as the main one, and its card’s Disconnect revokes the dApp’s approval in MyDoge, which also ends the main connection.
  • forceAccountSelection is ignored by the embedded wallet, because it holds the page’s main session.
  • forceAccountSelection: { revokePermissions: true } makes more wallets show their picker, but it also ends a main connection to the same wallet on the page. Leave it off when the page has one.
  • If your app uses Wagmi, Wagmi switches to the linked wallet when it connects, and stays on it until it disconnects, even after the linking panel closes. When linking is done, call disconnect() from useWalletConnect() inside the linking provider; Wagmi then falls back to the main connection. Until then, read the main account from the SDK’s useAccount() outside the linking provider.
  • In SDK 4.1.0, picking WalletConnect in the linking panel on a computer fails before the QR code appears, because of forceAccountSelection. Browser extensions, and wallet apps on phones, aren’t affected.
  • If the user links an account from the same browser wallet as their main connection, switching accounts in that wallet’s picker can also move the main connection to the new account. Check the main account again after linking.