Skip to content

Latest commit

 

History

History
2063 lines (1548 loc) · 78.4 KB

wallet-usage.mdx

File metadata and controls

2063 lines (1548 loc) · 78.4 KB

import Container from './../components/Container' import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import PlatformTabs from '../components/PlatformTabs' import PlatformTabItem from '../components/PlatformTabItem' import CloudBanner from '../components/CloudBanner'

Usage

This section provides instructions on how to initialize the Web3Wallet 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 Web3Wallet instance and initializing it with a projectId from Cloud.

Session: Connection between a dapp and a wallet.

Extra (Platform Specific): Additional information for platform specific usage. Some useful topics covered in this section are:

To check the full list of platform specific instructions for your preferred platform, go to Extra (Platform Specific) and select your platform.

Initialization

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}

Create a new instance from Core and initialize it with a projectId created from installation. Next, create web3Wallet instance by calling init on Web3Wallet. Passing in the options object containing metadata about the app and an optional relay URL.

import { Core } from '@walletconnect/core'
import { Web3Wallet } from '@walletconnect/web3wallet'

const core = new Core({
  projectId: process.env.PROJECT_ID
})

const web3wallet = await Web3Wallet.init({
  core, // <- pass the shared `core` instance
  metadata: {
    name: 'Demo app',
    description: 'Demo Client as Wallet/Peer',
    url: 'www.walletconnect.com',
    icons: []
  }
})

Confirm you have configured the Network Client first.

Starting from WalletConnect SDK version 1.9.5, the redirect field in the AppMetadata object is mandatory. Ensure that the provided value matches your app's URL scheme to prevent redirection-related issues.

Once you're done, 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"],
    redirect: AppMetadata.Redirect(native: "example://", universal: nil)
)

Web3Wallet.configure(
    metadata: metadata,
    crypto: DefaultCryptoProvider(),
    // Used for the Push: "echo.walletconnect.com" will be used by default if not provided
    pushHost: "echo.walletconnect.com",
    // Used for the Push: "APNSEnvironment.production" will be used by default if not provided
    environment: .production
)

In order to allow users to receive push notifications you have to communicate with Apple Push Notification service and receive unique device token. Register that token with following method:

try await Web3Wallet.instance.register(deviceToken: deviceToken)
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 = "Wallet Name",
    description = "Wallet Description",
    url = "Wallet URL",
    icons = /*list of icon url strings*/,
    redirect = "kotlin-wallet-wc:/request" // Custom Redirect URI
)

CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData)

val initParams = Wallet.Params.Init(core = CoreClient)

Web3Wallet.initialize(initParams) { error ->
    // Error will be thrown if there's an issue during initialization
}

The Web3Wallet client will always be responsible for exposing accounts (CAIP10 compatible) to a Dapp and therefore is also in charge of signing. To initialize the Web3Wallet client, create a Wallet.Params.Init object in the Android Application class with the Core Client. The Wallet.Params.Init object will then be passed to the Web3Walletinitialize function.

To create an instance of Web3Wallet, you need to pass in the core and metadata parameters.

final web3Wallet = await Web3Wallet.createInstance(
  projectId: '123.....',
  metadata: const PairingMetadata(
    name: 'Wallet Name',
    description: 'Wallet Description',
    url: 'https://your_wallet_url.com/',
    icons: [
      'https://your_wallet_icon.png'
    ],
    redirect: Redirect(
      native: 'yourwalletscheme://',
      universal: 'https://your_wallet_url.com',
    ),
  ),
);

:::info Note

@walletconnect/react-native-compat must be installed and imported before any @walletconnect/* dependencies for proper React Native polyfills.

import '@walletconnect/react-native-compat'
// Other imports

:::

Create a new instance from Core and initialize it with your projectId. Next, create a Web3Wallet instance by calling init on Web3Wallet. Passing in the options object containing metadata about the app.

In this code example, we wrapped it in a createWeb3Wallet function as this will be easier to call from your App.tsx or an initialization function as seen here.

The pair function will help us pair between the dapp and wallet and will be used shortly.

import { Core } from '@walletconnect/core'
import { Web3Wallet } from '@walletconnect/web3wallet'

const core = new Core({
  projectId: process.env.PROJECT_ID
})

const web3wallet = await Web3Wallet.init({
  core, // <- pass the shared `core` instance
  metadata: {
    name: 'Demo React Native Wallet',
    description: 'Demo RN Wallet to interface with Dapps',
    url: 'www.walletconnect.com',
    icons: ['https://your_wallet_icon.png'],
    redirect: {
      native: 'yourwalletscheme://'
    }
  }
})

First you must setup a WalletConnectCore 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 WalletConnectCore(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 WalletConnectSharpv2",
    Icons = new[] { "https://walletconnect.com/meta/favicon.ico" },
    Name = $"wallet-csharp-test",
    Url = "https://walletconnect.com",
};

Once you have both the WalletConnectCore and Metadata objects, you can initialize the Web3WalletClient

var sdk = await Web3WalletClient.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

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}

With Web3Wallet v1.5.1 (and @walletconnect/utils v2.6.1) we've published 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 ready-to-use namespaces object.

// util params
{
  proposal: ProposalTypes.Struct; // the proposal received by `.on("session_proposal")`
  supportedNamespaces: Record< // your Wallet's supported namespaces
    string, // the supported namespace key e.g. eip155
    {
      chains: string[]; // your supported chains in CAIP-2 format e.g. ["eip155:1", "eip155:2", ...]
      methods: string[]; // your supported methods e.g. ["personal_sign", "eth_sendTransaction"]
      events: string[]; // your supported events e.g. ["chainChanged", "accountsChanged"]
      accounts: string[] // your user's accounts in CAIP-10 format e.g. ["eip155:1:0x453d506b1543dcA64f57Ce6e7Bb048466e85e228"]
      }
  >;
};

Example usage

// import the builder util
import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'
import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils'

async function onSessionProposal({ id, params }: Web3WalletTypes.SessionProposal){
  try{
    // ------- namespaces builder util ------------ //
    const approvedNamespaces = buildApprovedNamespaces({
      proposal: params,
      supportedNamespaces: {
        eip155: {
          chains: ['eip155:1', 'eip155:137'],
          methods: ['eth_sendTransaction', 'personal_sign'],
          events: ['accountsChanged', 'chainChanged'],
          accounts: [
            'eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb',
            'eip155:137:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb'
          ]
        }
      }
    })
    // ------- end namespaces builder util ------------ //

    const session = await web3wallet.approveSession({
      id,
      namespaces: approvedNamespaces
    })
  }catch(error){
    // use the error.message to show toast/info-box letting the user know that the connection attempt was unsuccessful
    ....
    await web3wallet.rejectSession({
      id: proposal.id,
      reason: getSdkError("USER_REJECTED")
    })
  }
}


web3wallet.on('session_proposal', onSessionProposal)

If your wallet supports multiple namespaces e.g. eip155,cosmos & near Your supportedNamespaces should look like the following example.

// ------- namespaces builder util ------------ //
const approvedNamespaces = buildApprovedNamespaces({
    proposal: params,
    supportedNamespaces: {
        eip155: {...},
        cosmos: {...},
        near: {...}
    },
});
// ------- end namespaces builder util ------------ //

AutoNamespaces 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 ready-to-use SessionNamespace object.

public static func build(
    sessionProposal: Session.Proposal,
    chains: [Blockchain],
    methods: [String],
    events: [String],
    accounts: [Account]
) throws -> [String: SessionNamespace]

Example usage

do {
    sessionNamespaces = try AutoNamespaces.build(
        sessionProposal: proposal,
        chains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!],
        methods: ["eth_sendTransaction", "personal_sign"],
        events: ["accountsChanged", "chainChanged"],
        accounts: [
            Account(blockchain: Blockchain("eip155:1")!, address: "0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!,
            Account(blockchain: Blockchain("eip155:137")!, address: "0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
        ]
    )
} catch let error as AutoNamespacesError {
    // reject session proposal if AutoNamespace build function threw
    try await reject(proposal: proposal, reason: RejectionReason(from: error))
    return
}
// approve session with sessionNamespaces
try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces)

With Web3Wallet SDK 1.7.0 we've published 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 wallet's chains, methods, events, and accounts (supported namespaces) and returns ready-to-use namespaces object that has to be passed into Wallet.Params.SessionApprove when approving a session.

val supportedNamespaces: Wallet.Model.Namespaces.Session = /* a map of all supported namespaces created by a wallet */
val sessionProposal: Wallet.Model.SessionProposal =  /* an object received by `fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal)` in `Web3Wallet.WalletDelegate` */
val sessionNamespaces = Web3Wallet.generateApprovedNamespaces(sessionProposal, supportedNamespaces)

val approveParams: Wallet.Params.SessionApprove = Wallet.Params.SessionApprove(proposerPublicKey, sessionNamespaces)
Web3Wallet.approveSession(approveParams) { error -> /*callback for error while approving a session*/ }

Examples of supported namespaces:

 val supportedNamespaces = mapOf(
    "eip155" to Wallet.Model.Namespace.Session(
        chains = listOf("eip155:1", "eip155:137", "eip155:3"),
        methods = listOf("personal_sign", "eth_sendTransaction", "eth_signTransaction"),
        events = listOf("chainChanged"),
        accounts = listOf("eip155:1:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092", "eip155:137:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092", "eip155:3:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")
    )
)

 val anotherSupportedNamespaces = mapOf(
    "eip155" to Wallet.Model.Namespace.Session(
        chains = listOf("eip155:1", "eip155:2", "eip155:4"),
        methods = listOf("personal_sign", "eth_sendTransaction", "eth_signTransaction"),
        events = listOf("chainChanged", "accountsChanged"),
        accounts = listOf("eip155:1:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092", "eip155:2:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092", "eip155:4:0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")
    ),
    "cosmos" to Wallet.Model.Namespace.Session(
        chains = listOf("cosmos:cosmoshub-4"),
        methods = listOf("cosmos_method"),
        events = listOf("cosmos_event"),
        accounts = listOf("cosmos:cosmoshub-4:cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")
    )
)

On flutter you don't need to worry about Namespace Builder as Flutter SDK would handle that for you and generate a namespace object with the supported ones.

All you have to do is make sure you are registering

  1. events emitters with web3Wallet.registerEventEmitter() for events you want to support on your wallet
  2. request handlers with web3Wallet.registerRequestHandler() for methods you want to support on your wallet
  3. wallet's accounts with web3Wallet.registerAccount() for accounts you want these events and methods to be enabled on

for every chain you want to support.

When a dApp propose a session, with declared required and/or optional namespaces, your wallet will be able to approve an already generated set of namespaces based on your registered events, methods and accounts.

You can access this object in SessionProposalEvent during onSessionProposal event by querying event.params.generatedNamespaces. (See next section)

However, if you decide not to use web3Wallet.registerRequestHandler() and, instead, you decide to handle session requests by subscribing to the web3Wallet.onSessionRequest events then all you have to do is construct your approved namespaces as follows:

final approvedNamespaces = {
  'eip155': const Namespace(
    accounts: [ // every account you want to support
      'eip155:1:0x76..........',
      'eip155:137:0x76..........',
    ],
    methods: [ // every method you want to support
      'personal_sign',
      'eth_sendTransaction',
    ],
    events: [ // every event you want to support
      'chainChanged',
      'accountsChanged',
    ],
  ),
};

Flutter SDK provides a handy MethodsConstants and EventsConstants for already defined set of required and optional values.

With Web3Wallet v1.5.1 (and @walletconnect/utils v2.6.1) we've published 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 ready-to-use namespaces object.

// util params
{
  proposal: ProposalTypes.Struct; // the proposal received by `.on("session_proposal")`
  supportedNamespaces: Record< // your Wallet's supported namespaces
    string, // the supported namespace key e.g. eip155
    {
      chains: string[]; // your supported chains in CAIP-2 format e.g. ["eip155:1", "eip155:2", ...]
      methods: string[]; // your supported methods e.g. ["personal_sign", "eth_sendTransaction"]
      events: string[]; // your supported events e.g. ["chainChanged", "accountsChanged"]
      accounts: string[] // your user's accounts in CAIP-10 format e.g. ["eip155:1:0x453d506b1543dcA64f57Ce6e7Bb048466e85e228"]
      }
  >;
};

Example usage

// import the builder util
import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'
import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils'

async function onSessionProposal({ id, params }: Web3WalletTypes.SessionProposal){
  try{
    // ------- namespaces builder util ------------ //
    const approvedNamespaces = buildApprovedNamespaces({
      proposal: params,
      supportedNamespaces: {
        eip155: {
          chains: ['eip155:1', 'eip155:137'],
          methods: ['eth_sendTransaction', 'personal_sign'],
          events: ['accountsChanged', 'chainChanged'],
          accounts: [
            'eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb',
            'eip155:137:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb'
          ]
        }
      }
    })
    // ------- end namespaces builder util ------------ //

    const session = await web3wallet.approveSession({
      id,
      namespaces: approvedNamespaces
    })
  }catch(error){
    // use the error.message to show toast/info-box letting the user know that the connection attempt was unsuccessful
   ....

    await web3wallet.rejectSession({
      id: proposal.id,
      reason: getSdkError("USER_REJECTED")
    })
  }
}


web3wallet.on('session_proposal', onSessionProposal)

If your wallet supports multiple namespaces e.g. eip155,cosmos & near Your supportedNamespaces should look like the following example.

// ------- namespaces builder util ------------ //
const approvedNamespaces = buildApprovedNamespaces({
    proposal: params,
    supportedNamespaces: {
        eip155: {...},
        cosmos: {...},
        near: {...}
    },
});
// ------- end namespaces builder util ------------ //

To build a namespace mapping for either proposing a session OR approving a session, you can use C# 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",
  ]
}

EIP-5792 - Wallet Call API

WalletConnect supports EIP-5792, which defines new JSON-RPC methods that enable apps to ask a wallet to process and/or sponsor a batch of onchain write calls and to check on the status of those calls. Applications can specify that these onchain calls be executed taking advantage of specific capabilities previously expressed by the wallet; an additional, a novel wallet RPC is defined to enable apps to query the wallet for those capabilities.

  • wallet_sendCalls: Requests that a wallet submits a batch of calls.
  • wallet_getCallsStatus: Returns the status of a call batch that was sent via wallet_sendCalls.
  • wallet_showCallsStatus: Requests that a wallet shows information about a given call bundle that was sent with wallet_sendCalls.
  • wallet_getCapabilities: This RPC allows an application to request capabilities from a wallet (e.g. batch transactions, paymaster communication).

Session Approval

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}

The session_proposal event is emitted when a dapp initiates a new session with a user's wallet. The event will include a proposal object with information about the dapp and requested permissions. The wallet should display a prompt for the user to approve or reject the session. If approved, call approveSession and pass in the proposal.id and requested namespaces.

The pair method initiates a WalletConnect pairing process with a dapp using the given uri (QR code from the dapps). To learn more about pairing, checkout out the docs.

web3wallet.on('session_proposal', async proposal => {
  const session = await web3wallet.approveSession({
    id: proposal.id,
    namespaces
  })
})
await web3wallet.pair({ uri })

🛠️ Usage examples

⚠️ Expected Errors

  • No matching key. proposal id doesn't exist: 1

This rejection means the SDK can't find a record with the given proposal.id - in this example 1. This can happen when the proposal has expired (by default 5 minutes) or if you attempt to respond to a proposal that has already been approved/rejected. If you are seeing this error, please make sure that you are calling approveSession with the correct proposal.id that is available within the proposal payload.

  • Error: Missing or invalid. approve(), namespaces should be an object with data

This error means that the namespaces parameter passed to approveSession is either missing or invalid. Please check that you are passing a valid namespaces object that satisfies all required properties.

  • Non conforming namespaces. approve() namespaces <property> don't satisfy required namespaces.

This error indicates that some value(s) in your namespaces object do not satisfy the required namespaces requested by the dapp. To provide additional guidance, the message might include info about the exact property that is missing or invalid e.g. Required: eip155:1 Approved: eip155:137. Please check CAIP-25 to familiarize yourself with the standard and it's nuances. Additionally, we highly recommend you to use our namespace builder utility that would greatly simplify the process of parsing & building a valid namespaces object.

 Web3Wallet.instance.approve(
    proposalId: "proposal_id",
    namespaces: sessionNamespaces
)

When session is successfully approved sessionsPublishers will publish a Session

Web3Wallet.instance.sessionsPublishers
    .receive(on: DispatchQueue.main)
    .sink { [weak self] _ in
        self?.reloadSessions()
    }.store(in: &publishers)

Session object represents an active session connection with a dapp. It contains dapp’s metadata (that you may want to use for displaying an active session to the user), namespaces, and expiry date. There is also a topic property that you will use for linking requests with related sessions.

You can always query settled sessions from the client later with:

Web3Wallet.instance.getSessions()

Connect Clients

Your Wallet should allow users to scan a QR code generated by dapps. You are responsible for implementing it on your own. For testing, you can use our test dapp at: https://react-app.walletconnect.com/, which is v2 protocol compliant. Once you derive a URI from the QR code call pair method:

try await Web3Wallet.instance.pair(uri: uri)

if everything goes well, you should handle following event:

Web3Wallet.instance.sessionProposalPublisher
    .receive(on: DispatchQueue.main)
    .sink { [weak self] session in
        self?.verifyDapp(session.context)
        self?.showSessionProposal(session.proposal)
    }.store(in: &publishers)

Session proposal is a handshake sent by a dapp and it's purpose is to define a session rules. Handshake procedure is defined by CAIP-25. Session.Proposal object conveys set of required and optional ProposalNamespaces that contains blockchains methods and events. Dapp requests with methods and wallet will emit events defined in namespaces.

VerifyContext provides a domain verification information about Session.Proposal and Request. It consists of origin of a Dapp from where the request has been sent, validation enum that says whether origin is unknown, valid or invalid and verify URL server.

To enable or disable verification find the Verify SDK toggle in your project cloud.

public struct VerifyContext: Equatable, Hashable {
   public enum ValidationStatus {
       case unknown
       case valid
       case invalid
   }

   public let origin: String?
   public let validation: ValidationStatus
   public let verifyUrl: String
}

The user will either approve the session proposal (with session namespaces) or reject it. Session namespaces must at least contain requested methods, events and accounts associated with proposed blockchains.

Accounts must be provided according to CAIP10 specification and be prefixed with a chain identifier. chain_id + : + account_address. You can find more on blockchain identifiers in CAIP2. Our Account type meets the criteria.

let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!

Accounts sent in session approval must at least match all requested blockchains.

Example proposal namespaces request:

{
  "eip155": {
    "chains": ["eip155:137", "eip155:1"],
    "methods": ["eth_sign"],
    "events": ["accountsChanged"]
  },
  "cosmos": {
    "chains": ["cosmos:cosmoshub-4"],
    "methods": ["cosmos_signDirect"],
    "events": ["someCosmosEvent"]
  }
}

Example session namespaces response:

{
  "eip155": {
    "accounts": [
      "eip155:137:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb",
      "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"
    ],
    "methods": ["eth_sign"],
    "events": ["accountsChanged"]
  },
  "cosmos": {
    "accounts": ["cosmos:cosmoshub-4:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0"],
    "methods": ["cosmos_signDirect", "personal_sign"],
    "events": ["someCosmosEvent", "proofFinalized"]
  }
}

Track Sessions

When your Web3Wallet instance receives requests from a peer it will publish a related event. Set a subscription to handle them.

To track sessions subscribe to sessionsPublisher publisher

Web3Wallet.instance.sessionsPublisher
    .receive(on: DispatchQueue.main)
    .sink { [weak self] sessions in
        // Reload UI
    }.store(in: &publishers)

:::info Note

Addresses provided in accounts array should follow CAIP-10 semantics.

:::

val proposerPublicKey: String = /*Proposer publicKey from SessionProposal object*/
val namespace: String = /*Namespace identifier, see for reference: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md#syntax*/
val accounts: List<String> = /*List of accounts on chains*/
val methods: List<String> = /*List of methods that wallet approves*/
val events: List<String> = /*List of events that wallet approves*/
val namespaces: Map<String, Wallet.Model.Namespaces.Session> = mapOf(namespace, Wallet.Model.Namespaces.Session(accounts, methods, events))

val approveParams: Wallet.Params.SessionApprove = Wallet.Params.SessionApprove(proposerPublicKey, namespaces)
Web3Wallet.approveSession(approveParams) { error -> /*callback for error while approving a session*/ }

To send an approval, pass a Proposer's Public Key along with the map of namespaces to the Web3Wallet.approveSession function.

As mentioned before, the SessionProposalEvent is emitted when a dapp initiates a new session with your wallet. The event object will include the information about the dapp and requested namespaces. The wallet should display a prompt for the user to approve or reject the session.

To approve a session, call approveSession() and pass in the event.id and your approved namespaces.

  • If you decide to use the registerRequestHandler() method to register handlers for supported methods, as explained in previous section, you would use the generatedNamespaces object in the approveSession
  • If you decide to handle session requests by subscribing to the onSessionRequest event, you would need to pass your own set of approved namespaces.

Either way you decide you would subscribe to the onSessionProposal event and use approveSession() as follows:

web3Wallet.onSessionProposal.subscribe((SessionProposalEvent? event) {
  // display a prompt for the user to approve or reject the session
  // ....
  // If approved
  web3Wallet.approveSession(
    id: event.id,
    namespaces: // event.params.generatedNamespaces! or approvedNamespaces,
  );
});

Pairing

The pair method initiates a WalletConnect pairing process with a dapp using the given uri (QR code from the dapps). To learn more about pairing, checkout out the docs.

Scan the QR code and parse the URI, and pair with the dapp.
Upon the first pairing, you will immediately receive onSessionProposal and onAuthRequest events.

Uri uri = Uri.parse(scannedUriString);
await web3Wallet.pair(uri: uri);

In order to connect with a dapp, you will need to receive a WalletConnect URI (WCURI) and this will talk to our protocol to facilitate a pairing session. Therefore, you will need a test dapp in order to communicate with the wallet. We recommend testing with our React V2 Dapp as this is the most up-to-date development site.

In order to capture the WCURI, recommend having some sort of state management you will pass through a TextInput or QRcode instance.

The session_proposal event is emitted when a dapp initiates a new session with a user's wallet. The event will include a proposal object with information about the dapp and requested permissions. The wallet should display a prompt for the user to approve or reject the session. If approved, call approveSession and pass in the proposal.id and requested namespaces.

The pair method initiates a WalletConnect pairing process with a dapp using the given uri (QR code from the dapps). To learn more about pairing, checkout out the docs.

import { getSdkError } from '@walletconnect/utils'

// Approval: Using this listener for sessionProposal, you can accept the session
web3wallet.on('session_proposal', async proposal => {
  const session = await web3wallet.approveSession({
    id: proposal.id,
    namespaces
  })
})

// Call this after WCURI is received
await web3wallet.pair({ wcuri })

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

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}>

In the event you want to reject the session proposal, call the rejectSession method. The getSDKError function comes from the @walletconnect/utils library.

web3wallet.on('session_proposal', async proposal => {
  await web3wallet.rejectSession({
    id: proposal.id,
    reason: getSdkError('USER_REJECTED_METHODS')
  })
})

🛠️ Usage examples

⚠️ Expected Errors

  • No matching key. proposal id doesn't exist: 1

This rejection means the SDK can't find a record with the given proposal.id - in this example 1. This can happen when the proposal has expired (by default 5 minutes) or if you attempt to respond to a proposal that has already been approved/rejected. If you are seeing this error, please make sure that you are calling rejectSession with the correct proposal.id that is available within the proposal payload.

  • Error: Missing or invalid. reject() reason:

This rejection means the reason parameter passed to rejectSession is either missing or invalid. We recommend using the getSDKError function from the @walletconnect/utils library that will populate & format the parameter for you.

try await Web3Wallet.instance.reject(requestId: request.id)

val proposerPublicKey: String = /*Proposer publicKey from SessionProposal object*/
val rejectionReason: String = /*The reason for rejecting the Session Proposal*/
val rejectionCode: String = /*The code for rejecting the Session Proposal*/
For reference use CAIP-25: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-25.md

val rejectParams: Wallet.Params.SessionReject = SessionReject(proposerPublicKey, rejectionReason, rejectionCode)
Web3Wallet.rejectSession(rejectParams) { error -> /*callback for error while rejecting a session*/ }

To send a rejection for the Session Proposal, pass a proposerPublicKey, rejection reason and rejection code to the Web3Wallet.rejectSession function.

To reject the request, pass in an error code and reason. They can be found here.

To reject a session:

web3Wallet.onSessionProposal.subscribe((SessionProposalEvent? event) {
  // display a prompt for the user to approve or reject the session
  // ....
  // If rejected
  web3Wallet.rejectSession(
    id: event.id,
    reason: Errors.getSdkError(Errors.USER_REJECTED),
  );
});

You can use the getSDKError function, which is available in the @walletconnect/utils for the rejection function library.

import { getSdkError } from '@walletconnect/utils'

// Reject: Using this listener for sessionProposal, you can reject the session
web3wallet.on('session_proposal', async proposal => {
  await web3wallet.rejectSession({
    id: proposal.id,
    reason: getSdkError('USER_REJECTED_METHODS')
  })
})

// Call this after WCURI is received
await web3wallet.core.pairing.pair({ wcuri })

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

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}>

The session_request event is emitted when the SDK received a request from the peer and it needs the wallet to perform a specific action, such as signing a transaction. The event contains a topic and a request object, which will vary depending on the action requested.

To respond to the request, you can access the topic and request object by destructuring them from the event payload. To see a list of possible request and response objects, refer to the relevant JSON-RPC Methods for Ethereum, Solana, Cosmos, or Stellar.

As an example, if the dapp requests a personal_sign method, you can extract the params array from the request object. The first item in the array is the hex version of the message to be signed, which can be converted to UTF-8 and assigned to a message variable. The second item in params is the user's wallet address.

To sign the message, you can use your wallet's signMessage method and pass in the message. The signed message, along with the id from the event payload, can then be used to create a response object, which can be passed into respondSessionRequest.

web3wallet.on('session_request', async event => {
  const { topic, params, id } = event
  const { request } = params
  const requestParamsMessage = request.params[0]

  // convert `requestParamsMessage` by using a method like hexToUtf8
  const message = hexToUtf8(requestParamsMessage)

  // sign the message
  const signedMessage = await wallet.signMessage(message)

  const response = { id, result: signedMessage, jsonrpc: '2.0' }

  await web3wallet.respondSessionRequest({ topic, response })
})

To reject a session request, the response should be similar to this.

const response = {
  id,
  jsonrpc: '2.0',
  error: {
    code: 5000,
    message: 'User rejected.'
  }
}

🛠️ Usage examples

⚠️ Expected Errors

  • Error: No matching key. session topic doesn't exist: 'xyz...'

This rejection means the SDK can't find a session with the given topic - in this example xyz.... This can happen when the session has been disconnected by either the wallet or the dapp while the session request was being processed or if a session with such topic doesn't exist. If you are seeing this error, please make sure that you are using a correct topic that is available within the request payload.

  • Error: Missing or invalid. respond() response:

This rejection means the response parameter passed to respondSessionRequest is either missing or invalid. The response should be a valid JSON-RPC 2.0 response object. We recommend you to use our formatJsonRpcResult utility from "@walletconnect/jsonrpc-utils" that will format the response for you.

Example usage: id argument being the request id from the request payload.

import { formatJsonRpcResult } from '@walletconnect/jsonrpc-utils'

const signature = await cryptoWallet.signTransaction(signTransaction)
const response = await web3wallet.respondSessionRequest({
  topic: session.topic,
  response: formatJsonRpcResult(id, signature)
})

After the session is established, a dapp will request your wallet's users to sign a transaction or a message. Requests will be delivered by the following publisher.

Web3Wallet.instance.sessionRequestPublisher
  .receive(on: DispatchQueue.main)
  .sink { [weak self] session in
      self?.verifyDapp(session.context)
      self?.showSessionRequest(session.request)
  }.store(in: &publishers)

When a wallet receives a session request, you probably want to show it to the user. It’s method will be in scope of session namespaces. And it’s params are represented by AnyCodable type. An expected object can be derived as follows:

if sessionRequest.method == "personal_sign" {
    let params = try! sessionRequest.params.get([String].self)
} else if method == "eth_signTypedData" {
    let params = try! sessionRequest.params.get([String].self)
} else if method == "eth_sendTransaction" {
    let params = try! sessionRequest.params.get([EthereumTransaction].self)
}

Now, your wallet (as it owns your user’s private keys) is responsible for signing the transaction. After doing it, you can send a response to a dapp.

let response: AnyCodable = sign(request: sessionRequest) // Implement your signing method
try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .response(response))
val sessionTopic: String = /*Topic of Session*/
val jsonRpcResponse: Wallet.Model.JsonRpcResponse.JsonRpcResult = /*Active Session Request ID along with request data*/
val result = Wallet.Params.SessionRequestResponse(sessionTopic = sessionTopic, jsonRpcResponse = jsonRpcResponse)

Web3Wallet.respondSessionRequest(result) { error -> /*callback for error while responding session request*/ }

To respond to JSON-RPC method that were sent from Dapps for a session, submit a Wallet.Params.SessionRequestResponse with the session's topic and request ID along with the respond data to the Web3Wallet.respondSessionRequest function.

To handle a session request, such as personal_sign, you have two ways and they are mutually exclusive, so, you use either one way or the other:

  1. The default one is subscribing to onSessionRequest events and handle the request based on the method that is firing the event
web3Wallet.onSessionRequest.subscribe(_onSessionRequest);

void _onSessionRequest(SessionRequestEvent? event) async {
  if (event != null) {
    final id = event.id;
    final topic = event.topic;
    final method = event.method;
    final chainId = event.chainId;
    final params = event.params as List;

    // message should arrive encoded
    final decoded = hex.decode(params.first.substring(2));
    final message = utf8.decode(decoded);

    // display a prompt for the user to approve or reject the request
    // if approved
    if (approved) {
      // Your code to sign the message here
      final signature = ...

      return web3Wallet.respondSessionRequest(
        topic: topic,
        response: JsonRpcResponse(
          id: id,
          jsonrpc: '2.0',
          result: signature,
        ),
      );
    }
    // if rejected
    return web3Wallet.respondSessionRequest(
      topic: topic,
      response: JsonRpcResponse(
        id: id,
        jsonrpc: '2.0',
        error: const JsonRpcError(code: 5001, message: 'User rejected method'),
      ),
    );
  }
}
  1. The second way is to register a request handler for the methods and chains you want to support. So let's say your wallet supports eip155:1 and eip155:137. This would translate to:
final supportedChains = ['eip155:1', 'eip155:137'];
Map<String, dynamic Function(String, dynamic)> supportedMethods = {
  'personal_sign': _personalSignHandler,
  'eth_sendTransaction': _ethSendTransactionHandler,
};
for (var chainId in supportedChains) {
  for (var method in supportedMethods.entries) {
    web3Wallet.registerRequestHandler(
      chainId: chainId,
      method: method.key,
      handler: method.value,
    );
  }
}

Future<void> _personalSignHandler(String topic, dynamic params) async {
  final id = web3Wallet.pendingRequests.getAll().first;

  // message should arrive encoded
  final decoded = hex.decode(params.first.substring(2));
  final message = utf8.decode(decoded);

  // display a prompt for the user to approve or reject the request
  // if approved
  if (approved) {
    // Your code to sign the message here
    final signature = ...

    return web3Wallet.respondSessionRequest(
      topic: topic,
      response: JsonRpcResponse(
        id: id,
        jsonrpc: '2.0',
        result: signature,
      ),
    );
  }
  // if rejected
  return web3Wallet.respondSessionRequest(
    topic: topic,
    response: JsonRpcResponse(
      id: id,
      jsonrpc: '2.0',
      error: const JsonRpcError(code: 5001, message: 'User rejected method'),
    ),
  );
}

Future<void> _ethSendTransactionHandler(String topic, dynamic params) async {
  // ...
}

Once you have your handlers registered, this are going to be triggered INSTEAD OF the onSessionRequest event.

The main difference between these 2 ways of handling session requests is that the default one, using onSessionRequest events, carry more useful information such as the request id and the chain id which are not carried when using the reqeust handlers registration.

session-request-example

The session_request event is triggered by a dapp when it needs the wallet to perform a specific action, such as signing a transaction. The event contains a topic and a request object, which will vary depending on the action requested.

To respond to the request, the wallet can access the topic and request object by destructuring them from the event payload. To see a list of possible request and response objects, refer to the relevant JSON-RPC Methods for Ethereum, Solana, Cosmos, or Stellar.

As an example, if the dapp requests a personal_sign method, the wallet can extract the params array from the request object. The first item in the array is the hex version of the message to be signed, which can be converted to UTF-8 and assigned to a message variable. The second item in params is the user's wallet address.

To sign the message, the wallet can use the wallet.signMessage method and pass in the message. The signed message, along with the id from the event payload, can then be used to create a response object, which can be passed into respondSessionRequest.

The wallet then signs the message. signedMessage, along with the id from the event payload, can then be used to create a response object, which can be passed into respondSessionRequest.

web3wallet.on('session_request', async event => {
  const { topic, params, id } = event
  const { request } = params
  const requestParamsMessage = request.params[0]

  // convert `requestParamsMessage` by using a method like hexToUtf8
  const message = hexToUtf8(requestParamsMessage)

  // sign the message
  const signedMessage = await wallet.signMessage(message)

  const response = { id, result: signedMessage, jsonrpc: '2.0' }

  await web3wallet.respondSessionRequest({ topic, response })
})

To reject a session request, the response should be similar to this.

const response = {
  id,
  jsonrpc: '2.0',
  error: {
    code: 5000,
    message: 'User rejected.'
  }
}

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, 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 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

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}>

If you wish to include new accounts or chains or methods in an existing session, updateSession allows you to do so. You need pass in the topic and a new Namespaces object that contains all of the existing namespaces as well as the new data you wish to include. After you update the session, the other peer will receive a session_update event.

An example adding a new account to an existing session:

const namespaces = session.namespaces
const accounts = [
  'eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb',
  'eip155:1:0x1234567890123456789012345678901234567890'
]
const updatedNamespaces = {
  ...namespaces,
  eip155: {
    ...namespaces.eip155,
    accounts
  }
}
await web3wallet.updateSession({ topic: session.topic, namespaces: updatedNamespaces })

An example adding a new chain to an existing session:

const namespaces = session.namespaces
const chains = ['eip155:1', 'eip155:137']
const accounts = [
  'eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb',
  'eip155:137:0x1234567890123456789012345678901234567890'
]
const updatedNamespaces = {
  ...namespaces,
  eip155: {
    ...namespaces.eip155,
    accounts,
    chains
  }
}
await web3wallet.updateSession({ topic: session.topic, namespaces: updatedNamespaces })

🛠️ Usage examples

⚠️ Expected Errors

Note that all namespaces validation applies and you still have to satisfy the required namespaces requested by the dapp.

  • Error: No matching key. session topic doesn't exist: 'xyz...'

This rejection means the SDK can't find a session with the given topic - in this example xyz.... This can happen when the session you're trying to update has already been disconnected by either the wallet or the dapp or if a session with such topic doesn't exist. If you are seeing this error, please make sure that you are using a correct topic of an active session.

  • Error: Missing or invalid. update(), namespaces should be an object with data

This error means that the namespaces parameter passed to updateSession is either missing or invalid. Please check that you are passing a valid namespaces object that satisfies all required properties.

  • Non conforming namespaces. update() namespaces <property> don't satisfy required namespaces.

This error indicates that some value(s) in your namespaces object do not satisfy the required namespaces requested by the dapp. To provide additional guidance, the message might include info about the exact property that is missing or invalid e.g. Required: eip155:1 Approved: eip155:137. Please check CAIP-25 to familiarize yourself with the standard and it's nuances. Additionally, we highly recommend you to use our namespace builder utility that would greatly simplify the process of parsing & building a valid namespaces object.

If you want to update user session's chains, accounts, methods or events you can use session update method.

try await Web3Wallet.instance.update(topic: session.topic, namespaces: newNamespaces)

NOTE: addresses provided in accounts array should follow CAIP10 semantics.

val sessionTopic: String = /*Topic of Session*/
val namespace: String = /*Namespace identifier, see for reference: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md#syntax*/
val accounts: List<String> = /*List of accounts on chains*/
val methods: List<String> = /*List of methods that wallet approves*/
val events: List<String> = /*List of events that wallet approves*/
val namespaces: Map<String, Wallet.Model.Namespaces.Session> = mapOf(namespace, Wallet.Model.Namespaces.Session(accounts, methods, events))
val updateParams = Wallet.Params.SessionUpdate(sessionTopic, namespaces)

Web3Wallet.updateSession(updateParams) { error -> /*callback for error while sending session update*/ }

To update a session with namespaces, submit a Wallet.Params.SessionUpdate object with the session's topic and namespaces to update session with to Web3Wallet.updateSession.

If you wish to include new accounts or chains or methods in an existing session, updateSession allows you to do so. You need pass in the topic and a new Namespaces object that contains all of the existing namespaces as well as the new data you wish to include.

After you update the session, the dapp connected to your wallet will receive a SessionUpdate event.

await web3Wallet.updateSession(topic: 'topic', namespaces: '{}')

The session_update event is emitted from the wallet when the session is updated by calling updateSession. To update a session, pass in the topic and the new namespace.

await web3wallet.updateSession({ topic, namespaces: newNs })

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

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}>

Sessions have a default expiry of 7 days. To extend a session by an additional 7 days, call .extendSession method and pass in the topic of the session you wish to extend.

await web3wallet.extendSession({ topic })

🛠️ Usage examples

⚠️ Expected Errors

  • Error: No matching key. session topic doesn't exist: 'xyz...'

This rejection means the SDK can't find a session with the given topic - in this example xyz.... This can happen when the session you're trying to update has already been disconnected by either the wallet or the dapp or if a session with such topic doesn't exist. If you are seeing this error, please make sure that you are using a correct topic of an active 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 Web3Wallet.instance.extend(topic: session.topic)

Above method will extend a user's session to a week.

val sessionTopic: String = /*Topic of Session*/
val extendParams = Wallet.Params.SessionExtend(sessionTopic = sessionTopic)

Web3Wallet.extendSession(extendParams) { error -> /*callback for error while extending a session*/ }

To extend a session, create a Wallet.Params.SessionExtend object with the session's topic to update the session with to Web3Wallet.extendSession. Session is extended by 7 days.

To extend the session, call the extendSession method and pass in the new topic. The SessionUpdate event will be emitted from the wallet.

await web3Wallet.extendSession(topic: 'topic')

To extend the session, call the extendSession method and pass in the new topic. The session_update event will be emitted from the wallet.

await web3wallet.extendSession({ topic })

Extend a session's expiry time so the session remains open

var request = await walletClient.Extend(sessionTopic);
await request.Acknowledged();

Session Disconnect

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native","csharp"]}>

To initiate disconnect from a session(think session delete), call .disconnectSession by passing a topic & reason for the disconnect. The other peer will receive a session_delete and be notified that the session has been disconnected.

:::info Note It's important that you're subscribed to the session_delete event as well, to be notified when the other peer initiates a disconnect. ::: :::note Tip We recommend using the getSDKError utility function, that will provide ready-to-use reason payloads and is available in the @walletconnect/utils library. :::

await web3wallet.disconnectSession({
  topic,
  reason: getSdkError('USER_DISCONNECTED')
})

🛠️ Usage examples

⚠️ Expected Errors

  • Error: No matching key. session topic doesn't exist: 'xyz...'

This rejection means the SDK can't find a session with the given topic - in this example xyz.... This can happen when the session you're trying to update has already been disconnected by either the wallet or the dapp or if a session with such topic doesn't exist. If you are seeing this error, please make sure that you are using a correct topic of an active session.

For good user experience your wallet should allow users to disconnect unwanted sessions. In order to terminate a session use disconnect method.

try await Web3Wallet.instance.disconnect(topic: session.topic)
val disconnectionReason: String = /*The reason for disconnecting the Session*/
val disconnectionCode: String = /*The code for disconnecting the Session*/
val sessionTopic: String = /*Topic from the Session*/
For reference use CAIP-25: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-25.md
val disconnectParams = Wallet.Params.SessionDisconnect(sessionTopic, disconnectionReason, disconnectionCode)

Web3Wallet.disconnectSession(disconnectParams) { error -> /*callback for error while disconnecting a session*/ }

To disconnect from un active session, pass a disconnection reason with code and the Session topic to the Web3Wallet.disconnectSession function.

To initiate a session disconnect, call the disconnectSession method and pass in the topic and a reason.

When either the dapp or the wallet disconnects from a session, a SessionDelete event will be emitted. It's important to subscribe to this event so you could keep your state up-to-date.

await web3Wallet.disconnectSession(
  topic: session.topic,
  reason: Errors.getSdkError(Errors.USER_DISCONNECTED),
);

Using disconnectSession() alone will make the pairing topic persist, i.e, it can be re-used until it expires. If you want to disconnect (remove) the pairing topic as well you would have add another call as follows:

await web3Wallet.core.pairing.disconnect(
  topic: pairing.topic,
);

When either the dapp or the wallet disconnects from a session, a session_delete event will be emitted. It's important to subscribe to this event so you could keep your state up-to-date.

To initiate a session disconnect, call the disconnectSession method and pass in the topic and reason. You can use the getSDKError utility function, which is available in the @walletconnect/utils library.

await web3wallet.disconnectSession({
  topic,
  reason: getSdkError('USER_DISCONNECTED')
})

Disconnecting

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));

Extra (Platform Specific)

<PlatformTabs groupId="w3w" activeOptions={["web","ios","android","flutter","react-native"]}>

Emitting Session Events

To emit session events, call the emitSessionEvent and pass in the params. If you wish to switch to chain/account that is not approved (missing from session.namespaces) you will have to update the session first. In the following example, the wallet will emit session_event that will instruct the dapp to switch the active accounts.

await web3wallet.emitSessionEvent({
  topic,
  event: {
    name: 'accountsChanged',
    data: ['0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb']
  },
  chainId: 'eip155:1'
})

In the following example, the wallet will emit session_event when the wallet switches chains.

await web3wallet.emitSessionEvent({
  topic,
  event: {
    name: 'chainChanged',
    data: 1
  },
  chainId: 'eip155:1'
})

Subscribe for Web3Wallet Publishers

The following publishers are available to subscribe:

public var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never>
public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never>
public var authRequestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never>
public var sessionPublisher: AnyPublisher<[Session], 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>

Register Device Token

To register a wallet to receive WalletConnect push notifications, call register method and pass the device token received from the didRegisterForRemoteNotificationsWithDeviceToken method in the AppDelegate.

Web3Wallet.instance.register(deviceToken: deviceToken, enableEncrypted: true)

Web3Wallet.WalletDelegate

val walletDelegate = object : Web3Wallet.WalletDelegate {
    override fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal, verifyContext: Wallet.Model.VerifyContext) {
        // Triggered when wallet receives the session proposal sent by a Dapp
    }

    fun onSessionAuthenticate(sessionAuthenticate: Wallet.Model.SessionAuthenticate, verifyContext: Wallet.Model.VerifyContext) {
      // Triggered when wallet receives the session authenticate sent by a Dapp
    }

    override fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest, verifyContext: Wallet.Model.VerifyContext) {
        // Triggered when a Dapp sends SessionRequest to sign a transaction or a message
    }

    override fun onAuthRequest(authRequest: Wallet.Model.AuthRequest, verifyContext: Wallet.Model.VerifyContext) {
        // Triggered when Dapp / Requester makes an authorization request
    }

    override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
        // Triggered when the session is deleted by the peer
    }

    override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
        // Triggered when wallet receives the session settlement response from Dapp
    }

    override fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) {
        // Triggered when wallet receives the session update response from Dapp
    }

    override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) {
        //Triggered whenever the connection state is changed
    }

    override fun onError(error: Wallet.Model.Error) {
        // Triggered whenever there is an issue inside the SDK
    }
}
Web3Wallet.setWalletDelegate(walletDelegate)

Wallet.Event.VerifyContext provides a domain verification information about SessionProposal, SessionRequest and AuthRequest. It consists of origin of a Dapp from where the request has been sent, validation Enum that says whether origin is VALID, INVALID or UNKNOWN and verify url server.

data class VerifyContext(
    val id: Long,
    val origin: String,
    val validation: Model.Validation,
    val verifyUrl: String
)

enum class Validation {
    VALID, INVALID, UNKNOWN
}

The Web3Wallet needs a Web3Wallet.WalletDelegate passed to it for it to be able to expose asynchronous updates sent from the Dapp.

Format message

To receive formatted SIWE message, call formatMessage method with following parameters:

val payloadParams: Wallet.Params.PayloadParams = //PayloadParams received in the onAuthRequest callback
val issuer = //MUST be the same as send with the respond methods and follows: https://github.com/w3c-ccg/did-pkh/blob/main/did-pkh-method-draft.md
val formatMessage = Wallet.Params.FormatMessage(event.payloadParams, issuer)

Web3Wallet.formatMessage(formatMessage)

Register Device Token

This method enables wallets to receive push notifications from WalletConnect's Push Server via Firebase Cloud Messaging. This means you will have to setup your project with Firebase before being able to call registerDeviceToken method.

Make sure that a service extending the FirebaseMessagingService is added to your manifest as per the Firebase FCM documentation as well as any other setup Firebase requires Firebase setup documentation.

To register a wallet to receive WalletConnect push notifications, call Web3Wallet.registerDeviceToken and pass the Firebase Access Token.

val firebaseAccessToken: String = //FCM access token received through the Firebase Messaging SDK

Web3Wallet.registerDeviceToken(
    firebaseAccessToken,
    onSuccess = {
        // callback triggered once registered successfully with the Push Server
    },
    onError = { error: Wallet.Model.Error ->
        // callback triggered if there's an exception thrown during the registration process
    })

Supporting session events

In order to support session events, such as chainChanged or accountChanged, you would have to to register an event emitter for such events, for every chain you want to emit an event for (similar to request handlers).

final supportedChains = ['eip155:1', 'eip155:137'];
const supportedEvents = ['chainChanged', 'accountChanged'];
for (var chainId in supportedChains) {
  for (var event in supportedEvents) {
    _web3Wallet.registerEventEmitter(
      chainId: chainId,
      event: event,
    );
  }
}

And to emit an event, call emitSessionEvent() as follows:

await web3wallet.emitSessionEvent(
  topic: session.topic,
  chainId: 'eip155:1',
  event: SessionEventParams(
    name: 'chainChanged',
    data: 1,
  ),
);

For a better understanding please check out the example wallet and, in particular, the EVMService inside of it.

Emitting Session Events

To emit session events, call the emitSessionEvent and pass in the params. If you wish to switch to chain/account that is not approved (missing from session.namespaces) you will have to update the session first. In the following example, the wallet will emit session_event that will instruct the dapp to switch the active accounts.

await web3wallet.emitSessionEvent({
  topic,
  event: {
    name: 'accountsChanged',
    data: ['0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb']
  },
  chainId: 'eip155:1'
})

In the following example, the wallet will emit session_event when the wallet switches chains.

await web3wallet.emitSessionEvent({
  topic,
  event: {
    name: 'chainChanged',
    data: 1
  },
  chainId: 'eip155:1'
})