wallet connect 是设计给Dapp 与 Wallet App 交互的协议。
本文主要介绍集成v1 版本与v2 版本的一些差异,还有https://github.com/WalletConnect/WalletConnectSwiftV2迁移v2 的方案。
概要
接入swift wallet connect 主要是与Wallet App 进行连接,签名交易。
下图是使用Wallet connect 协议调用钱包App 进行签名交易的交互流程。
Config
在接入SDK 进入项目之后,我们要理解使用 wallet connect swift 协议通过钱包签名交易的流程是会通过 WalletConnect 的Relay Server。意味着我们不是直接与钱包App 建立连接,而且是通过中继服务器建立连接。
所以在使用SDK 的第一步就是如何建立与Relay Server的连接,这需要我们在连接前完成配置。v1 与 v2 版本的配置方式有不同的重点,下面我们各自介绍一下不同与相似之处。
- v1 Config
v1 版本配置会简单一些,主要包括用于 展示的 MetaInfo。这个信息会被钱包App 接受并使用。这通常作为在钱包App连接页面展示的所需要的信息,所以下面代码中的config 就需要对应自己的Dapp做自定义了。
let clientMeta = Session.ClientMeta(name: "ExampleDApp",
description: "WalletConnectSwift",
icons: [],
url: URL(string: "you url"))
let dAppInfo = Session.DAppInfo(peerId: UUID().uuidString, peerMeta: clientMeta)
client = Client(delegate: self, dAppInfo: dAppInfo)
之前我们有说过Relay Server,这是我们配置的重点,我们使用Wallet Connect连接与发送请求需要通过中继服务器转发。而在v1 版本中,配置Relay Server 是在建立连接前进行配置。
let wcUrl = WCURL(topic: UUID().uuidString,
bridgeURL: URL(string: "https://safe-walletconnect.gnosis.io/"),
key: randomKey())
其中bridgeURL
参数配置的url,就是Relay Server 的地址。
- v2 Config
v2 的 wallet connect swift 将Relay Server 细节隐藏了起来,使用 Networking
类代替了v1中WCURL的配置,同时,v2 要求一个projectId
这需要去他们的网站上申请。
Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory())
其中 socketFactory
是配置网络中具体使用的 websocket 实例的地方。这里可以使用 Starscream
或者 URLSession
Dapp 的meta info,在v2 是在Pair
这里配置
let metadata = AppMetadata(name: <String>,
description: <String>,
url: <String>,
icons: <[String]>)
Pair.configure(metadata: metadata)
和v1一样,这些信息主要用于在Wallet App 上的展示。
连接
向钱包签名交易前,我们需要通过Wallet Connect 协议与钱包App 建立连接,连接一旦建立,意味着Dapp与Wallet App 拥有会话关系,其中的Session
是我们需要特别关注的东西。
- v1
连接过程是先创建好WCURL
, 然后向中转层发起连接的请求,最后通过Deeplink
或者Universal Links
的方式去把WCURL 携带在跳转Link的参数里面跳转到目标的 Wallet App。也可以把 WCURL
生成QRCode 然后通过Wallet App 去扫码连接。
// 创建连接
let wcUrl = WCURL(topic: UUID().uuidString,
bridgeURL: bridgeURL,
key: randomKey)
//发起 Connect
do {
try client.connect(to: wcUrl)
} catch {
callback?(.failed(error))
}
//跳转Wallet App
let deepLink = self.getDeepLink(wallet: type, wcURL: wcUrl)
guard let url = URL(string: deepLink), UIApplication.shared.canOpenURL(url) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
- v2
v2 的过程与上面相似,功能会更加复杂和强大,但是我们现在还不需要关心这些。
// namespaces 定义了连接需要的信息,这些是v2协议的特性,可以支持多链多账号
let namespaces: [String: ProposalNamespace] = [
"eip155": ProposalNamespace(
chains: [
Blockchain(ChainIdV2)!
],
methods: [
"eth_sendTransaction",
"personal_sign",
"eth_estimateGas",
"eth_getTransactionReceipt",
"eth_call"
], events: [WCSessionEvent.chainChanged.rawValue, WCSessionEvent.accountsChanged.rawValue]
)
]
let date: Int = Int(Date().dateByAdding(day: 7)?.timeIntervalSince1970 ?? 0)
let sessionProperties: [String: String] = [
"sessionExpiry": String(date)
]
Task {
do {
disconnectUnusedPairings()
// 创建连接
let uri = try await Pair.instance.create()
// 发起connect
try await Sign.instance.connect(
requiredNamespaces: namespaces,
optionalNamespaces: [:],
sessionProperties: sessionProperties,
topic: uri.topic
)
callback?(.succes(uri.absoluteString))
} catch {
callback?(.failed(error))
}
}
//跳转Wallet App
let deepLink = self.getDeepLink(wallet: type, wcURL: wcUrl)
guard let url = URL(string: deepLink), UIApplication.shared.canOpenURL(url) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
发送Request
到了我们使用Wallet Connect 的重头戏了。我们连接之后就可以向 Wallet App 发送签名请求进行交易了。
这里主要展示 ethSendTransaction的使用,signMessage 方法我们也会经常用到,但是是类似的,就不再体现了。
我们需要认识到,在Dapp端发送了交易是向中继层发送消息,所以在收到中继层收到的回复后,我们还需要主动打开Wallet App 这样,建立过连接的Wallet App会主动向中继层拉取消息,拉取成功后就会展示我们发起的签名请求,等待用户的确定。
- v1
在v1,我们发消息的要携带的标识是 WCURL,所以我们需要把建立过连接的 Session
缓存在Dapp中。通过读取Session
中的URL信息。
func ethSendTransaction(_ transaction: Transaction, session: WCSession, callback: ((WCSendRequestResult) -> ())?) {
guard let v1Session = getSession(for: session) else {
callback?(.failed(WCConnectError.invaildSession))
return
}
do {
try client.eth_sendTransaction(url: v1Session.url, transaction: transaction.convertV1Transaction()) { Response in
if let error = Response.error {
callback?(.failed(error))
} else {
do {
let result = try Response.result(as: String.self)
callback?(.success(result))
} catch {
callback?(.failed(error))
}
}
}
} catch {
callback?(.failed(error))
}
}
请求成功后会收到回调,result 就是这次交易的Hash
- v2
v2 SDK 会自动管理我们的 Session
。所以我们只需要保持好当前连接的Topic
即可获取 Session
去完成交易。
func ethSendTransaction(_ transaction: DGTransaction, session: WCSession, sendSuccess: (() -> ())?, callback: ((WCSendRequestResult) -> ())?) {
let method = "eth_sendTransaction"
let requestParams = AnyCodable([transaction])
let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(session.chainId)!)
sendRequest(request, sendSuccess: sendSuccess, callback: callback)
}
// 通过request id 标识请求
private func sendRequest(_ request: Request, sendSuccess: (() -> ())?, callback: ((WCSendRequestResult) -> ())?) {
let id = request.id.string
if let cb = callback {
callBackPool[id] = cb
}
Task {
do {
try await Sign.instance.request(params: request)
sendSuccess?()
} catch {
callBackPool[id]?(.failed(error))
callBackPool[id] = nil
}
}
}
v2的请求回调会通过Sign.instance.sessionResponsePublisher
通知返回,在返回里面我们request id 去处理对应的回调。
总结
上面我们从 配置 - 连接 - 请求 三步流程,简单得概述 v1 向 v2迁移的处理方案,这里面也有很多很重要的点没讨论到。同时v2协议的设计理念更加先进,但是很多Wallet App 对 v2 协议支持情况实现程度都不同,所以其中还是有很多坑点可以讨论的