Skip to main content

Wallet Usage

Sign API establishes a session between a wallet and a dapp in order to expose a set of blockchain accounts that can sign transactions or messages using a secure remote JSON-RPC transport with methods and events.

Don't have a project ID?

Head over to Reown Cloud and create a new project now!

Get startedcloud illustration
info

This library is compatible with Node.js, browsers and React Native applications (Node.js modules require polyfills for React Native).

info

Migrating from v1.x

We recommend you install v1 and v2 together for maximum compatibility. If your wallet already uses @walletconnect/client@1.x.x, you should be able to add @walletconnect/sign-client@2.x.x without any issues.

If you experience dependency clashes or you require both @walletconnect/types@1.x.x and @walletconnect/types@2.x.x in parallel in your wallet's top-level dependencies, please refer to the legacy packages which were published explicitly for this purpose.

In the above scenario, you would replace @walletconnect/types@1.x.x with @walletconnect/legacy-types and then install @walletconnect/types@2.x.x.

Initializing the client

Initialize client as a controller using your Project ID.

const signClient = await SignClient.init({
projectId: '<YOUR PROJECT ID>',
// optional parameters
relayUrl: '<YOUR RELAY URL>',
metadata: {
name: 'Wallet name',
description: 'A short description for your wallet',
url: "<YOUR WALLET'S URL>",
icons: ["<URL TO WALLET'S LOGO/ICON>"]
}
})

Setting up event listeners

WalletConnect v2.0 emits events related to the current session. The listeners listed in the following code snippet represent typical events in a session's lifecycle that you can listen for to synchronise your application accordingly.

Example: when a session_delete event is emitted, it makes sense to change the UI from an active session state to an inactive/disconnected state.

1. Add listeners for desired SignClient events.

info

To listen to pairing-related events, please follow the guidance for Pairing API event listeners.

signClient.on('session_proposal', event => {
// Show session proposal data to the user i.e. in a modal with options to approve / reject it

interface Event {
id: number
params: {
id: number
expiry: number
relays: Array<{
protocol: string
data?: string
}>
proposer: {
publicKey: string
metadata: {
name: string
description: string
url: string
icons: string[]
}
}
requiredNamespaces: Record<
string,
{
chains: string[]
methods: string[]
events: string[]
}
>
pairingTopic?: string
}
}
})

signClient.on('session_event', event => {
// Handle session events, such as "chainChanged", "accountsChanged", etc.

interface Event {
id: number
topic: string
params: {
event: {
name: string
data: any
}
chainId: string
}
}
})

signClient.on('session_request', event => {
// Handle session method requests, such as "eth_sign", "eth_sendTransaction", etc.

interface Event {
id: number
topic: string
params: {
request: {
method: string
params: any
}
chainId: string
}
}
})

signClient.on('session_ping', event => {
// React to session ping event

interface Event {
id: number
topic: string
}
})

signClient.on('session_delete', event => {
// React to session delete event

interface Event {
id: number
topic: string
}
})

Pairing and session permissions

URI

The pairing proposal between a wallet and a dapp is made using an URI. In WalletConnect v2.0 the session and pairing are decoupled from each other. This means that a URI is shared to construct a pairing proposal, and only after settling the pairing the dapp can propose a session using that pairing. In simpler words, the dapp generates an URI that can be used by the wallet for pairing.

Namespaces

The namespaces parameter is used to specify the namespaces and chains that are intended to be used in the session. The following is an example:

namespaces: {
eip155: {
accounts: ["eip155:1:0x0000000000..., eip155:2:0x0000000000..."],
methods: ["personal_sign", "eth_sendTransaction"],
events: ["accountsChanged"]
},
};

Pairing with uri

To create a pairing proposal, simply pass the uri received from the dapp into the signClient.core.pairing.pair() function.

caution

As of 2.0.0 (stable), calling pairing-specific methods (such as signClient.pair()) directly on signClient will continue to work, but is considered deprecated and will be removed in a future major version.

It is recommended to instead call these methods directly via the Pairing API., e.g.: signClient.core.pairing.pair().

// This will trigger the `session_proposal` event
await signClient.core.pairing.pair({ uri })

// Approve session proposal, use id from session proposal event and respond with namespace(s) that satisfy dapps request and contain approved accounts
const { topic, acknowledged } = await signClient.approve({
id: 123,
namespaces: {
eip155: {
accounts: ['eip155:1:0x0000000000...'],
methods: ['personal_sign', 'eth_sendTransaction'],
events: ['accountsChanged']
}
}
})

// Optionally await acknowledgement from dapp
const session = await acknowledged()

// Or reject session proposal
await signClient.reject({
id: 123,
reason: {
code: 1,
message: 'rejected'
}
})

Pairing with QR Codes

To facilitate better user experience, it is possible to pair wallets with dapps by scanning QR codes. This can be implemented by using any QR code scanning library (example, react-qr-reader). After scanning the QR code, pass the obtained uri into the signClient.pair() function. A useful reference for implementing QR codes for pairing is the react wallet example.

Authenticated Session

This section outlines an innovative protocol method that facilitates the initiation of a Sign session and the authentication of a wallet through a Sign-In with Ethereum (SIWE) message, enhanced by ReCaps (ReCap Capabilities). This enhancement not only offers immediate authentication for dApps, paving the way for prompt user logins, but also integrates informed consent for authorization. Through this mechanism, dApps can request the delegation of specific capabilities to perform actions on behalf of the wallet user. These capabilities, encapsulated within SIWE messages as ReCap URIs, detail the scope of actions authorized by the user in an explicit and human-readable form.

By incorporating ReCaps, this method extends the utility of SIWE messages, allowing dApps to combine authentication with a nuanced authorization model. This model specifies the actions a dApp is authorized to execute on the user's behalf, enhancing security and user autonomy by providing clear consent for each delegated capability. As a result, dApps can utilize these consent-backed messages to perform predetermined actions, significantly enriching the interaction between dApps, wallets, and users within the Ethereum ecosystem.

Handling Authentication Requests

To handle incoming authentication requests, subscribe to the session_authenticate event. This will notify you of any authentication requests that need to be processed, allowing you to either approve or reject them based on your application logic.

walletKit.on('session_authenticate', async payload => {
// Process the authentication request here.
// Steps include:
// 1. Populate the authentication payload with the supported chains and methods
// 2. Format the authentication message using the payload and the user's account
// 3. Present the authentication message to the user
// 4. Sign the authentication message(s) to create a verifiable authentication object(s)
// 5. Approve the authentication request with the authentication object(s)
})

Populate Authentication Payload

import { populateAuthPayload } from "@walletconnect/utils";

// EVM chains that your wallet supports
const supportedChains = ["eip155:1", "eip155:2", 'eip155:137'];
// EVM methods that your wallet supports
const supportedMethods = ["personal_sign", "eth_sendTransaction", "eth_signTypedData"];
// Populate the authentication payload with the supported chains and methods
const authPayload = populateAuthPayload({
authPayload: payload.params.authPayload,
chains: supportedChains,
methods: supportedMethods,
});
// Prepare the user's address in CAIP10(https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md) format
const iss = `eip155:1:0x0Df6d2a56F90e8592B4FfEd587dB3D5F5ED9d6ef`;
// Now you can use the authPayload to format the authentication message
const message = walletKit.formatAuthMessage({
request: authPayload,
iss
});

// Present the authentication message to the user
...

Approving Authentication Requests

Note
  1. The recommended approach for secure authentication across multiple chains involves signing a SIWE (Sign-In with Ethereum) message for each chain and account. However, at a minimum, one SIWE message must be signed to establish a session. It is possible to create a session for multiple chains with just one issued authentication object.
  2. Sometimes a dapp may want to only authenticate the user without creating a session, not every approval will result with a new session.
// Approach 1
// Sign the authentication message(s) to create a verifiable authentication object(s)
const signature = await cryptoWallet.signMessage(message, privateKey)
// Build the authentication object(s)
const auth = buildAuthObject(
authPayload,
{
t: 'eip191',
s: signature
},
iss
)

// Approve
await walletKit.approveSessionAuthenticate({
id: payload.id,
auths: [auth]
})

// Approach 2
// Note that you can also sign multiple messages for every requested chain/address pair
const auths = []
authPayload.chains.forEach(async chain => {
const message = walletKit.formatAuthMessage({
request: authPayload,
iss: `${chain}:${cryptoWallet.address}`
})
const signature = await cryptoWallet.signMessage(message)
const auth = buildAuthObject(
authPayload,
{
t: 'eip191', // signature type
s: signature
},
`${chain}:${cryptoWallet.address}`
)
auths.push(auth)
})

// Approve
await walletKit.approveSessionAuthenticate({
id: payload.id,
auths
})

Rejecting Authentication Requests

If the authentication request cannot be approved or if the user chooses to reject it, use the rejectSession method.

import { getSdkError } from '@walletconnect/utils'

await walletKit.rejectSessionAuthenticate({
id: payload.id,
reason: getSdkError('USER_REJECTED') // or choose a different reason if applicable
})