From b26f3a6f7fcda11bdd422dae0ec40dedd5b00bdf Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Wed, 29 Dec 2021 15:43:58 +0200 Subject: [PATCH 01/31] moved keystore manager to web3 main object --- .../Web3+BrowserFunctions.swift | 4 +-- .../HookedFunctions/Web3+Wallet.swift | 6 ++-- .../Promise+Web3+Eth+GetAccounts.swift | 2 +- .../Promise+Web3+Eth+SendTransaction.swift | 4 +-- .../Promise+Web3+Personal+CreateAccount.swift | 2 +- .../Promises/Promise+Web3+Personal+Sign.swift | 4 +-- .../Promise+Web3+Personal+UnlockAccount.swift | 2 +- .../web3swift/Web3/Web3+Eth+Websocket.swift | 2 +- .../web3swift/Web3/Web3+HttpProvider.swift | 5 +-- .../web3swift/Web3/Web3+InfuraProviders.swift | 34 ++++++------------- Sources/web3swift/Web3/Web3+Instance.swift | 3 +- .../Web3/Web3+WebsocketProvider.swift | 9 ----- 12 files changed, 27 insertions(+), 50 deletions(-) diff --git a/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift b/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift index ed1576a3b..b9befcd5b 100755 --- a/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift +++ b/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift @@ -37,7 +37,7 @@ extension web3.BrowserFunctions { public func sign(_ personalMessage: Data, account: String, password: String = "web3swift") -> String? { do { - guard let keystoreManager = self.web3.provider.attachedKeystoreManager else {return nil} + guard let keystoreManager = self.web3.attachedKeystoreManager else {return nil} guard let from = EthereumAddress(account, ignoreChecksum: true) else {return nil} guard let signature = try Web3Signer.signPersonalMessage(personalMessage, keystore: keystoreManager, account: from, password: password) else {return nil} return signature.toHexString().addHexPrefix() @@ -164,7 +164,7 @@ extension web3.BrowserFunctions { do { var transaction = trans guard let from = transactionOptions.from else {return nil} - guard let keystoreManager = self.web3.provider.attachedKeystoreManager else {return nil} + guard let keystoreManager = self.web3.attachedKeystoreManager else {return nil} guard let gasPricePolicy = transactionOptions.gasPrice else {return nil} guard let gasLimitPolicy = transactionOptions.gasLimit else {return nil} guard let noncePolicy = transactionOptions.nonce else {return nil} diff --git a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift index c7b0e8c44..bcd3fece4 100755 --- a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift +++ b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift @@ -11,7 +11,7 @@ import BigInt extension web3.Web3Wallet { public func getAccounts() throws -> [EthereumAddress] { - guard let keystoreManager = self.web3.provider.attachedKeystoreManager else { + guard let keystoreManager = self.web3.attachedKeystoreManager else { throw Web3Error.walletError } guard let ethAddresses = keystoreManager.addresses else { @@ -30,7 +30,7 @@ extension web3.Web3Wallet { public func signTX(transaction:inout EthereumTransaction, account: EthereumAddress, password: String = "web3swift") throws -> Bool { do { - guard let keystoreManager = self.web3.provider.attachedKeystoreManager else { + guard let keystoreManager = self.web3.attachedKeystoreManager else { throw Web3Error.walletError } try Web3Signer.signTX(transaction: &transaction, keystore: keystoreManager, account: account, password: password) @@ -53,7 +53,7 @@ extension web3.Web3Wallet { public func signPersonalMessage(_ personalMessage: Data, account: EthereumAddress, password: String = "web3swift") throws -> Data { do { - guard let keystoreManager = self.web3.provider.attachedKeystoreManager else + guard let keystoreManager = self.web3.attachedKeystoreManager else { throw Web3Error.walletError } diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift index f8ccd0cd1..1f24f59b3 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift @@ -12,7 +12,7 @@ import PromiseKit extension web3.Eth { public func getAccountsPromise() -> Promise<[EthereumAddress]> { let queue = web3.requestDispatcher.queue - if (self.web3.provider.attachedKeystoreManager != nil) { + if (self.web3.attachedKeystoreManager != nil) { let promise = Promise<[EthereumAddress]>.pending() queue.async { do { diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift index 7fe53fd98..44496265e 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift @@ -38,7 +38,7 @@ extension web3.Eth { assembledTransaction = forAssemblyPipeline.0 mergedOptions = forAssemblyPipeline.1 - if self.web3.provider.attachedKeystoreManager == nil { + if self.web3.attachedKeystoreManager == nil { guard let request = EthereumTransaction.createRequest(method: .sendTransaction, transaction: assembledTransaction, transactionOptions: mergedOptions) else { throw Web3Error.processingError(desc: "Failed to create a request to send transaction") @@ -63,7 +63,7 @@ extension web3.Eth { throw Web3Error.inputError(desc: "No 'from' field provided") } do { - try Web3Signer.signTX(transaction: &assembledTransaction, keystore: self.web3.provider.attachedKeystoreManager!, account: from, password: password) + try Web3Signer.signTX(transaction: &assembledTransaction, keystore: self.web3.attachedKeystoreManager!, account: from, password: password) } catch { throw Web3Error.inputError(desc: "Failed to locally sign a transaction") } diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift index e5e83d556..1bf654119 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift @@ -13,7 +13,7 @@ extension web3.Personal { public func createAccountPromise(password:String = "web3swift") -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.provider.attachedKeystoreManager == nil { + if self.web3.attachedKeystoreManager == nil { let request = JSONRPCRequestFabric.prepareRequest(.createAccount, parameters: [password]) return self.web3.dispatch(request).map(on: queue) {response in guard let value: EthereumAddress = response.getValue() else { diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift index bf71b10c1..7f8467f82 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift @@ -14,7 +14,7 @@ extension web3.Personal { public func signPersonalMessagePromise(message: Data, from: EthereumAddress, password:String = "web3swift") -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.provider.attachedKeystoreManager == nil { + if self.web3.attachedKeystoreManager == nil { let hexData = message.toHexString().addHexPrefix() let request = JSONRPCRequestFabric.prepareRequest(.personalSign, parameters: [from.address.lowercased(), hexData]) return self.web3.dispatch(request).map(on: queue) {response in @@ -27,7 +27,7 @@ extension web3.Personal { return value } } - guard let signature = try Web3Signer.signPersonalMessage(message, keystore: self.web3.provider.attachedKeystoreManager!, account: from, password: password) else { throw Web3Error.inputError(desc: "Failed to locally sign a message") } + guard let signature = try Web3Signer.signPersonalMessage(message, keystore: self.web3.attachedKeystoreManager!, account: from, password: password) else { throw Web3Error.inputError(desc: "Failed to locally sign a message") } let returnPromise = Promise.pending() queue.async { returnPromise.resolver.fulfill(signature) diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift index 62512f6d4..67c2ca185 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift @@ -19,7 +19,7 @@ extension web3.Personal { public func unlockAccountPromise(account: String, password:String = "web3swift", seconds: UInt64 = 300) -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.provider.attachedKeystoreManager == nil { + if self.web3.attachedKeystoreManager == nil { let request = JSONRPCRequestFabric.prepareRequest(.unlockAccount, parameters: [account.lowercased(), password, seconds]) return self.web3.dispatch(request).map(on: queue) {response in guard let value: Bool = response.getValue() else { diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 46d76146b..d1ee9250e 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -19,7 +19,7 @@ extension web3.Eth { guard let infuraNetwork = provider.network else { throw Web3Error.processingError(desc: "Wrong network") } - guard let infuraProvider = InfuraWebsocketProvider(infuraNetwork, delegate: delegate, keystoreManager: provider.attachedKeystoreManager) else { + guard let infuraProvider = InfuraWebsocketProvider(infuraNetwork, delegate: delegate) else { throw Web3Error.processingError(desc: "Wrong network") } infuraWSProvider = infuraProvider diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index 683f7d36e..e9fce8dbd 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -13,7 +13,6 @@ public protocol Web3Provider { func sendAsync(_ request: JSONRPCrequest, queue: DispatchQueue) -> Promise func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue) -> Promise var network: Networks? {get set} - var attachedKeystoreManager: KeystoreManager? {get set} var url: URL {get} var session: URLSession {get} } @@ -23,13 +22,12 @@ public protocol Web3Provider { public class Web3HttpProvider: Web3Provider { public var url: URL public var network: Networks? - public var attachedKeystoreManager: KeystoreManager? = nil public var session: URLSession = {() -> URLSession in let config = URLSessionConfiguration.default let urlSession = URLSession(configuration: config) return urlSession }() - public init?(_ httpProviderURL: URL, network net: Networks? = nil, keystoreManager manager: KeystoreManager? = nil) { + public init?(_ httpProviderURL: URL, network net: Networks? = nil) { do { guard httpProviderURL.scheme == "http" || httpProviderURL.scheme == "https" else {return nil} url = httpProviderURL @@ -51,7 +49,6 @@ public class Web3HttpProvider: Web3Provider { } catch { return nil } - attachedKeystoreManager = manager } } diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index e96adcfc3..f3f274dcb 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -29,11 +29,11 @@ public enum BlockNumber { /// Custom Web3 HTTP provider of Infura nodes. public final class InfuraProvider: Web3HttpProvider { - public init?(_ net:Networks, accessToken token: String? = nil, keystoreManager manager: KeystoreManager? = nil) { + public init?(_ net:Networks, accessToken token: String? = nil) { var requestURLstring = "https://" + net.name + Constants.infuraHttpScheme requestURLstring += token != nil ? token! : Constants.infuraToken let providerURL = URL(string: requestURLstring) - super.init(providerURL!, network: net, keystoreManager: manager) + super.init(providerURL!, network: net) } } @@ -49,8 +49,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public init?(_ network: Networks, delegate: Web3SocketDelegate, - projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil) { + projectId: String? = nil) { guard network == Networks.Kovan || network == Networks.Rinkeby || network == Networks.Ropsten @@ -61,40 +60,33 @@ public final class InfuraWebsocketProvider: WebsocketProvider { super.init(urlString, delegate: delegate, projectId: projectId, - keystoreManager: manager, network: network) } public init?(_ endpoint: String, delegate: Web3SocketDelegate, - projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil) { + projectId: String? = nil) { guard URL(string: endpoint) != nil else {return nil} super.init(endpoint, delegate: delegate, - projectId: projectId, - keystoreManager: manager) + projectId: projectId) } public init?(_ endpoint: URL, delegate: Web3SocketDelegate, - projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil) { + projectId: String? = nil) { super.init(endpoint, delegate: delegate, - projectId: projectId, - keystoreManager: manager) + projectId: projectId) } override public class func connectToSocket(_ endpoint: String, delegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = InfuraWebsocketProvider(endpoint, delegate: delegate, - projectId: projectId, - keystoreManager: manager) else {return nil} + projectId: projectId) else {return nil} socketProvider.connectSocket() return socketProvider } @@ -102,24 +94,20 @@ public final class InfuraWebsocketProvider: WebsocketProvider { override public class func connectToSocket(_ endpoint: URL, delegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = InfuraWebsocketProvider(endpoint, delegate: delegate, - projectId: projectId, - keystoreManager: manager) else {return nil} + projectId: projectId) else {return nil} socketProvider.connectSocket() return socketProvider } public static func connectToInfuraSocket(_ network: Networks, delegate: Web3SocketDelegate, - projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil) -> InfuraWebsocketProvider? { + projectId: String? = nil) -> InfuraWebsocketProvider? { guard let socketProvider = InfuraWebsocketProvider(network, delegate: delegate, - projectId: projectId, - keystoreManager: manager) else {return nil} + projectId: projectId) else {return nil} socketProvider.connectSocket() return socketProvider } diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index f87c354c5..989b86b22 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -11,6 +11,7 @@ import PromiseKit /// A web3 instance bound to provider. All further functionality is provided under web.*. namespaces. public class web3 { public var provider : Web3Provider + public var attachedKeystoreManager: KeystoreManager? public var transactionOptions: TransactionOptions = TransactionOptions.defaultOptions public var defaultBlock = "latest" public var requestDispatcher: JSONRPCrequestDispatcher @@ -33,7 +34,7 @@ public class web3 { /// Keystore manager can be bound to Web3 instance. If some manager is bound all further account related functions, such /// as account listing, transaction signing, etc. are done locally using private keys and accounts found in a manager. public func addKeystoreManager(_ manager: KeystoreManager?) { - self.provider.attachedKeystoreManager = manager + self.attachedKeystoreManager = manager } var ethInstance: web3.Eth? diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index b163bb0b4..22be19156 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -109,7 +109,6 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg let urlSession = URLSession(configuration: config) return urlSession }() - public var attachedKeystoreManager: KeystoreManager? = nil public var socket: WebSocket public var delegate: Web3SocketDelegate @@ -123,7 +122,6 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) { websocketConnected = false var endpointString = endpoint.absoluteString @@ -155,7 +153,6 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg } url = URL(string: endpointString)! delegate = wsdelegate - attachedKeystoreManager = manager let request = URLRequest(url: url) socket = WebSocket(request: request) socket.delegate = self @@ -164,7 +161,6 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg public init?(_ endpoint: String, delegate wsdelegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) { guard URL(string: endpoint) != nil else {return nil} var finalEndpoint = endpoint @@ -197,7 +193,6 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg } url = URL(string: finalEndpoint)! delegate = wsdelegate - attachedKeystoreManager = manager let request = URLRequest(url: url) socket = WebSocket(request: request) socket.delegate = self @@ -224,12 +219,10 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg public class func connectToSocket(_ endpoint: String, delegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, delegate: delegate, projectId: projectId, - keystoreManager: manager, network: net) else { return nil } @@ -240,12 +233,10 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg public class func connectToSocket(_ endpoint: URL, delegate: Web3SocketDelegate, projectId: String? = nil, - keystoreManager manager: KeystoreManager? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, delegate: delegate, projectId: projectId, - keystoreManager: manager, network: net) else { return nil } From d3b410cd89c8e2690cda470be7a6102ee3700d73 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:08:17 +0200 Subject: [PATCH 02/31] renamed attachedKeystoreManager --- .../web3swift/HookedFunctions/Web3+BrowserFunctions.swift | 4 ++-- Sources/web3swift/HookedFunctions/Web3+Wallet.swift | 6 +++--- .../web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift | 2 +- .../Promises/Promise+Web3+Eth+SendTransaction.swift | 4 ++-- .../Promises/Promise+Web3+Personal+CreateAccount.swift | 2 +- Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift | 4 ++-- .../Promises/Promise+Web3+Personal+UnlockAccount.swift | 2 +- Sources/web3swift/Web3/Web3+Instance.swift | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift b/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift index b9befcd5b..b30b469df 100755 --- a/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift +++ b/Sources/web3swift/HookedFunctions/Web3+BrowserFunctions.swift @@ -37,7 +37,7 @@ extension web3.BrowserFunctions { public func sign(_ personalMessage: Data, account: String, password: String = "web3swift") -> String? { do { - guard let keystoreManager = self.web3.attachedKeystoreManager else {return nil} + guard let keystoreManager = self.web3.keystoreManager else {return nil} guard let from = EthereumAddress(account, ignoreChecksum: true) else {return nil} guard let signature = try Web3Signer.signPersonalMessage(personalMessage, keystore: keystoreManager, account: from, password: password) else {return nil} return signature.toHexString().addHexPrefix() @@ -164,7 +164,7 @@ extension web3.BrowserFunctions { do { var transaction = trans guard let from = transactionOptions.from else {return nil} - guard let keystoreManager = self.web3.attachedKeystoreManager else {return nil} + guard let keystoreManager = self.web3.keystoreManager else {return nil} guard let gasPricePolicy = transactionOptions.gasPrice else {return nil} guard let gasLimitPolicy = transactionOptions.gasLimit else {return nil} guard let noncePolicy = transactionOptions.nonce else {return nil} diff --git a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift index bcd3fece4..7fe22e560 100755 --- a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift +++ b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift @@ -11,7 +11,7 @@ import BigInt extension web3.Web3Wallet { public func getAccounts() throws -> [EthereumAddress] { - guard let keystoreManager = self.web3.attachedKeystoreManager else { + guard let keystoreManager = self.web3.keystoreManager else { throw Web3Error.walletError } guard let ethAddresses = keystoreManager.addresses else { @@ -30,7 +30,7 @@ extension web3.Web3Wallet { public func signTX(transaction:inout EthereumTransaction, account: EthereumAddress, password: String = "web3swift") throws -> Bool { do { - guard let keystoreManager = self.web3.attachedKeystoreManager else { + guard let keystoreManager = self.web3.keystoreManager else { throw Web3Error.walletError } try Web3Signer.signTX(transaction: &transaction, keystore: keystoreManager, account: account, password: password) @@ -53,7 +53,7 @@ extension web3.Web3Wallet { public func signPersonalMessage(_ personalMessage: Data, account: EthereumAddress, password: String = "web3swift") throws -> Data { do { - guard let keystoreManager = self.web3.attachedKeystoreManager else + guard let keystoreManager = self.web3.keystoreManager else { throw Web3Error.walletError } diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift index 1f24f59b3..c0db12208 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+GetAccounts.swift @@ -12,7 +12,7 @@ import PromiseKit extension web3.Eth { public func getAccountsPromise() -> Promise<[EthereumAddress]> { let queue = web3.requestDispatcher.queue - if (self.web3.attachedKeystoreManager != nil) { + if (self.web3.keystoreManager != nil) { let promise = Promise<[EthereumAddress]>.pending() queue.async { do { diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift index 44496265e..450a040f2 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+SendTransaction.swift @@ -38,7 +38,7 @@ extension web3.Eth { assembledTransaction = forAssemblyPipeline.0 mergedOptions = forAssemblyPipeline.1 - if self.web3.attachedKeystoreManager == nil { + if self.web3.keystoreManager == nil { guard let request = EthereumTransaction.createRequest(method: .sendTransaction, transaction: assembledTransaction, transactionOptions: mergedOptions) else { throw Web3Error.processingError(desc: "Failed to create a request to send transaction") @@ -63,7 +63,7 @@ extension web3.Eth { throw Web3Error.inputError(desc: "No 'from' field provided") } do { - try Web3Signer.signTX(transaction: &assembledTransaction, keystore: self.web3.attachedKeystoreManager!, account: from, password: password) + try Web3Signer.signTX(transaction: &assembledTransaction, keystore: self.web3.keystoreManager!, account: from, password: password) } catch { throw Web3Error.inputError(desc: "Failed to locally sign a transaction") } diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift index 1bf654119..2bbc0f24f 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+CreateAccount.swift @@ -13,7 +13,7 @@ extension web3.Personal { public func createAccountPromise(password:String = "web3swift") -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.attachedKeystoreManager == nil { + if self.web3.keystoreManager == nil { let request = JSONRPCRequestFabric.prepareRequest(.createAccount, parameters: [password]) return self.web3.dispatch(request).map(on: queue) {response in guard let value: EthereumAddress = response.getValue() else { diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift index 7f8467f82..0e6331072 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+Sign.swift @@ -14,7 +14,7 @@ extension web3.Personal { public func signPersonalMessagePromise(message: Data, from: EthereumAddress, password:String = "web3swift") -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.attachedKeystoreManager == nil { + if self.web3.keystoreManager == nil { let hexData = message.toHexString().addHexPrefix() let request = JSONRPCRequestFabric.prepareRequest(.personalSign, parameters: [from.address.lowercased(), hexData]) return self.web3.dispatch(request).map(on: queue) {response in @@ -27,7 +27,7 @@ extension web3.Personal { return value } } - guard let signature = try Web3Signer.signPersonalMessage(message, keystore: self.web3.attachedKeystoreManager!, account: from, password: password) else { throw Web3Error.inputError(desc: "Failed to locally sign a message") } + guard let signature = try Web3Signer.signPersonalMessage(message, keystore: self.web3.keystoreManager!, account: from, password: password) else { throw Web3Error.inputError(desc: "Failed to locally sign a message") } let returnPromise = Promise.pending() queue.async { returnPromise.resolver.fulfill(signature) diff --git a/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift b/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift index 67c2ca185..bef74e61a 100755 --- a/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Personal+UnlockAccount.swift @@ -19,7 +19,7 @@ extension web3.Personal { public func unlockAccountPromise(account: String, password:String = "web3swift", seconds: UInt64 = 300) -> Promise { let queue = web3.requestDispatcher.queue do { - if self.web3.attachedKeystoreManager == nil { + if self.web3.keystoreManager == nil { let request = JSONRPCRequestFabric.prepareRequest(.unlockAccount, parameters: [account.lowercased(), password, seconds]) return self.web3.dispatch(request).map(on: queue) {response in guard let value: Bool = response.getValue() else { diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index 989b86b22..d95696b62 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -11,7 +11,7 @@ import PromiseKit /// A web3 instance bound to provider. All further functionality is provided under web.*. namespaces. public class web3 { public var provider : Web3Provider - public var attachedKeystoreManager: KeystoreManager? + public var keystoreManager: KeystoreManager? public var transactionOptions: TransactionOptions = TransactionOptions.defaultOptions public var defaultBlock = "latest" public var requestDispatcher: JSONRPCrequestDispatcher @@ -34,7 +34,7 @@ public class web3 { /// Keystore manager can be bound to Web3 instance. If some manager is bound all further account related functions, such /// as account listing, transaction signing, etc. are done locally using private keys and accounts found in a manager. public func addKeystoreManager(_ manager: KeystoreManager?) { - self.attachedKeystoreManager = manager + self.keystoreManager = manager } var ethInstance: web3.Eth? From 791a863f53cef284e9a724b3df8888f93cf4a049 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:31:33 +0200 Subject: [PATCH 03/31] added Web3SubscriptionProvider protocol --- .../web3swift/Web3/Web3+Eth+Websocket.swift | 2 + .../web3swift/Web3/Web3+InfuraProviders.swift | 84 ++++++++++++++----- .../Web3/Web3+SubscriptionProvider.swift | 26 ++++++ .../Web3/Web3+WebsocketProvider.swift | 27 +++++- 4 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 Sources/web3swift/Web3/Web3+SubscriptionProvider.swift diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index d1ee9250e..2d7debea8 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -39,4 +39,6 @@ extension web3.Eth { let provider = try getWebsocketProvider(forDelegate: delegate) try provider.subscribeOnNewPendingTransactions() } + + // TODO: make 4 subscribe methods } diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index f3f274dcb..082b3ee3c 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -40,8 +40,8 @@ public final class InfuraProvider: Web3HttpProvider { /// Custom Websocket provider of Infura nodes. public final class InfuraWebsocketProvider: WebsocketProvider { public var filterID: String? - public var subscriptionIDs = Set() - private var subscriptionIDforUnsubscribing: String? = nil + public var subscriptions = [String: (sub: WebsocketSubscription, cb: (Result) -> Void)]() + private var requests = [UInt32: (Result) -> Void]() private var filterTimer: Timer? /// if set debugMode True then show websocket events logs in the console @@ -120,6 +120,16 @@ public final class InfuraWebsocketProvider: WebsocketProvider { writeMessage(requestData) } + private func send(id: UInt32, method: InfuraWebsocketMethod, params: [Encodable], cb: @escaping (Result) -> Void) { + do { + try writeMessage(method: method, params: params) + } catch { + cb(.failure(error)) + return + } + requests[id] = cb + } + public func setFilterAndGetChanges(method: InfuraWebsocketMethod, params: [Encodable]? = nil) throws { filterTimer?.invalidate() filterID = nil @@ -188,15 +198,51 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } } - public func subscribe(params: [Encodable]) throws { + override public func subscribe(filter: SubscribeEventFilter, + listener: @escaping Web3SubscriptionListener) throws -> Subscription { + let params: [Encodable] + switch filter { + case .newHeads: + params = ["newHeads"] + case .logs(let p): + params = ["logs", p] + case .newPendingTransactions: + params = ["newPendingTransactions"] + case .syncing: + params = ["syncing"] + } let method = InfuraWebsocketMethod.subscribe try writeMessage(method: method, params: params) - } - - public func unsubscribe(subscriptionID: String) throws { - let method = InfuraWebsocketMethod.unsubscribe - subscriptionIDforUnsubscribing = subscriptionID - try writeMessage(method: method, params: [subscriptionID]) + let subscription = WebsocketSubscription(unsubscribeCallback: { subscription in + let method = InfuraWebsocketMethod.unsubscribe + self.send(id: self.newID(), method: method, params: [subscription.id]) { result in + switch result { + case .success(let data): + let unsubscribed = data as! Bool + if unsubscribed { + self.subscriptions.removeValue(forKey: subscription.id) + } else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) + } + case .failure(let error): + // TODO: handle error + fatalError("Not implemented") + } + } + }) + let handler = { (result: Result) in + listener(result.map { $0 as! R }) + } + send(id: newID(), method: method, params: params) { result in + switch result { + case .success(let data): + let subscriptionID = data as! String + self.subscriptions[subscriptionID] = (subscription, handler) + case .failure(let error): + handler(.failure(error)) + } + } + fatalError() } public func subscribeOnNewHeads() throws { @@ -281,21 +327,17 @@ public final class InfuraWebsocketProvider: WebsocketProvider { let result = dictionary["result"] as? String { // setting filter id filterID = result + } else if let id = dictionary["id"] as? UInt32 { + if let request = requests.removeValue(forKey: id) { + request(.success(dictionary["result"] as! Decodable)) + } } else if let params = dictionary["params"] as? [String: Any], - let subscription = params["subscription"] as? String, + let subscriptionID = params["subscription"] as? String, let result = params["result"] { - // subscription result - subscriptionIDs.insert(subscription) - delegate.received(message: result) - } else if let unsubscribed = dictionary["result"] as? Bool { - // unsubsribe result - if unsubscribed == true, let id = subscriptionIDforUnsubscribing { - subscriptionIDs.remove(id) - } else if let id = subscriptionIDforUnsubscribing { - delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(id)")) - } else { - delegate.received(message: unsubscribed) + if let subscription = subscriptions[subscriptionID] { + subscription.cb(.success(result as! Decodable)) } + delegate.received(message: result) } else if let message = dictionary["result"] { // filter result delegate.received(message: message) diff --git a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift new file mode 100644 index 000000000..592569be1 --- /dev/null +++ b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift @@ -0,0 +1,26 @@ +// +// Web3+SubscriptionProvider.swift +// +// +// Created by Ostap Danylovych on 29.12.2021. +// + +import Foundation + +public enum SubscribeEventFilter { + case newHeads + case logs(params: Encodable) + case newPendingTransactions + case syncing +} + +public protocol Subscription { + func unsubscribe() throws +} + +public typealias Web3SubscriptionListener = (Result) -> Void + +public protocol Web3SubscriptionProvider: Web3Provider { + func subscribe(filter: SubscribeEventFilter, + listener: @escaping Web3SubscriptionListener) throws -> Subscription +} diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 22be19156..f2a4fcebe 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -91,8 +91,21 @@ public protocol Web3SocketDelegate { func gotError(error: Error) } +public struct WebsocketSubscription: Subscription { + public var id = "" + private let unsubscribeCallback: (Self) -> Void + + public init(unsubscribeCallback: @escaping (Self) -> Void) { + self.unsubscribeCallback = unsubscribeCallback + } + + public func unsubscribe() throws { + unsubscribeCallback(self) + } +} + /// The default websocket provider. -public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDelegate { +public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, WebSocketDelegate { public func sendAsync(_ request: JSONRPCrequest, queue: DispatchQueue) -> Promise { return Promise(error: Web3Error.inputError(desc: "Sending is unsupported for Websocket provider. Please, use \'sendMessage\'")) @@ -119,6 +132,8 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg private var messagesStringToWrite: [String] = [] private var messagesDataToWrite: [Data] = [] + private var currentID: UInt32 = 0 + public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate, projectId: String? = nil, @@ -202,6 +217,16 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg writeTimer?.invalidate() } + public func newID() -> UInt32 { + currentID = currentID == UInt32.max ? 1 : currentID + 1 + return currentID + } + + public func subscribe(filter: SubscribeEventFilter, + listener: @escaping Web3SubscriptionListener) throws -> Subscription { + fatalError("Not implemented") + } + public func connectSocket() { writeTimer?.invalidate() socket.connect() From 9368301bd3ec15d403dcb5afc7555310aee4fa81 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Fri, 31 Dec 2021 18:02:09 +0200 Subject: [PATCH 04/31] some fixes for subscription provider --- Sources/web3swift/Web3/Web3+InfuraProviders.swift | 8 ++++---- Sources/web3swift/Web3/Web3+SubscriptionProvider.swift | 4 ++-- Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 082b3ee3c..a3a1618f1 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -199,7 +199,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } override public func subscribe(filter: SubscribeEventFilter, - listener: @escaping Web3SubscriptionListener) throws -> Subscription { + listener: @escaping Web3SubscriptionListener) -> Subscription { let params: [Encodable] switch filter { case .newHeads: @@ -212,8 +212,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { params = ["syncing"] } let method = InfuraWebsocketMethod.subscribe - try writeMessage(method: method, params: params) - let subscription = WebsocketSubscription(unsubscribeCallback: { subscription in + var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in let method = InfuraWebsocketMethod.unsubscribe self.send(id: self.newID(), method: method, params: [subscription.id]) { result in switch result { @@ -237,12 +236,13 @@ public final class InfuraWebsocketProvider: WebsocketProvider { switch result { case .success(let data): let subscriptionID = data as! String + subscription.id = subscriptionID self.subscriptions[subscriptionID] = (subscription, handler) case .failure(let error): handler(.failure(error)) } } - fatalError() + return subscription } public func subscribeOnNewHeads() throws { diff --git a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift index 592569be1..5fc1e0242 100644 --- a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift +++ b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift @@ -15,12 +15,12 @@ public enum SubscribeEventFilter { } public protocol Subscription { - func unsubscribe() throws + func unsubscribe() } public typealias Web3SubscriptionListener = (Result) -> Void public protocol Web3SubscriptionProvider: Web3Provider { func subscribe(filter: SubscribeEventFilter, - listener: @escaping Web3SubscriptionListener) throws -> Subscription + listener: @escaping Web3SubscriptionListener) -> Subscription } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index f2a4fcebe..ac01c5476 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -99,7 +99,7 @@ public struct WebsocketSubscription: Subscription { self.unsubscribeCallback = unsubscribeCallback } - public func unsubscribe() throws { + public func unsubscribe() { unsubscribeCallback(self) } } @@ -223,7 +223,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } public func subscribe(filter: SubscribeEventFilter, - listener: @escaping Web3SubscriptionListener) throws -> Subscription { + listener: @escaping Web3SubscriptionListener) -> Subscription { fatalError("Not implemented") } From 98b0796a5232d2f65254e3d385a7c63aa4eec207 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 4 Jan 2022 17:19:48 +0200 Subject: [PATCH 05/31] optional websocket delegator --- .../web3swift/Web3/Web3+InfuraProviders.swift | 31 ++++++++++--------- .../Web3/Web3+WebsocketProvider.swift | 26 ++++++++++++---- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index a3a1618f1..9526810f3 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -48,7 +48,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public var debugMode: Bool = false public init?(_ network: Networks, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { guard network == Networks.Kovan || network == Networks.Rinkeby @@ -64,7 +64,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } public init?(_ endpoint: String, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { guard URL(string: endpoint) != nil else {return nil} super.init(endpoint, @@ -73,7 +73,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } public init?(_ endpoint: URL, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { super.init(endpoint, delegate: delegate, @@ -81,7 +81,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } override public class func connectToSocket(_ endpoint: String, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = InfuraWebsocketProvider(endpoint, @@ -92,7 +92,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } override public class func connectToSocket(_ endpoint: URL, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = InfuraWebsocketProvider(endpoint, @@ -224,8 +224,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) } case .failure(let error): - // TODO: handle error - fatalError("Not implemented") + self.delegate.gotError(error: error) } } }) @@ -327,17 +326,21 @@ public final class InfuraWebsocketProvider: WebsocketProvider { let result = dictionary["result"] as? String { // setting filter id filterID = result - } else if let id = dictionary["id"] as? UInt32 { - if let request = requests.removeValue(forKey: id) { - request(.success(dictionary["result"] as! Decodable)) + } else if let id = dictionary["id"] as? UInt32, + let result = dictionary["result"] as? Decodable { + guard let request = requests.removeValue(forKey: id) else { + delegate.received(message: dictionary) + return } + request(.success(result)) } else if let params = dictionary["params"] as? [String: Any], let subscriptionID = params["subscription"] as? String, - let result = params["result"] { - if let subscription = subscriptions[subscriptionID] { - subscription.cb(.success(result as! Decodable)) + let result = params["result"] as? Decodable { + guard let subscription = subscriptions[subscriptionID] else { + delegate.received(message: dictionary) + return } - delegate.received(message: result) + subscription.cb(.success(result)) } else if let message = dictionary["result"] { // filter result delegate.received(message: message) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index ac01c5476..5365ff643 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -91,6 +91,20 @@ public protocol Web3SocketDelegate { func gotError(error: Error) } +public struct DefaultWeb3SocketDelegate: Web3SocketDelegate { + public func socketConnected(_ headers: [String : String]) { + print("DefaultWeb3SocketDelegate.socketConnected: \(headers)") + } + + public func received(message: Any) { + print("DefaultWeb3SocketDelegate.received: \(message)") + } + + public func gotError(error: Error) { + print("DefaultWeb3SocketDelegate.gotError: \(error)") + } +} + public struct WebsocketSubscription: Subscription { public var id = "" private let unsubscribeCallback: (Self) -> Void @@ -135,7 +149,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We private var currentID: UInt32 = 0 public init?(_ endpoint: URL, - delegate wsdelegate: Web3SocketDelegate, + delegate wsdelegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) { websocketConnected = false @@ -167,14 +181,14 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } } url = URL(string: endpointString)! - delegate = wsdelegate + delegate = wsdelegate ?? DefaultWeb3SocketDelegate() let request = URLRequest(url: url) socket = WebSocket(request: request) socket.delegate = self } public init?(_ endpoint: String, - delegate wsdelegate: Web3SocketDelegate, + delegate wsdelegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) { guard URL(string: endpoint) != nil else {return nil} @@ -207,7 +221,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } } url = URL(string: finalEndpoint)! - delegate = wsdelegate + delegate = wsdelegate ?? DefaultWeb3SocketDelegate() let request = URLRequest(url: url) socket = WebSocket(request: request) socket.delegate = self @@ -242,7 +256,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } public class func connectToSocket(_ endpoint: String, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, @@ -256,7 +270,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } public class func connectToSocket(_ endpoint: URL, - delegate: Web3SocketDelegate, + delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, From 21779a255d16420cc0356d4fe6ed033a635a6185 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 4 Jan 2022 20:52:01 +0200 Subject: [PATCH 06/31] moved common methods to default websocket provider --- .../web3swift/Web3/Web3+InfuraProviders.swift | 165 ++---------------- Sources/web3swift/Web3/Web3+Methods.swift | 4 +- .../Web3/Web3+WebsocketProvider.swift | 129 ++++++++++++-- 3 files changed, 130 insertions(+), 168 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 9526810f3..90e1fbbb2 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -40,13 +40,8 @@ public final class InfuraProvider: Web3HttpProvider { /// Custom Websocket provider of Infura nodes. public final class InfuraWebsocketProvider: WebsocketProvider { public var filterID: String? - public var subscriptions = [String: (sub: WebsocketSubscription, cb: (Result) -> Void)]() - private var requests = [UInt32: (Result) -> Void]() private var filterTimer: Timer? - /// if set debugMode True then show websocket events logs in the console - public var debugMode: Bool = false - public init?(_ network: Networks, delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { @@ -112,25 +107,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { return socketProvider } - public func writeMessage(method: InfuraWebsocketMethod, params: [Encodable]) throws { - let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params) - let encoder = JSONEncoder() - let requestData = try encoder.encode(request) - print(String(decoding: requestData, as: UTF8.self)) - writeMessage(requestData) - } - - private func send(id: UInt32, method: InfuraWebsocketMethod, params: [Encodable], cb: @escaping (Result) -> Void) { - do { - try writeMessage(method: method, params: params) - } catch { - cb(.failure(error)) - return - } - requests[id] = cb - } - - public func setFilterAndGetChanges(method: InfuraWebsocketMethod, params: [Encodable]? = nil) throws { + public func setFilterAndGetChanges(method: WebsocketMethod, params: [Encodable]? = nil) throws { filterTimer?.invalidate() filterID = nil let params = params ?? [] @@ -142,12 +119,12 @@ public final class InfuraWebsocketProvider: WebsocketProvider { filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterChanges), userInfo: nil, repeats: true) } - public func setFilterAndGetChanges(method: InfuraWebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { + public func setFilterAndGetChanges(method: WebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) try setFilterAndGetChanges(method: method, params: [filterParams]) } - public func setFilterAndGetLogs(method: InfuraWebsocketMethod, params: [Encodable]? = nil) throws { + public func setFilterAndGetLogs(method: WebsocketMethod, params: [Encodable]? = nil) throws { filterTimer?.invalidate() filterID = nil let params = params ?? [] @@ -159,7 +136,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterLogs), userInfo: nil, repeats: true) } - public func setFilterAndGetLogs(method: InfuraWebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { + public func setFilterAndGetLogs(method: WebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) try setFilterAndGetLogs(method: method, params: [filterParams]) } @@ -167,7 +144,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { @objc public func getFilterChanges() throws { if let id = filterID { filterTimer?.invalidate() - let method = InfuraWebsocketMethod.getFilterChanges + let method = WebsocketMethod.getFilterChanges try writeMessage(method: method, params: [id]) } } @@ -175,7 +152,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { @objc public func getFilterLogs() throws { if let id = filterID { filterTimer?.invalidate() - let method = InfuraWebsocketMethod.getFilterLogs + let method = WebsocketMethod.getFilterLogs try writeMessage(method: method, params: [id]) } } @@ -183,7 +160,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public func getFilterLogs(address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { if let id = filterID { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) - let method = InfuraWebsocketMethod.getFilterLogs + let method = WebsocketMethod.getFilterLogs try writeMessage(method: method, params: [id, filterParams]) } } @@ -191,67 +168,21 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public func unistallFilter() throws { if let id = filterID { filterID = nil - let method = InfuraWebsocketMethod.uninstallFilter + let method = WebsocketMethod.uninstallFilter try writeMessage(method: method, params: [id]) } else { throw Web3Error.nodeError(desc: "No filter set") } } - override public func subscribe(filter: SubscribeEventFilter, - listener: @escaping Web3SubscriptionListener) -> Subscription { - let params: [Encodable] - switch filter { - case .newHeads: - params = ["newHeads"] - case .logs(let p): - params = ["logs", p] - case .newPendingTransactions: - params = ["newPendingTransactions"] - case .syncing: - params = ["syncing"] - } - let method = InfuraWebsocketMethod.subscribe - var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in - let method = InfuraWebsocketMethod.unsubscribe - self.send(id: self.newID(), method: method, params: [subscription.id]) { result in - switch result { - case .success(let data): - let unsubscribed = data as! Bool - if unsubscribed { - self.subscriptions.removeValue(forKey: subscription.id) - } else { - self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) - } - case .failure(let error): - self.delegate.gotError(error: error) - } - } - }) - let handler = { (result: Result) in - listener(result.map { $0 as! R }) - } - send(id: newID(), method: method, params: params) { result in - switch result { - case .success(let data): - let subscriptionID = data as! String - subscription.id = subscriptionID - self.subscriptions[subscriptionID] = (subscription, handler) - case .failure(let error): - handler(.failure(error)) - } - } - return subscription - } - public func subscribeOnNewHeads() throws { - let method = InfuraWebsocketMethod.subscribe + let method = WebsocketMethod.subscribe let params = ["newHeads"] try writeMessage(method: method, params: params) } public func subscribeOnLogs(addresses: [EthereumAddress]? = nil, topics: [String]? = nil) throws { - let method = InfuraWebsocketMethod.subscribe + let method = WebsocketMethod.subscribe var stringAddresses = [String]() if let addrs = addresses { for addr in addrs { @@ -264,7 +195,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } public func subscribeOnNewPendingTransactions() throws { - let method = InfuraWebsocketMethod.subscribe + let method = WebsocketMethod.subscribe let params = ["newPendingTransactions"] try writeMessage(method: method, params: params) } @@ -273,80 +204,8 @@ public final class InfuraWebsocketProvider: WebsocketProvider { guard network != Networks.Kovan else { throw Web3Error.inputError(desc: "Can't sync on Kovan") } - let method = InfuraWebsocketMethod.subscribe + let method = WebsocketMethod.subscribe let params = ["syncing"] try writeMessage(method: method, params: params) } - - /// override WebsocketDelegate - override public func didReceive(event: WebSocketEvent, client: WebSocket) { - switch event { - case .connected(let headers): - debugMode ? print("websocket is connected, headers:\n \(headers)") : nil - websocketConnected = true - delegate.socketConnected(headers) - case .disconnected(let reason, let code): - debugMode ? print("websocket is disconnected: \(reason) with code: \(code)") : nil - websocketConnected = false - delegate.gotError(error: Web3Error.connectionError) - case .text(let string): - debugMode ? print("received text: \(string)") : nil - websocketDidReceiveMessage(text: string) - break - case .binary(let data): - debugMode ? print("received text: \(String(data: data, encoding: .utf8) ?? "empty")") : nil - delegate.received(message: data) - case .ping(_): - debugMode ? print("ping") : nil - break - case .pong(_): - debugMode ? print("pong") : nil - break - case .viabilityChanged(_): - debugMode ? print("viabilityChanged") : nil - break - case .reconnectSuggested(_): - debugMode ? print("reconnectSuggested") : nil - break - case .cancelled: - debugMode ? print("cancelled") : nil - websocketConnected = false - delegate.gotError(error: Web3Error.nodeError(desc: "socket cancelled")) - case .error(let error): - debugMode ? print("error: \(String(describing: error))") : nil - websocketConnected = false - delegate.gotError(error: error!) - } - } - - private func websocketDidReceiveMessage(text: String) { - if let data = text.data(using: String.Encoding.utf8), - let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { - if filterID == nil, - let result = dictionary["result"] as? String { - // setting filter id - filterID = result - } else if let id = dictionary["id"] as? UInt32, - let result = dictionary["result"] as? Decodable { - guard let request = requests.removeValue(forKey: id) else { - delegate.received(message: dictionary) - return - } - request(.success(result)) - } else if let params = dictionary["params"] as? [String: Any], - let subscriptionID = params["subscription"] as? String, - let result = params["result"] as? Decodable { - guard let subscription = subscriptions[subscriptionID] else { - delegate.received(message: dictionary) - return - } - subscription.cb(.success(result)) - } else if let message = dictionary["result"] { - // filter result - delegate.received(message: message) - } else { - delegate.gotError(error: Web3Error.processingError(desc: "Can\'t get known result. Message is: \(text)")) - } - } - } } diff --git a/Sources/web3swift/Web3/Web3+Methods.swift b/Sources/web3swift/Web3/Web3+Methods.swift index f3ab0f28b..6f9be58f9 100755 --- a/Sources/web3swift/Web3/Web3+Methods.swift +++ b/Sources/web3swift/Web3/Web3+Methods.swift @@ -80,8 +80,8 @@ public struct JSONRPCRequestFabric { return request } - public static func prepareRequest(_ method: InfuraWebsocketMethod, parameters: [Encodable]) -> InfuraWebsocketRequest { - var request = InfuraWebsocketRequest() + public static func prepareRequest(_ method: WebsocketMethod, parameters: [Encodable]) -> WebsocketRequest { + var request = WebsocketRequest() request.method = method let pars = JSONRPCparams(params: parameters) request.params = pars diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 5365ff643..ceb0f4827 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -18,7 +18,7 @@ public protocol IWebsocketProvider { func writeMessage(_ message: T) } -public enum InfuraWebsocketMethod: String, Encodable { +public enum WebsocketMethod: String, Encodable { case newPendingTransactionFilter = "eth_newPendingTransactionFilter" case getFilterChanges = "eth_getFilterChanges" @@ -53,9 +53,9 @@ public enum InfuraWebsocketMethod: String, Encodable { } } -public struct InfuraWebsocketRequest: Encodable { +public struct WebsocketRequest: Encodable { public var jsonrpc: String = "2.0" - public var method: InfuraWebsocketMethod? + public var method: WebsocketMethod? public var params: JSONRPCparams? public var id: UInt64 = Counter.increment() @@ -87,7 +87,6 @@ public struct InfuraWebsocketRequest: Encodable { public protocol Web3SocketDelegate { func socketConnected(_ headers: [String:String]) - func received(message: Any) func gotError(error: Error) } @@ -96,10 +95,6 @@ public struct DefaultWeb3SocketDelegate: Web3SocketDelegate { print("DefaultWeb3SocketDelegate.socketConnected: \(headers)") } - public func received(message: Any) { - print("DefaultWeb3SocketDelegate.received: \(message)") - } - public func gotError(error: Error) { print("DefaultWeb3SocketDelegate.gotError: \(error)") } @@ -146,6 +141,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We private var messagesStringToWrite: [String] = [] private var messagesDataToWrite: [Data] = [] + /// if set debugMode True then show websocket events logs in the console + public var debugMode: Bool = false + + private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() + private var requests = [UInt32: (Swift.Result) -> Void]() + private var currentID: UInt32 = 0 public init?(_ endpoint: URL, @@ -231,14 +232,55 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer?.invalidate() } - public func newID() -> UInt32 { + private func newID() -> UInt32 { currentID = currentID == UInt32.max ? 1 : currentID + 1 return currentID } public func subscribe(filter: SubscribeEventFilter, listener: @escaping Web3SubscriptionListener) -> Subscription { - fatalError("Not implemented") + let params: [Encodable] + switch filter { + case .newHeads: + params = ["newHeads"] + case .logs(let p): + params = ["logs", p] + case .newPendingTransactions: + params = ["newPendingTransactions"] + case .syncing: + params = ["syncing"] + } + let method = WebsocketMethod.subscribe + var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in + let method = WebsocketMethod.unsubscribe + self.send(id: self.newID(), method: method, params: [subscription.id]) { result in + switch result { + case .success(let data): + let unsubscribed = data as! Bool + if unsubscribed { + self.subscriptions.removeValue(forKey: subscription.id) + } else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) + } + case .failure(let error): + self.delegate.gotError(error: error) + } + } + }) + let handler = { (result: Swift.Result) in + listener(result.map { $0 as! R }) + } + send(id: newID(), method: method, params: params) { result in + switch result { + case .success(let data): + let subscriptionID = data as! String + subscription.id = subscriptionID + self.subscriptions[subscriptionID] = (subscription, handler) + case .failure(let error): + handler(.failure(error)) + } + } + return subscription } public func connectSocket() { @@ -301,6 +343,27 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(performWriteOperations), userInfo: nil, repeats: true) } + public func writeMessage(method: WebsocketMethod, params: [Encodable]) throws { + let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params) + let encoder = JSONEncoder() + let requestData = try encoder.encode(request) + print(String(decoding: requestData, as: UTF8.self)) + writeMessage(requestData) + } + + private func send(id: UInt32, + method: WebsocketMethod, + params: [Encodable], + cb: @escaping (Swift.Result) -> Void) { + do { + try writeMessage(method: method, params: params) + } catch { + cb(.failure(error)) + return + } + requests[id] = cb + } + @objc private func performWriteOperations() { if websocketConnected { writeTimer?.invalidate() @@ -316,31 +379,71 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public func didReceive(event: WebSocketEvent, client: WebSocket) { switch event { case .connected(let headers): + debugMode ? print("websocket is connected, headers:\n \(headers)") : nil websocketConnected = true delegate.socketConnected(headers) case .disconnected(let reason, let code): - print("socket disconnected: \(reason) , code: \(code)") + debugMode ? print("websocket is disconnected: \(reason) with code: \(code)") : nil websocketConnected = false delegate.gotError(error: Web3Error.connectionError) case .text(let string): - delegate.received(message: string) + debugMode ? print("received text: \(string)") : nil + websocketDidReceiveMessage(text: string) break case .binary(let data): - delegate.received(message: data) + debugMode ? print("received text: \(String(data: data, encoding: .utf8) ?? "empty")") : nil + delegate.gotError(error: Web3Error.processingError(desc: "Unsupported data type")) case .ping(_): + debugMode ? print("ping") : nil break case .pong(_): + debugMode ? print("pong") : nil break case .viabilityChanged(_): + debugMode ? print("viabilityChanged") : nil break case .reconnectSuggested(_): + debugMode ? print("reconnectSuggested") : nil break case .cancelled: + debugMode ? print("cancelled") : nil websocketConnected = false delegate.gotError(error: Web3Error.nodeError(desc: "socket cancelled")) case .error(let error): + debugMode ? print("error: \(String(describing: error))") : nil websocketConnected = false delegate.gotError(error: error!) } } + + private func websocketDidReceiveMessage(text: String) { + if let data = text.data(using: String.Encoding.utf8), + let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + // TODO: filterID +// if filterID == nil, +// let result = dictionary["result"] as? String { +// // setting filter id +// filterID = result +// } else + if let id = dictionary["id"] as? UInt32, + let result = dictionary["result"] as? Decodable { + guard let request = requests.removeValue(forKey: id) else { + return + } + request(.success(result)) + } else if let params = dictionary["params"] as? [String: Any], + let subscriptionID = params["subscription"] as? String, + let result = params["result"] as? Decodable { + guard let subscription = subscriptions[subscriptionID] else { + return + } + subscription.cb(.success(result)) + } else if let message = dictionary["result"] { + // filter result + // TODO: process filter result + } else { + delegate.gotError(error: Web3Error.processingError(desc: "Can\'t get known result. Message is: \(text)")) + } + } + } } From d65eae1d7219f2024a6064c393ded6c545d8383f Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:47:15 +0200 Subject: [PATCH 07/31] merged jsonrpc methods --- .../web3swift/Web3/Web3+InfuraProviders.swift | 24 ++--- Sources/web3swift/Web3/Web3+Methods.swift | 35 +++++-- .../Web3/Web3+WebsocketProvider.swift | 97 +++---------------- 3 files changed, 49 insertions(+), 107 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 90e1fbbb2..6406c8c61 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -107,7 +107,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { return socketProvider } - public func setFilterAndGetChanges(method: WebsocketMethod, params: [Encodable]? = nil) throws { + public func setFilterAndGetChanges(method: JSONRPCmethod, params: [Encodable]? = nil) throws { filterTimer?.invalidate() filterID = nil let params = params ?? [] @@ -119,12 +119,12 @@ public final class InfuraWebsocketProvider: WebsocketProvider { filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterChanges), userInfo: nil, repeats: true) } - public func setFilterAndGetChanges(method: WebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { + public func setFilterAndGetChanges(method: JSONRPCmethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) try setFilterAndGetChanges(method: method, params: [filterParams]) } - public func setFilterAndGetLogs(method: WebsocketMethod, params: [Encodable]? = nil) throws { + public func setFilterAndGetLogs(method: JSONRPCmethod, params: [Encodable]? = nil) throws { filterTimer?.invalidate() filterID = nil let params = params ?? [] @@ -136,7 +136,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterLogs), userInfo: nil, repeats: true) } - public func setFilterAndGetLogs(method: WebsocketMethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { + public func setFilterAndGetLogs(method: JSONRPCmethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) try setFilterAndGetLogs(method: method, params: [filterParams]) } @@ -144,7 +144,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { @objc public func getFilterChanges() throws { if let id = filterID { filterTimer?.invalidate() - let method = WebsocketMethod.getFilterChanges + let method = JSONRPCmethod.getFilterChanges try writeMessage(method: method, params: [id]) } } @@ -152,7 +152,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { @objc public func getFilterLogs() throws { if let id = filterID { filterTimer?.invalidate() - let method = WebsocketMethod.getFilterLogs + let method = JSONRPCmethod.getFilterLogs try writeMessage(method: method, params: [id]) } } @@ -160,7 +160,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public func getFilterLogs(address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { if let id = filterID { let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) - let method = WebsocketMethod.getFilterLogs + let method = JSONRPCmethod.getFilterLogs try writeMessage(method: method, params: [id, filterParams]) } } @@ -168,7 +168,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { public func unistallFilter() throws { if let id = filterID { filterID = nil - let method = WebsocketMethod.uninstallFilter + let method = JSONRPCmethod.uninstallFilter try writeMessage(method: method, params: [id]) } else { throw Web3Error.nodeError(desc: "No filter set") @@ -176,13 +176,13 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } public func subscribeOnNewHeads() throws { - let method = WebsocketMethod.subscribe + let method = JSONRPCmethod.subscribe let params = ["newHeads"] try writeMessage(method: method, params: params) } public func subscribeOnLogs(addresses: [EthereumAddress]? = nil, topics: [String]? = nil) throws { - let method = WebsocketMethod.subscribe + let method = JSONRPCmethod.subscribe var stringAddresses = [String]() if let addrs = addresses { for addr in addrs { @@ -195,7 +195,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { } public func subscribeOnNewPendingTransactions() throws { - let method = WebsocketMethod.subscribe + let method = JSONRPCmethod.subscribe let params = ["newPendingTransactions"] try writeMessage(method: method, params: params) } @@ -204,7 +204,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { guard network != Networks.Kovan else { throw Web3Error.inputError(desc: "Can't sync on Kovan") } - let method = WebsocketMethod.subscribe + let method = JSONRPCmethod.subscribe let params = ["syncing"] try writeMessage(method: method, params: params) } diff --git a/Sources/web3swift/Web3/Web3+Methods.swift b/Sources/web3swift/Web3/Web3+Methods.swift index 6f9be58f9..124eb33fd 100755 --- a/Sources/web3swift/Web3/Web3+Methods.swift +++ b/Sources/web3swift/Web3/Web3+Methods.swift @@ -31,9 +31,16 @@ public enum JSONRPCmethod: String, Encodable { case getTxPoolInspect = "txpool_inspect" case getTxPoolStatus = "txpool_status" case getTxPoolContent = "txpool_content" - + case newPendingTransactionFilter = "eth_newPendingTransactionFilter" + case getFilterChanges = "eth_getFilterChanges" + case newFilter = "eth_newFilter" + case newBlockFilter = "eth_newBlockFilter" + case getFilterLogs = "eth_getFilterLogs" + case uninstallFilter = "eth_uninstallFilter" + case subscribe = "eth_subscribe" + case unsubscribe = "eth_unsubscribe" - public var requiredNumOfParameters: Int { + public var requiredNumOfParameters: Int? { get { switch self { case .call: @@ -64,6 +71,22 @@ public enum JSONRPCmethod: String, Encodable { return 0 case .getTxPoolInspect: return 0 + case .newPendingTransactionFilter: + return 0 + case .getFilterChanges: + return 1 + case .newFilter: + return nil + case .newBlockFilter: + return 0 + case .getFilterLogs: + return nil + case .uninstallFilter: + return 1 + case .subscribe: + return nil + case .unsubscribe: + return 1 default: return 1 } @@ -79,12 +102,4 @@ public struct JSONRPCRequestFabric { request.params = pars return request } - - public static func prepareRequest(_ method: WebsocketMethod, parameters: [Encodable]) -> WebsocketRequest { - var request = WebsocketRequest() - request.method = method - let pars = JSONRPCparams(params: parameters) - request.params = pars - return request - } } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index ceb0f4827..ff1fa4b4b 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -18,73 +18,6 @@ public protocol IWebsocketProvider { func writeMessage(_ message: T) } -public enum WebsocketMethod: String, Encodable { - - case newPendingTransactionFilter = "eth_newPendingTransactionFilter" - case getFilterChanges = "eth_getFilterChanges" - case newFilter = "eth_newFilter" - case newBlockFilter = "eth_newBlockFilter" - case getFilterLogs = "eth_getFilterLogs" - case uninstallFilter = "eth_uninstallFilter" - case subscribe = "eth_subscribe" - case unsubscribe = "eth_unsubscribe" - - public var requiredNumOfParameters: Int? { - get { - switch self { - case .newPendingTransactionFilter: - return 0 - case .getFilterChanges: - return 1 - case .newFilter: - return nil - case .newBlockFilter: - return 0 - case .getFilterLogs: - return nil - case .uninstallFilter: - return 1 - case .subscribe: - return nil - case .unsubscribe: - return 1 - } - } - } -} - -public struct WebsocketRequest: Encodable { - public var jsonrpc: String = "2.0" - public var method: WebsocketMethod? - public var params: JSONRPCparams? - public var id: UInt64 = Counter.increment() - - enum CodingKeys: String, CodingKey { - case jsonrpc - case method - case params - case id - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(jsonrpc, forKey: .jsonrpc) - try container.encode(method?.rawValue, forKey: .method) - try container.encode(params, forKey: .params) - try container.encode(id, forKey: .id) - } - - public var isValid: Bool { - get { - if self.method == nil { - return false - } - guard let method = self.method else {return false} - return method.requiredNumOfParameters == self.params?.params.count - } - } -} - public protocol Web3SocketDelegate { func socketConnected(_ headers: [String:String]) func gotError(error: Error) @@ -145,9 +78,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public var debugMode: Bool = false private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() - private var requests = [UInt32: (Swift.Result) -> Void]() - - private var currentID: UInt32 = 0 + private var requests = [UInt64: (Swift.Result) -> Void]() public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate? = nil, @@ -232,11 +163,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer?.invalidate() } - private func newID() -> UInt32 { - currentID = currentID == UInt32.max ? 1 : currentID + 1 - return currentID - } - public func subscribe(filter: SubscribeEventFilter, listener: @escaping Web3SubscriptionListener) -> Subscription { let params: [Encodable] @@ -250,10 +176,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We case .syncing: params = ["syncing"] } - let method = WebsocketMethod.subscribe + let method = JSONRPCmethod.subscribe var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in - let method = WebsocketMethod.unsubscribe - self.send(id: self.newID(), method: method, params: [subscription.id]) { result in + let method = JSONRPCmethod.unsubscribe + self.send(method: method, params: [subscription.id]) { result in switch result { case .success(let data): let unsubscribed = data as! Bool @@ -270,7 +196,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We let handler = { (result: Swift.Result) in listener(result.map { $0 as! R }) } - send(id: newID(), method: method, params: params) { result in + send(method: method, params: params) { result in switch result { case .success(let data): let subscriptionID = data as! String @@ -343,25 +269,26 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(performWriteOperations), userInfo: nil, repeats: true) } - public func writeMessage(method: WebsocketMethod, params: [Encodable]) throws { + public func writeMessage(method: JSONRPCmethod, params: [Encodable]) throws -> JSONRPCrequest { let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params) let encoder = JSONEncoder() let requestData = try encoder.encode(request) print(String(decoding: requestData, as: UTF8.self)) writeMessage(requestData) + return request } - private func send(id: UInt32, - method: WebsocketMethod, + private func send(method: JSONRPCmethod, params: [Encodable], cb: @escaping (Swift.Result) -> Void) { + let request: JSONRPCrequest do { - try writeMessage(method: method, params: params) + request = try writeMessage(method: method, params: params) } catch { cb(.failure(error)) return } - requests[id] = cb + requests[request.id] = cb } @objc private func performWriteOperations() { @@ -425,7 +352,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We // // setting filter id // filterID = result // } else - if let id = dictionary["id"] as? UInt32, + if let id = dictionary["id"] as? UInt64, let result = dictionary["result"] as? Decodable { guard let request = requests.removeValue(forKey: id) else { return From c716c265923107c72ccf1bd53f18bea0011c83c4 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 4 Jan 2022 23:55:17 +0200 Subject: [PATCH 08/31] added verification for supported request methods --- .../web3swift/Promises/Promise+HttpProvider.swift | 12 +++++++++++- Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Sources/web3swift/Promises/Promise+HttpProvider.swift b/Sources/web3swift/Promises/Promise+HttpProvider.swift index 94b1b5a50..4a4a358a4 100755 --- a/Sources/web3swift/Promises/Promise+HttpProvider.swift +++ b/Sources/web3swift/Promises/Promise+HttpProvider.swift @@ -98,11 +98,21 @@ extension Web3HttpProvider { if request.method == nil { return Promise(error: Web3Error.nodeError(desc: "RPC method is nill")) } - + guard ![.subscribe, .unsubscribe].contains(request.method!) else { + return Promise(error: Web3Error.inputError(desc: "Unsupported method \(request.method!)")) + } return Web3HttpProvider.post(request, providerURL: self.url, queue: queue, session: self.session) } public func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue = .main) -> Promise { + guard requests.requests.allSatisfy({ + guard let method = $0.method else { + return true + } + return ![.subscribe, .unsubscribe].contains(method) + }) else { + return Promise(error: Web3Error.inputError(desc: "Unsupported method in requests")) + } return Web3HttpProvider.post(requests, providerURL: self.url, queue: queue, session: self.session) } } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index ff1fa4b4b..e175e3b18 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -281,6 +281,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We private func send(method: JSONRPCmethod, params: [Encodable], cb: @escaping (Swift.Result) -> Void) { + guard [.subscribe, .unsubscribe].contains(method) else { + cb(.failure(Web3Error.inputError(desc: "Unsupported method \(method)"))) + return + } let request: JSONRPCrequest do { request = try writeMessage(method: method, params: params) From e0bff8dfb19a2aecebe3e7358667222047bfb47d Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Wed, 5 Jan 2022 22:05:53 +0200 Subject: [PATCH 09/31] implemented sendAsync in WebsocketProvider --- .../Web3/Web3+WebsocketProvider.swift | 99 +++++++++++-------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index e175e3b18..ebd089969 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -50,7 +50,23 @@ public struct WebsocketSubscription: Subscription { public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, WebSocketDelegate { public func sendAsync(_ request: JSONRPCrequest, queue: DispatchQueue) -> Promise { - return Promise(error: Web3Error.inputError(desc: "Sending is unsupported for Websocket provider. Please, use \'sendMessage\'")) + guard let method = request.method else { + return Promise(error: Web3Error.inputError(desc: "No method in request: \(request)")) + } + guard [.subscribe, .unsubscribe].contains(method) else { + return Promise(error: Web3Error.inputError(desc: "Unsupported method: \(method)")) + } + return Promise { resolver in + let requestData = try JSONEncoder().encode(request) + print(String(decoding: requestData, as: UTF8.self)) + writeMessage(requestData) + requests[request.id] = { result in + switch result { + case .success(let response): resolver.fulfill(response) + case .failure(let error): resolver.reject(error) + } + } + } } public func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue) -> Promise { @@ -67,6 +83,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public var socket: WebSocket public var delegate: Web3SocketDelegate + private var queue: DispatchQueue? = nil /// A flag that is true if socket connected or false if socket doesn't connected. public var websocketConnected: Bool = false @@ -78,7 +95,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public var debugMode: Bool = false private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() - private var requests = [UInt64: (Swift.Result) -> Void]() + private var requests = [UInt64: (Swift.Result) -> Void]() public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate? = nil, @@ -163,6 +180,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer?.invalidate() } + public func setQueue(queue: DispatchQueue) { + self.queue = queue + } + public func subscribe(filter: SubscribeEventFilter, listener: @escaping Web3SubscriptionListener) -> Subscription { let params: [Encodable] @@ -176,34 +197,39 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We case .syncing: params = ["syncing"] } - let method = JSONRPCmethod.subscribe var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in - let method = JSONRPCmethod.unsubscribe - self.send(method: method, params: [subscription.id]) { result in + let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.unsubscribe, parameters: [subscription.id]) + self.sendAsync(request, queue: self.queue!).pipe { result in switch result { - case .success(let data): - let unsubscribed = data as! Bool + case .fulfilled(let response): + guard let unsubscribed = response.result as? Bool else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) + return + } if unsubscribed { self.subscriptions.removeValue(forKey: subscription.id) } else { self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) } - case .failure(let error): + case .rejected(let error): self.delegate.gotError(error: error) } } }) - let handler = { (result: Swift.Result) in - listener(result.map { $0 as! R }) - } - send(method: method, params: params) { result in + let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: params) + sendAsync(request, queue: queue!).pipe { result in switch result { - case .success(let data): - let subscriptionID = data as! String + case .fulfilled(let response): + guard let subscriptionID = response.result as? String else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) + return + } subscription.id = subscriptionID - self.subscriptions[subscriptionID] = (subscription, handler) - case .failure(let error): - handler(.failure(error)) + self.subscriptions[subscriptionID] = (subscription, { result in + listener(result.map { $0 as! R }) + }) + case .rejected(let error): + listener(.failure(error)) } } return subscription @@ -269,30 +295,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We writeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(performWriteOperations), userInfo: nil, repeats: true) } - public func writeMessage(method: JSONRPCmethod, params: [Encodable]) throws -> JSONRPCrequest { + public func writeMessage(method: JSONRPCmethod, params: [Encodable]) throws { let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params) let encoder = JSONEncoder() let requestData = try encoder.encode(request) print(String(decoding: requestData, as: UTF8.self)) writeMessage(requestData) - return request - } - - private func send(method: JSONRPCmethod, - params: [Encodable], - cb: @escaping (Swift.Result) -> Void) { - guard [.subscribe, .unsubscribe].contains(method) else { - cb(.failure(Web3Error.inputError(desc: "Unsupported method \(method)"))) - return - } - let request: JSONRPCrequest - do { - request = try writeMessage(method: method, params: params) - } catch { - cb(.failure(error)) - return - } - requests[request.id] = cb } @objc private func performWriteOperations() { @@ -356,12 +364,23 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We // // setting filter id // filterID = result // } else - if let id = dictionary["id"] as? UInt64, - let result = dictionary["result"] as? Decodable { - guard let request = requests.removeValue(forKey: id) else { + if let _ = dictionary["id"] as? UInt64 { + let response: JSONRPCresponse + do { + response = try JSONDecoder().decode(JSONRPCresponse.self, from: data) + } catch { + delegate.gotError(error: error) return } - request(.success(result)) + if let request = requests.removeValue(forKey: UInt64(response.id)) { + if let error = response.error { + request(.failure(Web3Error.nodeError(desc: "Received an error message\n" + String(describing: error)))) + } else { + request(.success(response)) + } + } else { + delegate.gotError(error: Web3Error.processingError(desc: "Unknown response id. Message is: \(text)")) + } } else if let params = dictionary["params"] as? [String: Any], let subscriptionID = params["subscription"] as? String, let result = params["result"] as? Decodable { From a8a2120d58648e168850784b9fc808dde4bf5959 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 6 Jan 2022 00:06:45 +0200 Subject: [PATCH 10/31] implemented sendAsync for batch requests --- Sources/web3swift/Web3/Web3+JSONRPC.swift | 4 ++++ Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index 283ecbaf6..e64ced0f8 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -227,6 +227,10 @@ public struct JSONRPCresponse: Decodable{ public struct JSONRPCresponseBatch: Decodable { var responses: [JSONRPCresponse] + public init(responses: [JSONRPCresponse]) { + self.responses = responses + } + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let responses = try container.decode([JSONRPCresponse].self) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index ebd089969..462fc45de 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -70,7 +70,9 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } public func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue) -> Promise { - return Promise(error: Web3Error.inputError(desc: "Sending is unsupported for Websocket provider. Please, use \'sendMessage\'")) + when(fulfilled: requests.requests.map { sendAsync($0, queue: queue) }).map { responses in + JSONRPCresponseBatch(responses: responses) + } } public var network: Networks? From 0ed27cfea639ff2183db751f54920746dab22a66 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 6 Jan 2022 22:00:27 +0200 Subject: [PATCH 11/31] added filter requests --- .../Promises/Promise+Web3+Eth+Filters.swift | 72 +++++++++++++++ .../web3swift/Web3/Web3+Eth+Websocket.swift | 5 - .../web3swift/Web3/Web3+InfuraProviders.swift | 92 ------------------- Sources/web3swift/Web3/Web3+JSONRPC.swift | 2 + Sources/web3swift/Web3/Web3+Structures.swift | 14 +++ .../Web3/Web3+WebsocketProvider.swift | 11 +-- .../web3swift_Websockets_Tests.swift | 24 ----- 7 files changed, 89 insertions(+), 131 deletions(-) create mode 100644 Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift new file mode 100644 index 000000000..3728a9a06 --- /dev/null +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift @@ -0,0 +1,72 @@ +// +// Promise+Web3+Eth+Filters.swift +// +// +// Created by Ostap Danylovych on 06.01.2022. +// + +import Foundation +import PromiseKit +import BigInt + +public enum BlockNumber { + case pending + case latest + case earliest + case exact(BigUInt) + + public var stringValue: String { + switch self { + case .pending: + return "pending" + case .latest: + return "latest" + case .earliest: + return "earliest" + case .exact(let number): + return String(number, radix: 16).addHexPrefix() + } + } +} + +extension web3.Eth { + private func dispatchRequest(_ request: JSONRPCrequest) -> Promise { + web3.dispatch(request).map(on: web3.requestDispatcher.queue) { response in + guard let value: T = response.getValue() else { + if response.error != nil { + throw Web3Error.nodeError(desc: response.error!.message) + } + throw Web3Error.nodeError(desc: "Invalid value from Ethereum node") + } + return value + } + } + + public func newFilterPromise(addresses: [EthereumAddress], + fromBlock: BlockNumber? = .latest, + toBlock: BlockNumber? = .latest, + topics: [String]) -> Promise { + let addresses = addresses.map { $0.address.lowercased() } + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newFilter, parameters: [addresses, fromBlock?.stringValue, toBlock?.stringValue, topics])) + } + + public func newBlockFilterPromise() -> Promise { + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newBlockFilter, parameters: [])) + } + + public func newPendingTransactionFilterPromise() -> Promise { + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newPendingTransactionFilter, parameters: [])) + } + + public func uninstallFilterPromise(filterID: String) -> Promise { + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.uninstallFilter, parameters: [filterID])) + } + + public func getFilterChangesPromise(filterID: String) -> Promise { + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterChanges, parameters: [filterID])) + } + + public func getFilterLogsPromise(filterID: String) -> Promise { + return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterLogs, parameters: [filterID])) + } +} diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 2d7debea8..559c44276 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -30,11 +30,6 @@ extension web3.Eth { return infuraWSProvider } - public func getLatestPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws { - let provider = try getWebsocketProvider(forDelegate: delegate) - try provider.setFilterAndGetChanges(method: .newPendingTransactionFilter) - } - public func subscribeOnPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws { let provider = try getWebsocketProvider(forDelegate: delegate) try provider.subscribeOnNewPendingTransactions() diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 6406c8c61..88a2a082d 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -4,29 +4,8 @@ // Copyright © 2018 Alex Vlasov. All rights reserved. // import Foundation -import BigInt import Starscream -public enum BlockNumber { - case pending - case latest - case earliest - case exact(BigUInt) - - public var stringValue: String { - switch self { - case .pending: - return "pending" - case .latest: - return "latest" - case .earliest: - return "earliest" - case .exact(let number): - return String(number, radix: 16).addHexPrefix() - } - } -} - /// Custom Web3 HTTP provider of Infura nodes. public final class InfuraProvider: Web3HttpProvider { public init?(_ net:Networks, accessToken token: String? = nil) { @@ -39,9 +18,6 @@ public final class InfuraProvider: Web3HttpProvider { /// Custom Websocket provider of Infura nodes. public final class InfuraWebsocketProvider: WebsocketProvider { - public var filterID: String? - private var filterTimer: Timer? - public init?(_ network: Networks, delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { @@ -107,74 +83,6 @@ public final class InfuraWebsocketProvider: WebsocketProvider { return socketProvider } - public func setFilterAndGetChanges(method: JSONRPCmethod, params: [Encodable]? = nil) throws { - filterTimer?.invalidate() - filterID = nil - let params = params ?? [] - let paramsCount = params.count - guard method.requiredNumOfParameters == paramsCount || method.requiredNumOfParameters == nil else { - throw Web3Error.inputError(desc: "Wrong number of params: need - \(method.requiredNumOfParameters!), got - \(paramsCount)") - } - try writeMessage(method: method, params: params) - filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterChanges), userInfo: nil, repeats: true) - } - - public func setFilterAndGetChanges(method: JSONRPCmethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { - let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) - try setFilterAndGetChanges(method: method, params: [filterParams]) - } - - public func setFilterAndGetLogs(method: JSONRPCmethod, params: [Encodable]? = nil) throws { - filterTimer?.invalidate() - filterID = nil - let params = params ?? [] - let paramsCount = params.count - guard method.requiredNumOfParameters == paramsCount || method.requiredNumOfParameters == nil else { - throw Web3Error.inputError(desc: "Wrong number of params: need - \(method.requiredNumOfParameters!), got - \(paramsCount)") - } - try writeMessage(method: method, params: params) - filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterLogs), userInfo: nil, repeats: true) - } - - public func setFilterAndGetLogs(method: JSONRPCmethod, address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { - let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) - try setFilterAndGetLogs(method: method, params: [filterParams]) - } - - @objc public func getFilterChanges() throws { - if let id = filterID { - filterTimer?.invalidate() - let method = JSONRPCmethod.getFilterChanges - try writeMessage(method: method, params: [id]) - } - } - - @objc public func getFilterLogs() throws { - if let id = filterID { - filterTimer?.invalidate() - let method = JSONRPCmethod.getFilterLogs - try writeMessage(method: method, params: [id]) - } - } - - public func getFilterLogs(address: EthereumAddress? = nil, fromBlock: BlockNumber? = nil, toBlock: BlockNumber? = nil, topics: [String]? = nil) throws { - if let id = filterID { - let filterParams = EventFilterParameters(fromBlock: fromBlock?.stringValue, toBlock: toBlock?.stringValue, topics: [topics], address: [address?.address]) - let method = JSONRPCmethod.getFilterLogs - try writeMessage(method: method, params: [id, filterParams]) - } - } - - public func unistallFilter() throws { - if let id = filterID { - filterID = nil - let method = JSONRPCmethod.uninstallFilter - try writeMessage(method: method, params: [id]) - } else { - throw Web3Error.nodeError(desc: "No filter set") - } - } - public func subscribeOnNewHeads() throws { let method = JSONRPCmethod.subscribe let params = ["newHeads"] diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index e64ced0f8..428d5547c 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -160,6 +160,8 @@ public struct JSONRPCresponse: Decodable{ result = rawValue } else if let rawValue = try? container.decodeIfPresent([String:[String:[String:[String:String?]]]].self, forKey: .result) { result = rawValue + } else if let rawValue = try? container.decodeIfPresent(FilterChanges.self, forKey: .result) { + result = rawValue } self.init(id: id, jsonrpc: jsonrpc, result: result, error: nil) } diff --git a/Sources/web3swift/Web3/Web3+Structures.swift b/Sources/web3swift/Web3/Web3+Structures.swift index 32180fbcc..7a0084118 100755 --- a/Sources/web3swift/Web3/Web3+Structures.swift +++ b/Sources/web3swift/Web3/Web3+Structures.swift @@ -689,3 +689,17 @@ public struct TxPoolContentForNonce { public var nonce: BigUInt public var details: [TransactionDetails] } + +public enum FilterChanges: Decodable { + case hashes([String]) + case logs([EventLog]) + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let hashes = try? container.decode([String].self) { + self = .hashes(hashes) + } else { + self = .logs(try container.decode([EventLog].self)) + } + } +} diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 462fc45de..ea85b2961 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -359,13 +359,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We private func websocketDidReceiveMessage(text: String) { if let data = text.data(using: String.Encoding.utf8), - let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { - // TODO: filterID -// if filterID == nil, -// let result = dictionary["result"] as? String { -// // setting filter id -// filterID = result -// } else + let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { if let _ = dictionary["id"] as? UInt64 { let response: JSONRPCresponse do { @@ -390,9 +384,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We return } subscription.cb(.success(result)) - } else if let message = dictionary["result"] { - // filter result - // TODO: process filter result } else { delegate.gotError(error: Web3Error.processingError(desc: "Can\'t get known result. Message is: \(text)")) } diff --git a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift index 9e67e2f3b..1dc39d152 100644 --- a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift +++ b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift @@ -154,28 +154,4 @@ class web3swift_websocket_Tests: XCTestCase { XCTAssert(true) } } - - func testFilter() { - guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { - return XCTFail() - } - self.socketProvider = socketProvider - spyDelegate.asyncExpectation = expectation(description: "Delegate called") - try! self.socketProvider?.setFilterAndGetLogs(method: .newFilter, address: EthereumAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")!, fromBlock: .earliest, toBlock: .latest, topics: nil) - // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in - // try! self.socketProvider!.subscribeOnNewPendingTransactions() - // } - waitForExpectations(timeout: 1000) { error in - if let error = error { - XCTFail("waitForExpectationsWithTimeout errored: \(error)") - } - - guard self.spyDelegate.somethingWithDelegateResult != nil else { - XCTFail("Expected delegate to be called") - return - } - - XCTAssert(true) - } - } } From 400913e4177d9dcafcd76c88c245b5c651c8c422 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Fri, 7 Jan 2022 02:36:35 +0200 Subject: [PATCH 12/31] added subscribe methods to Eth --- .../Promises/Promise+Web3+Eth+Filters.swift | 14 +- .../web3swift/Web3/Web3+Eth+Websocket.swift | 47 +++--- .../web3swift/Web3/Web3+InfuraProviders.swift | 34 ----- Sources/web3swift/Web3/Web3+Instance.swift | 3 + Sources/web3swift/Web3/Web3+Structures.swift | 41 ++++++ .../Web3/Web3+WebsocketProvider.swift | 55 +++++-- .../web3swift_Websockets_Tests.swift | 137 +++++++++--------- 7 files changed, 187 insertions(+), 144 deletions(-) diff --git a/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift b/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift index 3728a9a06..bff5857a7 100644 --- a/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift +++ b/Sources/web3swift/Promises/Promise+Web3+Eth+Filters.swift @@ -30,7 +30,7 @@ public enum BlockNumber { } extension web3.Eth { - private func dispatchRequest(_ request: JSONRPCrequest) -> Promise { + private func _dispatchRequest(_ request: JSONRPCrequest) -> Promise { web3.dispatch(request).map(on: web3.requestDispatcher.queue) { response in guard let value: T = response.getValue() else { if response.error != nil { @@ -47,26 +47,26 @@ extension web3.Eth { toBlock: BlockNumber? = .latest, topics: [String]) -> Promise { let addresses = addresses.map { $0.address.lowercased() } - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newFilter, parameters: [addresses, fromBlock?.stringValue, toBlock?.stringValue, topics])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newFilter, parameters: [addresses, fromBlock?.stringValue, toBlock?.stringValue, topics])) } public func newBlockFilterPromise() -> Promise { - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newBlockFilter, parameters: [])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newBlockFilter, parameters: [])) } public func newPendingTransactionFilterPromise() -> Promise { - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newPendingTransactionFilter, parameters: [])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.newPendingTransactionFilter, parameters: [])) } public func uninstallFilterPromise(filterID: String) -> Promise { - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.uninstallFilter, parameters: [filterID])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.uninstallFilter, parameters: [filterID])) } public func getFilterChangesPromise(filterID: String) -> Promise { - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterChanges, parameters: [filterID])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterChanges, parameters: [filterID])) } public func getFilterLogsPromise(filterID: String) -> Promise { - return dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterLogs, parameters: [filterID])) + return _dispatchRequest(JSONRPCRequestFabric.prepareRequest(.getFilterLogs, parameters: [filterID])) } } diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 559c44276..0e6688a88 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -10,30 +10,39 @@ import BigInt import PromiseKit import Starscream +public struct SubscribeOnLogsParams: Encodable { + public let address: [String]? + public let topics: [String]? +} extension web3.Eth { - - public func getWebsocketProvider(forDelegate delegate: Web3SocketDelegate) throws -> InfuraWebsocketProvider { - var infuraWSProvider: InfuraWebsocketProvider - if !(provider is InfuraWebsocketProvider) { - guard let infuraNetwork = provider.network else { - throw Web3Error.processingError(desc: "Wrong network") - } - guard let infuraProvider = InfuraWebsocketProvider(infuraNetwork, delegate: delegate) else { - throw Web3Error.processingError(desc: "Wrong network") - } - infuraWSProvider = infuraProvider - } else { - infuraWSProvider = provider as! InfuraWebsocketProvider + private func _subscribe(filter: SubscribeEventFilter, + listener: @escaping Web3SubscriptionListener) throws -> Subscription { + guard let provider = provider as? Web3SubscriptionProvider else { + throw Web3Error.processingError(desc: "Provider is not subscribable") } - infuraWSProvider.connectSocket() - return infuraWSProvider + return provider.subscribe(filter: filter, listener: listener) + } + + public func subscribeOnNewHeads(listener: @escaping Web3SubscriptionListener) throws -> Subscription { + try _subscribe(filter: .newHeads, listener: listener) } - public func subscribeOnPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws { - let provider = try getWebsocketProvider(forDelegate: delegate) - try provider.subscribeOnNewPendingTransactions() + public func subscribeOnLogs(addresses: [EthereumAddress]? = nil, + topics: [String]? = nil, + listener: @escaping Web3SubscriptionListener) throws -> Subscription { + let params = SubscribeOnLogsParams(address: addresses?.map { $0.address }, topics: topics) + return try _subscribe(filter: .logs(params: params), listener: listener) } - // TODO: make 4 subscribe methods + public func subscribeOnNewPendingTransactions(listener: @escaping Web3SubscriptionListener) throws -> Subscription { + try _subscribe(filter: .newPendingTransactions, listener: listener) + } + + public func subscribeOnSyncing(listener: @escaping Web3SubscriptionListener) throws -> Subscription { + guard provider.network != Networks.Kovan else { + throw Web3Error.inputError(desc: "Can't sync on Kovan") + } + return try _subscribe(filter: .syncing, listener: listener) + } } diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 88a2a082d..2958c66bc 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -82,38 +82,4 @@ public final class InfuraWebsocketProvider: WebsocketProvider { socketProvider.connectSocket() return socketProvider } - - public func subscribeOnNewHeads() throws { - let method = JSONRPCmethod.subscribe - let params = ["newHeads"] - try writeMessage(method: method, params: params) - } - - public func subscribeOnLogs(addresses: [EthereumAddress]? = nil, topics: [String]? = nil) throws { - let method = JSONRPCmethod.subscribe - var stringAddresses = [String]() - if let addrs = addresses { - for addr in addrs { - stringAddresses.append(addr.address) - } - } -// let ts = topics == nil ? nil : [topics!] - let filterParams = EventFilterParameters(fromBlock: nil, toBlock: nil, topics: [topics], address: stringAddresses) - try writeMessage(method: method, params: ["logs", filterParams]) - } - - public func subscribeOnNewPendingTransactions() throws { - let method = JSONRPCmethod.subscribe - let params = ["newPendingTransactions"] - try writeMessage(method: method, params: params) - } - - public func subscribeOnSyncing() throws { - guard network != Networks.Kovan else { - throw Web3Error.inputError(desc: "Can't sync on Kovan") - } - let method = JSONRPCmethod.subscribe - let params = ["syncing"] - try writeMessage(method: method, params: params) - } } diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index d95696b62..7ca26103f 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -29,6 +29,9 @@ public class web3 { } else { self.requestDispatcher = requestDispatcher! } + if let provider = provider as? WebsocketProvider { + provider.setQueue(queue: self.requestDispatcher.queue) + } } /// Keystore manager can be bound to Web3 instance. If some manager is bound all further account related functions, such diff --git a/Sources/web3swift/Web3/Web3+Structures.swift b/Sources/web3swift/Web3/Web3+Structures.swift index 7a0084118..dfe60c75d 100755 --- a/Sources/web3swift/Web3/Web3+Structures.swift +++ b/Sources/web3swift/Web3/Web3+Structures.swift @@ -703,3 +703,44 @@ public enum FilterChanges: Decodable { } } } + +public struct BlockHeader: Decodable { + public let difficulty: String + public let extraData: String + public let gasLimit: String + public let gasUsed: String + public let logsBloom: String + public let miner: String + public let nonce: String + public let number: String + public let parentHash: String + public let receiptRoot: String + public let sha3Uncles: String + public let stateRoot: String + public let timestamp: String + public let transactionsRoot: String +} + +public struct LogItem: Decodable { + public let address: String + public let blockHash: String + public let blockNumber: String + public let data: String + public let logIndex: String + public let topics: [String] + public let transactionHash: String + public let transactionIndex: String +} + +public struct SyncingInfo: Decodable { + public struct Status: Decodable { + public let startingBlock: Int + public let currentBlock: Int + public let highestBlock: Int + public let pulledStates: Int + public let knownStates: Int + } + + public let syncing: Bool + public let status: Status? +} diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index ea85b2961..e82dc137d 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -46,6 +46,17 @@ public struct WebsocketSubscription: Subscription { } } +public struct JSONRPCSubscriptionEvent: Decodable { + public struct Params: Decodable { + public let result: R + public let subscription: String + } + + public let jsonrpc: String + public let method: String + public let params: Params +} + /// The default websocket provider. public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, WebSocketDelegate { @@ -57,13 +68,21 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We return Promise(error: Web3Error.inputError(desc: "Unsupported method: \(method)")) } return Promise { resolver in - let requestData = try JSONEncoder().encode(request) - print(String(decoding: requestData, as: UTF8.self)) - writeMessage(requestData) - requests[request.id] = { result in - switch result { - case .success(let response): resolver.fulfill(response) - case .failure(let error): resolver.reject(error) + queue.async { + let requestData: Data + do { + requestData = try JSONEncoder().encode(request) + } catch { + resolver.reject(error) + return + } + print(String(decoding: requestData, as: UTF8.self)) + self.writeMessage(requestData) + self.requests[request.id] = { result in + switch result { + case .success(let response): resolver.fulfill(response) + case .failure(let error): resolver.reject(error) + } } } } @@ -85,7 +104,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public var socket: WebSocket public var delegate: Web3SocketDelegate - private var queue: DispatchQueue? = nil + private var queue: DispatchQueue! /// A flag that is true if socket connected or false if socket doesn't connected. public var websocketConnected: Bool = false @@ -96,7 +115,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We /// if set debugMode True then show websocket events logs in the console public var debugMode: Bool = false - private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() + private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() private var requests = [UInt64: (Swift.Result) -> Void]() public init?(_ endpoint: URL, @@ -201,7 +220,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.unsubscribe, parameters: [subscription.id]) - self.sendAsync(request, queue: self.queue!).pipe { result in + self.sendAsync(request, queue: self.queue).pipe { result in switch result { case .fulfilled(let response): guard let unsubscribed = response.result as? Bool else { @@ -219,7 +238,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } }) let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: params) - sendAsync(request, queue: queue!).pipe { result in + sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): guard let subscriptionID = response.result as? String else { @@ -228,7 +247,11 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } subscription.id = subscriptionID self.subscriptions[subscriptionID] = (subscription, { result in - listener(result.map { $0 as! R }) + listener(result.flatMap { eventData in + Swift.Result { + try JSONDecoder().decode(JSONRPCSubscriptionEvent.self, from: eventData) + } + }.map { $0.params.result }) }) case .rejected(let error): listener(.failure(error)) @@ -365,7 +388,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We do { response = try JSONDecoder().decode(JSONRPCresponse.self, from: data) } catch { - delegate.gotError(error: error) + delegate.gotError(error: Web3Error.processingError(desc: "Cannot parse JSON-RPC response. Error: \(String(describing: error)). Response: \(text)")) return } if let request = requests.removeValue(forKey: UInt64(response.id)) { @@ -378,12 +401,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We delegate.gotError(error: Web3Error.processingError(desc: "Unknown response id. Message is: \(text)")) } } else if let params = dictionary["params"] as? [String: Any], - let subscriptionID = params["subscription"] as? String, - let result = params["result"] as? Decodable { + let subscriptionID = params["subscription"] as? String { guard let subscription = subscriptions[subscriptionID] else { + delegate.gotError(error: Web3Error.processingError(desc: "Unknown subscription id: \(subscriptionID)")) return } - subscription.cb(.success(result)) + subscription.cb(.success(data)) } else { delegate.gotError(error: Web3Error.processingError(desc: "Can\'t get known result. Message is: \(text)")) } diff --git a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift index 1dc39d152..5ca9c2a66 100644 --- a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift +++ b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift @@ -83,75 +83,76 @@ class web3swift_websocket_Tests: XCTestCase { } } - func testSubscribeOnPendingTXs() { - guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { - return XCTFail() - } - self.socketProvider = socketProvider - spyDelegate.asyncExpectation = expectation(description: "Delegate called") - try! self.socketProvider!.subscribeOnNewPendingTransactions() -// DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in -// try! self.socketProvider!.subscribeOnNewPendingTransactions() + // TODO: subscription tests +// func testSubscribeOnPendingTXs() { +// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { +// return XCTFail() // } - waitForExpectations(timeout: 1000) { error in - if let error = error { - XCTFail("waitForExpectationsWithTimeout errored: \(error)") - } - - guard self.spyDelegate.somethingWithDelegateResult != nil else { - XCTFail("Expected delegate to be called") - return - } - - XCTAssert(true) - } - } +// self.socketProvider = socketProvider +// spyDelegate.asyncExpectation = expectation(description: "Delegate called") +// try! self.socketProvider!.subscribeOnNewPendingTransactions() +//// DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in +//// try! self.socketProvider!.subscribeOnNewPendingTransactions() +//// } +// waitForExpectations(timeout: 1000) { error in +// if let error = error { +// XCTFail("waitForExpectationsWithTimeout errored: \(error)") +// } +// +// guard self.spyDelegate.somethingWithDelegateResult != nil else { +// XCTFail("Expected delegate to be called") +// return +// } +// +// XCTAssert(true) +// } +// } - func testSubscribeOnLogs() { - guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { - return XCTFail() - } - self.socketProvider = socketProvider - spyDelegate.asyncExpectation = expectation(description: "Delegate called") - try! self.socketProvider!.subscribeOnLogs(addresses: [EthereumAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")!], topics: nil) - // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in - // try! self.socketProvider!.subscribeOnNewPendingTransactions() - // } - waitForExpectations(timeout: 1000) { error in - if let error = error { - XCTFail("waitForExpectationsWithTimeout errored: \(error)") - } - - guard self.spyDelegate.somethingWithDelegateResult != nil else { - XCTFail("Expected delegate to be called") - return - } - - XCTAssert(true) - } - } +// func testSubscribeOnLogs() { +// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { +// return XCTFail() +// } +// self.socketProvider = socketProvider +// spyDelegate.asyncExpectation = expectation(description: "Delegate called") +// try! self.socketProvider!.subscribeOnLogs(addresses: [EthereumAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")!], topics: nil) +// // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in +// // try! self.socketProvider!.subscribeOnNewPendingTransactions() +// // } +// waitForExpectations(timeout: 1000) { error in +// if let error = error { +// XCTFail("waitForExpectationsWithTimeout errored: \(error)") +// } +// +// guard self.spyDelegate.somethingWithDelegateResult != nil else { +// XCTFail("Expected delegate to be called") +// return +// } +// +// XCTAssert(true) +// } +// } - func testSubscribeOnNewHeads() { - guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { - return XCTFail() - } - self.socketProvider = socketProvider - spyDelegate.asyncExpectation = expectation(description: "Delegate called") - try! self.socketProvider!.subscribeOnNewHeads() - // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in - // try! self.socketProvider!.subscribeOnNewPendingTransactions() - // } - waitForExpectations(timeout: 1000) { error in - if let error = error { - XCTFail("waitForExpectationsWithTimeout errored: \(error)") - } - - guard self.spyDelegate.somethingWithDelegateResult != nil else { - XCTFail("Expected delegate to be called") - return - } - - XCTAssert(true) - } - } +// func testSubscribeOnNewHeads() { +// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { +// return XCTFail() +// } +// self.socketProvider = socketProvider +// spyDelegate.asyncExpectation = expectation(description: "Delegate called") +// try! self.socketProvider!.subscribeOnNewHeads() +// // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in +// // try! self.socketProvider!.subscribeOnNewPendingTransactions() +// // } +// waitForExpectations(timeout: 1000) { error in +// if let error = error { +// XCTFail("waitForExpectationsWithTimeout errored: \(error)") +// } +// +// guard self.spyDelegate.somethingWithDelegateResult != nil else { +// XCTFail("Expected delegate to be called") +// return +// } +// +// XCTAssert(true) +// } +// } } From ec49477245dd546f6dd2213ff34209d97fb53723 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:27:17 +0200 Subject: [PATCH 13/31] set queue when calling subscribe --- Sources/web3swift/Web3/Web3+Eth+Websocket.swift | 3 +++ Sources/web3swift/Web3/Web3+Instance.swift | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 0e6688a88..89e55f96a 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -21,6 +21,9 @@ extension web3.Eth { guard let provider = provider as? Web3SubscriptionProvider else { throw Web3Error.processingError(desc: "Provider is not subscribable") } + if let provider = provider as? WebsocketProvider { + provider.setQueue(queue: web3.requestDispatcher.queue) + } return provider.subscribe(filter: filter, listener: listener) } diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index 7ca26103f..d95696b62 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -29,9 +29,6 @@ public class web3 { } else { self.requestDispatcher = requestDispatcher! } - if let provider = provider as? WebsocketProvider { - provider.setQueue(queue: self.requestDispatcher.queue) - } } /// Keystore manager can be bound to Web3 instance. If some manager is bound all further account related functions, such From a3f7b5ccb7742ae6cb121a99bcf1d2ad3a9574e3 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:38:10 +0200 Subject: [PATCH 14/31] added internalQueue to WebsocketProvider --- .../Web3/Web3+WebsocketProvider.swift | 124 ++++++++++-------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index e82dc137d..694905c88 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -34,7 +34,7 @@ public struct DefaultWeb3SocketDelegate: Web3SocketDelegate { } public struct WebsocketSubscription: Subscription { - public var id = "" + public var id: String? = nil private let unsubscribeCallback: (Self) -> Void public init(unsubscribeCallback: @escaping (Self) -> Void) { @@ -78,10 +78,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We } print(String(decoding: requestData, as: UTF8.self)) self.writeMessage(requestData) - self.requests[request.id] = { result in - switch result { - case .success(let response): resolver.fulfill(response) - case .failure(let error): resolver.reject(error) + self.internalQueue.sync { + self.requests[request.id] = { result in + switch result { + case .success(let response): resolver.fulfill(response) + case .failure(let error): resolver.reject(error) + } } } } @@ -117,6 +119,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() private var requests = [UInt64: (Swift.Result) -> Void]() + private var internalQueue: DispatchQueue public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate? = nil, @@ -153,6 +156,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We url = URL(string: endpointString)! delegate = wsdelegate ?? DefaultWeb3SocketDelegate() let request = URLRequest(url: url) + internalQueue = DispatchQueue( + label: "web3swift.websocketProvider.internalQueue", + target: .global() + ) socket = WebSocket(request: request) socket.delegate = self } @@ -193,6 +200,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We url = URL(string: finalEndpoint)! delegate = wsdelegate ?? DefaultWeb3SocketDelegate() let request = URLRequest(url: url) + internalQueue = DispatchQueue( + label: "web3swift.websocketProvider.internalQueue", + target: .global() + ) socket = WebSocket(request: request) socket.delegate = self } @@ -207,57 +218,62 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We public func subscribe(filter: SubscribeEventFilter, listener: @escaping Web3SubscriptionListener) -> Subscription { - let params: [Encodable] - switch filter { - case .newHeads: - params = ["newHeads"] - case .logs(let p): - params = ["logs", p] - case .newPendingTransactions: - params = ["newPendingTransactions"] - case .syncing: - params = ["syncing"] - } - var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in - let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.unsubscribe, parameters: [subscription.id]) - self.sendAsync(request, queue: self.queue).pipe { result in + internalQueue.sync { + let params: [Encodable] + switch filter { + case .newHeads: + params = ["newHeads"] + case .logs(let logsParam): + params = ["logs", logsParam] + case .newPendingTransactions: + params = ["newPendingTransactions"] + case .syncing: + params = ["syncing"] + } + var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in + guard let id = subscription.id else { + return + } + let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.unsubscribe, parameters: [id]) + self.sendAsync(request, queue: self.queue).pipe { result in + switch result { + case .fulfilled(let response): + guard let unsubscribed = response.result as? Bool else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) + return + } + if unsubscribed { + self.subscriptions.removeValue(forKey: id) + } else { + self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(id)")) + } + case .rejected(let error): + self.delegate.gotError(error: error) + } + } + }) + let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: params) + sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): - guard let unsubscribed = response.result as? Bool else { + guard let subscriptionID = response.result as? String else { self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) return } - if unsubscribed { - self.subscriptions.removeValue(forKey: subscription.id) - } else { - self.delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(subscription.id)")) - } + subscription.id = subscriptionID + self.subscriptions[subscriptionID] = (subscription, { result in + listener(result.flatMap { eventData in + Swift.Result { + try JSONDecoder().decode(JSONRPCSubscriptionEvent.self, from: eventData) + } + }.map { $0.params.result }) + }) case .rejected(let error): - self.delegate.gotError(error: error) + listener(.failure(error)) } } - }) - let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: params) - sendAsync(request, queue: queue).pipe { result in - switch result { - case .fulfilled(let response): - guard let subscriptionID = response.result as? String else { - self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) - return - } - subscription.id = subscriptionID - self.subscriptions[subscriptionID] = (subscription, { result in - listener(result.flatMap { eventData in - Swift.Result { - try JSONDecoder().decode(JSONRPCSubscriptionEvent.self, from: eventData) - } - }.map { $0.params.result }) - }) - case .rejected(let error): - listener(.failure(error)) - } + return subscription } - return subscription } public func connectSocket() { @@ -391,14 +407,16 @@ public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, We delegate.gotError(error: Web3Error.processingError(desc: "Cannot parse JSON-RPC response. Error: \(String(describing: error)). Response: \(text)")) return } - if let request = requests.removeValue(forKey: UInt64(response.id)) { - if let error = response.error { - request(.failure(Web3Error.nodeError(desc: "Received an error message\n" + String(describing: error)))) + internalQueue.sync { + if let request = requests.removeValue(forKey: UInt64(response.id)) { + if let error = response.error { + request(.failure(Web3Error.nodeError(desc: "Received an error message\n" + String(describing: error)))) + } else { + request(.success(response)) + } } else { - request(.success(response)) + delegate.gotError(error: Web3Error.processingError(desc: "Unknown response id. Message is: \(text)")) } - } else { - delegate.gotError(error: Web3Error.processingError(desc: "Unknown response id. Message is: \(text)")) } } else if let params = dictionary["params"] as? [String: Any], let subscriptionID = params["subscription"] as? String { From 46abd66a2868f510651e8237e674f3d88256aa03 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:44:50 +0200 Subject: [PATCH 15/31] removed unneeded IWebsocketProvider --- Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 694905c88..0e1b94d26 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -10,14 +10,6 @@ import PromiseKit import BigInt import Foundation -public protocol IWebsocketProvider { - var socket: WebSocket {get} - var delegate: Web3SocketDelegate {get set} - func connectSocket() throws - func disconnectSocket() throws - func writeMessage(_ message: T) -} - public protocol Web3SocketDelegate { func socketConnected(_ headers: [String:String]) func gotError(error: Error) @@ -58,7 +50,7 @@ public struct JSONRPCSubscriptionEvent: Decodable { } /// The default websocket provider. -public class WebsocketProvider: Web3SubscriptionProvider, IWebsocketProvider, WebSocketDelegate { +public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public func sendAsync(_ request: JSONRPCrequest, queue: DispatchQueue) -> Promise { guard let method = request.method else { From 4e6a096d98caf914287dd9700acdb3c60ec0c72f Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:02:35 +0200 Subject: [PATCH 16/31] added queue as param in subscribe --- Sources/web3swift/Web3/Web3+Eth+Websocket.swift | 5 +---- Sources/web3swift/Web3/Web3+SubscriptionProvider.swift | 1 + Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 8 ++------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 89e55f96a..2d6a4dd20 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -21,10 +21,7 @@ extension web3.Eth { guard let provider = provider as? Web3SubscriptionProvider else { throw Web3Error.processingError(desc: "Provider is not subscribable") } - if let provider = provider as? WebsocketProvider { - provider.setQueue(queue: web3.requestDispatcher.queue) - } - return provider.subscribe(filter: filter, listener: listener) + return provider.subscribe(filter: filter, queue: web3.requestDispatcher.queue, listener: listener) } public func subscribeOnNewHeads(listener: @escaping Web3SubscriptionListener) throws -> Subscription { diff --git a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift index 5fc1e0242..7f7e64990 100644 --- a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift +++ b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift @@ -22,5 +22,6 @@ public typealias Web3SubscriptionListener = (Result) -> public protocol Web3SubscriptionProvider: Web3Provider { func subscribe(filter: SubscribeEventFilter, + queue: DispatchQueue, listener: @escaping Web3SubscriptionListener) -> Subscription } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 0e1b94d26..68d3ac920 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -98,7 +98,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public var socket: WebSocket public var delegate: Web3SocketDelegate - private var queue: DispatchQueue! /// A flag that is true if socket connected or false if socket doesn't connected. public var websocketConnected: Bool = false @@ -204,11 +203,8 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { writeTimer?.invalidate() } - public func setQueue(queue: DispatchQueue) { - self.queue = queue - } - public func subscribe(filter: SubscribeEventFilter, + queue: DispatchQueue, listener: @escaping Web3SubscriptionListener) -> Subscription { internalQueue.sync { let params: [Encodable] @@ -227,7 +223,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { return } let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.unsubscribe, parameters: [id]) - self.sendAsync(request, queue: self.queue).pipe { result in + self.sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): guard let unsubscribed = response.result as? Bool else { From 90329c336151f21b5242f98e46975160a4f8c9c0 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:13:16 +0200 Subject: [PATCH 17/31] added method to convert SubscribeEventFilter to params --- .../web3swift/Web3/Web3+SubscriptionProvider.swift | 11 +++++++++++ Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 13 +------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift index 7f7e64990..fb5160e7e 100644 --- a/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift +++ b/Sources/web3swift/Web3/Web3+SubscriptionProvider.swift @@ -14,6 +14,17 @@ public enum SubscribeEventFilter { case syncing } +extension SubscribeEventFilter { + public var params: [Encodable] { + switch self { + case .newHeads: return ["newHeads"] + case .logs(let logsParam): return ["logs", logsParam] + case .newPendingTransactions: return ["newPendingTransactions"] + case .syncing: return ["syncing"] + } + } +} + public protocol Subscription { func unsubscribe() } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 68d3ac920..2cb1b7132 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -207,17 +207,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { queue: DispatchQueue, listener: @escaping Web3SubscriptionListener) -> Subscription { internalQueue.sync { - let params: [Encodable] - switch filter { - case .newHeads: - params = ["newHeads"] - case .logs(let logsParam): - params = ["logs", logsParam] - case .newPendingTransactions: - params = ["newPendingTransactions"] - case .syncing: - params = ["syncing"] - } var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in guard let id = subscription.id else { return @@ -240,7 +229,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { } } }) - let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: params) + let request = JSONRPCRequestFabric.prepareRequest(JSONRPCmethod.subscribe, parameters: filter.params) sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): From 904457305f2761f1ba3f49cf28da1e01f8e9c083 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:23:29 +0200 Subject: [PATCH 18/31] reworked writeTimer to pendingRequests --- .../Web3/Web3+WebsocketProvider.swift | 64 +++++-------------- 1 file changed, 15 insertions(+), 49 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 2cb1b7132..0eb79f2d5 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -69,7 +69,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { return } print(String(decoding: requestData, as: UTF8.self)) - self.writeMessage(requestData) self.internalQueue.sync { self.requests[request.id] = { result in switch result { @@ -77,6 +76,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { case .failure(let error): resolver.reject(error) } } + let writeRequest = { self.socket.write(data: requestData) } + if self.websocketConnected { + writeRequest() + } else { + self.pendingRequests.append(writeRequest) + } } } } @@ -101,15 +106,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { /// A flag that is true if socket connected or false if socket doesn't connected. public var websocketConnected: Bool = false - private var writeTimer: Timer? = nil - private var messagesStringToWrite: [String] = [] - private var messagesDataToWrite: [Data] = [] - /// if set debugMode True then show websocket events logs in the console public var debugMode: Bool = false private var subscriptions = [String: (sub: WebsocketSubscription, cb: (Swift.Result) -> Void)]() private var requests = [UInt64: (Swift.Result) -> Void]() + private var pendingRequests = [() -> Void]() private var internalQueue: DispatchQueue public init?(_ endpoint: URL, @@ -199,10 +201,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { socket.delegate = self } - deinit { - writeTimer?.invalidate() - } - public func subscribe(filter: SubscribeEventFilter, queue: DispatchQueue, listener: @escaping Web3SubscriptionListener) -> Subscription { @@ -254,12 +252,10 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { } public func connectSocket() { - writeTimer?.invalidate() socket.connect() } public func disconnectSocket() { - writeTimer?.invalidate() socket.disconnect() } @@ -295,49 +291,12 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { return socketProvider } - public func writeMessage(_ message: T) { - var sMessage: String? = nil - var dMessage: Data? = nil - if !(message.self is String) && !(message.self is Data) { - sMessage = "\(message)" - } else if message.self is String { - sMessage = message as? String - } else if message.self is Data { - dMessage = message as? Data - } - if sMessage != nil { - self.messagesStringToWrite.append(sMessage!) - } else if dMessage != nil { - self.messagesDataToWrite.append(dMessage!) - } - writeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(performWriteOperations), userInfo: nil, repeats: true) - } - - public func writeMessage(method: JSONRPCmethod, params: [Encodable]) throws { - let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params) - let encoder = JSONEncoder() - let requestData = try encoder.encode(request) - print(String(decoding: requestData, as: UTF8.self)) - writeMessage(requestData) - } - - @objc private func performWriteOperations() { - if websocketConnected { - writeTimer?.invalidate() - for s in messagesStringToWrite { - socket.write(string: s) - } - for d in messagesDataToWrite { - socket.write(data: d) - } - } - } - public func didReceive(event: WebSocketEvent, client: WebSocket) { switch event { case .connected(let headers): debugMode ? print("websocket is connected, headers:\n \(headers)") : nil websocketConnected = true + connected() delegate.socketConnected(headers) case .disconnected(let reason, let code): debugMode ? print("websocket is disconnected: \(reason) with code: \(code)") : nil @@ -373,6 +332,13 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { } } + private func connected() { + internalQueue.sync { + pendingRequests.forEach { $0() } + pendingRequests.removeAll() + } + } + private func websocketDidReceiveMessage(text: String) { if let data = text.data(using: String.Encoding.utf8), let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { From 1ba98855101b085312c939ef628f575f95eae399 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 11 Jan 2022 02:09:08 +0200 Subject: [PATCH 19/31] moved infura specific constructor --- .../web3swift/Web3/Web3+InfuraProviders.swift | 51 +++++++---- .../Web3/Web3+WebsocketProvider.swift | 87 ++----------------- 2 files changed, 44 insertions(+), 94 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+InfuraProviders.swift b/Sources/web3swift/Web3/Web3+InfuraProviders.swift index 2958c66bc..d32d3fc05 100755 --- a/Sources/web3swift/Web3/Web3+InfuraProviders.swift +++ b/Sources/web3swift/Web3/Web3+InfuraProviders.swift @@ -26,32 +26,51 @@ public final class InfuraWebsocketProvider: WebsocketProvider { || network == Networks.Ropsten || network == Networks.Mainnet else {return nil} let networkName = network.name - let urlString = "wss://" + networkName + Constants.infuraWsScheme - guard URL(string: urlString) != nil else {return nil} - super.init(urlString, - delegate: delegate, - projectId: projectId, - network: network) + var urlString = "wss://" + networkName + Constants.infuraWsScheme + urlString += projectId ?? Constants.infuraToken + guard let url = URL(string: urlString) else {return nil} + super.init(url, delegate: delegate, network: network) } public init?(_ endpoint: String, delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { - guard URL(string: endpoint) != nil else {return nil} - super.init(endpoint, - delegate: delegate, - projectId: projectId) + var endpoint = endpoint + if !(endpoint.hasPrefix("wss://") || endpoint.hasPrefix("ws://")) { + return nil + } + var network: Networks? = nil + if endpoint.hasPrefix("wss://") && endpoint.hasSuffix(Constants.infuraWsScheme) { + let networkString = endpoint.replacingOccurrences(of: "wss://", with: "") + .replacingOccurrences(of: Constants.infuraWsScheme, with: "") + switch networkString { + case "mainnet": + network = Networks.Mainnet + case "rinkeby": + network = Networks.Rinkeby + case "ropsten": + network = Networks.Ropsten + case "kovan": + network = Networks.Kovan + default: + break + } + } + guard let network = network else { + return nil + } + endpoint += projectId ?? Constants.infuraToken + guard let url = URL(string: endpoint) else {return nil} + super.init(url, delegate: delegate, network: network) } - public init?(_ endpoint: URL, + public convenience init?(_ endpoint: URL, delegate: Web3SocketDelegate? = nil, projectId: String? = nil) { - super.init(endpoint, - delegate: delegate, - projectId: projectId) + self.init(endpoint.absoluteString, delegate: delegate, projectId: projectId) } - override public class func connectToSocket(_ endpoint: String, + public class func connectToSocket(_ endpoint: String, delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { @@ -62,7 +81,7 @@ public final class InfuraWebsocketProvider: WebsocketProvider { return socketProvider } - override public class func connectToSocket(_ endpoint: URL, + public class func connectToSocket(_ endpoint: URL, delegate: Web3SocketDelegate? = nil, projectId: String? = nil, network net: Networks? = nil) -> WebsocketProvider? { diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 0eb79f2d5..4e1f8acc6 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -116,37 +116,9 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public init?(_ endpoint: URL, delegate wsdelegate: Web3SocketDelegate? = nil, - projectId: String? = nil, - network net: Networks? = nil) { - websocketConnected = false - var endpointString = endpoint.absoluteString - if !(endpointString.hasPrefix("wss://") || endpointString.hasPrefix("ws://")) { - return nil - } - if endpointString.hasPrefix("wss://") && endpointString.hasSuffix(Constants.infuraWsScheme) { - if net == nil { - let networkString = endpointString.replacingOccurrences(of: "wss://", with: "") - .replacingOccurrences(of: Constants.infuraWsScheme, with: "") - switch networkString { - case "mainnet": - network = Networks.Mainnet - case "rinkeby": - network = Networks.Rinkeby - case "ropsten": - network = Networks.Ropsten - case "kovan": - network = Networks.Kovan - default: - break - } - } else { - network = net - } - if network != nil { - endpointString += projectId ?? Constants.infuraToken - } - } - url = URL(string: endpointString)! + network: Networks) { + url = endpoint + self.network = network delegate = wsdelegate ?? DefaultWeb3SocketDelegate() let request = URLRequest(url: url) internalQueue = DispatchQueue( @@ -157,48 +129,11 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { socket.delegate = self } - public init?(_ endpoint: String, + public convenience init?(_ endpoint: String, delegate wsdelegate: Web3SocketDelegate? = nil, - projectId: String? = nil, - network net: Networks? = nil) { - guard URL(string: endpoint) != nil else {return nil} - var finalEndpoint = endpoint - websocketConnected = false - if !(endpoint.hasPrefix("wss://") || endpoint.hasPrefix("ws://")) { - return nil - } - if endpoint.hasPrefix("wss://") && endpoint.hasSuffix(Constants.infuraWsScheme) { - if net == nil { - let networkString = endpoint.replacingOccurrences(of: "wss://", with: "") - .replacingOccurrences(of: Constants.infuraWsScheme, with: "") - switch networkString { - case "mainnet": - network = Networks.Mainnet - case "rinkeby": - network = Networks.Rinkeby - case "ropsten": - network = Networks.Ropsten - case "kovan": - network = Networks.Kovan - default: - break - } - } else { - network = net - } - if network != nil { - finalEndpoint += projectId ?? Constants.infuraToken - } - } - url = URL(string: finalEndpoint)! - delegate = wsdelegate ?? DefaultWeb3SocketDelegate() - let request = URLRequest(url: url) - internalQueue = DispatchQueue( - label: "web3swift.websocketProvider.internalQueue", - target: .global() - ) - socket = WebSocket(request: request) - socket.delegate = self + network: Networks) { + guard let url = URL(string: endpoint) else {return nil} + self.init(url, delegate: wsdelegate, network: network) } public func subscribe(filter: SubscribeEventFilter, @@ -265,11 +200,9 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public class func connectToSocket(_ endpoint: String, delegate: Web3SocketDelegate? = nil, - projectId: String? = nil, - network net: Networks? = nil) -> WebsocketProvider? { + network net: Networks) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, delegate: delegate, - projectId: projectId, network: net) else { return nil } @@ -279,11 +212,9 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public class func connectToSocket(_ endpoint: URL, delegate: Web3SocketDelegate? = nil, - projectId: String? = nil, - network net: Networks? = nil) -> WebsocketProvider? { + network net: Networks) -> WebsocketProvider? { guard let socketProvider = WebsocketProvider(endpoint, delegate: delegate, - projectId: projectId, network: net) else { return nil } From 4db90c1c9ee09b8074dff72646e300cad830df96 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 11 Jan 2022 18:03:19 +0200 Subject: [PATCH 20/31] removed url session from websocket --- Sources/web3swift/Web3/Web3+HttpProvider.swift | 1 - Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 5 ----- 2 files changed, 6 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index e9fce8dbd..27c2bd7ad 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -14,7 +14,6 @@ public protocol Web3Provider { func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue) -> Promise var network: Networks? {get set} var url: URL {get} - var session: URLSession {get} } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 4e1f8acc6..d1c1ca043 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -95,11 +95,6 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { public var network: Networks? public var url: URL - public var session: URLSession = {() -> URLSession in - let config = URLSessionConfiguration.default - let urlSession = URLSession(configuration: config) - return urlSession - }() public var socket: WebSocket public var delegate: Web3SocketDelegate From bf9997c489ab726e6405596d1d85a56f7239e05d Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Wed, 12 Jan 2022 05:49:19 +0200 Subject: [PATCH 21/31] fixes for subscribe methods --- Sources/web3swift/Web3/Web3+JSONRPC.swift | 2 ++ Sources/web3swift/Web3/Web3+Structures.swift | 2 +- Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index 428d5547c..6e346d9de 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -278,6 +278,8 @@ public struct JSONRPCparams: Encodable{ try container.encode(p) } else if let p = par as? EventFilterParameters { try container.encode(p) + } else if let p = par as? SubscribeOnLogsParams { + try container.encode(p) } } } diff --git a/Sources/web3swift/Web3/Web3+Structures.swift b/Sources/web3swift/Web3/Web3+Structures.swift index dfe60c75d..b4221d889 100755 --- a/Sources/web3swift/Web3/Web3+Structures.swift +++ b/Sources/web3swift/Web3/Web3+Structures.swift @@ -714,7 +714,7 @@ public struct BlockHeader: Decodable { public let nonce: String public let number: String public let parentHash: String - public let receiptRoot: String + public let receiptsRoot: String public let sha3Uncles: String public let stateRoot: String public let timestamp: String diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index d1c1ca043..d0c64f68d 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -25,11 +25,11 @@ public struct DefaultWeb3SocketDelegate: Web3SocketDelegate { } } -public struct WebsocketSubscription: Subscription { +public class WebsocketSubscription: Subscription { public var id: String? = nil - private let unsubscribeCallback: (Self) -> Void + private let unsubscribeCallback: (WebsocketSubscription) -> Void - public init(unsubscribeCallback: @escaping (Self) -> Void) { + public init(unsubscribeCallback: @escaping (WebsocketSubscription) -> Void) { self.unsubscribeCallback = unsubscribeCallback } @@ -44,7 +44,6 @@ public struct JSONRPCSubscriptionEvent: Decodable { public let subscription: String } - public let jsonrpc: String public let method: String public let params: Params } @@ -135,7 +134,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { queue: DispatchQueue, listener: @escaping Web3SubscriptionListener) -> Subscription { internalQueue.sync { - var subscription = WebsocketSubscription(unsubscribeCallback: { subscription in + let subscription = WebsocketSubscription(unsubscribeCallback: { subscription in guard let id = subscription.id else { return } From 86088cf40e11d8f9ef992b11f30060a456f3a1d7 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Wed, 12 Jan 2022 05:51:15 +0200 Subject: [PATCH 22/31] tests for subscribe methods --- .../web3swift_Websockets_Tests.swift | 195 ++++++++++-------- 1 file changed, 111 insertions(+), 84 deletions(-) diff --git a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift index 5ca9c2a66..e2dcb034a 100644 --- a/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift +++ b/Tests/web3swiftTests/infura_tests/web3swift_Websockets_Tests.swift @@ -36,20 +36,6 @@ class SpyDelegate: Web3SocketDelegate { } } - func received(message: Any) { - somethingWithDelegateResult = message - guard let expectation = asyncExpectation else { - XCTFail("SpyDelegate was not setup correctly. Missing XCTExpectation reference") - return - } - print(message) - if !fulfilled { - print("fullfilled") - fulfilled = true - expectation.fulfill() - } - } - func gotError(error: Error) { XCTFail(error.localizedDescription) } @@ -83,76 +69,117 @@ class web3swift_websocket_Tests: XCTestCase { } } - // TODO: subscription tests -// func testSubscribeOnPendingTXs() { -// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { -// return XCTFail() -// } -// self.socketProvider = socketProvider -// spyDelegate.asyncExpectation = expectation(description: "Delegate called") -// try! self.socketProvider!.subscribeOnNewPendingTransactions() -//// DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in -//// try! self.socketProvider!.subscribeOnNewPendingTransactions() -//// } -// waitForExpectations(timeout: 1000) { error in -// if let error = error { -// XCTFail("waitForExpectationsWithTimeout errored: \(error)") -// } -// -// guard self.spyDelegate.somethingWithDelegateResult != nil else { -// XCTFail("Expected delegate to be called") -// return -// } -// -// XCTAssert(true) -// } -// } + func testSubscribeOnNewHeads() throws { + spyDelegate.asyncExpectation = expectation(description: "Delegate called") + let eventExpectation = expectation(description: "eventExpectation") + guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { + return XCTFail() + } + let eth = web3(provider: socketProvider).eth + var eventExpectationFulfilled = false + let subscription = try eth.subscribeOnNewHeads { result in + _ = try! result.get() + if !eventExpectationFulfilled { + eventExpectation.fulfill() + eventExpectationFulfilled = true + } + } + waitForExpectations(timeout: 1000) { error in + if let error = error { + XCTFail("waitForExpectationsWithTimeout errored: \(error)") + } + + guard self.spyDelegate.somethingWithDelegateResult != nil else { + XCTFail("Expected delegate to be called") + return + } + + XCTAssert(true) + } + subscription.unsubscribe() + } -// func testSubscribeOnLogs() { -// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { -// return XCTFail() -// } -// self.socketProvider = socketProvider -// spyDelegate.asyncExpectation = expectation(description: "Delegate called") -// try! self.socketProvider!.subscribeOnLogs(addresses: [EthereumAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")!], topics: nil) -// // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in -// // try! self.socketProvider!.subscribeOnNewPendingTransactions() -// // } -// waitForExpectations(timeout: 1000) { error in -// if let error = error { -// XCTFail("waitForExpectationsWithTimeout errored: \(error)") -// } -// -// guard self.spyDelegate.somethingWithDelegateResult != nil else { -// XCTFail("Expected delegate to be called") -// return -// } -// -// XCTAssert(true) -// } -// } + func testSubscribeOnLogs() throws { + spyDelegate.asyncExpectation = expectation(description: "Delegate called") + let eventExpectation = expectation(description: "eventExpectation") + guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { + return XCTFail() + } + let eth = web3(provider: socketProvider).eth + var eventExpectationFulfilled = false + let subscription = try eth.subscribeOnLogs { result in + _ = try! result.get() + if !eventExpectationFulfilled { + eventExpectation.fulfill() + eventExpectationFulfilled = true + } + } + waitForExpectations(timeout: 1000) { error in + if let error = error { + XCTFail("waitForExpectationsWithTimeout errored: \(error)") + } + + guard self.spyDelegate.somethingWithDelegateResult != nil else { + XCTFail("Expected delegate to be called") + return + } + + XCTAssert(true) + } + subscription.unsubscribe() + } -// func testSubscribeOnNewHeads() { -// guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { -// return XCTFail() -// } -// self.socketProvider = socketProvider -// spyDelegate.asyncExpectation = expectation(description: "Delegate called") -// try! self.socketProvider!.subscribeOnNewHeads() -// // DispatchQueue.main.asyncAfter(deadline: .now()+5) { [unowned self] in -// // try! self.socketProvider!.subscribeOnNewPendingTransactions() -// // } -// waitForExpectations(timeout: 1000) { error in -// if let error = error { -// XCTFail("waitForExpectationsWithTimeout errored: \(error)") -// } -// -// guard self.spyDelegate.somethingWithDelegateResult != nil else { -// XCTFail("Expected delegate to be called") -// return -// } -// -// XCTAssert(true) -// } -// } + func testSubscribeOnNewPendingTransactions() throws { + spyDelegate.asyncExpectation = expectation(description: "Delegate called") + let eventExpectation = expectation(description: "eventExpectation") + guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { + return XCTFail() + } + let eth = web3(provider: socketProvider).eth + var eventExpectationFulfilled = false + let subscription = try eth.subscribeOnNewPendingTransactions { result in + _ = try! result.get() + if !eventExpectationFulfilled { + eventExpectation.fulfill() + eventExpectationFulfilled = true + } + } + waitForExpectations(timeout: 1000) { error in + if let error = error { + XCTFail("waitForExpectationsWithTimeout errored: \(error)") + } + + guard self.spyDelegate.somethingWithDelegateResult != nil else { + XCTFail("Expected delegate to be called") + return + } + + XCTAssert(true) + } + subscription.unsubscribe() + } + + func testSubscribeOnSyncing() throws { + spyDelegate.asyncExpectation = expectation(description: "Delegate called") + guard let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: spyDelegate) else { + return XCTFail() + } + let eth = web3(provider: socketProvider).eth + let subscription = try eth.subscribeOnSyncing { result in + _ = try! result.get() + } + waitForExpectations(timeout: 1000) { error in + if let error = error { + XCTFail("waitForExpectationsWithTimeout errored: \(error)") + } + + guard self.spyDelegate.somethingWithDelegateResult != nil else { + XCTFail("Expected delegate to be called") + return + } + + XCTAssert(true) + } + subscription.unsubscribe() + } } From b5705453f3bc26322d4f1c08bf140a3358b10c3a Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 20 Jan 2022 01:37:24 +0200 Subject: [PATCH 23/31] added init to ErrorMessage, made batch requests public --- Sources/web3swift/Web3/Web3+JSONRPC.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index 6e346d9de..4b5d724d7 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -57,7 +57,7 @@ public struct JSONRPCrequest: Encodable { /// JSON RPC batch request structure for serialization and deserialization purposes. public struct JSONRPCrequestBatch: Encodable { - var requests: [JSONRPCrequest] + public var requests: [JSONRPCrequest] public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -90,6 +90,11 @@ public struct JSONRPCresponse: Decodable{ public struct ErrorMessage: Decodable { public var code: Int public var message: String + + public init(code: Int, message: String) { + self.code = code + self.message = message + } } internal var decodableTypes: [Decodable.Type] = [[EventLog].self, From 69b0a3f43c9fc2b087ba44f33f70a63745c50690 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:17:57 +0200 Subject: [PATCH 24/31] created JSONRPCresponse.Result struct --- .../Promises/Promise+Web3+TxPool.swift | 2 +- Sources/web3swift/Web3/Web3+JSONRPC.swift | 216 ++++++++++-------- .../Web3/Web3+WebsocketProvider.swift | 6 +- .../local_tests/web3swift_Tests.swift | 2 +- 4 files changed, 122 insertions(+), 104 deletions(-) diff --git a/Sources/web3swift/Promises/Promise+Web3+TxPool.swift b/Sources/web3swift/Promises/Promise+Web3+TxPool.swift index 996fae706..6584a67af 100755 --- a/Sources/web3swift/Promises/Promise+Web3+TxPool.swift +++ b/Sources/web3swift/Promises/Promise+Web3+TxPool.swift @@ -29,7 +29,7 @@ extension web3.TxPool { let rp = web3.dispatch(request) let queue = web3.requestDispatcher.queue return rp.map(on: queue ) { response in - guard let value: TxPoolStatus = response.result as? TxPoolStatus else { + guard let value: TxPoolStatus = response.getValue() else { if response.error != nil { throw Web3Error.nodeError(desc: response.error!.message) } diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index 4b5d724d7..4001e2740 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -69,7 +69,7 @@ public struct JSONRPCrequestBatch: Encodable { public struct JSONRPCresponse: Decodable{ public var id: Int public var jsonrpc = "2.0" - public var result: Any? + public var result: Result public var error: ErrorMessage? public var message: String? @@ -80,13 +80,125 @@ public struct JSONRPCresponse: Decodable{ case error = "error" } - public init(id: Int, jsonrpc: String, result: Any?, error: ErrorMessage?) { + public init(id: Int, jsonrpc: String, result: Result, error: ErrorMessage?) { self.id = id self.jsonrpc = jsonrpc self.result = result self.error = error } + public struct Result: Decodable { + private let value: Any? + + public init(value: Any?) { + self.value = value + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + var value: Any? = nil + if let rawValue = try? container.decode(String.self) { + value = rawValue + } else if let rawValue = try? container.decode(Int.self) { + value = rawValue + } else if let rawValue = try? container.decode(Bool.self) { + value = rawValue + } else if let rawValue = try? container.decode(EventLog.self) { + value = rawValue + } else if let rawValue = try? container.decode(Block.self) { + value = rawValue + } else if let rawValue = try? container.decode(TransactionReceipt.self) { + value = rawValue + } else if let rawValue = try? container.decode(TransactionDetails.self) { + value = rawValue + } else if let rawValue = try? container.decode([EventLog].self) { + value = rawValue + } else if let rawValue = try? container.decode([Block].self) { + value = rawValue + } else if let rawValue = try? container.decode([TransactionReceipt].self) { + value = rawValue + } else if let rawValue = try? container.decode([TransactionDetails].self) { + value = rawValue + } else if let rawValue = try? container.decode(TxPoolStatus.self) { + value = rawValue + } else if let rawValue = try? container.decode(TxPoolContent.self) { + value = rawValue + } else if let rawValue = try? container.decode([Bool].self) { + value = rawValue + } else if let rawValue = try? container.decode([Int].self) { + value = rawValue + } else if let rawValue = try? container.decode([String].self) { + value = rawValue + } else if let rawValue = try? container.decode([String: String].self) { + value = rawValue + } else if let rawValue = try? container.decode([String: Int].self) { + value = rawValue + } else if let rawValue = try? container.decode([String:[String:[String:String]]].self) { + value = rawValue + } else if let rawValue = try? container.decode([String:[String:[String:[String:String?]]]].self) { + value = rawValue + } else if let rawValue = try? container.decode(FilterChanges.self) { + value = rawValue + } + self.value = value + } + + public func getValue() -> T? { + let slf = T.self + if slf == BigUInt.self { + guard let string = self.value as? String else {return nil} + guard let value = BigUInt(string.stripHexPrefix(), radix: 16) else {return nil} + return value as? T + } else if slf == BigInt.self { + guard let string = self.value as? String else {return nil} + guard let value = BigInt(string.stripHexPrefix(), radix: 16) else {return nil} + return value as? T + } else if slf == Data.self { + guard let string = self.value as? String else {return nil} + guard let value = Data.fromHex(string) else {return nil} + return value as? T + } else if slf == EthereumAddress.self { + guard let string = self.value as? String else {return nil} + guard let value = EthereumAddress(string, ignoreChecksum: true) else {return nil} + return value as? T + } + // else if slf == String.self { + // guard let value = self.result as? T else {return nil} + // return value + // } else if slf == Int.self { + // guard let value = self.result as? T else {return nil} + // return value + // } + else if slf == [BigUInt].self { + guard let string = self.value as? [String] else {return nil} + let values = string.compactMap { (str) -> BigUInt? in + return BigUInt(str.stripHexPrefix(), radix: 16) + } + return values as? T + } else if slf == [BigInt].self { + guard let string = self.value as? [String] else {return nil} + let values = string.compactMap { (str) -> BigInt? in + return BigInt(str.stripHexPrefix(), radix: 16) + } + return values as? T + } else if slf == [Data].self { + guard let string = self.value as? [String] else {return nil} + let values = string.compactMap { (str) -> Data? in + return Data.fromHex(str) + } + return values as? T + } else if slf == [EthereumAddress].self { + guard let string = self.value as? [String] else {return nil} + let values = string.compactMap { (str) -> EthereumAddress? in + return EthereumAddress(str, ignoreChecksum: true) + } + return values as? T + } + guard let value = self.value as? T else {return nil} + return value + } + } + public struct ErrorMessage: Decodable { public var code: Int public var message: String @@ -121,53 +233,10 @@ public struct JSONRPCresponse: Decodable{ let jsonrpc: String = try container.decode(String.self, forKey: .jsonrpc) let errorMessage = try container.decodeIfPresent(ErrorMessage.self, forKey: .error) if errorMessage != nil { - self.init(id: id, jsonrpc: jsonrpc, result: nil, error: errorMessage) + self.init(id: id, jsonrpc: jsonrpc, result: Result(value: nil), error: errorMessage) return } - var result: Any? = nil - if let rawValue = try? container.decodeIfPresent(String.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(Int.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(Bool.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(EventLog.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(Block.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(TransactionReceipt.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(TransactionDetails.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([EventLog].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([Block].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([TransactionReceipt].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([TransactionDetails].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(TxPoolStatus.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(TxPoolContent.self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([Bool].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([Int].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([String].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([String: String].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([String: Int].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([String:[String:[String:String]]].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent([String:[String:[String:[String:String?]]]].self, forKey: .result) { - result = rawValue - } else if let rawValue = try? container.decodeIfPresent(FilterChanges.self, forKey: .result) { - result = rawValue - } + let result = try container.decode(Result.self, forKey: .result) self.init(id: id, jsonrpc: jsonrpc, result: result, error: nil) } @@ -175,58 +244,7 @@ public struct JSONRPCresponse: Decodable{ /// /// Returns nil if serialization fails public func getValue() -> T? { - let slf = T.self - if slf == BigUInt.self { - guard let string = self.result as? String else {return nil} - guard let value = BigUInt(string.stripHexPrefix(), radix: 16) else {return nil} - return value as? T - } else if slf == BigInt.self { - guard let string = self.result as? String else {return nil} - guard let value = BigInt(string.stripHexPrefix(), radix: 16) else {return nil} - return value as? T - } else if slf == Data.self { - guard let string = self.result as? String else {return nil} - guard let value = Data.fromHex(string) else {return nil} - return value as? T - } else if slf == EthereumAddress.self { - guard let string = self.result as? String else {return nil} - guard let value = EthereumAddress(string, ignoreChecksum: true) else {return nil} - return value as? T - } -// else if slf == String.self { -// guard let value = self.result as? T else {return nil} -// return value -// } else if slf == Int.self { -// guard let value = self.result as? T else {return nil} -// return value -// } - else if slf == [BigUInt].self { - guard let string = self.result as? [String] else {return nil} - let values = string.compactMap { (str) -> BigUInt? in - return BigUInt(str.stripHexPrefix(), radix: 16) - } - return values as? T - } else if slf == [BigInt].self { - guard let string = self.result as? [String] else {return nil} - let values = string.compactMap { (str) -> BigInt? in - return BigInt(str.stripHexPrefix(), radix: 16) - } - return values as? T - } else if slf == [Data].self { - guard let string = self.result as? [String] else {return nil} - let values = string.compactMap { (str) -> Data? in - return Data.fromHex(str) - } - return values as? T - } else if slf == [EthereumAddress].self { - guard let string = self.result as? [String] else {return nil} - let values = string.compactMap { (str) -> EthereumAddress? in - return EthereumAddress(str, ignoreChecksum: true) - } - return values as? T - } - guard let value = self.result as? T else {return nil} - return value + result.getValue() } } diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index d0c64f68d..d434079a7 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -21,7 +21,7 @@ public struct DefaultWeb3SocketDelegate: Web3SocketDelegate { } public func gotError(error: Error) { - print("DefaultWeb3SocketDelegate.gotError: \(error)") + print("DefaultWeb3SocketDelegate.gotError: \(String(describing: error))") } } @@ -142,7 +142,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { self.sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): - guard let unsubscribed = response.result as? Bool else { + guard let unsubscribed: Bool = response.getValue() else { self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) return } @@ -160,7 +160,7 @@ public class WebsocketProvider: Web3SubscriptionProvider, WebSocketDelegate { sendAsync(request, queue: queue).pipe { result in switch result { case .fulfilled(let response): - guard let subscriptionID = response.result as? String else { + guard let subscriptionID: String = response.getValue() else { self.delegate.gotError(error: Web3Error.processingError(desc: "Wrong result in response: \(response)")) return } diff --git a/Tests/web3swiftTests/local_tests/web3swift_Tests.swift b/Tests/web3swiftTests/local_tests/web3swift_Tests.swift index 3610fa38a..5fc74b828 100755 --- a/Tests/web3swiftTests/local_tests/web3swift_Tests.swift +++ b/Tests/web3swiftTests/local_tests/web3swift_Tests.swift @@ -105,7 +105,7 @@ class web3swift_Tests: XCTestCase { func testGenericRPCresponse() throws { let hex = "0x1" - let rpcResponse = JSONRPCresponse(id: 1, jsonrpc: "2.0", result: hex, error: nil) + let rpcResponse = JSONRPCresponse(id: 1, jsonrpc: "2.0", result: JSONRPCresponse.Result(value: hex), error: nil) let value: BigUInt? = rpcResponse.getValue() XCTAssert(value == 1) } From 428251dd4d74cfd29a5a5fcfe904a58b46db5abb Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 24 Feb 2022 01:43:46 +0200 Subject: [PATCH 25/31] minor improvement: removed guard statement --- Sources/web3swift/Web3/Web3+JSONRPC.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+JSONRPC.swift b/Sources/web3swift/Web3/Web3+JSONRPC.swift index 4001e2740..985be2426 100755 --- a/Sources/web3swift/Web3/Web3+JSONRPC.swift +++ b/Sources/web3swift/Web3/Web3+JSONRPC.swift @@ -194,8 +194,7 @@ public struct JSONRPCresponse: Decodable{ } return values as? T } - guard let value = self.value as? T else {return nil} - return value + return self.value as? T } } From 2d90894205261f4494e32cb66a88a78b66e5cf03 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Thu, 24 Feb 2022 02:13:15 +0200 Subject: [PATCH 26/31] removed LogItem and used EventLog --- Sources/web3swift/Web3/Web3+Eth+Websocket.swift | 2 +- Sources/web3swift/Web3/Web3+Structures.swift | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift index 2d6a4dd20..a0a11c7a8 100644 --- a/Sources/web3swift/Web3/Web3+Eth+Websocket.swift +++ b/Sources/web3swift/Web3/Web3+Eth+Websocket.swift @@ -30,7 +30,7 @@ extension web3.Eth { public func subscribeOnLogs(addresses: [EthereumAddress]? = nil, topics: [String]? = nil, - listener: @escaping Web3SubscriptionListener) throws -> Subscription { + listener: @escaping Web3SubscriptionListener) throws -> Subscription { let params = SubscribeOnLogsParams(address: addresses?.map { $0.address }, topics: topics) return try _subscribe(filter: .logs(params: params), listener: listener) } diff --git a/Sources/web3swift/Web3/Web3+Structures.swift b/Sources/web3swift/Web3/Web3+Structures.swift index b4221d889..4d76f51a8 100755 --- a/Sources/web3swift/Web3/Web3+Structures.swift +++ b/Sources/web3swift/Web3/Web3+Structures.swift @@ -721,17 +721,6 @@ public struct BlockHeader: Decodable { public let transactionsRoot: String } -public struct LogItem: Decodable { - public let address: String - public let blockHash: String - public let blockNumber: String - public let data: String - public let logIndex: String - public let topics: [String] - public let transactionHash: String - public let transactionIndex: String -} - public struct SyncingInfo: Decodable { public struct Status: Decodable { public let startingBlock: Int From d1add3b149d7a14168f08be9c0013c252baf45d1 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Fri, 6 May 2022 21:28:00 +0300 Subject: [PATCH 27/31] added hash field in BlockHeader struct --- Sources/web3swift/Web3/Web3+Structures.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/web3swift/Web3/Web3+Structures.swift b/Sources/web3swift/Web3/Web3+Structures.swift index 36256c809..8849399de 100755 --- a/Sources/web3swift/Web3/Web3+Structures.swift +++ b/Sources/web3swift/Web3/Web3+Structures.swift @@ -403,6 +403,7 @@ public enum FilterChanges: Decodable { } public struct BlockHeader: Decodable { + public let hash: String public let difficulty: String public let extraData: String public let gasLimit: String From 907d3dad26aeac169c28de81b64ea541175e9027 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Sat, 7 May 2022 04:04:32 +0300 Subject: [PATCH 28/31] included new files in project --- web3swift.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web3swift.xcodeproj/project.pbxproj b/web3swift.xcodeproj/project.pbxproj index 9e6f66e26..5d247f0ae 100755 --- a/web3swift.xcodeproj/project.pbxproj +++ b/web3swift.xcodeproj/project.pbxproj @@ -204,6 +204,8 @@ 6049F4182806171300DFE624 /* Web3+Signing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6049F4172806171300DFE624 /* Web3+Signing.swift */; }; 604FA4FF27ECBDC80021108F /* DataConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604FA4FE27ECBDC80021108F /* DataConversionTests.swift */; }; 60C1786E2809BA0C0083F064 /* EthereumMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1786D2809BA0C0083F064 /* EthereumMetadata.swift */; }; + BAA6DA082825FAD5002B3A27 /* Web3+SubscriptionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6DA072825FAD5002B3A27 /* Web3+SubscriptionProvider.swift */; }; + BAA6DA0A2825FAF2002B3A27 /* Promise+Web3+Eth+Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6DA092825FAF2002B3A27 /* Promise+Web3+Eth+Filters.swift */; }; CB50A52827060BD600D7E39B /* EIP712Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB50A52727060BD600D7E39B /* EIP712Tests.swift */; }; D606A56B279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D606A56A279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift */; }; D6A3D9B827F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3D9B727F1E785009E3BCF /* ABIEncoderSoliditySha3Test.swift */; }; @@ -436,6 +438,8 @@ 6049F4172806171300DFE624 /* Web3+Signing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Web3+Signing.swift"; sourceTree = ""; }; 604FA4FE27ECBDC80021108F /* DataConversionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataConversionTests.swift; sourceTree = ""; }; 60C1786D2809BA0C0083F064 /* EthereumMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumMetadata.swift; sourceTree = ""; }; + BAA6DA072825FAD5002B3A27 /* Web3+SubscriptionProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Web3+SubscriptionProvider.swift"; sourceTree = ""; }; + BAA6DA092825FAF2002B3A27 /* Promise+Web3+Eth+Filters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Web3+Eth+Filters.swift"; sourceTree = ""; }; CB50A52727060BD600D7E39B /* EIP712Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Tests.swift; sourceTree = ""; }; CB50A52927060C5300D7E39B /* EIP712.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712.swift; sourceTree = ""; }; D606A56A279F5D59003643ED /* web3swiftDecodeSolidityErrorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = web3swiftDecodeSolidityErrorType.swift; sourceTree = ""; }; @@ -761,6 +765,7 @@ 3AA8154D2276E44100F5DB52 /* Web3 */ = { isa = PBXGroup; children = ( + BAA6DA072825FAD5002B3A27 /* Web3+SubscriptionProvider.swift */, 6049F4172806171300DFE624 /* Web3+Signing.swift */, 5C26D89D27F3724600431EB0 /* Web3+EIP1559.swift */, 5C26D8A127F5AC0300431EB0 /* Web3+GasOracle.swift */, @@ -821,6 +826,7 @@ 3AA815702276E44100F5DB52 /* Promises */ = { isa = PBXGroup; children = ( + BAA6DA092825FAF2002B3A27 /* Promise+Web3+Eth+Filters.swift */, 3AA815712276E44100F5DB52 /* Promise+Web3+Eth+GetBlockNumber.swift */, 3AA815722276E44100F5DB52 /* Promise+Web3+Personal+Sign.swift */, 3AA815732276E44100F5DB52 /* Promise+Web3+Eth+GetGasPrice.swift */, @@ -1319,6 +1325,7 @@ 3AA815EF2276E44100F5DB52 /* Promise+HttpProvider.swift in Sources */, 3AA8163A2276E4AE00F5DB52 /* SECP256k1.swift in Sources */, 3AA8160D2276E44100F5DB52 /* Web3+ERC721.swift in Sources */, + BAA6DA082825FAD5002B3A27 /* Web3+SubscriptionProvider.swift in Sources */, 3AA815DC2276E44100F5DB52 /* Web3+Structures.swift in Sources */, 6049F411280616FC00DFE624 /* EIP1559Envelope.swift in Sources */, 3AA815AE2276E44100F5DB52 /* ENSResolver.swift in Sources */, @@ -1331,6 +1338,7 @@ 3AA815B02276E44100F5DB52 /* EIP67Code.swift in Sources */, 3AA815BB2276E44100F5DB52 /* AbstractKeystore.swift in Sources */, 3AA815B12276E44100F5DB52 /* EIP681.swift in Sources */, + BAA6DA0A2825FAF2002B3A27 /* Promise+Web3+Eth+Filters.swift in Sources */, 3AA815C82276E44100F5DB52 /* ABIParsing.swift in Sources */, 3AA815202276E42F00F5DB52 /* ContractProtocol.swift in Sources */, 3AA815F72276E44100F5DB52 /* Promise+Web3+Eth+GetTransactionCount.swift in Sources */, From 7c9048938dd32760cc5bb5e293516f1ba524aa5d Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Fri, 13 May 2022 04:01:05 +0300 Subject: [PATCH 29/31] updated websockets documentation --- Documentation/Usage.md | 155 +++++++++++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 45 deletions(-) diff --git a/Documentation/Usage.md b/Documentation/Usage.md index d622ad339..a140b4dde 100755 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -31,27 +31,32 @@ - [Send Ether](#send-ether) - [Send ERC-20 Token](#send-erc-20-token) - [Write Transaction and call smart contract method](#write-transaction-and-call-smart-contract-method) - - [Read Transaction to call smart contract method](#read-transaction-to-call-smart-contract-method) - - [Other Transaction Types (EIP-1559)](#other-transaction-types) + - [Read Transaction from call smart contract method](#read-transaction-from-call-smart-contract-method) - [Send Transaction](#send-transaction) - [Write](#write) - [Read](#read) + - [Other Transaction Types](#other-transaction-types) - [Chain state](#chain-state) - [Get Block number](#get-block-number) - [Websockets](#websockets) - [Web3socketDelegate](#web3socketdelegate) - [Custom Websocket Provider](#custom-websocket-provider) - [Connect to custom endpoint](#connect-to-custom-endpoint) - - [Send message](#send-message) + - [Send request](#send-request) - [Infura Websocket interactions](#infura-websocket-interactions) - [Connect to Infura endpoint](#connect-to-infura-endpoint) - [Connect to custom Infura-like endpoint](#connect-to-custom-infura-like-endpoint) - [Set a filter in the node to notify when something happened](#set-a-filter-in-the-node-to-notify-when-something-happened) - - [Get new pending transactions](#get-new-pending-transactions) + - [New filter](#new-filter) + - [Get filter changes](#get-filter-changes) + - [Get filter logs](#get-filter-logs) + - [Uninstall filter](#uninstall-filter) - [Create a new subscription over particular events](#create-a-new-subscription-over-particular-events) - - [Subscribe on new pending transactions](#subscribe-on-new-pending-transactions) - - [Subscribe on logs](https://github.com/matter-labs/web3swift/blob/develop/Documentation/Usage.md#subscribe-on-logs) - - [Subscribe on new heads](https://github.com/matter-labs/web3swift/blob/develop/Documentation/Usage.md#subscribe-on-new-heads) + - [Subscribe on new pending transactions](#subscribe-on-new-pending-transactions) + - [Subscribe on logs](#subscribe-on-logs) + - [Subscribe on new heads](#subscribe-on-new-heads) + - [Subscribe on syncing](#subscribe-on-syncing) + - [Unsubscribe](#unsubscribe) - [ENS](#ens) - [Registry](#registry) - [Resolver](#resolver) @@ -402,17 +407,17 @@ let blockNumber = try! web3.eth.getBlockNumber() ### Web3socketDelegate -To receive messages from endpoint you need to create a class that adopts to Web3SocketDelegate protocol. +You can create a class that adopts to Web3SocketDelegate protocol in order to receive notifications about connection state or errors. Later, to open a connection to WebSocket server, you will use socket provider (`WebsocketProvider` or `InfuraWebsocketProvider`). And we recommend you to make it a property, so it doesn't get deallocated right after being setup. ```swift -class DelegateClass: Web3SocketDelegate { - var socketProvider: WebsocketProvider? = nil // WebSocket Provider - var socketProvider: InfuraWebsocketProvider? = nil // Infura WebSocket Provider - - // Protocol method, here will be messages, received from WebSocket server - func received(message: Any) { - // Make something with message - } +public class DelegateClass: Web3SocketDelegate { + public func socketConnected(_ headers: [String : String]) { + // Do something when socket connected + } + + public func gotError(error: Error) { + // Do something when got an error from socket connection + } } ``` @@ -422,7 +427,7 @@ class DelegateClass: Web3SocketDelegate { You can create WebsocketProvider and connect/disconnect it. ```swift -socketProvider = WebsocketProvider("ws://your.endpoint", delegate: delegate) +let socketProvider = WebsocketProvider("ws://your.endpoint", delegate: delegate, network: .Mainnet) socketProvider.connectSocket() /// Some code /// ... @@ -431,16 +436,21 @@ socketProvider.disconnectSocket() Or you can create already connected WebsocketProvider ```swift -socketProvider = WebsocketProvider.connectToSocket("ws://your.endpoint", delegate: delegate) +let socketProvider = WebsocketProvider.connectToSocket("ws://your.endpoint", delegate: delegate, network: .Mainnet) ``` -#### Send message +#### Send request ```swift -// String message -socketProvider.writeMessage(String()) -// Data message -socketProvider.writeMessage(Data()) +// One request +socketProvider.sendAsync(, queue: ).map { response in + // Use response +} + +// A batch of requests +socketProvider.sendAsync(, queue: ).map { responses in + // Use responses +} ``` ### Infura Websocket interactions @@ -448,63 +458,118 @@ socketProvider.writeMessage(Data()) #### Connect to Infura endpoint ```swift -socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: delegate) +let socketProvider = InfuraWebsocketProvider.connectToInfuraSocket(.Mainnet, delegate: delegate) ``` #### Connect to custom Infura-like endpoint ```swift -socketProvider = InfuraWebsocketProvider.connectToSocket("ws://your.endpoint", delegate: delegate) +let socketProvider = InfuraWebsocketProvider.connectToSocket("ws://your.endpoint", delegate: delegate, network: .Mainnet) ``` #### Set a filter in the node to notify when something happened -To study possible filters read [Infura WSS filters documentation](https://infura.io/docs/ethereum/wss/introduction) +To study possible filters read [Infura WSS filters documentation](https://docs.infura.io/infura/networks/ethereum/json-rpc-methods/filter-methods) + +```swift +// Use websocket provider +let eth = web3(provider: socketProvider).eth +``` + +##### New filter ```swift -// Getting logs -try! socketProvider.setFilterAndGetLogs(method: , params: <[Encodable]?>) -// Getting changes -try! socketProvider.setFilterAndGetChanges(method: , params: <[Encodable]?>) +// New filter +let filterPromise = eth.newFilterPromise(addresses: <[EthereumAddress]>, topics: <[String]>) +// or +let filterPromise = eth.newFilterPromise(addresses: <[EthereumAddress]>, fromBlock: , toBlock: , topics: <[String]>) + +// New block filter +let filterPromise = eth.newBlockFilterPromise() + +// New pending transactions filter +let filterPromise = eth.newPendingTransactionFilterPromise() ``` -Or you can provide parameters in more convenient way: + +##### Get filter changes + ```swift -// Getting logs -try! socketProvider.setFilterAndGetLogs(method: , address: , fromBlock: , toBlock: , topics: <[String]?>) -// Getting changes -try! socketProvider.setFilterAndGetChanges(method: , address: , fromBlock: , toBlock: , topics: <[String]?>) +filterPromise.map { filterID in + eth.getFilterChangesPromise(filterID: filterID).map { filterChanges in + // Use filterChanges + } +} ``` -#### Get new pending transactions +##### Get filter logs ```swift -try! socketProvider.setFilterAndGetLogs(method: .newPendingTransactionFilter) +filterPromise.map { filterID in + eth.getFilterLogsPromise(filterID: filterID).map { filterLogs in + // Use filterLogs + } +} +``` + +##### Uninstall filter + +```swift +filterPromise.map { filterID in + eth.uninstallFilterPromise(filterID: filterID).map { isUninstalled in + // Use isUninstalled + } +} ``` #### Create a new subscription over particular events -To study possible subscriptions read [Infura WSS subscriptions documentation](https://infura.io/docs/ethereum/wss/eth_subscribe) +To study possible subscriptions read [Infura WSS subscriptions documentation](https://docs.infura.io/infura/networks/ethereum/json-rpc-methods/subscription-methods) ```swift -try! socketProvider.subscribe(params: <[Encodable]>) +// Use websocket provider +let eth = web3(provider: socketProvider).eth ``` -#### Subscribe on new pending transactions +##### Subscribe on new pending transactions ```swift -try! socketProvider.subscribeOnNewPendingTransactions() +let subscription = try eth.subscribeOnNewPendingTransactions { result in + let transactionHash = try! result.get() + // Use transactionHash +} ``` -#### Subscribe on logs +##### Subscribe on logs ```swift -try! socketProvider.subscribeOnLogs(addresses: <[EthereumAddress]?>, topics: <[String]?>) +let subscription = try eth.subscribeOnLogs { result in + let logItem = try! result.get() + // Use logItem +} +``` + +##### Subscribe on new heads + +```swift +let subscription = try eth.subscribeOnNewHeads { result in + let blockHeader = try! result.get() + // Use blockHeader +} +``` + +##### Subscribe on syncing + +```swift +let subscription = try eth.subscribeOnSyncing { result in + let syncingInfo = try! result.get() + // Use syncingInfo +} ``` -#### Subscribe on new heads +##### Unsubscribe ```swift -try! socketProvider.subscribeOnNewHeads() +subscription.unsubscribe() ``` ## ENS From 4cfa03bce1e7da7a31f01ad864fc8b0492c59338 Mon Sep 17 00:00:00 2001 From: Ostap Danylovych <19220558+odanylovych@users.noreply.github.com> Date: Tue, 17 May 2022 21:28:13 +0300 Subject: [PATCH 30/31] made EthereumTransaction.encode public --- Sources/web3swift/Transaction/EthereumTransaction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Transaction/EthereumTransaction.swift b/Sources/web3swift/Transaction/EthereumTransaction.swift index 3c90172d4..5494a1a7a 100755 --- a/Sources/web3swift/Transaction/EthereumTransaction.swift +++ b/Sources/web3swift/Transaction/EthereumTransaction.swift @@ -204,7 +204,7 @@ public struct EthereumTransaction: CustomStringConvertible { } /// - Returns: a raw bytestream of the transaction, encoded according to the transactionType - func encode(for type: EncodeType = .transaction) -> Data? { + public func encode(for type: EncodeType = .transaction) -> Data? { return self.envelope.encode(for: type) } From 686140822be754ffb6f8fdd563fd45cd153ac232 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 20 Dec 2022 10:47:28 +0200 Subject: [PATCH 31/31] fix: returned back the type of `network` to `Networks?` (optional) --- Sources/Web3Core/Structure/Web3ProviderProtocol.swift | 2 +- Sources/web3swift/Web3/Web3+HttpProvider.swift | 2 +- Sources/web3swift/Web3/Web3+WebsocketProvider.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Web3Core/Structure/Web3ProviderProtocol.swift b/Sources/Web3Core/Structure/Web3ProviderProtocol.swift index 1620d687e..b1c729ef3 100644 --- a/Sources/Web3Core/Structure/Web3ProviderProtocol.swift +++ b/Sources/Web3Core/Structure/Web3ProviderProtocol.swift @@ -7,7 +7,7 @@ import Foundation public protocol Web3Provider { - var network: Networks { get } + var network: Networks? { get } var keystoreManager: KeystoreManager? { get set } var policies: Policies { get set } var url: URL { get } diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index 837f69bbf..bcc90d9fd 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -10,7 +10,7 @@ import Web3Core /// The default http provider. public class Web3HttpProvider: Web3Provider { public var url: URL - public var network: Networks + public var network: Networks? public var policies: Policies = .auto public var keystoreManager: KeystoreManager? diff --git a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift index 604aa2bd4..43c919de5 100644 --- a/Sources/web3swift/Web3/Web3+WebsocketProvider.swift +++ b/Sources/web3swift/Web3/Web3+WebsocketProvider.swift @@ -84,11 +84,11 @@ public protocol Web3SocketClient { /// The default websocket provider. public class Web3SocketProvider: Web3SubscriptionProvider, Web3SocketDelegate { - + public var url: URL { web3SocketClient.url } - public var network: Networks? + public private(set) var network: Networks? public var policies: Policies = .auto public var keystoreManager: KeystoreManager? // TODO: Consider removing `public var session: URLSession` completely