From ccb2764bcadcd98b5793b8cab077dd8dc7ae4a4f Mon Sep 17 00:00:00 2001 From: Dionysios Karatzas Date: Thu, 9 Jun 2022 10:39:01 +0300 Subject: [PATCH] Refactor: - EthereumClient - ThereumClient+Call - JSONRPC - EthereumClient+Static - ERC20 Refactor ERC165 Refactor: - ERC165 - ERC721 - Multicall Refactor: EthereumNameService --- web3sTests/Client/EthereumClientTests.swift | 8 +- .../src/Client/EthereumClient+Call.swift | 114 +++- web3swift/src/Client/EthereumClient.swift | 638 +++++++++++------- web3swift/src/Client/JSONRPC.swift | 46 +- .../src/Client/RecursiveLogCollector.swift | 9 +- .../EthereumClient+Static.swift | 410 +++++++---- web3swift/src/ENS/EthereumNameService.swift | 120 ++-- web3swift/src/ERC165/ERC165.swift | 34 +- web3swift/src/ERC20/ERC20.swift | 279 +++++--- web3swift/src/ERC721/ERC721.swift | 498 +++++++++----- web3swift/src/Multicall/Multicall.swift | 35 +- 11 files changed, 1360 insertions(+), 831 deletions(-) diff --git a/web3sTests/Client/EthereumClientTests.swift b/web3sTests/Client/EthereumClientTests.swift index 29ba38cf..4b0e7a07 100644 --- a/web3sTests/Client/EthereumClientTests.swift +++ b/web3sTests/Client/EthereumClientTests.swift @@ -75,7 +75,9 @@ class EthereumClientTests: XCTestCase { _ = try await client?.eth_getBalance(address: EthereumAddress("0xnig42niog2"), block: .Latest) XCTFail("Expected to throw while awaiting, but succeeded") } catch { - XCTAssertEqual(error as? EthereumClientError, .unexpectedReturnValue) + XCTAssertEqual(error as? EthereumClientError, .executionError( + .init(code: -32602, message: "invalid argument 0: hex string has length 10, want 40 for common.Address", data: nil) + )) } } @@ -227,7 +229,9 @@ class EthereumClientTests: XCTestCase { let _ = try await client?.eth_getTransaction(byHash: "0x01234") XCTFail("Expected to throw while awaiting, but succeeded") } catch { - XCTAssertEqual(error as? EthereumClientError, .unexpectedReturnValue) + XCTAssertEqual(error as? EthereumClientError, .executionError( + .init(code: -32602, message: "invalid argument 0: json: cannot unmarshal hex string of odd length into Go value of type common.Hash", data: nil) + )) } } diff --git a/web3swift/src/Client/EthereumClient+Call.swift b/web3swift/src/Client/EthereumClient+Call.swift index 0d130938..55b26230 100644 --- a/web3swift/src/Client/EthereumClient+Call.swift +++ b/web3swift/src/Client/EthereumClient+Call.swift @@ -30,10 +30,11 @@ extension EthereumClient { _ transaction: EthereumTransaction, resolution: CallResolution = .noOffchain(failOnExecutionError: true), block: EthereumBlock = .Latest, - completion: @escaping ((EthereumClientError?, String?) -> Void) + completionHandler: @escaping (Result) -> Void ) { guard let transactionData = transaction.data else { - return completion(EthereumClientError.noInputData, nil) + completionHandler(.failure(.noInputData)) + return } struct CallParams: Encodable { @@ -67,46 +68,51 @@ extension EthereumClient { block: block.stringValue ) - EthereumRPC.execute( - session: session, - url: url, - method: "eth_call", - params: params, - receive: String.self - ) { (error, response) in - if let resDataString = response as? String { - completion(nil, resDataString) - } else if case let .executionError(result) = error as? JSONRPCError { - switch resolution { - case .noOffchain: - completion(.executionError(result.error), nil) - case .offchainAllowed(let redirects): - if let lookup = result.offchainLookup, lookup.address == transaction.to { - self.offchainRead( - lookup: lookup, - maxReads: redirects - ).sink(receiveCompletion: { offchainCompletion in - if case .failure = offchainCompletion { - completion(.noResultFound, nil) + EthereumRPC.execute(session: session, + url: url, + method: "eth_call", + params: params, + receive: String.self) { result in + switch result { + case .success(let data): + if let resDataString = data as? String { + completionHandler(.success(resDataString)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + if case let .executionError(result) = error as? JSONRPCError { + switch resolution { + case .noOffchain: + completionHandler(.failure(.executionError(result.error))) + case .offchainAllowed(let redirects): + if let lookup = result.offchainLookup, lookup.address == transaction.to { + self.offchainRead( + lookup: lookup, + maxReads: redirects + ).sink(receiveCompletion: { offchainCompletion in + if case .failure = offchainCompletion { + completionHandler(.failure(.noResultFound)) + } + }, receiveValue: { data in + self.eth_call( + .init( + to: lookup.address, + data: lookup.encodeCall(withResponse: data) + ), + resolution: .noOffchain(failOnExecutionError: true), + block: block, completionHandler: completionHandler + ) } - }, receiveValue: { data in - self.eth_call( - .init( - to: lookup.address, - data: lookup.encodeCall(withResponse: data) - ), - resolution: .noOffchain(failOnExecutionError: true), - block: block, completion: completion ) + .store(in: &cancellables) + } else { + completionHandler(.failure(.executionError(result.error))) } - ) - .store(in: &cancellables) - } else { - completion(.executionError(result.error), nil) } + } else { + completionHandler(.failure(.unexpectedReturnValue)) } - } else { - completion(.unexpectedReturnValue, nil) } } } @@ -211,6 +217,40 @@ extension EthereumClient { } } +// MARK: - Async/Await +extension EthereumClient { + public func eth_call(_ transaction: EthereumTransaction, + resolution: CallResolution = .noOffchain(failOnExecutionError: true), + block: EthereumBlock = .Latest) async throws -> String { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + eth_call( + transaction, + resolution: resolution, + block: block, + completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +extension EthereumClient { + @available(*, deprecated, renamed: "eth_call(_:resolution:block:completionHandler:)") + public func eth_call( _ transaction: EthereumTransaction, + resolution: CallResolution = .noOffchain(failOnExecutionError: true), + block: EthereumBlock = .Latest, + completion: @escaping ((EthereumClientError?, String?) -> Void) + ) { + eth_call(transaction, resolution: resolution, block: block) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } +} + fileprivate struct OffchainReadJSONBody: Encodable { let sender: EthereumAddress let data: String diff --git a/web3swift/src/Client/EthereumClient.swift b/web3swift/src/Client/EthereumClient.swift index 21f3fa3b..f75e6301 100644 --- a/web3swift/src/Client/EthereumClient.swift +++ b/web3swift/src/Client/EthereumClient.swift @@ -23,57 +23,65 @@ public protocol EthereumClientProtocol: AnyObject { init(url: URL) var network: EthereumNetwork? { get } - func net_version(completion: @escaping((EthereumClientError?, EthereumNetwork?) -> Void)) - func eth_gasPrice(completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) - func eth_blockNumber(completion: @escaping((EthereumClientError?, Int?) -> Void)) - func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) - func eth_getCode(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, String?) -> Void)) - func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) - func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, String?) -> Void)) - func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, Int?) -> Void)) - func eth_getTransaction(byHash txHash: String, completion: @escaping((EthereumClientError?, EthereumTransaction?) -> Void)) - func eth_getTransactionReceipt(txHash: String, completion: @escaping((EthereumClientError?, EthereumTransactionReceipt?) -> Void)) + func net_version(completionHandler: @escaping(Result) -> Void) + func eth_gasPrice(completionHandler: @escaping(Result) -> Void) + func eth_blockNumber(completionHandler: @escaping(Result) -> Void) + func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completionHandler: @escaping(Result) -> Void) + func eth_getCode(address: EthereumAddress, block: EthereumBlock, completionHandler: @escaping(Result) -> Void) + func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completionHandler: @escaping(Result) -> Void) + func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completionHandler: @escaping(Result) -> Void) + func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completionHandler: @escaping(Result) -> Void) + func eth_getTransaction(byHash txHash: String, completionHandler: @escaping(Result) -> Void) + func eth_getTransactionReceipt(txHash: String, completionHandler: @escaping(Result) -> Void) func eth_call( _ transaction: EthereumTransaction, resolution: CallResolution, block: EthereumBlock, - completion: @escaping((EthereumClientError?, String?) -> Void) - ) - func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) - func eth_getLogs(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) - func eth_getBlockByNumber(_ block: EthereumBlock, completion: @escaping((EthereumClientError?, EthereumBlockInfo?) -> Void)) + completionHandler: @escaping(Result) -> Void) + func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping(Result<[EthereumLog], EthereumClientError>) -> Void) + func eth_getLogs(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping(Result<[EthereumLog], EthereumClientError>) -> Void) + func eth_getBlockByNumber(_ block: EthereumBlock, completionHandler: @escaping(Result) -> Void) + // Async/Await func net_version() async throws -> EthereumNetwork - func eth_gasPrice() async throws -> BigUInt - func eth_blockNumber() async throws -> Int - func eth_getBalance(address: EthereumAddress, block: EthereumBlock) async throws -> BigUInt - func eth_getCode(address: EthereumAddress, block: EthereumBlock) async throws -> String - func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol) async throws -> BigUInt - func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol) async throws -> String - func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock) async throws -> Int - func eth_getTransaction(byHash txHash: String) async throws -> EthereumTransaction - func eth_getTransactionReceipt(txHash: String) async throws -> EthereumTransactionReceipt - func eth_call( _ transaction: EthereumTransaction, resolution: CallResolution, block: EthereumBlock ) async throws -> String - func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [EthereumLog] - func eth_getLogs(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [EthereumLog] - func eth_getBlockByNumber(_ block: EthereumBlock) async throws -> EthereumBlockInfo + + // Deprecated + func net_version(completion: @escaping((EthereumClientError?, EthereumNetwork?) -> Void)) + func eth_gasPrice(completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) + func eth_blockNumber(completion: @escaping((EthereumClientError?, Int?) -> Void)) + func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) + func eth_getCode(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, String?) -> Void)) + func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) + func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, String?) -> Void)) + func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completion: @escaping((EthereumClientError?, Int?) -> Void)) + func eth_getTransaction(byHash txHash: String, completion: @escaping((EthereumClientError?, EthereumTransaction?) -> Void)) + func eth_getTransactionReceipt(txHash: String, completion: @escaping((EthereumClientError?, EthereumTransactionReceipt?) -> Void)) + func eth_call( + _ transaction: EthereumTransaction, + resolution: CallResolution, + block: EthereumBlock, + completion: @escaping((EthereumClientError?, String?) -> Void) + ) + func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) + func eth_getLogs(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) + func eth_getBlockByNumber(_ block: EthereumBlock, completion: @escaping((EthereumClientError?, EthereumBlockInfo?) -> Void)) } public enum EthereumClientError: Error, Equatable { @@ -104,12 +112,13 @@ public class EthereumClient: EthereumClientProtocol { group.enter() var network: EthereumNetwork? - self.net_version { (error, retreivedNetwork) in - if let error = error { - print("Client has no network: \(error.localizedDescription)") - } else { - network = retreivedNetwork + self.net_version { result in + switch result { + case .success(let data): + network = data self.retreivedNetwork = network + case .failure(let error): + print("Client has no network: \(error.localizedDescription)") } group.leave() @@ -144,66 +153,90 @@ public class EthereumClient: EthereumClientProtocol { self.session.invalidateAndCancel() } - public func net_version(completion: @escaping ((EthereumClientError?, EthereumNetwork?) -> Void)) { + public func net_version(completionHandler: @escaping (Result) -> Void) { let emptyParams: Array = [] - EthereumRPC.execute(session: session, url: url, method: "net_version", params: emptyParams, receive: String.self) { (error, response) in - if let resString = response as? String { - let network = EthereumNetwork.fromString(resString) - completion(nil, network) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + EthereumRPC.execute(session: session, url: url, method: "net_version", params: emptyParams, receive: String.self) { result in + switch result { + case .success(let data): + if let resString = data as? String { + let network = EthereumNetwork.fromString(resString) + completionHandler(.success(network)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_gasPrice(completion: @escaping ((EthereumClientError?, BigUInt?) -> Void)) { + public func eth_gasPrice(completionHandler: @escaping (Result) -> Void) { let emptyParams: Array = [] - EthereumRPC.execute(session: session, url: url, method: "eth_gasPrice", params: emptyParams, receive: String.self) { (error, response) in - if let hexString = response as? String { - completion(nil, BigUInt(hex: hexString)) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + EthereumRPC.execute(session: session, url: url, method: "eth_gasPrice", params: emptyParams, receive: String.self) { result in + switch result { + case .success(let data): + if let hexString = data as? String, let bigUInt = BigUInt(hex: hexString) { + completionHandler(.success(bigUInt)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_blockNumber(completion: @escaping ((EthereumClientError?, Int?) -> Void)) { + public func eth_blockNumber(completionHandler: @escaping (Result) -> Void) { let emptyParams: Array = [] - EthereumRPC.execute(session: session, url: url, method: "eth_blockNumber", params: emptyParams, receive: String.self) { (error, response) in - if let hexString = response as? String { - if let integerValue = Int(hex: hexString) { - completion(nil, integerValue) + EthereumRPC.execute(session: session, url: url, method: "eth_blockNumber", params: emptyParams, receive: String.self) { result in + switch result { + case .success(let data): + if let hexString = data as? String { + if let integerValue = Int(hex: hexString) { + completionHandler(.success(integerValue)) + } else { + completionHandler(.failure(.decodeIssue)) + } } else { - completion(EthereumClientError.decodeIssue, nil) + completionHandler(.failure(.unexpectedReturnValue)) } - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completion: @escaping ((EthereumClientError?, BigUInt?) -> Void)) { - EthereumRPC.execute(session: session, url: url, method: "eth_getBalance", params: [address.value, block.stringValue], receive: String.self) { (error, response) in - if let resString = response as? String, let balanceInt = BigUInt(hex: resString.web3.noHexPrefix) { - completion(nil, balanceInt) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + public func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completionHandler: @escaping (Result) -> Void) { + EthereumRPC.execute(session: session, url: url, method: "eth_getBalance", params: [address.value, block.stringValue], receive: String.self) { result in + switch result { + case .success(let data): + if let resString = data as? String, let balanceInt = BigUInt(hex: resString.web3.noHexPrefix) { + completionHandler(.success(balanceInt)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_getCode(address: EthereumAddress, block: EthereumBlock = .Latest, completion: @escaping((EthereumClientError?, String?) -> Void)) { - EthereumRPC.execute(session: session, url: url, method: "eth_getCode", params: [address.value, block.stringValue], receive: String.self) { (error, response) in - if let resDataString = response as? String { - completion(nil, resDataString) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + public func eth_getCode(address: EthereumAddress, block: EthereumBlock = .Latest, completionHandler: @escaping (Result) -> Void) { + EthereumRPC.execute(session: session, url: url, method: "eth_getCode", params: [address.value, block.stringValue], receive: String.self) { result in + switch result { + case .success(let data): + if let resDataString = data as? String { + completionHandler(.success(resDataString)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) { - + public func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completionHandler: @escaping (Result) -> Void) { struct CallParams: Encodable { let from: String? let to: String @@ -261,30 +294,28 @@ public class EthereumClient: EthereumClientProtocol { gasPrice: transaction.gasPrice?.web3.hexString, value: value?.web3.hexString, data: transaction.data?.web3.hexString) - EthereumRPC.execute(session: session, url: url, method: "eth_estimateGas", params: params, receive: String.self) { (error, response) in - if let gasHex = response as? String, let gas = BigUInt(hex: gasHex) { - completion(nil, gas) - } else if case let .executionError(result) = error as? JSONRPCError { - completion(.executionError(result.error), nil) - } else { - completion(.unexpectedReturnValue, nil) + EthereumRPC.execute(session: session, url: url, method: "eth_estimateGas", params: params, receive: String.self) { result in + switch result { + case .success(let data): + if let gasHex = data as? String, let gas = BigUInt(hex: gasHex) { + completionHandler(.success(gas)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping ((EthereumClientError?, String?) -> Void)) { - + public func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completionHandler: @escaping (Result) -> Void) { concurrentQueue.addOperation { let group = DispatchGroup() group.enter() // Inject pending nonce - self.eth_getTransactionCount(address: account.address, block: .Pending) { (error, count) in - guard let nonce = count else { - group.leave() - return completion(EthereumClientError.unexpectedReturnValue, nil) - } - + self.eth_getTransactionCount(address: account.address, block: .Pending) { result in switch result { + case .success(let nonce): var transaction = transaction transaction.nonce = nonce @@ -294,77 +325,120 @@ public class EthereumClient: EthereumClientProtocol { guard let _ = transaction.chainId, let signedTx = (try? account.sign(transaction: transaction)), let transactionHex = signedTx.raw?.web3.hexString else { group.leave() - return completion(EthereumClientError.encodeIssue, nil) + completionHandler(.failure(.encodeIssue)) + return } - EthereumRPC.execute(session: self.session, url: self.url, method: "eth_sendRawTransaction", params: [transactionHex], receive: String.self) { (error, response) in + EthereumRPC.execute(session: self.session, url: self.url, method: "eth_sendRawTransaction", params: [transactionHex], receive: String.self) { result in group.leave() - if let resDataString = response as? String { - completion(nil, resDataString) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + switch result { + case .success(let data): + if let resDataString = data as? String { + completionHandler(.success(resDataString)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } - + case .failure(let error): + group.leave() + self.failureHandler(error, completionHandler: completionHandler) + } } group.wait() } } - public func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completion: @escaping ((EthereumClientError?, Int?) -> Void)) { - EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionCount", params: [address.value, block.stringValue], receive: String.self) { (error, response) in - if let resString = response as? String { - let count = Int(hex: resString) - completion(nil, count) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + public func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completionHandler: @escaping (Result) -> Void) { + EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionCount", params: [address.value, block.stringValue], receive: String.self) { result in + switch result { + case .success(let data): + if let resString = data as? String, let count = Int(hex: resString) { + completionHandler(.success(count)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_getTransactionReceipt(txHash: String, completion: @escaping ((EthereumClientError?, EthereumTransactionReceipt?) -> Void)) { - EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionReceipt", params: [txHash], receive: EthereumTransactionReceipt.self) { (error, response) in - if let receipt = response as? EthereumTransactionReceipt { - completion(nil, receipt) - } else if let _ = response { - completion(EthereumClientError.noResultFound, nil) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + public func eth_getTransaction(byHash txHash: String, completionHandler: @escaping (Result) -> Void) { + EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionByHash", params: [txHash], receive: EthereumTransaction.self) { result in + switch result { + case .success(let data): + if let transaction = data as? EthereumTransaction { + completionHandler(.success(transaction)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_getTransaction(byHash txHash: String, completion: @escaping((EthereumClientError?, EthereumTransaction?) -> Void)) { - - EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionByHash", params: [txHash], receive: EthereumTransaction.self) { (error, response) in - if let transaction = response as? EthereumTransaction { - completion(nil, transaction) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) + public func eth_getTransactionReceipt(txHash: String, completionHandler: @escaping (Result) -> Void) { + EthereumRPC.execute(session: session, url: url, method: "eth_getTransactionReceipt", params: [txHash], receive: EthereumTransactionReceipt.self) { result in + switch result { + case .success(let data): + if let receipt = data as? EthereumTransactionReceipt { + completionHandler(.success(receipt)) + } else { + completionHandler(.failure(.noResultFound)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) } } } - public func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completion: @escaping ((EthereumClientError?, [EthereumLog]?) -> Void)) { - eth_getLogs(addresses: addresses, topics: topics.map(Topics.plain), fromBlock: from, toBlock: to, completion: completion) + public func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completionHandler: @escaping (Result<[EthereumLog], EthereumClientError>) -> Void) { + eth_getLogs(addresses: addresses, topics: topics.map(Topics.plain), fromBlock: from, toBlock: to, completion: completionHandler) } - public func eth_getLogs(addresses: [EthereumAddress]?, orTopics topics: [[String]?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) { - eth_getLogs(addresses: addresses, topics: topics.map(Topics.composed), fromBlock: from, toBlock: to, completion: completion) + public func eth_getLogs(addresses: [EthereumAddress]?, orTopics topics: [[String]?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completionHandler: @escaping (Result<[EthereumLog], EthereumClientError>) -> Void) { + eth_getLogs(addresses: addresses, topics: topics.map(Topics.composed), fromBlock: from, toBlock: to, completion: completionHandler) + } + + public func eth_getBlockByNumber(_ block: EthereumBlock, completionHandler: @escaping (Result) -> Void) { + struct CallParams: Encodable { + let block: EthereumBlock + let fullTransactions: Bool + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(block.stringValue) + try container.encode(fullTransactions) + } + } + + let params = CallParams(block: block, fullTransactions: false) + + EthereumRPC.execute(session: session, url: url, method: "eth_getBlockByNumber", params: params, receive: EthereumBlockInfo.self) { result in + switch result { + case .success(let data): + if let blockData = data as? EthereumBlockInfo { + completionHandler(.success(blockData)) + } else { + completionHandler(.failure(.unexpectedReturnValue)) + } + case .failure(let error): + self.failureHandler(error, completionHandler: completionHandler) + } + } } - private func eth_getLogs(addresses: [EthereumAddress]?, topics: Topics?, fromBlock from: EthereumBlock, toBlock to: EthereumBlock, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) { + private func eth_getLogs(addresses: [EthereumAddress]?, topics: Topics?, fromBlock from: EthereumBlock, toBlock to: EthereumBlock, completion: @escaping((Result<[EthereumLog], EthereumClientError>) -> Void)) { DispatchQueue.global(qos: .default) .async { let result = RecursiveLogCollector(ethClient: self) .getAllLogs(addresses: addresses, topics: topics, from: from, to: to) - switch result { - case .success(let logs): - completion(nil, logs) - case .failure(let error): - completion(error, nil) - } + completion(result) } } @@ -379,10 +453,15 @@ public class EthereumClient: EthereumClientProtocol { let params = CallParams(fromBlock: fromBlock.stringValue, toBlock: toBlock.stringValue, address: addresses, topics: topics) - EthereumRPC.execute(session: session, url: url, method: "eth_getLogs", params: [params], receive: [EthereumLog].self) { (error, response) in - if let logs = response as? [EthereumLog] { - completion(.success(logs)) - } else { + EthereumRPC.execute(session: session, url: url, method: "eth_getLogs", params: [params], receive: [EthereumLog].self) { result in + switch result { + case .success(let data): + if let logs = data as? [EthereumLog] { + completion(.success(logs)) + } else { + completion(.failure(.unexpectedReturnValue)) + } + case .failure(let error): if let error = error as? JSONRPCError, case let .executionError(innerError) = error, innerError.error.code == JSONRPCErrorCode.tooManyResults { @@ -394,203 +473,250 @@ public class EthereumClient: EthereumClientProtocol { } } - public func eth_getBlockByNumber(_ block: EthereumBlock, completion: @escaping((EthereumClientError?, EthereumBlockInfo?) -> Void)) { - - struct CallParams: Encodable { - let block: EthereumBlock - let fullTransactions: Bool - - func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(block.stringValue) - try container.encode(fullTransactions) - } - } - - let params = CallParams(block: block, fullTransactions: false) - - EthereumRPC.execute(session: session, url: url, method: "eth_getBlockByNumber", params: params, receive: EthereumBlockInfo.self) { (error, response) in - if let blockData = response as? EthereumBlockInfo { - completion(nil, blockData) - } else { - completion(EthereumClientError.unexpectedReturnValue, nil) - } + private func failureHandler(_ error: Error, completionHandler: @escaping (Result) -> Void) { + if case let .executionError(result) = error as? JSONRPCError { + completionHandler(.failure(.executionError(result.error))) + } else { + completionHandler(.failure(.unexpectedReturnValue)) } } } +// MARK: - Async/Await extension EthereumClient { public func net_version() async throws -> EthereumNetwork { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - net_version { error, ethereumNetwork in - if let error = error { - continuation.resume(throwing: error) - } else if let ethereumNetwork = ethereumNetwork { - continuation.resume(returning: ethereumNetwork) - } - } + net_version(completionHandler: continuation.resume) } } public func eth_gasPrice() async throws -> BigUInt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_gasPrice { error, gasPrice in - if let error = error { - continuation.resume(throwing: error) - } else if let gasPrice = gasPrice { - continuation.resume(returning: gasPrice) - } - } + eth_gasPrice(completionHandler: continuation.resume) } } public func eth_blockNumber() async throws -> Int { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_blockNumber { error, blockNumber in - if let error = error { - continuation.resume(throwing: error) - } else if let blockNumber = blockNumber { - continuation.resume(returning: blockNumber) - } - } + eth_blockNumber(completionHandler: continuation.resume) } } public func eth_getBalance(address: EthereumAddress, block: EthereumBlock) async throws -> BigUInt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getBalance(address: address, block: block) { error, balance in - if let error = error { - continuation.resume(throwing: error) - } else if let balance = balance { - continuation.resume(returning: balance) - } - } + eth_getBalance(address: address, block: block, completionHandler: continuation.resume) } } public func eth_getCode(address: EthereumAddress, block: EthereumBlock = .Latest) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getCode(address: address, block: block) { error, code in - if let error = error { - continuation.resume(throwing: error) - } else if let code = code { - continuation.resume(returning: code) - } - } + eth_getCode(address: address, block: block, completionHandler: continuation.resume) } } public func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol) async throws -> BigUInt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_estimateGas(transaction, withAccount: account) { error, gas in - if let error = error { - continuation.resume(throwing: error) - } else if let gas = gas { - continuation.resume(returning: gas) - } - } + eth_estimateGas(transaction, withAccount: account, completionHandler: continuation.resume) } } public func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_sendRawTransaction(transaction, withAccount: account) { error, txHash in - if let error = error { - continuation.resume(throwing: error) - } else if let txHash = txHash { - continuation.resume(returning: txHash) - } - } + eth_sendRawTransaction(transaction, withAccount: account, completionHandler: continuation.resume) } } public func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock) async throws -> Int { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getTransactionCount(address: address, block: block) { error, count in - if let error = error { - continuation.resume(throwing: error) - } else if let count = count { - continuation.resume(returning: count) - } - } + eth_getTransactionCount(address: address, block: block, completionHandler: continuation.resume) } } public func eth_getTransaction(byHash txHash: String) async throws -> EthereumTransaction { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getTransaction(byHash: txHash) { error, transaction in - if let error = error { - continuation.resume(throwing: error) - } else if let transaction = transaction { - continuation.resume(returning: transaction) - } - } + eth_getTransaction(byHash: txHash, completionHandler: continuation.resume) } } public func eth_getTransactionReceipt(txHash: String) async throws -> EthereumTransactionReceipt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getTransactionReceipt(txHash: txHash) { error, transactionReceipt in - if let error = error { - continuation.resume(throwing: error) - } else if let transactionReceipt = transactionReceipt { - continuation.resume(returning: transactionReceipt) - } + eth_getTransactionReceipt(txHash: txHash, completionHandler: continuation.resume) + } + } + + public func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest) async throws -> [EthereumLog] { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[EthereumLog], Error>) in + eth_getLogs(addresses: addresses, topics: topics, fromBlock: from, toBlock: to, completionHandler: continuation.resume) + } + } + + public func eth_getLogs(addresses: [EthereumAddress]?, orTopics topics: [[String]?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest) async throws -> [EthereumLog] { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[EthereumLog], Error>) in + eth_getLogs(addresses: addresses, orTopics: topics, fromBlock: from, toBlock: to, completionHandler: continuation.resume) + } + } + + public func eth_getBlockByNumber(_ block: EthereumBlock) async throws -> EthereumBlockInfo { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + eth_getBlockByNumber(block, completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +extension EthereumClient { + @available(*, deprecated, renamed: "net_version(completionHandler:)") + public func net_version(completion: @escaping ((EthereumClientError?, EthereumNetwork?) -> Void)) { + net_version { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func eth_call( - _ transaction: EthereumTransaction, - resolution: CallResolution = .noOffchain(failOnExecutionError: true), - block: EthereumBlock = .Latest) async throws -> String { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_call( - transaction, - resolution: resolution, - block: block - ) { error, txHash in - if let error = error { - continuation.resume(throwing: error) - } else if let txHash = txHash { - continuation.resume(returning: txHash) - } + @available(*, deprecated, renamed: "eth_gasPrice(completionHandler:)") + public func eth_gasPrice(completion: @escaping ((EthereumClientError?, BigUInt?) -> Void)) { + eth_gasPrice { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest) async throws -> [EthereumLog] { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[EthereumLog], Error>) in - eth_getLogs(addresses: addresses, topics: topics, fromBlock: from, toBlock: to) { error, logs in - if let error = error { - continuation.resume(throwing: error) - } else if let logs = logs { - continuation.resume(returning: logs) - } + @available(*, deprecated, renamed: "eth_blockNumber(completionHandler:)") + public func eth_blockNumber(completion: @escaping ((EthereumClientError?, Int?) -> Void)) { + eth_blockNumber { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func eth_getLogs(addresses: [EthereumAddress]?, orTopics topics: [[String]?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest) async throws -> [EthereumLog] { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[EthereumLog], Error>) in - eth_getLogs(addresses: addresses, orTopics: topics, fromBlock: from, toBlock: to) { error, logs in - if let error = error { - continuation.resume(throwing: error) - } else if let logs = logs { - continuation.resume(returning: logs) - } + @available(*, deprecated, renamed: "eth_getBalance(address:block:completionHandler:)") + public func eth_getBalance(address: EthereumAddress, block: EthereumBlock, completion: @escaping ((EthereumClientError?, BigUInt?) -> Void)) { + eth_getBalance(address: address, block: block) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func eth_getBlockByNumber(_ block: EthereumBlock) async throws -> EthereumBlockInfo { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - eth_getBlockByNumber(block) { error, blockInfo in - if let error = error { - continuation.resume(throwing: error) - } else if let blockInfo = blockInfo { - continuation.resume(returning: blockInfo) - } + @available(*, deprecated, renamed: "eth_getCode(address:block:completionHandler:)") + public func eth_getCode(address: EthereumAddress, block: EthereumBlock = .Latest, completion: @escaping((EthereumClientError?, String?) -> Void)) { + eth_getCode(address: address, block: block) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_estimateGas(_:withAccount:completionHandler:)") + public func eth_estimateGas(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, BigUInt?) -> Void)) { + eth_estimateGas(transaction, withAccount: account) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_sendRawTransaction(_:withAccount:completionHandler:)") + public func eth_sendRawTransaction(_ transaction: EthereumTransaction, withAccount account: EthereumAccountProtocol, completion: @escaping ((EthereumClientError?, String?) -> Void)) { + eth_sendRawTransaction(transaction, withAccount: account) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getTransactionCount(address:block:completionHandler:)") + public func eth_getTransactionCount(address: EthereumAddress, block: EthereumBlock, completion: @escaping ((EthereumClientError?, Int?) -> Void)) { + eth_getTransactionCount(address: address, block: block) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getTransactionReceipt(txHash:completionHandler:)") + public func eth_getTransactionReceipt(txHash: String, completion: @escaping ((EthereumClientError?, EthereumTransactionReceipt?) -> Void)) { + eth_getTransactionReceipt(txHash: txHash) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getTransaction(byHash:completionHandler:)") + public func eth_getTransaction(byHash txHash: String, completion: @escaping((EthereumClientError?, EthereumTransaction?) -> Void)) { + eth_getTransaction(byHash: txHash) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getLogs(addresses:topics:fromBlock:toBlock:completionHandler:)") + public func eth_getLogs(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completion: @escaping ((EthereumClientError?, [EthereumLog]?) -> Void)) { + eth_getLogs(addresses: addresses, topics: topics) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getLogs(addresses:orTopics:fromBlock:toBlock:completionHandler:)") + public func eth_getLogs(addresses: [EthereumAddress]?, orTopics topics: [[String]?]?, fromBlock from: EthereumBlock = .Earliest, toBlock to: EthereumBlock = .Latest, completion: @escaping((EthereumClientError?, [EthereumLog]?) -> Void)) { + eth_getLogs(addresses: addresses, orTopics: topics) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "eth_getBlockByNumber(_:completionHandler:)") + public func eth_getBlockByNumber(_ block: EthereumBlock, completion: @escaping((EthereumClientError?, EthereumBlockInfo?) -> Void)) { + eth_getBlockByNumber(block) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } diff --git a/web3swift/src/Client/JSONRPC.swift b/web3swift/src/Client/JSONRPC.swift index e258c397..7956a397 100644 --- a/web3swift/src/Client/JSONRPC.swift +++ b/web3swift/src/Client/JSONRPC.swift @@ -76,14 +76,11 @@ public enum JSONRPCError: Error { } public class EthereumRPC { - // Swift4 warning bug - https://bugs.swift.org/browse/SR-6265 - // static func execute(session: URLSession, url: URL, method: String, params: T, receive: U.Type, id: Int = 1, completion: @escaping ((Error?, JSONRPCResult?) -> Void)) -> Void { - public static func execute(session: URLSession, url: URL, method: String, params: T, receive: U.Type, id: Int = 1, completion: @escaping ((Error?, Any?) -> Void)) -> Void { - + public static func execute(session: URLSession, url: URL, method: String, params: T, receive: U.Type, id: Int = 1, completionHandler: @escaping(Result) -> Void) { if type(of: params) == [Any].self { // If params are passed in with Array and not caught, runtime fatal error - completion(JSONRPCError.encodingError, nil) + completionHandler(.failure(JSONRPCError.encodingError)) return } @@ -94,7 +91,7 @@ public class EthereumRPC { let rpcRequest = JSONRPCRequest(jsonrpc: "2.0", method: method, params: params, id: id) guard let encoded = try? JSONEncoder().encode(rpcRequest) else { - completion(JSONRPCError.encodingError, nil) + completionHandler(.failure(JSONRPCError.encodingError)) return } request.httpBody = encoded @@ -102,37 +99,46 @@ public class EthereumRPC { let task = session.dataTask(with: request) { (data, response, error) in if let data = data { if let result = try? JSONDecoder().decode(JSONRPCResult.self, from: data) { - return completion(nil, result.result) + completionHandler(.success(result.result)) } else if let result = try? JSONDecoder().decode([JSONRPCResult].self, from: data) { let resultObjects = result.map{ return $0.result } - return completion(nil, resultObjects) + completionHandler(.success(resultObjects)) } else if let errorResult = try? JSONDecoder().decode(JSONRPCErrorResult.self, from: data) { - return completion(JSONRPCError.executionError(errorResult), nil) + completionHandler(.failure(JSONRPCError.executionError(errorResult))) } else if let response = response as? HTTPURLResponse, response.statusCode < 200 || response.statusCode > 299 { - return completion(JSONRPCError.requestRejected(data), nil) + completionHandler(.failure(JSONRPCError.requestRejected(data))) } else { - return completion(JSONRPCError.noResult, nil) + completionHandler(.failure(JSONRPCError.noResult)) } + } else { + completionHandler(.failure(JSONRPCError.unknownError)) } - - completion(JSONRPCError.unknownError, nil) } task.resume() } } +// MARK: - Async/Await extension EthereumRPC { public static func execute(session: URLSession, url: URL, method: String, params: T, receive: U.Type, id: Int = 1) async throws -> Any { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - Self.execute(session: session, url: url, method: method, params: params, receive: receive, id: id) { error, result in - if let error = error { - continuation.resume(throwing: error) - } else if let result = result { - continuation.resume(returning: result) - } - } + Self.execute(session: session, url: url, method: method, params: params, receive: receive, id: id, completionHandler: continuation.resume) } } } +// MARK: - Deprecated +extension EthereumRPC { + @available(*, deprecated, renamed: "execute(session:url:method:params:receive:id:completionHandler:)") + public static func execute(session: URLSession, url: URL, method: String, params: T, receive: U.Type, id: Int = 1, completion: @escaping ((Error?, Any?) -> Void)) -> Void { + Self.execute(session: session, url: url, method: method, params: params, receive: receive, id: id) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } +} diff --git a/web3swift/src/Client/RecursiveLogCollector.swift b/web3swift/src/Client/RecursiveLogCollector.swift index a9855ac6..9c5ed071 100644 --- a/web3swift/src/Client/RecursiveLogCollector.swift +++ b/web3swift/src/Client/RecursiveLogCollector.swift @@ -109,9 +109,12 @@ struct RecursiveLogCollector { let sem = DispatchSemaphore(value: 0) var responseValue: EthereumBlock? - self.ethClient.eth_blockNumber { (error, blockInt) in - if let blockInt = blockInt { - responseValue = EthereumBlock(rawValue: blockInt) + self.ethClient.eth_blockNumber { result in + switch result { + case .success(let block): + responseValue = EthereumBlock(rawValue: block) + default: + break } sem.signal() } diff --git a/web3swift/src/Contract/Statically Typed/EthereumClient+Static.swift b/web3swift/src/Contract/Statically Typed/EthereumClient+Static.swift index 1902b5fe..67f490fc 100644 --- a/web3swift/src/Contract/Statically Typed/EthereumClient+Static.swift +++ b/web3swift/src/Contract/Statically Typed/EthereumClient+Static.swift @@ -9,21 +9,13 @@ import Foundation public extension ABIFunction { - func execute(withClient client: EthereumClientProtocol, account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, String?) -> Void)) { - + func execute(withClient client: EthereumClientProtocol, account: EthereumAccountProtocol, completionHandler: @escaping(Result) -> Void) { guard let tx = try? self.transaction() else { - return completion(EthereumClientError.encodeIssue, nil) - } - - - client.eth_sendRawTransaction(tx, withAccount: account) { (error, res) in - guard let res = res, error == nil else { - return completion(EthereumClientError.unexpectedReturnValue, nil) - } - - return completion(nil, res) + completionHandler(.failure(.encodeIssue)) + return } + client.eth_sendRawTransaction(tx, withAccount: account, completionHandler: completionHandler) } func call( @@ -31,39 +23,43 @@ public extension ABIFunction { responseType: T.Type, block: EthereumBlock = .Latest, resolution: CallResolution = .noOffchain(failOnExecutionError: true), - completion: @escaping((EthereumClientError?, T?) -> Void) + completionHandler: @escaping(Result) -> Void ) { guard let tx = try? self.transaction() else { - return completion(EthereumClientError.encodeIssue, nil) + completionHandler(.failure(.encodeIssue)) + return } - client.eth_call( - tx, - resolution: resolution, - block: block - ) { (error, res) in + client.eth_call(tx, + resolution: resolution, + block: block) { result in + let parseOrFail: (String) -> Void = { data in guard let response = (try? T(data: data)) else { - return completion(EthereumClientError.decodeIssue, nil) + completionHandler(.failure(.decodeIssue)) + return } - return completion(nil, response) + completionHandler(.success(response)) + return } - switch (error, res) { - case (.executionError, _): - if resolution.failOnExecutionError { - return completion(error, nil) - } else { - return parseOrFail("0x") - } - case (let error?, _): - return completion(error, nil) - case (nil, let data?): + switch result { + case .success(let data): parseOrFail(data) - case (nil, nil): - return completion(EthereumClientError.unexpectedReturnValue, nil) + case .failure(let error): + switch (error) { + case (.executionError): + if resolution.failOnExecutionError { + completionHandler(.failure(error)) + return + } else { + return parseOrFail("0x") + } + default: + completionHandler(.failure(error)) + } } } } @@ -89,55 +85,247 @@ extension CallResolution { } } +public struct EventFilter { + public let type: ABIEvent.Type + public let allowedSenders: [EthereumAddress] + + public init(type: ABIEvent.Type, + allowedSenders: [EthereumAddress]) { + self.type = type + self.allowedSenders = allowedSenders + } +} + +public struct Events { + let events: [ABIEvent] + let logs: [EthereumLog] +} + +public extension EthereumClientProtocol { + typealias EventsCompletionHandler = (Result) -> Void + + func getEvents(addresses: [EthereumAddress]?, + orTopics: [[String]?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + matching matches: [EventFilter], + completionHandler: @escaping EventsCompletionHandler) { + self.eth_getLogs(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] result in + self?.handleLogs(result, matches, completionHandler) + } + } + + func getEvents(addresses: [EthereumAddress]?, + orTopics: [[String]?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + eventTypes: [ABIEvent.Type], + completionHandler: @escaping EventsCompletionHandler) { + let unfiltered = eventTypes.map { EventFilter(type: $0, allowedSenders: []) } + self.eth_getLogs(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] result in + self?.handleLogs(result, unfiltered, completionHandler) + } + } + + func getEvents(addresses: [EthereumAddress]?, + topics: [String?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + eventTypes: [ABIEvent.Type], + completionHandler: @escaping EventsCompletionHandler) { + let unfiltered = eventTypes.map { EventFilter(type: $0, allowedSenders: []) } + getEvents(addresses: addresses, + topics: topics, + fromBlock: fromBlock, + toBlock: toBlock, + matching: unfiltered, + completionHandler: completionHandler) + } + + func getEvents(addresses: [EthereumAddress]?, + topics: [String?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + matching matches: [EventFilter], + completionHandler: @escaping EventsCompletionHandler) { + + self.eth_getLogs(addresses: addresses, topics: topics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] result in + self?.handleLogs(result, matches, completionHandler) + } + } + + func handleLogs(_ result: Result<[EthereumLog], EthereumClientError>, + _ matches: [EventFilter], + _ completionHandler: EventsCompletionHandler) { + switch result { + case .failure(let error): + completionHandler(.failure(error)) + case .success(let logs): + var events: [ABIEvent] = [] + var unprocessed: [EthereumLog] = [] + + var filtersBySignature: [String: [EventFilter]] = [:] + for filter in matches { + if let sig = try? filter.type.signature() { + var filters = filtersBySignature[sig, default: [EventFilter]()] + filters.append(filter) + filtersBySignature[sig] = filters + } + } + + let parseEvent: (EthereumLog, ABIEvent.Type) -> ABIEvent? = { log, eventType in + let topicTypes = eventType.types.enumerated() + .filter { eventType.typesIndexed[$0.offset] == true } + .compactMap { $0.element } + + let dataTypes = eventType.types.enumerated() + .filter { eventType.typesIndexed[$0.offset] == false } + .compactMap { $0.element } + + guard let data = try? ABIDecoder.decodeData(log.data, types: dataTypes, asArray: true) else { + return nil + } + + guard data.count == dataTypes.count else { + return nil + } + + let rawTopics = Array(log.topics.dropFirst()) + + guard let parsedTopics = (try? zip(rawTopics, topicTypes).map { pair in + try ABIDecoder.decodeData(pair.0, types: [pair.1]) + }) else { + return nil + } + + guard let eventOpt = ((try? eventType.init(topics: parsedTopics.flatMap { $0 }, data: data, log: log)) as ABIEvent??), let event = eventOpt else { + return nil + } + + return event + } + + for log in logs { + guard let signature = log.topics.first, + let filters = filtersBySignature[signature] else { + unprocessed.append(log) + continue + } + + for filter in filters { + let allowedSenders = Set(filter.allowedSenders) + if allowedSenders.count > 0 && !allowedSenders.contains(log.address) { + unprocessed.append(log) + } else if let event = parseEvent(log, filter.type) { + events.append(event) + } else { + unprocessed.append(log) + } + } + } + completionHandler(.success(Events(events: events, logs: unprocessed))) + } + } +} + +// MARK: - Async/Await public extension ABIFunction { func execute(withClient client: EthereumClientProtocol, account: EthereumAccountProtocol) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - execute(withClient: client, account: account) { error, response in - if let error = error { - continuation.resume(throwing: error) - } else if let response = response { - continuation.resume(returning: response) - } - } + execute(withClient: client, account: account, completionHandler: continuation.resume) } } - func call( - withClient client: EthereumClientProtocol, - responseType: T.Type, - block: EthereumBlock = .Latest, - resolution: CallResolution = .noOffchain(failOnExecutionError: true) - ) async throws -> T { + func call(withClient client: EthereumClientProtocol, + responseType: T.Type, + block: EthereumBlock = .Latest, + resolution: CallResolution = .noOffchain(failOnExecutionError: true)) async throws -> T { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - call( - withClient: client, - responseType: responseType, - block: block, - resolution: resolution - ) { error, response in - if let error = error { - continuation.resume(throwing: error) - } else if let response = response { - continuation.resume(returning: response) - } - } + call(withClient: client, responseType: responseType, block: block, resolution: resolution, completionHandler: continuation.resume) } } } -public struct EventFilter { - public let type: ABIEvent.Type - public let allowedSenders: [EthereumAddress] +public extension EthereumClientProtocol { + func getEvents(addresses: [EthereumAddress]?, + orTopics: [[String]?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + matching matches: [EventFilter]) async throws -> Events { - public init(type: ABIEvent.Type, - allowedSenders: [EthereumAddress]) { - self.type = type - self.allowedSenders = allowedSenders + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.getEvents(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock, matching: matches, completionHandler: continuation.resume) + } + } + + func getEvents(addresses: [EthereumAddress]?, + orTopics: [[String]?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + eventTypes: [ABIEvent.Type]) async throws -> Events { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.getEvents(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock, eventTypes: eventTypes, completionHandler: continuation.resume) + } + } + + func getEvents(addresses: [EthereumAddress]?, + topics: [String?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + eventTypes: [ABIEvent.Type]) async throws -> Events { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.getEvents(addresses: addresses, topics: topics, fromBlock: fromBlock, toBlock: toBlock, eventTypes: eventTypes, completionHandler: continuation.resume) + } + } + + func getEvents(addresses: [EthereumAddress]?, + topics: [String?]?, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + matching matches: [EventFilter]) async throws -> Events { + + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.getEvents(addresses: addresses, topics: topics, fromBlock: fromBlock, toBlock: toBlock, matching: matches, completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +public extension ABIFunction { + @available(*, deprecated, renamed: "execute(withClient:account:completionHandler:)") + func execute(withClient client: EthereumClientProtocol, account: EthereumAccountProtocol, completion: @escaping((EthereumClientError?, String?) -> Void)) { + execute(withClient: client, account: account) { result in + switch result { + case .success(let value): + completion(nil, value) + case.failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "call(withClient:responseType:block:resolution:completionHandler:)") + func call(withClient client: EthereumClientProtocol, + responseType: T.Type, + block: EthereumBlock = .Latest, + resolution: CallResolution = .noOffchain(failOnExecutionError: true), + completion: @escaping((EthereumClientError?, T?) -> Void)) { + call(withClient: client, responseType: responseType) { result in + switch result { + case .success(let value): + completion(nil, value) + case.failure(let error): + completion(error, nil) + } + } } } public extension EthereumClientProtocol { + @available(*, deprecated, renamed: "EventsCompletionHandler") typealias EventsCompletion = (EthereumClientError?, [ABIEvent], [EthereumLog]) -> Void + + @available(*, deprecated, renamed: "getEvents(addresses:orTopics:fromBlock:toBlock:matching:completionHandler:)") func getEvents(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, @@ -149,6 +337,7 @@ public extension EthereumClientProtocol { } } + @available(*, deprecated, renamed: "getEvents(addresses:orTopics:fromBlock:toBlock:eventTypes:completionHandler:)") func getEvents(addresses: [EthereumAddress]?, orTopics: [[String]?]?, fromBlock: EthereumBlock, @@ -161,6 +350,7 @@ public extension EthereumClientProtocol { } } + @available(*, deprecated, renamed: "getEvents(addresses:topics:fromBlock:toBlock:eventTypes:completionHandler:)") func getEvents(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, @@ -176,6 +366,7 @@ public extension EthereumClientProtocol { completion: completion) } + @available(*, deprecated, renamed: "getEvents(addresses:topics:fromBlock:toBlock:matching:completionHandler:)") func getEvents(addresses: [EthereumAddress]?, topics: [String?]?, fromBlock: EthereumBlock, @@ -188,10 +379,11 @@ public extension EthereumClientProtocol { } } + @available(*, deprecated) func handleLogs(_ error: EthereumClientError?, - _ logs: [EthereumLog]?, - _ matches: [EventFilter], - _ completion: EventsCompletion) { + _ logs: [EthereumLog]?, + _ matches: [EventFilter], + _ completion: EventsCompletion) { if let error = error { return completion(error, [], []) } @@ -245,9 +437,9 @@ public extension EthereumClientProtocol { for log in logs { guard let signature = log.topics.first, let filters = filtersBySignature[signature] else { - unprocessed.append(log) - continue - } + unprocessed.append(log) + continue + } for filter in filters { let allowedSenders = Set(filter.allowedSenders) @@ -264,79 +456,3 @@ public extension EthereumClientProtocol { return completion(error, events, unprocessed) } } - -public struct Events { - let events: [ABIEvent] - let logs: [EthereumLog] -} - -public extension EthereumClient { - func getEvents(addresses: [EthereumAddress]?, - orTopics: [[String]?]?, - fromBlock: EthereumBlock, - toBlock: EthereumBlock, - matching matches: [EventFilter]) async throws -> Events { - - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.eth_getLogs(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] (error, logs) in - self?.handleLogs(error, logs, matches) { error, events, logs in - if let error = error { - continuation.resume(throwing: error) - } - continuation.resume(returning: Events(events: events, logs: logs)) - } - } - } - } - - func getEvents(addresses: [EthereumAddress]?, - orTopics: [[String]?]?, - fromBlock: EthereumBlock, - toBlock: EthereumBlock, - eventTypes: [ABIEvent.Type]) async throws -> Events { - let unfiltered = eventTypes.map { EventFilter(type: $0, allowedSenders: []) } - - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.eth_getLogs(addresses: addresses, orTopics: orTopics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] (error, logs) in - self?.handleLogs(error, logs, unfiltered) { error, events, logs in - if let error = error { - continuation.resume(throwing: error) - } - continuation.resume(returning: Events(events: events, logs: logs)) - } - } - } - } - - func getEvents(addresses: [EthereumAddress]?, - topics: [String?]?, - fromBlock: EthereumBlock, - toBlock: EthereumBlock, - eventTypes: [ABIEvent.Type]) async throws -> Events { - let unfiltered = eventTypes.map { EventFilter(type: $0, allowedSenders: []) } - return try await getEvents(addresses: addresses, - topics: topics, - fromBlock: fromBlock, - toBlock: toBlock, - matching: unfiltered) - } - - func getEvents(addresses: [EthereumAddress]?, - topics: [String?]?, - fromBlock: EthereumBlock, - toBlock: EthereumBlock, - matching matches: [EventFilter]) async throws -> Events { - - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.eth_getLogs(addresses: addresses, topics: topics, fromBlock: fromBlock, toBlock: toBlock) { [weak self] (error, logs) in - self?.handleLogs(error, logs, matches) { error, events, logs in - if let error = error { - continuation.resume(throwing: error) - } - continuation.resume(returning: Events(events: events, logs: logs)) - } - } - } - } -} - diff --git a/web3swift/src/ENS/EthereumNameService.swift b/web3swift/src/ENS/EthereumNameService.swift index e0e4988c..2f517e39 100644 --- a/web3swift/src/ENS/EthereumNameService.swift +++ b/web3swift/src/ENS/EthereumNameService.swift @@ -75,59 +75,49 @@ public class EthereumNameService: EthereumNameServiceProtocol { self.maximumRedirections = maximumRedirections } - public func resolve( - address: EthereumAddress, - mode: ResolutionMode, - completion: @escaping ((EthereumNameServiceError?, String?) -> Void) - ) { - guard - let network = client.network, - let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else { - return completion(EthereumNameServiceError.noNetwork, nil) - } + public func resolve(address: EthereumAddress, + mode: ResolutionMode, + completionHandler: @escaping(Result) -> Void) { + guard let network = client.network, + let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else { + completionHandler(.failure(.noNetwork)) + return + } Task { do { - let resolver = try await getResolver( - for: address, - registryAddress: registryAddress, - mode: mode - ) + let resolver = try await getResolver(for: address, + registryAddress: registryAddress, + mode: mode) let name = try await resolver.resolve(address: address) - completion(nil, name) + completionHandler(.success(name)) } catch let error { - completion(error as? EthereumNameServiceError ?? .ensUnknown, nil) + completionHandler(.failure(error as? EthereumNameServiceError ?? .ensUnknown)) } } } - public func resolve( - ens: String, - mode: ResolutionMode, - completion: @escaping ((EthereumNameServiceError?, EthereumAddress?) -> Void) - ) { - guard - let network = client.network, - let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else { - return completion(EthereumNameServiceError.noNetwork, nil) + public func resolve(ens: String, + mode: ResolutionMode, + completionHandler: @escaping(Result) -> Void) { + guard let network = client.network, + let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else { + completionHandler(.failure(.noNetwork)) + return } Task { do { - let (resolver, supportingWildCard) = try await getResolver( - for: ens, - fullName: ens, - registryAddress: registryAddress, - mode: mode - ) + let (resolver, supportingWildCard) = try await getResolver(for: ens, + fullName: ens, + registryAddress: registryAddress, + mode: mode) - let address = try await resolver.resolve( - name: ens, - supportingWildcard: supportingWildCard - ) - completion(nil, address) + let address = try await resolver.resolve(name: ens, + supportingWildcard: supportingWildCard) + completionHandler(.success(address)) } catch let error { - completion(error as? EthereumNameServiceError ?? .ensUnknown, nil) + completionHandler(.failure(error as? EthereumNameServiceError ?? .ensUnknown)) } } } @@ -143,22 +133,14 @@ public class EthereumNameService: EthereumNameServiceProtocol { } } +// MARK: - Async/Await extension EthereumNameService { public func resolve( address: EthereumAddress, mode: ResolutionMode ) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - resolve( - address: address, - mode: mode - ) { error, ensHex in - if let error = error { - continuation.resume(throwing: error) - } else if let ensHex = ensHex { - continuation.resume(returning: ensHex) - } - } + resolve(address: address, mode: mode, completionHandler: continuation.resume) } } @@ -167,16 +149,7 @@ extension EthereumNameService { mode: ResolutionMode ) async throws -> EthereumAddress { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - resolve( - ens: ens, - mode: mode - ) { error, address in - if let error = error { - continuation.resume(throwing: error) - } else if let address = address { - continuation.resume(returning: address) - } - } + resolve(ens: ens, mode: mode, completionHandler: continuation.resume) } } } @@ -274,3 +247,34 @@ extension EthereumNameService { } } + +// MARK: - Deprecated +extension EthereumNameService { + @available(*, deprecated, renamed: "resolve(address:mode:completionHandler:)") + public func resolve(address: EthereumAddress, + mode: ResolutionMode, + completion: @escaping ((EthereumNameServiceError?, String?) -> Void)) { + resolve(address: address, mode: mode) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "resolve(ens:mode:completionHandler:)") + public func resolve(ens: String, + mode: ResolutionMode, + completion: @escaping ((EthereumNameServiceError?, EthereumAddress?) -> Void)) { + resolve(ens: ens, mode: mode) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } +} diff --git a/web3swift/src/ERC165/ERC165.swift b/web3swift/src/ERC165/ERC165.swift index 28452797..14eb7067 100644 --- a/web3swift/src/ERC165/ERC165.swift +++ b/web3swift/src/ERC165/ERC165.swift @@ -15,25 +15,39 @@ public class ERC165 { self.client = client } - public func supportsInterface(contract: EthereumAddress, id: Data, completion: @escaping((Error?, Bool?) -> Void)) { + public func supportsInterface(contract: EthereumAddress, id: Data, completionHandler: @escaping(Result) -> Void) { let function = ERC165Functions.supportsInterface(contract: contract, interfaceId: id) - function.call(withClient: self.client, responseType: ERC165Responses.supportsInterfaceResponse.self) { (error, response) in - return completion(error, response?.supported) + function.call(withClient: self.client, responseType: ERC165Responses.supportsInterfaceResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.supported)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - } +// MARK: - Async/Await extension ERC165 { public func supportsInterface(contract: EthereumAddress, id: Data) async throws -> Bool { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - supportsInterface(contract: contract, id: id) { error, supported in - if let error = error { - continuation.resume(throwing: error) - } else if let supported = supported { - continuation.resume(returning: supported) - } + supportsInterface(contract: contract, id: id, completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +extension ERC165 { + @available(*, deprecated, renamed: "supportsInterface(contract:id:completionHandler:)") + public func supportsInterface(contract: EthereumAddress, id: Data, completion: @escaping((Error?, Bool?) -> Void)) { + supportsInterface(contract: contract, id: id) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } diff --git a/web3swift/src/ERC20/ERC20.swift b/web3swift/src/ERC20/ERC20.swift index 7ba6ba78..f901fd72 100644 --- a/web3swift/src/ERC20/ERC20.swift +++ b/web3swift/src/ERC20/ERC20.swift @@ -11,27 +11,32 @@ import BigInt public protocol ERC20Protocol { init(client: EthereumClient) - func name(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) - func symbol(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) - func decimals(tokenContract: EthereumAddress, completion: @escaping((Error?, UInt8?) -> Void)) - func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) - func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) - func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) - func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) - func name(tokenContract: EthereumAddress) async throws -> String + func name(tokenContract: EthereumAddress, completionHandler: @escaping(Result) -> Void) + func symbol(tokenContract: EthereumAddress, completionHandler: @escaping(Result) -> Void) + func decimals(tokenContract: EthereumAddress, completionHandler: @escaping(Result) -> Void) + func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completionHandler: @escaping(Result) -> Void) + func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completionHandler: @escaping(Result) -> Void) + func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping(Result<[ERC20Events.Transfer], Error>) -> Void) + func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping(Result<[ERC20Events.Transfer], Error>) -> Void) + // async + func name(tokenContract: EthereumAddress) async throws -> String func symbol(tokenContract: EthereumAddress) async throws -> String - func decimals(tokenContract: EthereumAddress) async throws -> UInt8 - func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress) async throws -> BigUInt - func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress) async throws -> BigUInt - func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC20Events.Transfer] - func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC20Events.Transfer] + + // deprecated + func name(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) + func symbol(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) + func decimals(tokenContract: EthereumAddress, completion: @escaping((Error?, UInt8?) -> Void)) + func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) + func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) + func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) + func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) } public class ERC20: ERC20Protocol { @@ -41,49 +46,71 @@ public class ERC20: ERC20Protocol { self.client = client } - public func name(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) { + public func name(tokenContract: EthereumAddress, completionHandler: @escaping (Result) -> Void) { let function = ERC20Functions.name(contract: tokenContract) - function.call(withClient: self.client, responseType: ERC20Responses.nameResponse.self) { (error, nameResponse) in - return completion(error, nameResponse?.value) + function.call(withClient: self.client, responseType: ERC20Responses.nameResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - public func symbol(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) { + public func symbol(tokenContract: EthereumAddress, completionHandler: @escaping (Result) -> Void) { let function = ERC20Functions.symbol(contract: tokenContract) - function.call(withClient: self.client, responseType: ERC20Responses.symbolResponse.self) { (error, symbolResponse) in - return completion(error, symbolResponse?.value) + function.call(withClient: self.client, responseType: ERC20Responses.symbolResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - public func decimals(tokenContract: EthereumAddress, completion: @escaping((Error?, UInt8?) -> Void)) { + public func decimals(tokenContract: EthereumAddress, completionHandler: @escaping (Result) -> Void) { let function = ERC20Functions.decimals(contract: tokenContract) - function.call( - withClient: self.client, - responseType: ERC20Responses.decimalsResponse.self, - resolution: .noOffchain(failOnExecutionError: false) - ) { (error, decimalsResponse) in - return completion(error, decimalsResponse?.value) + function.call(withClient: self.client, + responseType: ERC20Responses.decimalsResponse.self, + resolution: .noOffchain(failOnExecutionError: false)) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - public func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) { + public func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completionHandler: @escaping (Result) -> Void) { let function = ERC20Functions.balanceOf(contract: tokenContract, account: address) - function.call(withClient: self.client, responseType: ERC20Responses.balanceResponse.self) { (error, balanceResponse) in - return completion(error, balanceResponse?.value) + function.call(withClient: self.client, responseType: ERC20Responses.balanceResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - public func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) { + public func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completionHandler: @escaping (Result) -> Void) { let function = ERC20Functions.allowance(contract: tokenContract, owner: address, spender: spender) - function.call(withClient: self.client, responseType: ERC20Responses.balanceResponse.self) { (error, balanceResponse) in - return completion(error, balanceResponse?.value) + function.call(withClient: self.client, responseType: ERC20Responses.balanceResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } - public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) { - + public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping (Result<[ERC20Events.Transfer], Error>) -> Void) { guard let result = try? ABIEncoder.encode(recipient).bytes, let sig = try? ERC20Events.Transfer.signature() else { - completion(EthereumSignerError.unknownError, nil) + completionHandler(.failure(EthereumSignerError.unknownError)) return } @@ -91,21 +118,23 @@ public class ERC20: ERC20Protocol { topics: [ sig, nil, String(hexFromBytes: result)], fromBlock: fromBlock, toBlock: toBlock, - eventTypes: [ERC20Events.Transfer.self]) { (error, events, unprocessedLogs) in - - if let events = events as? [ERC20Events.Transfer] { - return completion(error, events) - } else { - return completion(error ?? EthereumClientError.decodeIssue, nil) + eventTypes: [ERC20Events.Transfer.self]) { result in + switch result { + case .success(let data): + if let events = data.events as? [ERC20Events.Transfer] { + completionHandler(.success(events)) + } else { + completionHandler(.failure(EthereumClientError.decodeIssue)) + } + case .failure(let error): + completionHandler(.failure(error)) } - } } - public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) { - + public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completionHandler: @escaping (Result<[ERC20Events.Transfer], Error>) -> Void) { guard let result = try? ABIEncoder.encode(sender).bytes, let sig = try? ERC20Events.Transfer.signature() else { - completion(EthereumSignerError.unknownError, nil) + completionHandler(.failure(EthereumSignerError.unknownError)) return } @@ -113,99 +142,149 @@ public class ERC20: ERC20Protocol { topics: [ sig, String(hexFromBytes: result), nil ], fromBlock: fromBlock, toBlock: toBlock, - eventTypes: [ERC20Events.Transfer.self]) { (error, events, unprocessedLogs) in - - if let events = events as? [ERC20Events.Transfer] { - return completion(error, events) - } else { - return completion(error ?? EthereumClientError.decodeIssue, nil) + eventTypes: [ERC20Events.Transfer.self]) { result in + + switch result { + case .success(let data): + if let events = data.events as? [ERC20Events.Transfer] { + completionHandler(.success(events)) + } else { + completionHandler(.failure(EthereumClientError.decodeIssue)) + } + case .failure(let error): + completionHandler(.failure(error)) } - } } } +// MARK: - Async/Await extension ERC20 { public func name(tokenContract: EthereumAddress) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - name(tokenContract: tokenContract) { error, name in - if let error = error { - continuation.resume(throwing: error) - } else if let name = name { - continuation.resume(returning: name) - } - } + name(tokenContract: tokenContract, completionHandler: continuation.resume) } } public func symbol(tokenContract: EthereumAddress) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - symbol(tokenContract: tokenContract) { error, symbol in - if let error = error { - continuation.resume(throwing: error) - } else if let symbol = symbol { - continuation.resume(returning: symbol) - } - } + symbol(tokenContract: tokenContract, completionHandler: continuation.resume) } } public func decimals(tokenContract: EthereumAddress) async throws -> UInt8 { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - decimals(tokenContract: tokenContract) { error, decimals in - if let error = error { - continuation.resume(throwing: error) - } else if let decimals = decimals { - continuation.resume(returning: decimals) - } - } + decimals(tokenContract: tokenContract, completionHandler: continuation.resume) } } public func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress) async throws -> BigUInt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - balanceOf(tokenContract: tokenContract, address: address) { error, balance in - if let error = error { - continuation.resume(throwing: error) - } else if let balance = balance { - continuation.resume(returning: balance) - } - } + balanceOf(tokenContract: tokenContract, address: address, completionHandler: continuation.resume) } } public func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress) async throws -> BigUInt { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - allowance(tokenContract: tokenContract, address: address, spender: spender) { error, allowance in - if let error = error { - continuation.resume(throwing: error) - } else if let allowance = allowance { - continuation.resume(returning: allowance) - } - } + allowance(tokenContract: tokenContract, address: address, spender: spender, completionHandler: continuation.resume) } } public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC20Events.Transfer] { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC20Events.Transfer], Error>) in - transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock) { error, events in - if let error = error { - continuation.resume(throwing: error) - } else if let events = events { - continuation.resume(returning: events) - } - } + transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock, completionHandler: continuation.resume) } } public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC20Events.Transfer] { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC20Events.Transfer], Error>) in - transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock) { error, events in - if let error = error { - continuation.resume(throwing: error) - } else if let events = events { - continuation.resume(returning: events) - } + transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock, completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +extension ERC20 { + @available(*, deprecated, renamed: "name(tokenContract:completionHandler:)") + public func name(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) { + name(tokenContract: tokenContract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "symbol(tokenContract:completionHandler:)") + public func symbol(tokenContract: EthereumAddress, completion: @escaping((Error?, String?) -> Void)) { + symbol(tokenContract: tokenContract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "decimals(tokenContract:completionHandler:)") + public func decimals(tokenContract: EthereumAddress, completion: @escaping((Error?, UInt8?) -> Void)) { + decimals(tokenContract: tokenContract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "balanceOf(tokenContract:address:completionHandler:)") + public func balanceOf(tokenContract: EthereumAddress, address: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) { + balanceOf(tokenContract: tokenContract, address: address) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "allowance(tokenContract:address:spender:completionHandler:)") + public func allowance(tokenContract: EthereumAddress, address: EthereumAddress, spender: EthereumAddress, completion: @escaping((Error?, BigUInt?) -> Void)) { + allowance(tokenContract: tokenContract, address: address, spender: spender) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "transferEventsTo(recipient:fromBlock:toBlock:completionHandler:)") + public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) { + transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "transferEventsFrom(sender:fromBlock:toBlock:completionHandler:)") + public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, completion: @escaping((Error?, [ERC20Events.Transfer]?) -> Void)) { + transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } diff --git a/web3swift/src/ERC721/ERC721.swift b/web3swift/src/ERC721/ERC721.swift index 5bb67df2..19cb2841 100644 --- a/web3swift/src/ERC721/ERC721.swift +++ b/web3swift/src/ERC721/ERC721.swift @@ -16,30 +16,40 @@ import FoundationNetworking public class ERC721: ERC165 { public func balanceOf(contract: EthereumAddress, address: EthereumAddress, - completion: @escaping((Error?, BigUInt?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { let function = ERC721Functions.balanceOf(contract: contract, owner: address) function.call(withClient: client, - responseType: ERC721Responses.balanceResponse.self) { (error, response) in - return completion(error, response?.value) + responseType: ERC721Responses.balanceResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } public func ownerOf(contract: EthereumAddress, tokenId: BigUInt, - completion: @escaping((Error?, EthereumAddress?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { let function = ERC721Functions.ownerOf(contract: contract, tokenId: tokenId) function.call(withClient: client, - responseType: ERC721Responses.ownerResponse.self) { (error, response) in - return completion(error, response?.value) + responseType: ERC721Responses.ownerResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, - completion: @escaping((Error?, [ERC721Events.Transfer]?) -> Void)) { + completionHandler: @escaping(Result<[ERC721Events.Transfer], Error>) -> Void) { guard let result = try? ABIEncoder.encode(recipient).bytes, let sig = try? ERC721Events.Transfer.signature() else { - completion(EthereumSignerError.unknownError, nil) + completionHandler(.failure(EthereumSignerError.unknownError)) return } @@ -47,12 +57,17 @@ public class ERC721: ERC165 { topics: [ sig, nil, String(hexFromBytes: result)], fromBlock: fromBlock, toBlock: toBlock, - eventTypes: [ERC721Events.Transfer.self]) { (error, events, unprocessedLogs) in + eventTypes: [ERC721Events.Transfer.self]) { result in - if let events = events as? [ERC721Events.Transfer] { - return completion(error, events) - } else { - return completion(error ?? EthereumClientError.decodeIssue, nil) + switch result { + case .success(let data): + if let events = data.events as? [ERC721Events.Transfer] { + completionHandler(.success(events)) + } else { + completionHandler(.failure(EthereumClientError.decodeIssue)) + } + case .failure(let error): + completionHandler(.failure(error)) } } } @@ -60,9 +75,9 @@ public class ERC721: ERC165 { public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock, - completion: @escaping((Error?, [ERC721Events.Transfer]?) -> Void)) { + completionHandler: @escaping(Result<[ERC721Events.Transfer], Error>) -> Void) { guard let result = try? ABIEncoder.encode(sender).bytes, let sig = try? ERC721Events.Transfer.signature() else { - completion(EthereumSignerError.unknownError, nil) + completionHandler(.failure(EthereumSignerError.unknownError)) return } @@ -70,62 +85,17 @@ public class ERC721: ERC165 { topics: [ sig, String(hexFromBytes: result)], fromBlock: fromBlock, toBlock: toBlock, - eventTypes: [ERC721Events.Transfer.self]) { (error, events, unprocessedLogs) in - - if let events = events as? [ERC721Events.Transfer] { - return completion(error, events) - } else { - return completion(error ?? EthereumClientError.decodeIssue, nil) - } - } - } -} + eventTypes: [ERC721Events.Transfer.self]) { result in -extension ERC721 { - public func balanceOf(contract: EthereumAddress, address: EthereumAddress) async throws -> BigUInt { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - balanceOf(contract: contract, address: address) { error, balance in - if let error = error { - continuation.resume(throwing: error) - } else if let balance = balance { - continuation.resume(returning: balance) - } - } - } - } - - public func ownerOf(contract: EthereumAddress, tokenId: BigUInt) async throws -> EthereumAddress { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - ownerOf(contract: contract, tokenId: tokenId) { error, owner in - if let error = error { - continuation.resume(throwing: error) - } else if let owner = owner { - continuation.resume(returning: owner) - } - } - } - } - - public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC721Events.Transfer] { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC721Events.Transfer], Error>) in - transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock) { error, events in - if let error = error { - continuation.resume(throwing: error) - } else if let events = events { - continuation.resume(returning: events) - } - } - } - } - - public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC721Events.Transfer] { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC721Events.Transfer], Error>) in - transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock) { error, events in - if let error = error { - continuation.resume(throwing: error) - } else if let events = events { - continuation.resume(returning: events) + switch result { + case .success(let data): + if let events = data.events as? [ERC721Events.Transfer] { + completionHandler(.success(events)) + } else { + completionHandler(.failure(EthereumClientError.decodeIssue)) } + case .failure(let error): + completionHandler(.failure(error)) } } } @@ -201,67 +171,153 @@ public class ERC721Metadata: ERC721 { } public func name(contract: EthereumAddress, - completion: @escaping((Error?, String?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { let function = ERC721MetadataFunctions.name(contract: contract) - function.call(withClient: client, responseType: ERC721MetadataResponses.nameResponse.self) { error, response in - return completion(error, response?.value) + function.call(withClient: client, responseType: ERC721MetadataResponses.nameResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } public func symbol(contract: EthereumAddress, - completion: @escaping((Error?, String?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { let function = ERC721MetadataFunctions.symbol(contract: contract) - function.call(withClient: client, responseType: ERC721MetadataResponses.symbolResponse.self) { error, response in - return completion(error, response?.value) + function.call(withClient: client, responseType: ERC721MetadataResponses.symbolResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } public func tokenURI(contract: EthereumAddress, tokenID: BigUInt, - completion: @escaping((Error?, URL?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { let function = ERC721MetadataFunctions.tokenURI(contract: contract, tokenID: tokenID) - function.call(withClient: client, responseType: ERC721MetadataResponses.tokenURIResponse.self) { error, response in - return completion(error, response?.value) + function.call(withClient: client, responseType: ERC721MetadataResponses.tokenURIResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } } } public func tokenMetadata(contract: EthereumAddress, tokenID: BigUInt, - completion: @escaping((Error?, Token?) -> Void)) { + completionHandler: @escaping(Result) -> Void) { tokenURI(contract: contract, - tokenID: tokenID) { [weak self] error, response in - guard let response = response else { - return completion(error, nil) + tokenID: tokenID) { [weak self] result in + switch result { + case .success(let baseURL): + let task = self?.session.dataTask(with: baseURL, + completionHandler: { (data, response, error) in + guard let data = data else { + completionHandler(.failure(EthereumClientError.unexpectedReturnValue)) + return + } + if let error = error { + completionHandler(.failure(error)) + return + } + + do { + var metadata = try JSONDecoder().decode(Token.self, from: data) + + if let image = metadata.properties?.image.description, image.host == nil, let relative = URL(string: image.absoluteString, relativeTo: baseURL) { + metadata.properties?.image = Token.Property(description: relative) + } + completionHandler(.success(metadata)) + } catch let decodeError { + completionHandler(.failure(decodeError)) + } + }) + + task?.resume() + case .failure(let error): + completionHandler(.failure(error)) } + } + } +} - if let error = error { - return completion(error, nil) +public class ERC721Enumerable: ERC721 { + public func totalSupply(contract: EthereumAddress, + completionHandler: @escaping(Result) -> Void) { + let function = ERC721EnumerableFunctions.totalSupply(contract: contract) + function.call(withClient: client, responseType: ERC721EnumerableResponses.numberResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) } + } + } - let baseURL = response - let task = self?.session.dataTask(with: baseURL, - completionHandler: { (data, response, error) in - guard let data = data else { - return completion(error, nil) - } - if let error = error { - return completion(error, nil) - } + public func tokenByIndex(contract: EthereumAddress, + index: BigUInt, + completionHandler: @escaping(Result) -> Void) { + let function = ERC721EnumerableFunctions.tokenByIndex(contract: contract, index: index) + function.call(withClient: client, responseType: ERC721EnumerableResponses.numberResponse.self) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } + } + } + + public func tokenOfOwnerByIndex(contract: EthereumAddress, + owner: EthereumAddress, + index: BigUInt, + completionHandler: @escaping(Result) -> Void) { + let function = ERC721EnumerableFunctions.tokenOfOwnerByIndex(contract: contract, address: owner, index: index) + function.call(withClient: client, + responseType: ERC721EnumerableResponses.numberResponse.self, + resolution: .noOffchain(failOnExecutionError: false)) { result in + switch result { + case .success(let data): + completionHandler(.success(data.value)) + case .failure(let error): + completionHandler(.failure(error)) + } + } + } +} - do { - var metadata = try JSONDecoder().decode(Token.self, from: data) +// MARK: - Async/Await +extension ERC721 { + public func balanceOf(contract: EthereumAddress, address: EthereumAddress) async throws -> BigUInt { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + balanceOf(contract: contract, address: address, completionHandler: continuation.resume) + } + } - if let image = metadata.properties?.image.description, image.host == nil, let relative = URL(string: image.absoluteString, relativeTo: baseURL) { - metadata.properties?.image = Token.Property(description: relative) - } - completion(nil, metadata) - } catch let decodeError { - completion(decodeError, nil) - } - }) + public func ownerOf(contract: EthereumAddress, tokenId: BigUInt) async throws -> EthereumAddress { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + ownerOf(contract: contract, tokenId: tokenId, completionHandler: continuation.resume) + } + } + + public func transferEventsTo(recipient: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC721Events.Transfer] { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC721Events.Transfer], Error>) in + transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock, completionHandler: continuation.resume) + } + } - task?.resume() + public func transferEventsFrom(sender: EthereumAddress, fromBlock: EthereumBlock, toBlock: EthereumBlock) async throws -> [ERC721Events.Transfer] { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[ERC721Events.Transfer], Error>) in + transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock, completionHandler: continuation.resume) } } } @@ -269,119 +325,205 @@ public class ERC721Metadata: ERC721 { extension ERC721Metadata { public func name(contract: EthereumAddress) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - name(contract: contract) { error, name in - if let error = error { - continuation.resume(throwing: error) - } else if let name = name { - continuation.resume(returning: name) - } - } + name(contract: contract, completionHandler: continuation.resume) } } public func symbol(contract: EthereumAddress) async throws -> String { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - symbol(contract: contract) { error, symbol in - if let error = error { - continuation.resume(throwing: error) - } else if let symbol = symbol { - continuation.resume(returning: symbol) - } - } + symbol(contract: contract, completionHandler: continuation.resume) } } public func tokenURI(contract: EthereumAddress, tokenID: BigUInt) async throws -> URL { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - tokenURI(contract: contract, tokenID: tokenID) { error, tokenURI in - if let error = error { - continuation.resume(throwing: error) - } else if let tokenURI = tokenURI { - continuation.resume(returning: tokenURI) - } - } + tokenURI(contract: contract, tokenID: tokenID, completionHandler: continuation.resume) } } public func tokenMetadata(contract: EthereumAddress, tokenID: BigUInt) async throws -> Token { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - tokenMetadata(contract: contract, tokenID: tokenID) { error, tokenMetadata in - if let error = error { - continuation.resume(throwing: error) - } else if let tokenMetadata = tokenMetadata { - continuation.resume(returning: tokenMetadata) - } + tokenMetadata(contract: contract, tokenID: tokenID, completionHandler: continuation.resume) + } + } +} + +extension ERC721Enumerable { + public func totalSupply(contract: EthereumAddress) async throws -> BigUInt { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + totalSupply(contract: contract, completionHandler: continuation.resume) + } + } + + public func tokenByIndex(contract: EthereumAddress, index: BigUInt) async throws -> BigUInt { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + tokenByIndex(contract: contract, index: index, completionHandler: continuation.resume) + } + } + + public func tokenOfOwnerByIndex(contract: EthereumAddress, owner: EthereumAddress, index: BigUInt) async throws -> BigUInt { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + tokenOfOwnerByIndex(contract: contract, owner: owner, index: index, completionHandler: continuation.resume) + } + } +} + +// MARK: - Deprecated +extension ERC721 { + @available(*, deprecated, renamed: "balanceOf(contract:address:completionHandler:)") + public func balanceOf(contract: EthereumAddress, + address: EthereumAddress, + completion: @escaping((Error?, BigUInt?) -> Void)) { + balanceOf(contract: contract, address: address) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "ownerOf(contract:tokenId:completionHandler:)") + public func ownerOf(contract: EthereumAddress, + tokenId: BigUInt, + completion: @escaping((Error?, EthereumAddress?) -> Void)) { + ownerOf(contract: contract, tokenId: tokenId) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "transferEventsTo(recipient:fromBlock:toBlock:completionHandler:)") + public func transferEventsTo(recipient: EthereumAddress, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + completion: @escaping((Error?, [ERC721Events.Transfer]?) -> Void)) { + transferEventsTo(recipient: recipient, fromBlock: fromBlock, toBlock: toBlock) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "transferEventsFrom(sender:fromBlock:toBlock:completionHandler:)") + public func transferEventsFrom(sender: EthereumAddress, + fromBlock: EthereumBlock, + toBlock: EthereumBlock, + completion: @escaping((Error?, [ERC721Events.Transfer]?) -> Void)) { + transferEventsFrom(sender: sender, fromBlock: fromBlock, toBlock: toBlock) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } } -public class ERC721Enumerable: ERC721 { - public func totalSupply(contract: EthereumAddress, - completion: @escaping((Error?, BigUInt?) -> Void)) { - let function = ERC721EnumerableFunctions.totalSupply(contract: contract) - function.call(withClient: client, responseType: ERC721EnumerableResponses.numberResponse.self) { error, response in - return completion(error, response?.value) +extension ERC721Metadata { + @available(*, deprecated, renamed: "name(contract:completionHandler:)") + public func name(contract: EthereumAddress, + completion: @escaping((Error?, String?) -> Void)) { + name(contract: contract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } } } - public func tokenByIndex(contract: EthereumAddress, - index: BigUInt, - completion: @escaping((Error?, BigUInt?) -> Void)) { - let function = ERC721EnumerableFunctions.tokenByIndex(contract: contract, index: index) - function.call(withClient: client, responseType: ERC721EnumerableResponses.numberResponse.self) { error, response in - return completion(error, response?.value) + @available(*, deprecated, renamed: "symbol(contract:completionHandler:)") + public func symbol(contract: EthereumAddress, + completion: @escaping((Error?, String?) -> Void)) { + symbol(contract: contract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } } } - public func tokenOfOwnerByIndex(contract: EthereumAddress, - owner: EthereumAddress, - index: BigUInt, - completion: @escaping((Error?, BigUInt?) -> Void)) { - let function = ERC721EnumerableFunctions.tokenOfOwnerByIndex(contract: contract, address: owner, index: index) - function.call( - withClient: client, - responseType: ERC721EnumerableResponses.numberResponse.self, - resolution: .noOffchain(failOnExecutionError: false) - ) { error, response in - return completion(error, response?.value) + @available(*, deprecated, renamed: "tokenURI(contract:tokenID:completionHandler:)") + public func tokenURI(contract: EthereumAddress, + tokenID: BigUInt, + completion: @escaping((Error?, URL?) -> Void)) { + tokenURI(contract: contract, tokenID: tokenID) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } + } + } + + @available(*, deprecated, renamed: "tokenMetadata(contract:tokenID:completionHandler:)") + public func tokenMetadata(contract: EthereumAddress, + tokenID: BigUInt, + completion: @escaping((Error?, Token?) -> Void)) { + tokenMetadata(contract: contract, tokenID: tokenID) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) + } } } } extension ERC721Enumerable { - public func totalSupply(contract: EthereumAddress) async throws -> BigUInt { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - totalSupply(contract: contract) { error, totalSupply in - if let error = error { - continuation.resume(throwing: error) - } else if let totalSupply = totalSupply { - continuation.resume(returning: totalSupply) - } + @available(*, deprecated, renamed: "totalSupply(contract:completionHandler:)") + public func totalSupply(contract: EthereumAddress, + completion: @escaping((Error?, BigUInt?) -> Void)) { + totalSupply(contract: contract) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func tokenByIndex(contract: EthereumAddress, index: BigUInt) async throws -> BigUInt { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - tokenByIndex(contract: contract, index: index) { error, token in - if let error = error { - continuation.resume(throwing: error) - } else if let token = token { - continuation.resume(returning: token) - } + @available(*, deprecated, renamed: "tokenByIndex(contract:index:completionHandler:)") + public func tokenByIndex(contract: EthereumAddress, + index: BigUInt, + completion: @escaping((Error?, BigUInt?) -> Void)) { + tokenByIndex(contract: contract, index: index) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } - public func tokenOfOwnerByIndex(contract: EthereumAddress, owner: EthereumAddress, index: BigUInt) async throws -> BigUInt { - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - tokenOfOwnerByIndex(contract: contract, owner: owner, index: index) { error, token in - if let error = error { - continuation.resume(throwing: error) - } else if let token = token { - continuation.resume(returning: token) - } + @available(*, deprecated, renamed: "tokenOfOwnerByIndex(contract:owner:index:completionHandler:)") + public func tokenOfOwnerByIndex(contract: EthereumAddress, + owner: EthereumAddress, + index: BigUInt, + completion: @escaping((Error?, BigUInt?) -> Void)) { + tokenOfOwnerByIndex(contract: contract, owner: owner, index: index) { result in + switch result { + case .success(let data): + completion(nil, data) + case .failure(let error): + completion(error, nil) } } } diff --git a/web3swift/src/Multicall/Multicall.swift b/web3swift/src/Multicall/Multicall.swift index 820de339..b42793a8 100644 --- a/web3swift/src/Multicall/Multicall.swift +++ b/web3swift/src/Multicall/Multicall.swift @@ -18,42 +18,37 @@ public struct Multicall { self.client = client } - public func aggregate( - calls: [Call], - completion: @escaping (Result) -> Void - ) { - guard - let network = client.network, - let contract = Contract.registryAddress(for: network) - else { return completion(.failure(MulticallError.contractUnavailable)) } + public func aggregate(calls: [Call], + completionHandler: @escaping (Result) -> Void) { + guard let network = client.network, + let contract = Contract.registryAddress(for: network) + else { return completionHandler(.failure(MulticallError.contractUnavailable)) } let function = Contract.Functions.aggregate(contract: contract, calls: calls) - function.call(withClient: client, responseType: Response.self) { (error, response) in - if let response = response { - guard calls.count == response.outputs.count + function.call(withClient: client, responseType: Response.self) { result in + switch result { + case .success(let data): + guard calls.count == data.outputs.count else { fatalError("Outputs do not match the number of calls done") } - zip(calls, response.outputs) + zip(calls, data.outputs) .forEach { call, output in try? call.handler?(output) } - - completion(.success(response)) - } else { - completion(.failure(MulticallError.executionFailed(error))) + completionHandler(.success(data)) + case .failure(let error): + completionHandler(.failure(MulticallError.executionFailed(error))) } } } } - +// MARK: - Async/Await extension Multicall { public func aggregate(calls: [Call]) async -> Result { return await withCheckedContinuation { (continuation: CheckedContinuation, Never>) in - aggregate(calls: calls) { result in - continuation.resume(returning: result) - } + aggregate(calls: calls, completionHandler: continuation.resume) } } }