2021-11-30 15:56:00 +03:00
|
|
|
// Copyright © 2021 Tokenary. All rights reserved.
|
2021-06-12 20:37:56 +03:00
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import WalletConnect
|
|
|
|
|
|
|
|
class WalletConnect {
|
|
|
|
|
2021-12-03 16:15:53 +03:00
|
|
|
private lazy var agent = Agent.shared
|
2021-07-10 22:54:55 +03:00
|
|
|
private let sessionStorage = SessionStorage.shared
|
2021-07-11 18:47:51 +03:00
|
|
|
private let networkMonitor = NetworkMonitor.shared
|
2021-07-17 20:20:51 +03:00
|
|
|
private let ethereum = Ethereum.shared
|
2021-08-01 19:42:27 +03:00
|
|
|
private let walletsManager = WalletsManager.shared
|
2021-07-17 20:20:51 +03:00
|
|
|
|
2021-06-12 20:37:56 +03:00
|
|
|
static let shared = WalletConnect()
|
2021-07-11 18:47:51 +03:00
|
|
|
|
|
|
|
private init() {
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(connectionAppeared), name: .connectionAppeared, object: nil)
|
|
|
|
}
|
|
|
|
|
2021-06-12 20:37:56 +03:00
|
|
|
private var interactors = [WCInteractor]()
|
2021-07-11 18:47:51 +03:00
|
|
|
private var interactorsPendingReconnection = [String: WCInteractor]()
|
2021-07-01 22:33:39 +03:00
|
|
|
private var peers = [String: WCPeerMeta]()
|
2021-06-12 20:37:56 +03:00
|
|
|
|
2021-06-13 13:13:47 +03:00
|
|
|
func sessionWithLink(_ link: String) -> WCSession? {
|
|
|
|
return WCSession.from(string: link)
|
|
|
|
}
|
|
|
|
|
2021-08-06 18:58:32 +03:00
|
|
|
func connect(session: WCSession, chainId: Int, walletId: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
|
2021-11-30 15:56:00 +03:00
|
|
|
let clientMeta = WCPeerMeta(name: Strings.tokenary, url: "https://tokenary.io", description: Strings.walletConnectClientDescription, icons: ["https://tokenary.io/icon.png"])
|
2021-07-10 22:54:55 +03:00
|
|
|
let interactor = WCInteractor(session: session, meta: clientMeta, uuid: uuid)
|
2021-08-06 18:58:32 +03:00
|
|
|
configure(interactor: interactor, chainId: chainId, walletId: walletId)
|
2021-06-12 20:37:56 +03:00
|
|
|
|
|
|
|
interactor.connect().done { connected in
|
|
|
|
completion(connected)
|
2021-07-11 16:22:49 +03:00
|
|
|
}.catch { [weak self, weak interactor] _ in
|
2021-06-13 15:07:54 +03:00
|
|
|
completion(false)
|
2021-07-11 16:22:49 +03:00
|
|
|
if let interactor = interactor {
|
|
|
|
self?.didDisconnect(interactor: interactor)
|
|
|
|
}
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
interactors.append(interactor)
|
|
|
|
}
|
|
|
|
|
2021-07-10 22:54:55 +03:00
|
|
|
func restartSessions() {
|
|
|
|
let items = sessionStorage.loadAll()
|
2021-07-11 15:07:40 +03:00
|
|
|
|
2021-07-10 22:54:55 +03:00
|
|
|
for item in items {
|
2021-07-11 15:07:40 +03:00
|
|
|
guard let uuid = UUID(uuidString: item.clientId) else { continue }
|
2021-08-06 18:58:32 +03:00
|
|
|
connect(session: item.session, chainId: item.chainId ?? 1, walletId: item.walletId, uuid: uuid) { _ in }
|
2021-07-11 15:07:40 +03:00
|
|
|
peers[item.clientId] = item.sessionDetails.peerMeta
|
2021-07-11 16:22:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-11 18:47:51 +03:00
|
|
|
@objc private func connectionAppeared() {
|
|
|
|
if !interactorsPendingReconnection.isEmpty {
|
|
|
|
reconnectPendingInteractors()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func reconnectPendingInteractors() {
|
|
|
|
let pending = interactorsPendingReconnection.values
|
|
|
|
interactorsPendingReconnection = [:]
|
|
|
|
pending.forEach {
|
|
|
|
$0.resume()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-11 16:22:49 +03:00
|
|
|
private func didDisconnect(interactor: WCInteractor) {
|
|
|
|
if sessionStorage.shouldReconnect(interactor: interactor) {
|
|
|
|
reconnectWhenPossible(interactor: interactor)
|
|
|
|
} else {
|
|
|
|
removeInteractor(id: interactor.clientId)
|
2021-06-20 20:36:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 20:38:44 +03:00
|
|
|
private func removeInteractor(id: String) {
|
|
|
|
interactors.removeAll(where: { $0.clientId == id })
|
2021-07-01 22:33:39 +03:00
|
|
|
peers.removeValue(forKey: id)
|
2021-07-11 17:05:20 +03:00
|
|
|
sessionStorage.remove(clientId: id)
|
2021-06-20 20:38:44 +03:00
|
|
|
}
|
|
|
|
|
2021-07-02 23:00:00 +03:00
|
|
|
private func getPeerOfInteractor(_ interactor: WCInteractor?) -> WCPeerMeta? {
|
|
|
|
guard let id = interactor?.clientId else { return nil }
|
|
|
|
return peers[id]
|
|
|
|
}
|
|
|
|
|
2021-08-06 18:58:32 +03:00
|
|
|
private func configure(interactor: WCInteractor, chainId: Int, walletId: String) {
|
2021-08-01 19:42:27 +03:00
|
|
|
guard let address = walletsManager.getWallet(id: walletId)?.ethereumAddress else { return }
|
2021-06-12 20:37:56 +03:00
|
|
|
let accounts = [address]
|
2021-08-06 18:58:32 +03:00
|
|
|
|
2021-06-13 15:07:54 +03:00
|
|
|
interactor.onError = { _ in }
|
2021-06-12 20:37:56 +03:00
|
|
|
|
2021-12-18 21:46:43 +03:00
|
|
|
interactor.onSessionRequest = { [weak self, weak interactor] (_, peerParam) in
|
2021-07-11 15:26:41 +03:00
|
|
|
guard let interactor = interactor else { return }
|
|
|
|
self?.peers[interactor.clientId] = peerParam.peerMeta
|
2021-08-06 18:58:32 +03:00
|
|
|
self?.sessionStorage.add(interactor: interactor, chainId: chainId, walletId: walletId, sessionDetails: peerParam)
|
2021-07-11 15:26:41 +03:00
|
|
|
interactor.approveSession(accounts: accounts, chainId: chainId).cauterize()
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
|
2021-07-11 14:43:17 +03:00
|
|
|
interactor.onDisconnect = { [weak interactor, weak self] _ in
|
2021-07-11 16:22:49 +03:00
|
|
|
if let interactor = interactor {
|
|
|
|
self?.didDisconnect(interactor: interactor)
|
2021-07-10 18:49:43 +03:00
|
|
|
}
|
2021-06-20 20:38:44 +03:00
|
|
|
}
|
2021-06-12 20:37:56 +03:00
|
|
|
|
|
|
|
interactor.eth.onSign = { [weak self, weak interactor] (id, payload) in
|
2021-08-01 19:42:27 +03:00
|
|
|
self?.approveSign(id: id, payload: payload, walletId: walletId, interactor: interactor)
|
2021-07-11 17:23:15 +03:00
|
|
|
self?.sessionStorage.didInteractWith(clientId: interactor?.clientId)
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
|
2021-12-18 21:46:43 +03:00
|
|
|
interactor.eth.onTransaction = { [weak self, weak interactor] (id, _, transaction) in
|
2021-08-06 23:39:58 +03:00
|
|
|
self?.approveTransaction(id: id, wct: transaction, walletId: walletId, chainId: chainId, interactor: interactor)
|
2021-07-11 17:23:15 +03:00
|
|
|
self?.sessionStorage.didInteractWith(clientId: interactor?.clientId)
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-11 14:43:17 +03:00
|
|
|
private func reconnectWhenPossible(interactor: WCInteractor) {
|
2021-07-11 18:47:51 +03:00
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
|
|
self?.interactorsPendingReconnection[interactor.clientId] = interactor
|
|
|
|
if self?.interactorsPendingReconnection.count == 1 && self?.networkMonitor.hasConnection == true {
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(7)) {
|
|
|
|
self?.reconnectPendingInteractors()
|
|
|
|
}
|
|
|
|
}
|
2021-07-11 14:43:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 23:39:58 +03:00
|
|
|
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, walletId: String, chainId: Int, interactor: WCInteractor?) {
|
|
|
|
guard let to = wct.to, let chain = EthereumChain(rawValue: chainId) else {
|
2021-08-27 23:50:55 +03:00
|
|
|
rejectRequest(id: id, interactor: interactor, message: Strings.somethingWentWrong)
|
2021-06-27 11:20:43 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-28 13:02:38 +03:00
|
|
|
let peer = PeerMeta(wcPeerMeta: getPeerOfInteractor(interactor))
|
2021-06-28 16:15:53 +03:00
|
|
|
let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data)
|
2022-02-12 21:30:05 +03:00
|
|
|
agent.showApprove(transaction: transaction, chain: chain, peerMeta: peer) { [weak self, weak interactor] transaction in
|
2021-06-28 16:15:53 +03:00
|
|
|
if let transaction = transaction {
|
2021-08-06 23:39:58 +03:00
|
|
|
self?.sendTransaction(transaction, walletId: walletId, chainId: chainId, requestId: id, interactor: interactor)
|
2022-02-12 21:30:05 +03:00
|
|
|
Window.closeAllAndActivateBrowser(force: nil)
|
2021-06-13 09:32:07 +03:00
|
|
|
} else {
|
2022-02-12 21:30:05 +03:00
|
|
|
Window.closeAllAndActivateBrowser(force: nil)
|
2021-12-12 16:17:15 +03:00
|
|
|
self?.rejectRequest(id: id, interactor: interactor, message: Strings.canceled)
|
2021-06-27 14:13:34 +03:00
|
|
|
}
|
|
|
|
}
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
|
2021-08-01 19:42:27 +03:00
|
|
|
private func approveSign(id: Int64, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) {
|
2021-06-12 21:11:55 +03:00
|
|
|
var message: String?
|
2021-08-15 12:23:37 +03:00
|
|
|
let approvalSubject: ApprovalSubject
|
2021-06-12 20:37:56 +03:00
|
|
|
switch payload {
|
2021-06-12 21:11:55 +03:00
|
|
|
case let .sign(data: data, raw: _):
|
2021-08-13 22:03:04 +03:00
|
|
|
message = String(data: data, encoding: .utf8) ?? data.hexString
|
2021-08-15 12:23:37 +03:00
|
|
|
approvalSubject = .signMessage
|
2021-06-12 20:37:56 +03:00
|
|
|
case let .personalSign(data: data, raw: _):
|
2021-08-13 22:03:04 +03:00
|
|
|
message = String(data: data, encoding: .utf8) ?? data.hexString
|
2021-08-15 12:23:37 +03:00
|
|
|
approvalSubject = .signPersonalMessage
|
2021-06-12 20:37:56 +03:00
|
|
|
case let .signTypeData(id: _, data: _, raw: raw):
|
2021-08-15 12:23:37 +03:00
|
|
|
approvalSubject = .signTypedData
|
2021-06-12 20:37:56 +03:00
|
|
|
if raw.count >= 2 {
|
|
|
|
message = raw[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 13:02:38 +03:00
|
|
|
let peer = PeerMeta(wcPeerMeta: getPeerOfInteractor(interactor))
|
2022-02-12 21:30:05 +03:00
|
|
|
agent.showApprove(subject: approvalSubject, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in
|
2021-06-13 09:32:07 +03:00
|
|
|
if approved {
|
2021-08-13 22:03:04 +03:00
|
|
|
self?.sign(id: id, payload: payload, walletId: walletId, interactor: interactor)
|
2022-02-12 21:30:05 +03:00
|
|
|
Window.closeAllAndActivateBrowser(force: nil)
|
2021-06-13 09:32:07 +03:00
|
|
|
} else {
|
2022-02-12 21:30:05 +03:00
|
|
|
Window.closeAllAndActivateBrowser(force: nil)
|
2021-12-12 16:17:15 +03:00
|
|
|
self?.rejectRequest(id: id, interactor: interactor, message: Strings.canceled)
|
2021-06-13 09:32:07 +03:00
|
|
|
}
|
|
|
|
}
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
|
2021-06-20 19:51:45 +03:00
|
|
|
private func rejectRequest(id: Int64, interactor: WCInteractor?, message: String) {
|
2021-06-12 20:37:56 +03:00
|
|
|
interactor?.rejectRequest(id: id, message: message).cauterize()
|
|
|
|
}
|
|
|
|
|
2021-08-06 23:39:58 +03:00
|
|
|
private func sendTransaction(_ transaction: Transaction, walletId: String, chainId: Int, requestId: Int64, interactor: WCInteractor?) {
|
|
|
|
guard let wallet = walletsManager.getWallet(id: walletId), let chain = EthereumChain(rawValue: chainId) else {
|
2021-08-27 23:50:55 +03:00
|
|
|
rejectRequest(id: requestId, interactor: interactor, message: Strings.somethingWentWrong)
|
2021-06-13 13:17:13 +03:00
|
|
|
return
|
|
|
|
}
|
2021-08-06 23:39:58 +03:00
|
|
|
guard let hash = try? ethereum.send(transaction: transaction, wallet: wallet, chain: chain) else {
|
2021-08-28 16:25:37 +03:00
|
|
|
rejectRequest(id: requestId, interactor: interactor, message: Strings.failedToSend)
|
2021-06-13 13:17:13 +03:00
|
|
|
return
|
|
|
|
}
|
2021-06-27 11:20:43 +03:00
|
|
|
interactor?.approveRequest(id: requestId, result: hash).cauterize()
|
2021-08-14 13:43:38 +03:00
|
|
|
ReviewRequster.requestReviewIfNeeded()
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
|
|
|
|
2021-08-13 22:03:04 +03:00
|
|
|
private func sign(id: Int64, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) {
|
|
|
|
guard let wallet = walletsManager.getWallet(id: walletId) else {
|
2021-08-27 23:50:55 +03:00
|
|
|
rejectRequest(id: id, interactor: interactor, message: Strings.somethingWentWrong)
|
2021-06-12 20:58:41 +03:00
|
|
|
return
|
|
|
|
}
|
2021-06-12 21:11:55 +03:00
|
|
|
var signed: String?
|
|
|
|
switch payload {
|
2021-08-13 22:03:04 +03:00
|
|
|
case let .personalSign(data: data, raw: _):
|
|
|
|
signed = try? ethereum.signPersonalMessage(data: data, wallet: wallet)
|
|
|
|
case let .signTypeData(id: _, data: _, raw: raw):
|
|
|
|
let typedData = raw.count >= 2 ? raw[1] : ""
|
|
|
|
signed = try? ethereum.sign(typedData: typedData, wallet: wallet)
|
|
|
|
case let .sign(data: data, raw: _):
|
|
|
|
signed = try? ethereum.sign(data: data, wallet: wallet)
|
2021-06-12 21:11:55 +03:00
|
|
|
}
|
|
|
|
guard let result = signed else {
|
2021-08-27 23:50:55 +03:00
|
|
|
rejectRequest(id: id, interactor: interactor, message: Strings.somethingWentWrong)
|
2021-06-12 21:11:55 +03:00
|
|
|
return
|
|
|
|
}
|
2021-06-12 20:58:41 +03:00
|
|
|
interactor?.approveRequest(id: id, result: result).cauterize()
|
2021-08-14 13:43:38 +03:00
|
|
|
ReviewRequster.requestReviewIfNeeded()
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|
2021-06-20 19:51:45 +03:00
|
|
|
|
2021-06-12 20:37:56 +03:00
|
|
|
}
|