Usage
This section provides instructions on how to use WalletConnectModal in your project.
Implementation
- Web
- iOS
- Android
- Flutter
- React Native
- Unity
Start by importing WalletConnectModal
and initializing it.
import { WalletConnectModal } from '@walletconnect/modal'
const modal = new WalletConnectModal({
projectId: 'YOUR_PROJECT_ID',
chains: ['eip155:1']
})
Trigger the modal
Once you have obtained your connection uri, you can open or close the modal.
From here on, use provider
as you normally would, WalletConnectModal will be shown and hidden automatically i.e.
await modal.openModal({
uri: 'YOUR_CONNECTION_URI'
})
// Do some work...
modal.closeModal()
Configure Networking and Pair clients
Make sure that you properly configure Networking and Pair Clients first.
Initialize WalletConnectModal Client
In order to initialize a client just call a configure
method from the Web3Wallet instance wrapper
let metadata = AppMetadata(
name: "Example Wallet",
description: "Wallet description",
url: "example.wallet",
icons: ["https://avatars.githubusercontent.com/u/37784886"],
// Used for the Verify: to opt-out verification ignore this parameter
verifyUrl: "verify.walletconnect.com"
)
WalletConnectModal.configure(
projectId: PROJECT_ID,
metadata: metadata
)
This example will default to using following namespaces.
let methods: Set<String> = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"]
let events: Set<String> = ["chainChanged", "accountsChanged"]
let blockchains: Set<Blockchain> = [Blockchain("eip155:1")!]
let namespaces: [String: ProposalNamespace] = [
"eip155": ProposalNamespace(
chains: blockchains,
methods: methods,
events: events
)
]
let defaultSessionParams = SessionParams(
requiredNamespaces: namespaces,
optionalNamespaces: nil,
sessionProperties: nil
)
If you want to change that you can call configure and define your own session parameters like this.
let metadata = AppMetadata(...)
let sessionParams = SessionParams(...)
WalletConnectModal.configure(
projectId: PROJECT_ID,
metadata: metadata,
sessionParams: sessionParams
)
or you can change them later by calling WalletConnectModal.set(sessionParams: SessionParams(...))
WalletConnectModal
is a singleton that interacts with the WalletConnectModal SDK.
Initialize
val connectionType = ConnectionType.AUTOMATIC or ConnectionType.MANUAL
val projectId = "" // Get Project ID at https://cloud.walletconnect.com/
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=${projectId}"
val appMetaData = Core.Model.AppMetaData(
name = "Kotlin.WalletConnectModal",
description = "Kotlin WalletConnectModal Implementation",
url = "kotlin.walletconnect.com",
icons = listOf("https://raw.githubusercontent.com/WalletConnect/walletconnect-assets/master/Icon/Gradient/Icon.png"),
redirect = "kotlin-modal://request"
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData)
WalletConnectModal.initialize(
init = Modal.Params.Init(CoreClient),
onSuccess = {
// Callback will be called if initialization is successful
},
onError = { error ->
// Error will be thrown if there's an issue during initialization
}
)
SessionParams
This example will default to using following namespaces. You can define your own session parameters like this.
val methods: List<String> = listOf("eth_sendTransaction", "personal_sign", "eth_sign", "eth_signTypedData")
val events: List<String> = listOf("chainChanged", "accountsChanged")
val chains: List<String> = listOf("eip155:1")
val namespaces = mapOf(
"eip155" to Modal.Model.Namespace.Proposal(
chains = chains,
methods = methods,
events = events
)
)
val sessionParams = Modal.Params.SessionParams(
requiredNamespaces = namespaces,
optionalNamespaces = null,
properties = null
)
WalletConnectModal.setSessionParams(sessionParams)
IMPORTANT: SessionParams
must be set before opening the modal.
Create your WalletConnectModalService
which is your primary class for opening, closing, disconnecting, etc.
Be sure to update the project ID and metadata with your own.
WalletConnectModalService service = WalletConnectModalService(
projectId: 'YOUR_PROJECT_ID',
metadata: const PairingMetadata(
name: 'Flutter WalletConnect',
description: 'Flutter WalletConnectModal Sign Example',
url: 'https://walletconnect.com/',
icons: ['https://walletconnect.com/walletconnect-logo.png'],
redirect: Redirect(
native: 'flutterdapp://',
universal: 'https://www.walletconnect.com',
),
),
);
await service.init();
The service must be initialized before it can be used.
With the WalletConnectModalService
created and ready, you can call _service.open()
to open the modal.
To make things easy, you can use the WalletConnectModalConnect widget to open the modal. This is a button that changes its state based on the modal and connection. This widget requires the WalletConnectModalService to be passed in.
WalletConnectModalConnect(
walletConnectModalService: _service,
),
iOS Setup
For each app you would like to be able to deep link to, you must add that app's link into the ios/Runner/Info.plist
file like so:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>metamask</string>
<string>rainbow</string>
<string>trust</string>
</array>
To handle deep linking to your app, you will also need to add the following to the plist file:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>flutterdapp</string> <!-- Change "flutterdapp" to be your deep link -->
</array>
<key>CFBundleURLName</key>
<string>com.walletconnect.flutterdapp</string> <!-- Change this package name to be your package -->
</dict>
</array>
Android Setup
On android 11+ you must specify that use can use the internet, along with the different packages you would like to be able to deep link to in the android/app/src/main/AndroidManifest.xml
file like so:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Intent so you can deep link to wallets -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<package android:name="io.metamask"/>
<package android:name="com.wallet.crypto.trustapp"/>
<package android:name="io.gnosis.safe"/>
<package android:name="me.rainbow"/>
<package android:name="io.zerion.android"/>
<package android:name="com.imtoken.app"/>
<!-- Add other wallets you would like to launch from within the app -->
</queries>
<!-- Permission to access the internet -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Update your activity to handle the deep linking from other apps -->
<activity
...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "flutterdapp://”, change this to be your deep link -->
<data android:scheme="flutterdapp" />
</intent-filter>
</activity>
...
</manifest>
For other packages, see the example project
For some reason, multiple wallets have the metamask
intent, and will launch metamask as a result.
This is a bug in the wallets, not this package.
Start by importing @walletconnect/react-native-compat
at the top of your app. Then import the WalletConnect Modal package, replace YOUR_PROJECT_ID
with your WalletConnect Cloud Project ID and add your Project's info in providerMetadata
import '@walletconnect/react-native-compat'
import { WalletConnectModal } from '@walletconnect/modal-react-native'
const projectId = 'YOUR_PROJECT_ID'
const providerMetadata = {
name: 'YOUR_PROJECT_NAME',
description: 'YOUR_PROJECT_DESCRIPTION',
url: 'https://your-project-website.com/',
icons: ['https://your-project-logo.com/'],
redirect: {
native: 'YOUR_APP_SCHEME://',
universal: 'YOUR_APP_UNIVERSAL_LINK.com'
}
}
function App() {
return (
<>
<WalletConnectModal projectId={projectId} providerMetadata={providerMetadata} />
</>
)
}
- 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.
- Add
WalletConnectModal
prefab fromWalletConnectUnity Modal
package to the first scene in your game.
Usage
- Web
- iOS
- Android
- Flutter
- React Native
- Unity
openModal
Action to open the modal. Returns promise that resolves once modal is visible.
Example
await modal.openModal({
uri: 'YOUR_CONNECTION_URI'
})
Reference
openModal: (options?: OpenOptions) => Promise<void>
interface OpenOptions {
// Uri that will be used to generate qrcode and mobile links, required
uri: string
// CAIP-2 compliant chain ids to override initial chains defined when creating the modal
// Learn about CAIP-10: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md
chains?: string[]
}
closeModal
Action to close the modal.
Example
modal.closeModal()
Reference
closeModal: () => void
subscribeModal
Action to subscribe to modal state changes.
Example
modal.subscribeModal(state => console.log(state))
Reference
subscribeModal: (callback: (state: ModalState) => void) => void
interface ModalState {
open: boolean
}
To actually present the modal you can simply call.
WalletConnectModal.present()
It will traverse the view hierarchy and try to present from top most controller. This is meant more towards SwiftUI.
Otherwise you can specify the viewController to present from.
WalletConnectModal.present(from: viewController)
Subscribe for WalletConnectModal Publishers
The following publishers are available to subscribe:
public var sessionPublisher: AnyPublisher<[Session], Never>
public var sessionSettlePublisher: AnyPublisher<Session, Never>
public var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never>
public var sessionDeletePublisher: AnyPublisher<(String, Reason), Never>
public var sessionResponsePublisher: AnyPublisher<Response, Never>
public var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>
Sign methods
WalletConnectModal is internally using Sign SDK and most of its method are being exposed through WalletConnectModal interface.
Where to go from here
Check the WalletConnectModal usage in our Example Showcase app that is part of WalletConnectSwiftV2 repository.
Build API documentation in Xcode by going to Product -> Build Documentation
Android Compose
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetState
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.navigation.material.BottomSheetNavigator
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
import com.google.accompanist.navigation.material.bottomSheet
import com.walletconnect.wcmodal.ui.walletConnectModalGraph
setContent {
val modalSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val bottomSheetNavigator = BottomSheetNavigator(modalSheetState)
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen()
}
walletConnectModalGraph(navController)
}
}
}
IMPORTANT: WalletConnectModal uses accompanist navigation material inside. ModalBottomSheetLayout
should be imported from Accompanist Navigation Material
import com.walletconnect.wcmodal.ui.openWalletConnectModal
navController.openWalletConnectModal()
Android View
Navigation Component
<navigation >
<fragment
android:id="@+id/HomeFragment"
android:name="com.walletconnect.sample.HomeFragment">
<action
android:id="@+id/action_to_bottomSheet"
app:destination="@id/bottomSheet" />
</fragment>
<dialog
android:id="@+id/bottomSheet"
android:name="com.walletconnect.wcmodal.ui.WalletConnectModalSheet" />
</navigation>
import androidx.navigation.fragment.findNavController
import com.walletconnect.wcmodal.ui.openWalletConnectModal
findNavController().openWalletConnectModal(id = R.id.action_to_bottomSheet)
Kotlin DSL
import androidx.navigation.createGraph
import androidx.navigation.fragment.fragment
import com.walletconnect.wcmodal.ui.walletConnectModal
navController.graph = navController.createGraph("Home") {
fragment<HomeFragment>("Home")
walletConnectModal()
}
import androidx.navigation.fragment.findNavController
import com.walletconnect.wcmodal.ui.openWalletConnectModal
findNavController().openWalletConnectModal()
WalletConnectModal.ModalDelegate
val walletConnectModalDelegate = object : WalletConnectModal.ModalDelegate {
override fun onSessionApproved(approvedSession: Modal.Model.ApprovedSession) {
// Triggered when receives the session approval from wallet
}
override fun onSessionRejected(rejectedSession: Modal.Model.RejectedSession) {
// Triggered when receives the session rejection from wallet
}
override fun onSessionUpdate(updatedSession: Modal.Model.UpdatedSession) {
// Triggered when receives the session update from wallet
}
override fun onSessionExtend(session: Modal.Model.Session) {
// Triggered when receives the session extend from wallet
}
override fun onSessionEvent(sessionEvent: Modal.Model.SessionEvent) {
// Triggered when the peer emits events that match the list of events agreed upon session settlement
}
override fun onSessionDelete(deletedSession: Modal.Model.DeletedSession) {
// Triggered when receives the session delete from wallet
}
override fun onSessionRequestResponse(response: Modal.Model.SessionRequestResponse) {
// Triggered when 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: Modal.Model.ConnectionState) {
//Triggered whenever the connection state is changed
}
override fun onError(error: Modal.Model.Error) {
// Triggered whenever there is an issue inside the SDK
}
}
The WalletConnectModal needs a WalletConnectModal.ModalDelegate
passed to it for it to be able to expose asynchronously updates sent from the Wallet. It can only be called after successful WalletConnectModal
initialization
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, Modal.Model.Namespaces.Proposal> = mapOf(namespace, Modal.Model.Namespaces.Proposal(accounts, methods, events)) /*Required namespaces to setup a session*/
val optionalNamespaces: Map<String, Modal.Model.Namespaces.Proposal> = mapOf(namespace, Modal.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 = Modal.Params.Connect(requiredNamespaces, optionalNamespaces, pairing)
WalletConnectModal.connect(
connect = connectParams,
onSuccess = {
/* callback that letting you know that you have successfully initiated connecting */
},
onError = { error =>
/* callback for error while trying to initiate a connection with a peer */
}
)
More about optional and required namespaces can be found here
Disconnect
val disconnectParams = WalletConnectModal.Params.Disconnect(topic)
WalletConnectModal.disconnect(
disconnect = disconnectParams,
onSuccess = {
/* callback that letting you know that you have successfully disconnected */
},
onError = { error ->
/* callback for error while trying to disconnection with a peer */
}
)
Request
val requestParams = Modal.Params.Request(
sessionTopic = sessionTopic,
method = /* Selected method */,
params = /* Method params */,
chainId = /* Chain id */
)
WalletConnectModal.request(
request = requestParams,
onSuccess = {
/* callback that letting you know that you have successful request */
},
onError = { error ->
/* callback for error */
}
)
Get List of Active Sessions
WalletConnectModal.getListOfActiveSessions()
To get a list of active sessions, call WalletConnectModal.getListOfActiveSessions()
which will return a list of type Modal.Model.Session
.
Get list of pending session requests for a topic
WalletConnectModal.getActiveSessionByTopic(topic)
To get an active session for a topic, call WalletConnectModal.getActiveSessionByTopic()
and pass a topic which will return
a Modal.Model.Session
object containing requestId, method, chainIs and params for pending request.
You can launch the currently connected wallet by calling service.launchCurrentWallet()
.
useWalletConnectModal
Hook to programmatically control the modal. Useful when you want to use your own UI elements and subscribe to modals state.
*Note: A new session is created automatically when the modal is opened, so avoid calling provider.connect
by yourself.
import { useWalletConnectModal } from "@walletconnect/modal-react-native";
const { isOpen, open, close, provider, isConnected, address } = useWalletConnectModal();
// Modal's open state
isOpen;
// Open modal
interface Options {
route?: 'ConnectWallet' | 'Qrcode' | 'WalletExplorer';
}
await open(options?: Options);
// Close modal
close();
// Initialized provider
provider;
// Wallet connection state
isConnected;
// Connected account's address
address;
Example
import { Pressable, Text } from 'react-native'
import '@walletconnect/react-native-compat'
import { WalletConnectModal, useWalletConnectModal } from '@walletconnect/modal-react-native'
const projectId = 'YOUR_PROJECT_ID'
const providerMetadata = {
name: 'YOUR_PROJECT_NAME',
description: 'YOUR_PROJECT_DESCRIPTION',
url: 'https://your-project-website.com/',
icons: ['https://your-project-logo.com/'],
redirect: {
native: 'YOUR_APP_SCHEME://',
universal: 'YOUR_APP_UNIVERSAL_LINK.com'
}
}
function App() {
const { open, isConnected, provider } = useWalletConnectModal()
const onPress = () => {
if (isConnected) {
provider.disconnect()
} else {
open()
}
}
return (
<>
<Pressable onPress={onPress}>
<Text>{isConnected ? 'Disconnect' : 'Connect'}</Text>
</Pressable>
<WalletConnectModal projectId={projectId} providerMetadata={providerMetadata} />
</>
)
}
Connection and Events
- WalletConnect Modal is a singleton that can be accessed from any scene.
- By default Modal will initialize itself asynchronously on Awake. During initialization it will also try to connect to the last session.
- After initialization, Modal invokes
WalletConnectModal.Ready
static event. - If
Ready
argument'sSessionResumed
istrue
, it means that Modal has successfully connected to the last session. In this case you don't need to open the modal. Otherwise, open the modal withWalletConnectModal.Open()
static method.
private void Start()
{
WalletConnectModal.Ready += (sender, args) =>
{
if (args.SessionResumed)
{
// Session has been resumed, proceed to the game
}
else
{
// Session hasn't been resumed
// Define required namespaces for new session
var requiredNamespaces = new RequiredNamespaces
{
{
"eip155", new ProposedNamespace
{
Methods = new[]
{
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
},
Chains = new[]
{
"eip155:1"
},
Events = new[]
{
"chainChanged",
"accountsChanged"
}
}
}
};
var connectOptions = new ConnectOptions
{
RequiredNamespaces = requiredNamespaces
};
// Open the modal
WalletConnectModal.Open(new WalletConnectModalOptions
{
ConnectOptions = connectOptions
});
}
};
}
Subscribe to ActiveSessionChanged
and SessionDeleted
events. It's recommended to do it in Ready
event handler.
WalletConnectModal.Ready += (sender, args) =>
{
// ....
// Invoked after wallet connected
WalletConnect.Instance.ActiveSessionChanged += (_, sessionStruct) =>
{
// Session connected/updated, proceed to the game if sessionStruct.topic is not null/empty
};
// Invoked after wallet disconnected
WalletConnect.Instance.SessionDisconnected += (_, _) =>
{
// Session deleted, show sign in screen
};
};
Disconnection
To disconnect from the current session, call WalletConnectModal.Disconnect()
static method.
Interaction with RPC
The WalletConnect Modal is responsible for facilitating communication between the game and the wallet.
Some methods do not require the user to interact with the wallet. For example, eth_getBalance
is used to get the address balance,
and eth_call
is used to read data from a smart contract without modifying its state, hence no signature is required.
To call these methods, you can use the Nethereum.Web3 package.
private static async Task GetAccountBalance()
{
var session = WalletConnect.Instance.ActiveSession;
// Because one session can have multiple namespaces, we need to select one.
// In most cases, especially in games, dapp will use only one namespace.
var @namespace = session.Namespaces.First();
var address = session.CurrentAddress(@namespace.Key).Address;
var config = ProjectConfiguration.Load();
// Using WalletConnect Blockchain API: https://docs.walletconnect.com/cloud/blockchain-api
var url = $"https://rpc.walletconnect.com/v1?chainId={@namespace.Value.Chains[0]}&projectId={config.Id}";
var web3 = new Nethereum.Web3.Web3(url);
var balance = await web3.Eth.GetBalance.SendRequestAsync(address);
Debug.Log($"Balance of {address} in Wei: {balance.Value}");
var etherAmount = Nethereum.Web3.Web3.Convert.FromWei(balance.Value);
Debug.Log($"Balance of {address} in Ether: {etherAmount}");
}
Interaction with Smart Contracts
To query smart contracts, you can use Nethereum.Web3 package
to make eth_call
requests directly to the RPC endpoint.
However, to call methods that modify the state of the smart contract, you need user to sign the transaction in the wallet.
The example below shows how to call approve
method of WETH9 (Wrapped Ether) smart contract. It encodes data with Nethereum and makes request with WalletConnect.
public async Task ContractTransaction()
{
var session = WalletConnect.Instance.ActiveSession;
// Because one session can have multiple namespaces, we need to select one.
// In most cases, especially in games, dapp will use only one namespace.
var @namespace = session.Namespaces.First();
var myAddress = session.CurrentAddress(@namespace.Key).Address;
// Define contract and function details
var contractAddress = "0x4200000000000000000000000000000000000006";
var toAddress = myAddress; // Use sender's address for the sake of example
var amount = new BigInteger(12345);
// Define the parameters for the approve function
var parameters = new Parameter[] {
new("address", "guy"),
new("uint256", "wad")
};
var functionCallEncoder = new FunctionCallEncoder();
var sha3Signature = new Sha3Keccack().CalculateHash("approve(address,uint256)");
// Encode the parameters
var encodedParameters = functionCallEncoder
.EncodeParameters(parameters, toAddress, amount)
.ToHex();
// Combine signature and parameters
var data = "0x" + sha3Signature[..8] + encodedParameters;
// Create transaction
var ethSendTransaction = new EthSendTransaction(new Transaction
{
From = myAddress,
To = contractAddress,
Value = "0",
Data = data
});
try
{
var result = await WalletConnect.Instance.RequestAsync<EthSendTransaction, string>(ethSendTransaction);
Debug.Log($"Transaction success! TxHash: {result}", this);
}
catch (Exception e)
{
Debug.LogError(e, this);
}
}
public class Transaction
{
[JsonProperty("from")] public string From { get; set; }
[JsonProperty("to")] public string To { get; set; }
[JsonProperty("gas", NullValueHandling = NullValueHandling.Ignore)]
public string Gas { get; set; }
[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";
}
[RpcMethod("eth_sendTransaction"), RpcRequestOptions(Clock.ONE_MINUTE, 99997)]
public class EthSendTransaction : List<Transaction>
{
public EthSendTransaction(params Transaction[] transactions) : base(transactions)
{
}
[Preserve]
public EthSendTransaction()
{
}
}
Please refer to Nethereum documentation for more details. Nethereum provides tools to simplify encoding and decoding. These tools hadn't been used in the example above to better illustrate the process.
Was this helpful?