2021-06-12 20:37:56 +03:00
// C o p y r i g h t © 2 0 2 1 E n c r y p t e d I n k . A l l r i g h t s r e s e r v e d .
import Foundation
import WalletConnect
class WalletConnect {
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-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-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-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
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
}
interactor . eth . onTransaction = { [ weak self , weak interactor ] ( id , event , 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-06-27 11:20:43 +03:00
rejectRequest ( id : id , interactor : interactor , message : " Something went wrong. " )
return
}
2021-07-02 23:00:00 +03:00
let peer = 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 )
2021-08-06 23:39:58 +03:00
Agent . shared . 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 )
2021-06-13 09:32:07 +03:00
} else {
2021-06-28 16:15:53 +03:00
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-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-10 12:14:14 +03:00
2021-08-15 12:04:34 +03:00
let approvalReason : ApprovalReason
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:04:34 +03:00
approvalReason = . 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:04:34 +03:00
approvalReason = . signPersonalMessage
2021-06-12 20:37:56 +03:00
case let . signTypeData ( id : _ , data : _ , raw : raw ) :
2021-08-15 12:04:34 +03:00
approvalReason = . signTypedData
2021-06-12 20:37:56 +03:00
if raw . count >= 2 {
message = raw [ 1 ]
}
}
2021-07-02 23:00:00 +03:00
let peer = getPeerOfInteractor ( interactor )
2021-08-15 12:04:34 +03:00
Agent . shared . showApprove ( reason : approvalReason , 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 )
2021-06-13 09:32:07 +03:00
} else {
2021-06-28 16:15:53 +03:00
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-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-06-28 16:15:53 +03:00
rejectRequest ( id : requestId , interactor : interactor , message : " Something went wrong. " )
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-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-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-06-28 16:15:53 +03:00
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 {
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-06-28 16:15:53 +03:00
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-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
}