Usage
This section provides instructions on how to initialize the WalletKit client, approve sessions with supported namespaces, and respond to session requests, enabling easy integration of Web3 wallets with dapps through a simple and intuitive interface.
Content
Links to sections on this page. Some sections are platform specific and are only visible when the platform is selected. To view a summary of useful platform specific topics, check out Extra (Platform Specific) under this section.
Initialization: Creating a new WalletKit instance and initializing it with a projectId from Cloud.
Session: Connection between a dapp and a wallet.
- Namespace Builder: Namespace Builder is a helper utility that greatly reduces the complexity of parsing the required and optional namespaces. It accepts as parameters a session proposal along with your user's chains/methods/events/accounts and returns a ready-to-use object
- Session Approval: Approving a session sent from a dapp
- Session Rejection: Rejecting a session sent from a dapp
- Responding to Session Requests: Responding to session requests sent from a dapp
- Updating a Session: Updating a session sent between a dapp and wallet
- Extending a Session: Extending a session between a dapp and wallet
- Session Disconnect: Disconnecting a session between a dapp and wallet
Don't have a project ID?
Head over to Reown Cloud and create a new project now!
Initialization
First you must setup a Core
instance with a specific Name
and ProjectId
. You may optionally specify other CoreOption
values, such as RelayUrl
and Storage
var options = new CoreOptions()
{
ProjectId = "...",
Name = "my-app",
}
var core = new Core(options);
Next, you must define a Metadata
object which describes your Wallet. This includes a Name
, Description
, Url
and Icons
url.
var metadata = new Metadata()
{
Description = "An example wallet to showcase WalletKit",
Icons = new[] { "https://walletconnect.com/meta/favicon.ico" },
Name = $"wallet-csharp-test",
Url = "https://walletconnect.com",
};
Once you have both the Core
and Metadata
objects, you can initialize the WalletKitClient
var sdk = await WalletKitClient.Init(core, metadata, metadata.Name);
Session
A session is a connection between a dapp and a wallet. It is established when a user approves a session proposal from a dapp. A session is active until the user disconnects from the dapp or the session expires.
Namespace Builder
To build a namespace mapping for either proposing a session OR approving a session, you can use .NET dictionary + class constructors directly, or use the built-in builder methods
C# Constructor Style
var TestNamespaces = new Namespaces()
{
{
"eip155", new Namespace()
{
Accounts = new [] { "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb" },
Chains = new []{ "eip155:1" },
Methods = new[] { "eth_signTransaction" },
Events = new[] { "chainChanged" }
}
},
};
Builder Style
var TestNamespaces = new Namespaces()
.WithNamespace("eip155", new Namespace()
.WithChain("eip155:1")
.WithMethod("eth_signTransaction")
.WithEvent("chainChanged")
.WithAccount("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")
);
The Namespaces
mapping is required when approving a proposed session from a dApp. Because of this, you may
also construct a Namespaces
from a RequiredNamespaces
, which auto-populates all Methods
, Events
and
Chains
from the given RequiredNamespaces
. This is provided for convenience.
RequiredNamespaces
sdk.SessionProposed += async (sender, @event) =>
{
var proposal = @event.Proposal;
var requiredNamespaces = proposal.RequiredNamespaces;
var approvedNamespaces = new Namespaces(requiredNamespaces);
approvedNamespaces["eip155"].WithAccount("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb");
});
The RequiredNamespaces
is required when setting up a session between a dApp and Wallet. The
dApp will provide a RequiredNamespaces
when proposing the session. The RequiredNamespaces
and
ProposedNamespace
use the same style constructors + builder functions as Namespaces
and Namespace
.
EVM methods & events
In @walletconnect/ethereum-provider, (our abstracted EVM SDK for apps) we support by default the following Ethereum methods and events:
{
//...
methods: [
"eth_accounts",
"eth_requestAccounts",
"eth_sendRawTransaction",
"eth_sign",
"eth_signTransaction",
"eth_signTypedData",
"eth_signTypedData_v3",
"eth_signTypedData_v4",
"eth_sendTransaction",
"personal_sign",
"wallet_switchEthereumChain",
"wallet_addEthereumChain",
"wallet_getPermissions",
"wallet_requestPermissions",
"wallet_registerOnboarding",
"wallet_watchAsset",
"wallet_scanQRCode",
"wallet_sendCalls",
"wallet_getCallsStatus",
"wallet_showCallsStatus",
"wallet_getCapabilities",
],
events: [
"chainChanged",
"accountsChanged",
"message",
"disconnect",
"connect",
]
}
Session Approval
Wallets can pair an incoming session using the session's Uri. Pairing a session lets the Wallet obtain the connection proposal which can then be approved or denied.
var uri = "...";
ProposalStruct proposal = await sdk.Pair(uri);
The wallet can then approve the proposal by constructing an approved Namespaces
. The approved
Namespaces
should include the RequiredNamespaces
under proposal.RequiredNamespaces
, and may optionally include any optional namespaces
specified under proposal.OptionalNamespaces
sdk.SessionProposed += async (sender, @event) =>
{
var proposal = @event.Proposal;
var requiredNamespaces = proposal.RequiredNamespaces;
var approvedNamespaces = new Namespaces(requiredNamespaces);
approvedNamespaces["eip155"].WithAccount("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb");
var sessionData = await sdk.ApproveSession(proposal.Id, approvedNamespaces);
var sessionTopic = sessionData.Topic;
});
You may also just provide the addresses that will connect, and the SDK will create this approved
Namespaces
for you. This function will not approve optional namespaces
sdk.SessionProposed += async (sender, @event) =>
{
var proposal = @event.Proposal;
var sessionData = await sdk.ApproveSession(proposal, new[] { "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb" });
var sessionTopic = sessionData.Topic;
});
or
sdk.SessionProposed += async (sender, @event) =>
{
var proposal = @event.Proposal;
var sessionData = await sdk.ApproveSession(proposal, "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb");
var sessionTopic = sessionData.Topic;
});
Session Rejection
The wallet can reject the proposal using the following:
sdk.SessionProposed += async (sender, @event) =>
{
var proposal = @event.Proposal;
await sdk.Reject(proposal, "User rejected");
});
Responding to Session requests
Responding to session requests is very similar to sending session requests. See dApp usage on how sending session requests works. All custom session requests requires a request class and response class to be created that matches the params
field type in the custom session request. C# is a static typed language, so these types must be given whenever you do a session request (or do any querying for session requests).
Currently, WalletKit does not automatically assume the object type for params
is an array. This is very important, since most EVM RPC requests have params
as an array type. Use List<T>
to workaround this. For example, for eth_sendTransaction
, use List<Transaction>
instead of Transaction
.
Newtonsoft.Json is used for JSON serialization/deserialization, therefore you can use Newtonsoft.Json attributes when defining fields in your request/response classes.
Building a Response type
Create a class for the response and populate it with the JSON properties the response object has. For this example, we will use eth_getTransactionReceipt
The params
field for eth_getTransactionReceipt
has the object type
using Newtonsoft.Json;
using System.Numerics;
[RpcMethod("eth_getTransactionReceipt"), RpcRequestOptions(Clock.ONE_MINUTE, 99995)]
public class TransactionReceipt
{
[JsonProperty("transactionHash")]
public string TransactionHash;
[JsonProperty("transactionIndex")]
public BigInteger TransactionIndex;
[JsonProperty("blockHash")]
public string BlockHash;
[JsonProperty("blockNumber")]
public BigInteger BlockNumber;
[JsonProperty("from")]
public string From;
[JsonProperty("to")]
public string To;
[JsonProperty("cumulativeGasUsed")]
public BigInteger CumulativeGasUsed;
[JsonProperty("effectiveGasPrice ")]
public BigInteger EffectiveGasPrice ;
[JsonProperty("gasUsed")]
public BigInteger GasUsed;
[JsonProperty("contractAddress")]
public string ContractAddress;
[JsonProperty("logs")]
public object[] Logs;
[JsonProperty("logsBloom")]
public string LogBloom;
[JsonProperty("type")]
public BigInteger Type;
[JsonProperty("status")]
public BigInteger Status;
}
The RpcMethod
class attributes defines the rpc method this response uses, this is optional. The RpcResponseOptions
class attributes define the expiry time and tag attached to the response, this is required.
Sending a response
To respond to requests from a dApp, you must define the class representing the request object type. The request type for eth_getTransactionReceipt
is the following:
[RpcMethod("eth_getTransactionReceipt"), RpcRequestOptions(Clock.ONE_MINUTE, 99994)]
public class EthGetTransactionReceipt : List<string>
{
public EthGetTransactionReceipt(params string[] hashes) : base(hashes)
{
}
// needed for proper json deserialization
public EthGetTransactionReceipt()
{
}
}
We can handle the eth_getTransactionReceipt
session request by doing the following:
walletClient.Engine.SessionRequestEvents<EthGetTransactionReceipt, TransactionReceipt>().OnRequest += OnEthTransactionReceiptRequest;
private Task OnEthTransactionReceiptRequest(RequestEventArgs<EthGetTransactionReceipt, TransactionReceipt> e)
{
// logic for request goes here
// set e.Response to return a response
}
The callback function gets invoked whenever the wallet receives the eth_getTransactionReceipt
request from a connected dApp. You may optionally filter further which requests are handled using the FilterRequests
function
walletClient.Engine.SessionRequestEvents<EthGetTransactionReceipt, TransactionReceipt>()
.FilterRequests(r => r.Topic == sessionTopic)
.OnRequest += OnEthTransactionReceiptRequest;
The callback returns a Task
, so the callback can be made async. To return a response, you must set the Response
field in RequestEventArgs<T, TR>
with the desired response.
private async Task OnEthTransactionReceiptRequest(RequestEventArgs<EthGetTransactionReceipt, TransactionReceipt> e)
{
var txHash = e.Request.Params[0];
var receipt = await EthGetTransactionReceipt(txHash);
e.Response = receipt;
}
Updating a Session
Update a session, adding/removing additional namespaces in the given topic.
var newNamespaces = new Namespaces(...);
var request = await walletClient.UpdateSession(sessionTopic, newNamespaces);
await request.Acknowledged();
Extending a Session
Extend a session's expiry time so the session remains open
var request = await walletClient.Extend(sessionTopic);
await request.Acknowledged();
Session Disconnect
To disconnect a session, use the Disconnect
function. You may optional provide a reason for the disconnect.
Disconnecting requires the topic
of the session to be given. This can be found in the SessionStruct
object given when a session has been given approval by the Wallet.
var sessionTopic = sessionData.Topic;
await walletClient.Disconnect(sessionTopic);
// or
await walletClient.Disconnect(sessionTopic, Error.FromErrorType(ErrorType.USER_DISCONNECTED));