
Walletconnect
- 5 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Connect wallets to dApps with WalletConnect v2 - Sign Client sessions, namespaces, Universal Provider, and the EIP-1193 Ethereum Provider.
About
WalletConnect is an open protocol connecting wallets to dApps over an encrypted relay, with Sign Client, Universal Provider, and Ethereum Provider. A developer uses it to add multi-chain wallet connections to a dApp.
- Sign Client sessions, pairings, proposals, and requests
- Universal Provider (multi-chain) and EIP-1193 Ethereum Provider
Walletconnect by the numbers
- 5 all-time installs (skills.sh)
- Ranked #338 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill walletconnectAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Connect wallets to dApps with WalletConnect v2 - Sign Client sessions, namespaces, Universal Provider, and the EIP-1193 Ethereum Provider.
Files
Skills are based on WalletConnect monorepo (sign-client v2.23.x), generated from source AGENTS.md, package READMEs, and types.
WalletConnect is an open protocol for connecting wallets to dApps via end-to-end encrypted relay. The SDK provides Sign Client (sessions, pairings, proposals, requests), Universal Provider (multi-chain), and Ethereum Provider (EIP-1193).
Core References
| Topic | Description | Reference |
|---|---|---|
| Sign Client | Init, connect, pair, approve, reject, request, respond, disconnect, events | core-sign-client |
| Sessions and Namespaces | Session/pairing lifecycle, CAIP-25 namespaces, required/optional | core-sessions-namespaces |
| Pairing and URI | wc: URI format, QR/deep link, reusing pairings | core-pairing-uri |
Features
Providers
| Topic | Description | Reference |
|---|---|---|
| Universal Provider | Multi-chain provider, connect, request, setDefaultChain, events | features-universal-provider |
| Ethereum Provider | EIP-1193 provider, connect, request, events, Next.js/SSR | features-ethereum-provider |
| Signer Connection | IJsonRpcConnection wrapper, open/close/send, signer events | features-signer-connection |
Pay
| Topic | Description | Reference |
|---|---|---|
| Pay SDK | getPaymentOptions, getRequiredPaymentActions, confirmPayment, React Native | features-pay |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Security and Debugging | Keys, validation, URIs; DEBUG logs, session/pairing inspection | best-practices-security-debugging |
| Error Handling | getSdkError, SDK error codes, reject/disconnect reasons | best-practices-error-handling |
Generation Info
- Source:
sources/walletconnect - Git SHA:
282a488d36991ddf40b3985b4f6a89183c3b29f3 - Generated: 2026-02-24
Error Handling
WalletConnect uses structured errors from @walletconnect/utils: SDK_ERRORS, INTERNAL_ERRORS, getSdkError(key, context?), and getInternalError(key, context?). Use them when rejecting session proposals or returning error responses.
SDK errors (user-facing)
Use getSdkError(key) for rejections and JSON-RPC error responses. Returns { message, code }. Categories: Invalid (1xxx), Unauthorized (3xxx), Rejected (5xxx), Unsupported (51xx), USER_DISCONNECTED (6xxx), SESSION_SETTLEMENT_FAILED (7xxx), WC_METHOD_UNSUPPORTED (10xxx).
Rejecting and disconnecting
import { getSdkError } from "@walletconnect/utils";
await client.reject({ id: proposalId, reason: getSdkError("USER_REJECTED") });
await client.disconnect({ topic: sessionTopic, reason: getSdkError("USER_DISCONNECTED") });Key points
- Always use getSdkError when calling reject(), disconnect(), or returning JSON-RPC errors. Pass optional context as second argument.
<!-- Source references:
- sources/walletconnect/packages/utils/src/errors.ts
-->
Security
- Private keys – Never log or expose; clear from memory when not needed. The SDK uses X25519 + ChaCha20-Poly1305 for encryption; keys stay in the client.
- Session validation – Verify
requiredNamespacesandoptionalNamespacesin proposals; only approve chains/methods/events the wallet supports. - Input validation – Validate CAIP-2 chain IDs and CAIP-10 accounts before building namespaces or responding.
- Message integrity – Reject malformed or unexpected payloads; rely on the SDK’s encryption and avoid tampering with envelopes.
- URI handling – Validate
wc:protocol URIs before passing topair({ uri }); do not trust unverified sources.
Debugging
- Logs – Set
DEBUG=walletconnect:*(orwalletconnect:sign-client:*) when running Node to get detailed logs. - Relay – Inspect WebSocket traffic to confirm connect/publish/subscribe if relay issues are suspected.
- Sessions – Use
client.session.getAll()to list active sessions and their topics/namespaces. - Pairings – Use
client.core.pairing.getPairings()(orclient.pairing) to list pairings and topics. - Pending requests – Use
client.getPendingSessionRequests()to see pending session_request payloads.
Request queue (wallet)
By default, the Sign Client processes session requests sequentially. You can disable the queue with signConfig: { disableRequestQueue: true }. If disabled, implement your own deduplication (e.g. by request id) because the relay has at-least-once delivery and duplicates can occur especially shortly after init.
Key points
- Use a dedicated
projectIdper app from WalletConnect Cloud. - For production, validate proposer metadata and namespace constraints before calling
approve. - When debugging, avoid logging full session or pairing objects in production; use topic/ids only if needed.
<!-- Source references:
- sources/walletconnect/AGENTS.md
- sources/walletconnect/packages/types/src/sign-client/client.ts (SignConfig)
-->
Security and Debugging
Security
- Private keys – Never log or expose; clear from memory when not needed. The SDK uses X25519 + ChaCha20-Poly1305 for encryption; keys stay in the client.
- Session validation – Verify
requiredNamespacesandoptionalNamespacesin proposals; only approve chains/methods/events the wallet supports. - Input validation – Validate CAIP-2 chain IDs and CAIP-10 accounts before building namespaces or responding.
- Message integrity – Reject malformed or unexpected payloads; rely on the SDK’s encryption and avoid tampering with envelopes.
- URI handling – Validate
wc:protocol URIs before passing topair({ uri }); do not trust unverified sources.
Debugging
- Logs – Set
DEBUG=walletconnect:*(orwalletconnect:sign-client:*) when running Node to get detailed logs. - Relay – Inspect WebSocket traffic to confirm connect/publish/subscribe if relay issues are suspected.
- Sessions – Use
client.session.getAll()to list active sessions and their topics/namespaces. - Pairings – Use
client.core.pairing.getPairings()(orclient.pairing) to list pairings and topics. - Pending requests – Use
client.getPendingSessionRequests()to see pending session_request payloads.
Request queue (wallet)
By default, the Sign Client processes session requests sequentially. You can disable the queue with signConfig: { disableRequestQueue: true }. If disabled, implement your own deduplication (e.g. by request id) because the relay has at-least-once delivery and duplicates can occur especially shortly after init.
Key points
- Use a dedicated
projectIdper app from WalletConnect Cloud. - For production, validate proposer metadata and namespace constraints before calling
approve. - When debugging, avoid logging full session or pairing objects in production; use topic/ids only if needed.
<!-- Source references:
- sources/walletconnect/AGENTS.md
- sources/walletconnect/packages/types/src/sign-client/client.ts (SignConfig)
-->
Pairing and URI
Pairings are the initial handshake between dApp and wallet. A pairing is created when the user scans a QR code or opens a deep link; the resulting pairingTopic can be reused so the dApp does not show a new QR on subsequent connections.
URI format (wc:)
WalletConnect URIs use the form:
wc:<topic>@2?relay-protocol=irn&symKey=<symKey>- topic – Pairing topic (used in
pairingTopicand inclient.pair({ uri })). - relay-protocol – Relay protocol (e.g.
irn). - symKey – Symmetric key material for the relay.
Do not construct URIs manually; use the URI returned from client.connect() (dApp) or from the relay when pairing. Validate that the URI starts with wc: and matches the expected version before calling pair({ uri }).
dApp: obtaining the URI
const { uri, approval } = await client.connect({
requiredNamespaces: { eip155: { ... } },
});
// Show uri to user (QR or deep link)
if (uri) displayQrOrDeepLink(uri);
const session = await approval();The same connect() call can pass an existing pairingTopic to reuse a pairing and avoid showing a new QR.
Wallet: pairing by URI
const { topic } = await client.pair({ uri: "wc:..." });
// Store topic to reuse later or use in session_proposal handlingReusing a pairing
- dApp: Pass
pairingTopicintoconnect({ pairingTopic: existingTopic, requiredNamespaces, ... })so the existing pairing is used and a new URI may not be emitted. - Universal Provider: Pass
pairingTopicinconnect({ namespaces, pairingTopic }). - Ethereum Provider: Pass
pairingTopicinconnect({ pairingTopic, ... }).
When reusing, ensure the pairing is still valid (not expired and not deleted). Use client.core.pairing.getPairings() to list pairings and their topics.
Key points
- One pairing can back multiple sessions over time; one session is typically one dApp–wallet pair.
- URIs are single-use; after a successful pair/session, do not reuse the same URI.
- For security, validate URIs (protocol, format) and only pass them from trusted flows (e.g. your own QR/deep-link UI).
<!-- Source references:
- sources/walletconnect/AGENTS.md
- sources/walletconnect/packages/core (pairing, URI)
- sources/walletconnect/packages/types (pairing types)
-->
Sessions and Namespaces
Sessions are persistent connections between a wallet and a dApp with agreed permissions. Namespaces are chain-agnostic (CAIP-25) and define chains, methods, and events.
Session structure
A session has: topic, pairingTopic, relay, expiry, namespaces, self/peer metadata. Use client.session.get(topic) or client.session.getAll() to read.
Namespace shape (CAIP-25)
Each namespace key is a chain namespace (e.g. eip155, solana). Value:
{
chains?: string[]; // e.g. ["eip155:1", "eip155:137"]
accounts: string[]; // CAIP-10 e.g. "eip155:1:0x..."
methods: string[];
events: string[];
}Required vs optional namespaces
- requiredNamespaces – Chains/methods/events the dApp requires; connection fails if wallet cannot satisfy.
- optionalNamespaces – Additional chains/methods the dApp can use; wallet may approve a subset.
Use both in connect() and in proposal handling:
await client.connect({
requiredNamespaces: {
eip155: {
chains: ["eip155:1"],
methods: ["eth_sendTransaction", "personal_sign"],
events: ["chainChanged", "accountsChanged"],
},
},
optionalNamespaces: {
eip155: {
chains: ["eip155:137", "eip155:42161"],
methods: ["eth_signTypedData_v4"],
events: [],
},
},
});Pairing vs session
- Pairing – Initial handshake (QR/deep link); creates a pairing with a
topicand optional expiry. - Session – Created when the wallet approves the dApp’s proposal; one session per dApp–wallet pair, identified by
session.topic.
Reusing an existing pairing: pass pairingTopic into connect() so the dApp doesn’t show a new QR.
Key points
- Chain IDs are namespace-prefixed (e.g.
eip155:1). Use them inrequest({ chainId })and when building namespaces. - Session expiry can be extended with
client.extend({ topic }). - On
session_deleteorsession_expire, clear local UI state and optionally reconnect withconnect().
<!-- Source references:
- sources/walletconnect/AGENTS.md
- sources/walletconnect/packages/types/src/sign-client/session.ts
- sources/walletconnect/packages/types/src/sign-client/proposal.ts
-->
Sign Client
The main entry point for WalletConnect v2 is @walletconnect/sign-client. Use it for both dApp (proposer) and wallet (responder) flows. Requires a projectId from WalletConnect Cloud.
Initialization
import { SignClient } from "@walletconnect/sign-client";
const client = await SignClient.init({
projectId: "YOUR_PROJECT_ID",
metadata: {
name: "My dApp",
description: "Description",
url: "https://myapp.com",
icons: ["https://myapp.com/icon.png"],
},
// optional: custom logger, storage, relayUrl
});dApp flow: connect and request
// Connect (creates pairing + session proposal)
const { uri, approval } = await client.connect({
requiredNamespaces: {
eip155: {
chains: ["eip155:1", "eip155:137"],
methods: ["eth_sendTransaction", "personal_sign", "eth_signTypedData"],
events: ["chainChanged", "accountsChanged"],
},
},
optionalNamespaces: { /* ... */ },
pairingTopic: undefined, // or existing topic to reuse
});
// Show uri to user (QR or deep link)
if (uri) console.log(uri);
const session = await approval();
// Send JSON-RPC request
const result = await client.request({
topic: session.topic,
chainId: "eip155:1",
request: { method: "personal_sign", params: [message, account] },
});Wallet flow: handle proposal and respond
client.on("session_proposal", async ({ id, params, verifyContext }) => {
// Validate params.requiredNamespaces, then approve or reject
await client.approve({
id,
namespaces: {
eip155: {
accounts: ["eip155:1:0x..."],
methods: ["eth_sendTransaction", "personal_sign"],
events: ["chainChanged", "accountsChanged"],
},
},
});
});
client.on("session_request", async ({ id, topic, params }) => {
const { request, chainId } = params;
const result = await handleRequest(request, chainId);
await client.respond({
topic,
response: { id, result, jsonrpc: "2.0" },
});
});Pairing by URI (wallet)
const session = await client.pair({ uri: "wc:..." });Other APIs
- reject – Reject a session proposal:
client.reject({ id, reason }) - update – Update session namespaces:
client.update({ topic, namespaces }) - extend – Extend session expiry:
client.extend({ topic }) - disconnect – End session:
client.disconnect({ topic, reason }) - ping – Session/pairing liveness:
client.ping({ topic }) - find – Find sessions by required namespaces:
client.find({ requiredNamespaces }) - getPendingSessionRequests – Pending session_request payloads
Key points
client.sessionis the session store;client.core.pairing.pairings(orclient.pairing) for pairings.- Use
optionalNamespacesfor chains/methods the dApp can use but does not require. - Event names:
session_proposal,session_update,session_extend,session_ping,session_delete,session_expire,session_request,session_request_sent,session_event,session_authenticate,proposal_expire,session_request_expire,session_connect.
<!-- Source references:
- sources/walletconnect/AGENTS.md
- sources/walletconnect/packages/sign-client/README.md
- sources/walletconnect/packages/sign-client/src/client.ts
- sources/walletconnect/packages/types/src/sign-client/client.ts
- sources/walletconnect/packages/types/src/sign-client/engine.ts
-->
Ethereum Provider
@walletconnect/ethereum-provider is an EIP-1193–compliant provider for EVM dApps. It supports optional QR modal, chain/account events, and works with ethers/web3.
Initialization
import { EthereumProvider } from "@walletconnect/ethereum-provider";
const provider = await EthereumProvider.init({
projectId: "YOUR_PROJECT_ID",
optionalChains: [1, 10, 137, 42161],
showQrModal: true,
methods: ["eth_sendTransaction", "personal_sign", "eth_signTypedData_v4"],
events: ["chainChanged", "accountsChanged"],
rpcMap: { 1: "https://eth.llamarpc.com", 137: "https://polygon-rpc.com" },
metadata: { name: "My App", description: "...", url: "...", icons: ["..."] },
storage: undefined,
qrModalOptions: undefined,
});Use optionalChains (required); the deprecated chains is only for legacy behavior.
Connect and enable
await provider.connect({ chains: [1], rpcMap: { 1: "..." }, pairingTopic: undefined });
// or
await provider.enable();If showQrModal is false, handle the URI yourself:
provider.on("display_uri", (uri: string) => {
// show QR or deep link
});
await provider.connect();Sending requests
const accounts = await provider.request({ method: "eth_requestAccounts" });
const balance = await provider.request({
method: "eth_getBalance",
params: [accounts[0], "latest"],
});
// or sendAsync(args, callback)Events
connect– Session established (payload haschainId)disconnect– Session endedchainChanged– User switched chainaccountsChanged– Accounts changedsession_event– Generic session eventdisplay_uri– Connection URI for custom UI
Usage with Next.js (SSR)
The provider uses window/document/localStorage. Use it only on the client:
1. Put provider logic in a Client Component ("use client"). 2. Dynamically import that component with ssr: false:
const WalletConnectLogic = dynamic(
() => import("@/components/WalletConnectLogic"),
{ ssr: false }
);Initialize the provider inside useEffect or similar so it runs only in the browser.
Key points
- Prefer
optionalChainsand optionalrpcMap/metadatafor production. - For modal-less flows, set
showQrModal: falseand subscribe todisplay_uri. - Check
provider.sessionandprovider.accountsafter init to restore existing session state.
<!-- Source references:
- sources/walletconnect/providers/ethereum-provider/README.md
- sources/walletconnect/providers/ethereum-provider/src/EthereumProvider.ts
-->
WalletConnect Pay
@walletconnect/pay is the TypeScript SDK for WalletConnect Pay: payment flows for React Native and (later) web. You get payment options from a link, resolve required wallet RPC actions, sign with the wallet, then confirm the payment.
Initialization
import { WalletConnectPay } from "@walletconnect/pay";
const client = new WalletConnectPay({
appId: "your-app-id",
// or apiKey: "your-api-key",
clientId: undefined,
baseUrl: undefined,
logger: undefined,
});Either appId or apiKey is required for authentication.
Get payment options
const options = await client.getPaymentOptions({
paymentLink: "https://pay.walletconnect.com/pay_123",
accounts: ["eip155:8453:0xYourAddress"], // CAIP-10
includePaymentInfo: true,
});
// options.paymentId, options.options (array of PaymentOption)Get required actions and confirm
const actions = await client.getRequiredPaymentActions({
paymentId: options.paymentId,
optionId: options.options[0].id,
});
// Each action has action.walletRpc: { chainId, method, params (JSON string) }
// Sign with wallet (e.g. signTypedData per action)
const signatures = await Promise.all(
actions.map((action) =>
wallet.signTypedData(
action.walletRpc.chainId,
JSON.parse(action.walletRpc.params)
)
)
);
const result = await client.confirmPayment({
paymentId: options.paymentId,
optionId: options.options[0].id,
signatures,
collectedData: undefined, // optional, if options.collectData was set
});
// result.status: "requires_action" | "processing" | "succeeded" | "failed" | "expired"Collected data
When getPaymentOptions returns options.collectData, collect the required fields from the user and pass them as collectedData into confirmPayment.
Provider detection (React Native / Web)
- React Native: Requires
@walletconnect/react-native-compatand the native Pay module. UseisProviderAvailable()orisNativeProviderAvailable()before using the client. - Web: WASM provider is planned; use
detectProviderType()for'native' | 'wasm' | null. - Manually set native module if needed:
setNativeModule(NativeModules.RNWalletConnectPay).
import { isProviderAvailable, detectProviderType, setNativeModule } from "@walletconnect/pay";
if (isProviderAvailable()) {
const providerType = detectProviderType();
}Errors
The SDK throws PayError, PaymentOptionsError, ConfirmPaymentError. Check error instanceof PaymentOptionsError and use error.originalMessage / error.code for handling.
Key points
- Use CAIP-10 accounts in
getPaymentOptionsand when confirming. getRequiredPaymentActionsreturns wallet RPC actions; sign them with the same wallet that owns the accounts.- For React Native, install and link
@walletconnect/react-native-compatand the Pay native module.
<!-- Source references:
- sources/walletconnect/packages/pay/README.md
-->
Signer Connection
@walletconnect/signer-connection provides a connection-style API on top of the Sign Client: SignerConnection implements IJsonRpcConnection with open(), close(), send(payload, context?), and events. Use it when integrating with libraries that expect a single connection object rather than a session + request API.
Setup
import SignerConnection from "@walletconnect/signer-connection";
import { SignClient } from "@walletconnect/sign-client";
const connection = new SignerConnection({
requiredNamespaces: {
eip155: {
chains: ["eip155:1"],
methods: ["eth_sendTransaction", "personal_sign"],
events: ["chainChanged", "accountsChanged"],
},
},
client: undefined, // optional: SignClient instance or SignClient.init options
});If client is omitted, the first call to open() or send() will call SignClient.init(client) with the options you passed (or create a client internally). Pass an existing SignClient to share it.
Open and close
await connection.open();
// Emits signer_uri with { uri } for QR/deep link; then signer_created with session when approved
// If an existing compatible session exists, opens without showing URI
connection.close();
// Disconnects session with reason USER_DISCONNECTED and emits closeSend request
connection.send(
{ id: 1, jsonrpc: "2.0", method: "personal_sign", params: [message, account] },
{ chainId: "eip155:1" }
);
// Listens for "payload" event for the JSON-RPC response (result or error)
connection.on("payload", (response) => { ... });Events
signer_init– Sign Client initializedsigner_uri–{ uri }for pairing (show QR or deep link)signer_created– Session created after approvalsigner_updated– Session namespaces updatedsigner_deleted– Session deletedsigner_event– Session event receivedopen– Connection openedclose– Connection closedopen_error– Open failedpayload– JSON-RPC response (result or error)
Key points
connection.connectedandconnection.connectingreflect state;connection.chainsandconnection.accountscome from the session or required namespaces.- Use when you need a single connection object (e.g. for a library that takes an IJsonRpcConnection). For direct control, use Sign Client and providers instead.
<!-- Source references:
- sources/walletconnect/providers/signer-connection/README.md
- sources/walletconnect/providers/signer-connection/src/index.ts
-->
Universal Provider
@walletconnect/universal-provider is a chain-agnostic JSON-RPC provider for WalletConnect. It supports EVM, Solana, Cosmos, and other namespaces via connect({ namespaces }), routes requests by chain, and can be used with ethers/Web3.
Initialization
import UniversalProvider from "@walletconnect/universal-provider";
const provider = await UniversalProvider.init({
projectId: "YOUR_PROJECT_ID",
relayUrl: "wss://relay.walletconnect.com",
logger: "info",
metadata: {
name: "My App",
description: "...",
url: "https://myapp.com",
icons: ["https://myapp.com/icon.png"],
},
client: undefined, // optional: pass existing SignClient instance
});Connect
await provider.connect({
namespaces: {
eip155: {
chains: ["eip155:80001", "eip155:1"],
methods: [
"eth_sendTransaction",
"eth_sign",
"personal_sign",
"eth_signTypedData",
],
events: ["chainChanged", "accountsChanged"],
rpcMap: {
80001: "https://rpc.walletconnect.org?chainId=eip155:80001&projectId=...",
1: "https://eth.llamarpc.com",
},
},
},
pairingTopic: "<existing-topic>", // optional: reuse pairing
skipPairing: false, // optional: skip pairing (resume later with .pair())
});Use namespaces with CAIP-2 chains, wallet methods, events, and optional rpcMap for chain RPCs. Optional optionalNamespaces follow the same shape.
Request
// payload: EIP-1193 RequestArguments; chain: optional "<namespace>:<chainId>"
const result = await provider.request(
{ method: "eth_getBalance", params: [address, "latest"] },
"eip155:1"
);If chain is omitted, the provider uses its default chain (first chain from connect). Use setDefaultChain to change it.
setDefaultChain (multi-chain)
provider.setDefaultChain("eip155:56", "https://bsc-dataseed.binance.org");Events
display_uri– Pairing URI (for QR or deep link)session_ping–{ id, topic }session_event–{ event, chainId }session_update–{ topic, params }session_delete–{ id, topic }
enable() and sendAsync
const accounts = await provider.enable();
provider.sendAsync(args, (err, response) => { ... }, chain?);Key points
- Namespace keys are chain namespaces (eip155, solana, etc.); each value has
chains,methods,events, and optionalrpcMap. - Pass an existing
SignClientinclientto share one client across providers. - Use
request(payload, chain)to target a specific chain; omitchainfor default. - For custom namespace support, implement
IProviderunder the provider'sproviders/and register it.
<!-- Source references:
- sources/walletconnect/providers/universal-provider/README.md
- sources/walletconnect/providers/universal-provider/src/UniversalProvider.ts
- sources/walletconnect/AGENTS.md
-->