Dapp Usage
Implementationβ
- Web
- iOS
- Android
- Flutter
- Unity
- C#
This library is compatible with Node.js, browsers and React Native applications (Node.js modules require polyfills for React Native).
Dapps will also need to install WalletConnectModal for the UI.
- npm
- Yarn
- Bun
- pnpm
npm install @walletconnect/modal
yarn add @walletconnect/modal
bun add @walletconnect/modal
pnpm add @walletconnect/modal
For an example implementation, please refer to our react-dapp-v2
example.
Install Packagesβ
Dapps will also need to install WalletConnectModal
for the UI.
- npm
- Yarn
- Bun
- pnpm
npm install @walletconnect/modal
yarn add @walletconnect/modal
bun add @walletconnect/modal
pnpm add @walletconnect/modal
Create a Sessionβ
1. Initiate your WalletConnect client with the relay server, using your Project ID.
import SignClient from '@walletconnect/sign-client'
const signClient = await SignClient.init({
projectId: '<YOUR_PROJECT_ID>',
// optional parameters
relayUrl: '<YOUR RELAY URL>',
metadata: {
name: 'Example Dapp',
description: 'Example Dapp',
url: '#',
icons: ['https://walletconnect.com/walletconnect-logo.png']
}
})
2. Add listeners for desired SignClient
events.
To listen to pairing-related events, please follow the guidance for Pairing API event listeners.
signClient.on('session_event', ({ event }) => {
// Handle session events, such as "chainChanged", "accountsChanged", etc.
})
signClient.on('session_update', ({ topic, params }) => {
const { namespaces } = params
const _session = signClient.session.get(topic)
// Overwrite the `namespaces` of the existing session with the incoming one.
const updatedSession = { ..._session, namespaces }
// Integrate the updated session state into your dapp state.
onSessionUpdate(updatedSession)
})
signClient.on('session_delete', () => {
// Session was deleted -> reset the dapp state, clean up from user session, etc.
})
3. Create a new WalletConnectModal instance.
import { WalletConnectModal } from '@walletconnect/modal'
const walletConnectModal = new WalletConnectModal({
projectId: '<YOUR_PROJECT_ID>',
// `standaloneChains` can also be specified when calling `walletConnectModal.openModal(...)` later on.
standaloneChains: ['eip155:1']
})
4. Connect the application and specify session permissions.
try {
const { uri, approval } = await signClient.connect({
// Optionally: pass a known prior pairing (e.g. from `signClient.core.pairing.getPairings()`) to skip the `uri` step.
pairingTopic: pairing?.topic,
// Provide the namespaces and chains (e.g. `eip155` for EVM-based chains) we want to use in this session.
requiredNamespaces: {
eip155: {
methods: [
'eth_sendTransaction',
'eth_signTransaction',
'eth_sign',
'personal_sign',
'eth_signTypedData'
],
chains: ['eip155:1'],
events: ['chainChanged', 'accountsChanged']
}
}
})
// Open QRCode modal if a URI was returned (i.e. we're not connecting an existing pairing).
if (uri) {
walletConnectModal.openModal({ uri })
// Await session approval from the wallet.
const session = await approval()
// Handle the returned session (e.g. update UI to "connected" state).
// * You will need to create this function *
onSessionConnect(session)
// Close the QRCode modal in case it was open.
walletConnectModal.closeModal()
}
} catch (e) {
console.error(e)
}
Making Requestsβ
Once the session has been established successfully, you can start making JSON-RPC requests to be approved and signed by the wallet:
const result = await signClient.request({
topic: session.topic,
chainId: 'eip155:1',
request: {
method: 'personal_sign',
params: [
'0x7468697320697320612074657374206d65737361676520746f206265207369676e6564',
'0x1d85568eEAbad713fBB5293B45ea066e552A90De'
]
}
})
For more information on available JSON-RPC requests, see the JSON-RPC reference.
Restoring a Sessionβ
Sessions are saved to localstorage, meaning that even if the web page is reloaded, the session can still be retrieved, as demonstrated in the following code:
const lastKeyIndex = signClient.session.getAll().length - 1
const lastSession = signClient.session.getAll()[lastKeyIndex]
Finding a Specific Sessionβ
If you need to find a specific session, you can do so by passing in a known requiredNamespace
and calling find
.
const specificSession = _client.find({
requiredNamespaces: {
eip155: {
methods: [
'eth_sendTransaction',
'eth_signTransaction',
'eth_sign',
'personal_sign',
'eth_signTypedData'
],
chains: ['eip155:5'],
events: ['chainChanged', 'accountsChanged']
}
}
})
Configure Networking and Pair clientsβ
Make sure that you properly configure Networking and Pair Clients first.
Subscribe for Sign publishersβ
When your Sign
instance receives requests from a peer it will publish related event. So you should set subscription to handle them.
To track sessions subscribe to sessionsPublisher
publisher
Sign.instance.sessionsPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] (sessions: [Session]) in
// reload UI
}.store(in: &publishers)
Following publishers are available to subscribe:
public var sessionsPublisher: AnyPublisher<[Session], Never>
public var sessionProposalPublisher: AnyPublisher<Session.Proposal, Never>
public var sessionRequestPublisher: AnyPublisher<Request, Never>
public var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>
public var sessionSettlePublisher: AnyPublisher<Session, Never>
public var sessionDeletePublisher: AnyPublisher<(String, Reason), Never>
public var sessionResponsePublisher: AnyPublisher<Response, Never>
public var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never>
public var sessionUpdatePublisher: AnyPublisher<(sessionTopic: String, namespaces: [String : SessionNamespace]), Never>
public var sessionEventPublisher: AnyPublisher<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never>
public var sessionUpdateExpiryPublisher: AnyPublisher<(sessionTopic: String, expiry: Date), Never>
Connect Clientsβ
- Prepare namespaces that constraints minimal requirements for your dApp:
let methods: Set<String> = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"]
let blockchains: Set<Blockchain> = [Blockchain("eip155:1")!, Blockchain("eip155:137")!]
let namespaces: [String: ProposalNamespace] = ["eip155": ProposalNamespace(chains: blockchains, methods: methods, events: []]
To learn more on namespaces, check out our specs.
- Your App should generate a pairing URI and share it with a wallet. Uri can be presented as a QR code or sent via a universal link. Wallet begins subscribing for session proposals after receiving URI. In order to create a pairing and send a session proposal, you need to call the following:
let uri = try await Pair.instance.create()
try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic)
Send Request to the Walletβ
Once the session has been established sessionSettlePublisher
will publish an event. Your dApp can start requesting wallet now.
let method = "personal_sign"
let walletAddress = "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" // This should match the connected address
let requestParams = AnyCodable(["0x4d7920656d61696c206973206a6f686e40646f652e636f6d202d2031363533333933373535313531", walletAddress])
let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(chainId)!)
try await Sign.instance.request(params: request)
When wallet respond sessionResponsePublisher
will publish an event so you can verify the response.
Extending a Sessionβ
By default, session lifetime is set for 7 days and after that time user's session will expire. But if you consider that a session should be extended you can call:
try await Sign.instance.extend(topic: session.topic)
Above method will extend a user's session to a week.
Where to go from hereβ
- Try our Example dApp that is part of WalletConnectSwiftV2 repository.
- Build API documentation in XCode: go to Product -> Build Documentation
Initializationβ
val projectId = "" // Get Project ID at https://cloud.walletconnect.com/
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.AUTOMATIC or ConnectionType.MANUAL
val appMetaData = Core.Model.AppMetaData(
name = "Dapp Name",
description = "Dapp Description",
url = "Dapp URL",
icons = /*list of icon url strings*/,
redirect = "kotlin-dapp-wc:/request" // Custom Redirect URI
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData)
val init = Sign.Params.Init(core = CoreClient)
SignClient.initialize(init) { error ->
// Error will be thrown if there's an issue during initialization
}
The Dapp client is responsible for initiating the connection with wallets and defining the required namespaces (CAIP-2) from the Wallet and is also in charge of sending requests. To initialize the Sign client, create a Sign.Params.Init
object in the Android Application class with the Core Client. The Sign.Params.Init
object will then be passed to the SignClient
initialize function.
Dapp
SignClient.DappDelegateβ
val dappDelegate = object : SignClient.DappDelegate {
override fun onSessionApproved(approvedSession: Sign.Model.ApprovedSession) {
// Triggered when Dapp receives the session approval from wallet
}
override fun onSessionRejected(rejectedSession: Sign.Model.RejectedSession) {
// Triggered when Dapp receives the session rejection from wallet
}
override fun onSessionUpdate(updatedSession: Sign.Model.UpdatedSession) {
// Triggered when Dapp receives the session update from wallet
}
override fun onSessionExtend(session: Sign.Model.Session) {
// Triggered when Dapp receives the session extend from wallet
}
override fun onSessionEvent(sessionEvent: Sign.Model.SessionEvent) {
// Triggered when the peer emits events that match the list of events agreed upon session settlement
}
override fun onSessionDelete(deletedSession: Sign.Model.DeletedSession) {
// Triggered when Dapp receives the session delete from wallet
}
override fun onSessionRequestResponse(response: Sign.Model.SessionRequestResponse) {
// Triggered when Dapp receives the session request response from wallet
}
override fun onProposalExpired(proposal: Modal.Model.ExpiredProposal) {
// Triggered when a proposal becomes expired
}
override fun onRequestExpired(request: Modal.Model.ExpiredRequest) {
// Triggered when a request becomes expired
}
override fun onConnectionStateChange(state: Sign.Model.ConnectionState) {
//Triggered whenever the connection state is changed
}
override fun onError(error: Sign.Model.Error) {
// Triggered whenever there is an issue inside the SDK
}
}
SignClient.setDappDelegate(dappDelegate)
The SignClient needs a SignClient.DappDelegate
passed to it for it to be able to expose asynchronously updates sent from the Wallet.
Connectβ
val namespace: String = /*Namespace identifier, see for reference: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md#syntax*/
val chains: List<String> = /*List of chains that wallet will be requested for*/
val methods: List<String> = /*List of methods that wallet will be requested for*/
val events: List<String> = /*List of events that wallet will be requested for*/
val requiredNamespaces: Map<String, Sign.Model.Namespaces.Proposal> = mapOf(namespace, Sign.Model.Namespaces.Proposal(accounts, methods, events)) /*Required namespaces to setup a session*/
val optionalNamespaces: Map<String, Sign.Model.Namespaces.Proposal> = mapOf(namespace, Sign.Model.Namespaces.Proposal(accounts, methods, events)) /*Optional namespaces to setup a session*/
val pairing: Core.Model.Pairing = /*Either an active or inactive pairing*/
val connectParams = Sign.Params.Connect(requiredNamespaces, optionalNamespaces, pairing)
fun SignClient.connect(connectParams,
{ onSuccess ->
/*callback that returns letting you know that you have successfully initiated connecting*/
},
{ error ->
/*callback for error while trying to initiate a connection with a peer*/
}
)
More about optional and required namespaces can be found here
Get List of Settled Sessionsβ
SignClient.getListOfSettledSessions()
To get a list of the most current settled sessions, call SignClient.getListOfSettledSessions()
which will return a list of type Session
.
Get list of pending session requests for a topicβ
SignClient.getPendingRequests(topic: String)
To get a list of pending session requests for a topic, call SignClient.getPendingRequests()
and pass a topic which will return
a PendingRequest
object containing requestId, method, chainIs and params for pending request.
Initializationβ
To create an instance of SignClient
, you need to pass in the core and metadata parameters.
SignClient signClient = await SignClient.createInstance(
relayUrl: 'wss://relay.walletconnect.com', // The relay websocket URL, leave blank to use the default
projectId: '123',
metadata: PairingMetadata(
name: 'dapp (Requester)',
description: 'A dapp that can request that transactions be signed',
url: 'https://walletconnect.com',
icons: ['https://avatars.githubusercontent.com/u/37784886'],
),
);
Connectionβ
To connect with specific parameters and display the returned URI, use connect
with the required namespaces.
ConnectResponse response = await signClient.connect(
requiredNamespaces: {
'eip155': RequiredNamespace(
chains: ['eip155:1'], // Ethereum chain
methods: ['eth_signTransaction'], // Requestable Methods
),
'kadena': RequiredNamespace(
chains: ['kadena:mainnet01'], // Kadena chain
methods: ['kadena_quicksign_v1'], // Requestable Methods
),
}
);
Uri? uri = response.uri;
You will use that URI to display a QR code or handle a deep link.
We recommend not handling deep linking yourself. If you want to deep link, then use the walletconnect_modal_flutter package.
Session Dataβ
Once you've displayed the URI you can wait for the future and hide the QR code once you've received session data.
final SessionData session = await response.session.future;
Request Signaturesβ
Once the session had been created, you can request signatures.
final signature = await signClient.request(
topic: session.topic,
chainId: 'eip155:1',
request: SessionRequestParams(
method: 'eth_signTransaction',
params: 'json serializable parameters',
),
);
Respond to Eventsβ
You can also respond to events from the wallet, like chain changed, using onSessionEvent
and registerEventHandler
.
signClient.onSessionEvent.subscribe((SessionEvent? session) {
// Do something with the event
});
signClient.registerEventHandler(
namespace: 'kadena',
event: 'kadena_transaction_updated',
);
To Test
Run tests using flutter test
.
Expected flutter version is: >3.3.10
Useful Commands
flutter pub run build_runner build --delete-conflicting-outputs
- Regenerates JSON Generatorsflutter doctor -v
- get paths of everything installed.flutter pub get
flutter upgrade
flutter clean
flutter pub cache clean
flutter pub deps
flutter pub run dependency_validator
- show unused dependencies and moredart format lib/* -l 120
flutter analyze
Setupβ
First you must setup SignClientOptions
which stores both the ProjectId
and Metadata
. You may also optionally specify the storage module to use. By default, the FileSystemStorage
module is used if none is specified.
var dappOptions = new SignClientOptions()
{
ProjectId = "39f3dc0a2c604ec9885799f9fc5feb7c",
Metadata = new Metadata()
{
Description = "An example dapp to showcase WalletConnectSharpv2",
Icons = new[] { "https://walletconnect.com/meta/favicon.ico" },
Name = "WalletConnectSharpv2 Dapp Example",
Url = "https://walletconnect.com"
},
// Uncomment to disable persistent storage
// Storage = new InMemoryStorage()
};
Then, you must setup the ConnectOptions
which define what blockchain, RPC methods and events your dapp will use.
C# Constructor
var dappConnectOptions = new ConnectOptions()
{
RequiredNamespaces = new RequiredNamespaces()
{
{
"eip155", new RequiredNamespace()
{
Methods = new[]
{
"eth_sendTransaction",
"eth_signTransaction",
"eth_sign",
"personal_sign",
"eth_signTypedData",
},
Chains = new[]
{
"eip155:1"
},
Events = new[]
{
"chainChanged",
"accountsChanged",
}
}
}
}
};
Builder Functions Style
var dappConnectOptions1 = new ConnectOptions()
.RequireNamespace("eip155", new RequiredNamespace()
.WithMethod("eth_sendTransaction")
.WithMethod("eth_signTransaction")
.WithMethod("eth_sign")
.WithMethod("personal_sign")
.WithMethod("eth_signTypedData")
.WithChain("eip155:1")
.WithEvent("chainChanged")
.WithEvent("accountsChanged")
);
With both options defined, you can initialize and connect the SDK.
var dappClient = await WalletConnectSignClient.Init(dappOptions);
var connectData = await dappClient.Connect(dappConnectOptions);
You can grab the Uri
for the connection request from connectData
.
ExampleShowQRCode(connectData.Uri);
Then await connection approval using the Approval
Task object.
Task<SessionStruct> sessionConnectTask = connectData.Approval;
SessionStruct sessionData = await sessionConnectTask;
// or
// SessionStruct sessionData = await connectData.Approval;
This Task
will return the SessionStruct
when the session was approved, or throw an exception when the session request has either
- Timed out
- Been Rejected
Connected Addressβ
To get the currently connected address, use the following function
public class Caip25Address
{
public string Address;
public string ChainId;
}
public Caip25Address GetCurrentAddress(string chain)
{
if (string.IsNullOrWhiteSpace(chain))
return null;
var defaultNamespace = currentSession.Namespaces[chain];
if (defaultNamespace.Accounts.Length == 0)
return null;
var fullAddress = defaultNamespace.Accounts[0];
var addressParts = fullAddress.Split(":");
var address = addressParts[2];
var chainId = string.Join(':', addressParts.Take(2));
return new Caip25Address()
{
Address = address,
ChainId = chainId,
};
}
public Caip25Address GetCurrentAddress()
{
var currentSession = dappClient.Session.Get(dappClient.Session.Keys[0]);
var defaultChain = currentSession.Namespaces.Keys.FirstOrDefault();
if (string.IsNullOrWhiteSpace(defaultChain))
return null;
return GetCurrentAddress(defaultChain);
}
WalletConnect Methodsβ
All sign methods require 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 user.
var sessionTopic = sessionData.Topic;
Update Sessionβ
Update a session, adding/removing additional namespaces in the given topic.
var newNamespaces = new Namespaces(...);
var request = await dappClient.UpdateSession(sessionTopic, newNamespaces);
await request.Acknowledged();
Extend Sessionβ
Extend a session's expiry time so the session remains open
var request = await dappClient.Extend(sessionTopic);
await request.Acknowledged();
Pingβ
Send a ping to the session
var request = await dappClient.Ping(sessionTopic);
await request.Acknowledged();
Session Requestsβ
Sending session requests as a dapp requires to build the request and response classes that the session request params
will be structured. C# is a statically typed language, so these types must be given whenever you do a session request (or do any querying for session requests).
Currently, WalletConnectSharp 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 Request typeβ
Create a class for the request and populate it with the JSON properties the request object has. For this example, we will use eth_sendTransaction
The params
field for eth_sendTransaction
has the object type
using Newtonsoft.Json;
public class Transaction
{
public string from;
// Newtonsoft.Json attributes can be used
[JsonProperty("to")]
public string To;
[JsonProperty("gas", NullValueHandling = NullValueHandling.Ignore)]
public string Gas;
// Properties have limited support
[JsonProperty("gasPrice", NullValueHandling = NullValueHandling.Ignore)]
public string GasPrice { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
public string Data { get; set; } = "0x";
}
params: [
{
from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
gas: "0x76c0", // 30400
gasPrice: "0x9184e72a000", // 10000000000000
value: "0x9184e72a", // 2441406250
data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
},
]
Now, let's define the actual request class we'll use in dappClient.Request
[RpcMethod("eth_sendTransaction"), RpcRequestOptions(Clock.ONE_MINUTE, 99997)]
public class EthSendTransaction : List<Transaction>
{
public EthSendTransaction(params Transaction[] transactions) : base(transactions)
{
}
}
The RpcMethod
class attributes defines the rpc method this request uses. The RpcRequestOptions
class attributes define the expiry time and tag attached to the request. Both of these attributes are required
We use List<Transaction>
since the params
field for eth_sendTransaction
is actually sent as an object array. If the params
field was a normal object, then we could use Transaction
or define the fields directly into this class.
Sending a requestβ
The response type for eth_sendTransaction
is a string
, so no response type is required to be made. You only need to create a response type if the response type is a custom object.
var wallet = GetCurrentAddress();
var result = new EthSendTransaction(new Transaction()
{
From = wallet.Address,
To = wallet.Address,
Value = "0"
});
// Returns the transaction hash or throws an error
string result = await dappClient.Request<EthSendTransaction, string>(sessionTopic, request, wallet.ChainId);
Disconnectingβ
To disconnect a session, use the Disconnect
function. You may optional provide a reason for the disconnect
await dappClient.Disconnect(sessionTopic);
// or
await dappClient.Disconnect(sessionTopic, Error.FromErrorType(ErrorType.USER_DISCONNECTED));
WalletConnectUnity is a wrapper for WalletConnectSharp. It simplifies managing a single active session, addressing a common challenge with the original library.
Features of WalletConnectUnityβ
-
Simplified Session Management: WalletConnectSharp is designed to support multiple sessions, requiring developers to manually track and restore the active session. WalletConnectUnity simplifies this process by focusing on a single session, making it easier to manage session restoration.
-
Session Restoration: WalletConnectUnity includes methods to easily access and restore the active session from storage.
-
Deep Linking Support: WalletConnectUnity automatically handles deep linking for mobile and desktop wallets.
-
QR Code Generation: WalletConnectUnity provides a utility for generating QR codes.
Usageβ
To use WalletConnectUnity in your project:
- Fill in the Project ID and Metadata fields in the
Assets/WalletConnectUnity/Resources/WalletConnectProjectConfig
asset.- If you donβt have a Project ID, you can create one at WalletConnect Cloud.
- The
Redirect
fields are optional. They are used to redirect the user back to your app after they approve or reject the session.
- Initialize
WalletConnect
and connect the wallet:
// Initialize singleton
await WalletConnect.Instance.InitializeAsync();
// Or handle instancing manually
var walletConnectUnity = new WalletConnect();
await walletConnectUnity.InitializeAsync();
// Try to resume the last session
var sessionResumed = await WalletConnect.Instance.TryResumeSessionAsync();
if (!sessionResumed)
{
var connectedData = await WalletConnect.Instance.ConnectAsync(connectOptions);
// Create QR code texture
var texture = WalletConnectUnity.Core.Utils.QRCode.EncodeTexture(connectedData.Uri);
// ... Display QR code texture
// Wait for wallet approval
await connectedData.Approval;
}
All features of WalletConnectSharp are accessible in WalletConnectUnity.
For complex scenarios, the SignClient
can be accessed directly through WalletConnect.SignClient
.
Refer to the C#
documentation for details on using the Sign API within WalletConnectUnity.
The usage of the WalletConnectSharp.Sign API remains consistent with C#
.
Was this helpful?