tokenary/Encrypted Ink/WalletConnect.swift

176 lines
7.4 KiB
Swift
Raw Normal View History

2021-06-12 20:37:56 +03:00
// Copyright © 2021 Encrypted Ink. All rights reserved.
import Foundation
import WalletConnect
class WalletConnect {
2021-07-10 22:54:55 +03:00
private let sessionStorage = SessionStorage.shared
2021-06-12 20:37:56 +03:00
static let shared = WalletConnect()
private init() {}
private var interactors = [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-07-10 22:54:55 +03:00
func connect(session: WCSession, address: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
2021-07-06 21:29:54 +03:00
let clientMeta = WCPeerMeta(name: "Encrypted Ink", url: "https://encrypted.ink", description: "Ethereum agent for macOS", icons: ["https://encrypted.ink/icon.png"])
2021-07-10 22:54:55 +03:00
let interactor = WCInteractor(session: session, meta: clientMeta, uuid: uuid)
2021-06-20 20:38:44 +03:00
let id = interactor.clientId
2021-06-12 20:37:56 +03:00
configure(interactor: interactor, address: address)
interactor.connect().done { connected in
completion(connected)
2021-06-20 20:38:44 +03:00
}.catch { [weak self] _ in
2021-06-13 15:07:54 +03:00
completion(false)
2021-06-20 20:38:44 +03:00
self?.removeInteractor(id: id)
2021-07-11 15:49:27 +03:00
// TODO: maybe should retry here as well
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 }
connect(session: item.session, address: item.address, uuid: uuid) { _ in }
peers[item.clientId] = item.sessionDetails.peerMeta
2021-07-10 22:54:55 +03:00
// TODO: maybe should remove from storage on unsuccessful connection attempt
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)
// TODO: remove from storage as well
2021-06-20 20:38:44 +03:00
}
private func getPeerOfInteractor(_ interactor: WCInteractor?) -> WCPeerMeta? {
guard let id = interactor?.clientId else { return nil }
return peers[id]
}
2021-06-20 19:51:45 +03:00
private func configure(interactor: WCInteractor, address: String) {
2021-06-12 20:37:56 +03:00
let accounts = [address]
let chainId = 1
2021-06-13 15:07:54 +03:00
interactor.onError = { _ in }
2021-06-12 20:37:56 +03:00
2021-07-01 22:33:39 +03:00
interactor.onSessionRequest = { [weak self, weak interactor] (id, peerParam) in
2021-07-11 15:26:41 +03:00
guard let interactor = interactor else { return }
self?.peers[interactor.clientId] = peerParam.peerMeta
self?.sessionStorage.add(interactor: interactor, address: address, sessionDetails: peerParam)
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
guard let interactor = interactor else { return }
if self?.sessionStorage.shouldReconnect(interactor: interactor) == true {
2021-07-11 14:43:17 +03:00
self?.reconnectWhenPossible(interactor: interactor)
} else {
self?.removeInteractor(id: interactor.clientId)
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
self?.approveSign(id: id, payload: payload, address: address, interactor: interactor)
}
interactor.eth.onTransaction = { [weak self, weak interactor] (id, event, transaction) in
self?.approveTransaction(id: id, wct: transaction, address: address, interactor: interactor)
}
}
2021-07-11 14:43:17 +03:00
private func reconnectWhenPossible(interactor: WCInteractor) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) { [weak interactor] in
interactor?.resume() // TODO: reconnect when appropriate.
}
}
2021-06-12 20:37:56 +03:00
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, address: String, interactor: WCInteractor?) {
2021-06-27 11:20:43 +03:00
guard let to = wct.to else {
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
return
}
let peer = getPeerOfInteractor(interactor)
let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data)
Agent.shared.showApprove(transaction: transaction, peerMeta: peer) { [weak self, weak interactor] transaction in
if let transaction = transaction {
2021-06-27 11:20:43 +03:00
self?.sendTransaction(transaction, address: address, requestId: id, interactor: interactor)
2021-06-13 09:32:07 +03:00
} else {
self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled")
2021-06-27 14:13:34 +03:00
}
}
2021-06-12 20:37:56 +03:00
}
2021-06-20 19:51:45 +03:00
private func approveSign(id: Int64, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) {
2021-06-12 21:11:55 +03:00
var message: String?
2021-06-13 10:40:03 +03:00
let title: String
2021-06-12 20:37:56 +03:00
switch payload {
2021-06-12 21:11:55 +03:00
case let .sign(data: data, raw: _):
message = String(data: data, encoding: .utf8)
2021-06-13 10:40:03 +03:00
title = "Sign Message"
2021-06-12 20:37:56 +03:00
case let .personalSign(data: data, raw: _):
2021-06-12 21:11:55 +03:00
message = String(data: data, encoding: .utf8)
2021-06-13 10:40:03 +03:00
title = "Sign Personal Message"
2021-06-12 20:37:56 +03:00
case let .signTypeData(id: _, data: _, raw: raw):
2021-06-13 10:40:03 +03:00
title = "Sign Typed Data"
2021-06-12 20:37:56 +03:00
if raw.count >= 2 {
message = raw[1]
}
}
let peer = getPeerOfInteractor(interactor)
Agent.shared.showApprove(title: title, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in
2021-06-13 09:32:07 +03:00
if approved {
self?.sign(id: id, message: message, payload: payload, address: address, interactor: interactor)
} else {
self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled")
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-06-27 14:20:47 +03:00
private func sendTransaction(_ transaction: Transaction, address: String, requestId: Int64, interactor: WCInteractor?) {
2021-06-27 11:20:43 +03:00
guard let account = AccountsService.getAccountForAddress(address) else {
rejectRequest(id: requestId, interactor: interactor, message: "Something went wrong.")
2021-06-13 13:17:13 +03:00
return
}
guard let hash = try? Ethereum.send(transaction: transaction, account: account) else {
2021-06-27 11:20:43 +03:00
rejectRequest(id: requestId, interactor: interactor, message: "Failed to send")
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-06-12 20:37:56 +03:00
}
2021-06-20 19:51:45 +03:00
private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) {
2021-06-13 15:07:54 +03:00
guard let message = message, let account = AccountsService.getAccountForAddress(address) else {
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
2021-06-12 20:58:41 +03:00
return
}
2021-06-12 21:11:55 +03:00
var signed: String?
switch payload {
case .personalSign:
signed = try? Ethereum.signPersonal(message: message, account: account)
case .signTypeData:
signed = try? Ethereum.sign(typedData: message, account: account)
case .sign:
signed = try? Ethereum.sign(message: message, account: account)
}
guard let result = signed else {
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
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-06-12 20:37:56 +03:00
}
2021-06-20 19:51:45 +03:00
2021-06-12 20:37:56 +03:00
}