Skip to main content


Swift implementation of WalletConnect v.2 protocol for native iOS applications.

Getting started with wallet integration

Set up a project

In order to connect to WalletConnect Cloud, you need to create a new project first:

  1. Go to
  2. Tap New Project
  3. Give it a name and tap Create button
  4. Your new project should appear on the projects list
  5. You should see a project ID string if you tap on your project. for more info on Project ID look at Project ID.

Add SDK for your project.

You can add a WalletConnect SDK to your project with Swift Package Manager. In order to do that:

  1. Open XCode
  2. Go to File -> Add Packages
  3. Paste the repo GitHub url:
  4. Tap Add Package

Instantiate a client

Create an AppMetadata object first. It will describe your application and define its appearance in a web browser. Then configure Sign instance with a metadata object you have instantiated and set a project ID generated when starting a project on WalletConnect Cloud.

Note that you want to have only one instance of a client in your app, and you don’t want to deinitialize that instance.

let metadata = AppMetadata(name: <String>,
description: <String>,
url: <String>,
icons: <[String]>)

Sign.configure(Sign.Config(metadata: <AppMetadata>, projectId: <String>))

Subscribe for Sign publishers

When your Sign instance receives requests from a peer it will publish related event. So you should set subscription to handle them.

.receive(on: DispatchQueue.main)
.sink { [unowned self] _ in
//handle event
}.store(in: &publishers)

Following publishers are available to subscribe:

    public var sessionProposalPublisher: AnyPublisher<Session.Proposal, Never> 
public var sessionRequestPublisher: AnyPublisher<Request, Never>
public var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>
public var sessionSettlePublisher: AnyPublisher<Session, Never>
public var sessionDeletePublisher: AnyPublisher<(String, Reason), Never>
public var sessionResponsePublisher: AnyPublisher<Response, Never>
public var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never>
public var sessionUpdatePublisher: AnyPublisher<(sessionTopic: String, namespaces: [String : SessionNamespace]), Never>
public var sessionEventPublisher: AnyPublisher<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never>
public var sessionUpdateExpiryPublisher: AnyPublisher<(sessionTopic: String, expiry: Date), Never>

Connect Clients

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:, which is v2 protocol compliant. Once you derive a URI from the QR code call pair method:

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

if everything goes well, you should handle following event:

.receive(on: DispatchQueue.main)
.sink { [weak self] sessionProposal in
// present proposal to the user
}.store(in: &publishers)

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

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:

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

Example session namespaces response:

"accounts": ["eip155:137:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb", "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"],
"methods": ["eth_sign"],
"events": ["accountsChanged"]
"accounts": ["cosmos:cosmoshub-4:cosmos1t2uflqwqe0fsj0shcfkrvpukewcw40yjj6hdc0"],
"methods": ["cosmos_signDirect", "personal_sign"],
"events": ["someCosmosEvent", "proofFinalized"]
Approve Session
 Sign.instance.approve(proposalId: "proposal_id", namespaces: [String: SessionNamespace])

When session is sucessfully approved sessionSettlePublisher will publish a Session

.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
}.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:


Handle requests from dapp

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.

.receive(on: DispatchQueue.main)
.sink { [weak self] sessionRequest in
}.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 privete keys) is responsible for signing the transaction. After doing it, you can send a response to a dapp.

let result = sign(request: sessionRequest) // implement your signing method
let response = JSONRPCResponse<AnyCodable>(id:, result: result)
Sign.instance.respond(topic: sessionRequest.topic, response: .response(response))

Update Session

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

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

Extend Session

By default, session lifetime is set for 7 days and after that time user's session will expire. But if you consider that a session should be extended you can call:

try await Sign.instance.extend(topic: session.topic)

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

Disconnect 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 Sign.instance.disconnect(topic: session.topic, reason: reason)

Web Socket Connection

By default web socket connection is handled internally by the SDK. That means that Web socket will be safely disconnected when apps go to background and it will connect back when app reaches foreground. But if it is not expeted for your app and you want to handle socket connection manually you can do it as follows:

  1. set socketConnectionType in an Sign instance config for manual
Sign.Config(metadata: metadata, projectId: "", socketConnectionType: .manual)
  1. control socket connection:

Where to go from here

  • Try our example wallet implementation that is part of WalletConnectSwiftV2 repository.
  • To dive deeper into protocol concepts check out our documentation
  • Build API documentation in XCode: go to Product -> Build Documentation