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-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: m a y b e s h o u l d r e t r y h e r e a s w e l l
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: m a y b e s h o u l d r e m o v e f r o m s t o r a g e o n u n s u c c e s s f u l c o n n e c t i o n a t t e m p t
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 15:47:46 +03:00
// TODO: r e m o v e f r o m s t o r a g e a s w e l l
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-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
2021-07-11 15:47:46 +03:00
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 )
2021-07-11 15:47:46 +03:00
} 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: r e c o n n e c t w h e n a p p r o p r i a t e .
}
}
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
}
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-07-02 23:00:00 +03:00
Agent . shared . showApprove ( transaction : transaction , peerMeta : peer ) { [ weak self , weak interactor ] transaction in
2021-06-28 16:15:53 +03:00
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 {
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-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 ]
}
}
2021-07-02 23:00:00 +03:00
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 {
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-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 {
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
}
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 {
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 {
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 {
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-06-12 20:37:56 +03:00
}
2021-06-20 19:51:45 +03:00
2021-06-12 20:37:56 +03:00
}