Skip to content

Commit b689a05

Browse files
Merge pull request #734 from albertopeam/feature/add-bip-44
2 parents 9a227f8 + 5b720e5 commit b689a05

File tree

4 files changed

+30
-22
lines changed

4 files changed

+30
-22
lines changed

Sources/Web3Core/KeystoreManager/BIP44.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import Foundation
77

88
public protocol BIP44 {
99
/**
10-
Derive an ``HDNode`` based on the provided path. The function will throw ``BIP44Error.warning`` if it was invoked with throwOnWarning equal to
11-
`true` and the root key doesn't have a previous child with at least one transaction. If it is invoked with throwOnError equal to `false` the child node will be
12-
derived directly using the derive function of ``HDNode``. This function needs to query the blockchain history when throwOnWarning is `true`, so it can throw
10+
Derive an ``HDNode`` based on the provided path. The function will throw ``BIP44Error.warning`` if it was invoked with `throwOnWarning` equal to
11+
`true` and the root key doesn't have a previous child with at least one transaction. If it is invoked with `throwOnWarning` equal to `false` the child node will be
12+
derived directly using the derive function of ``HDNode``. This function needs to query the blockchain history when `throwOnWarning` is `true`, so it can throw
1313
network errors.
1414
- Parameter path: valid BIP44 path.
1515
- Parameter throwOnWarning: `true` to use
@@ -41,7 +41,7 @@ public protocol TransactionChecker {
4141
- Throws: any error related to query the blockchain provider
4242
- Returns: `true` if the address has at least one transaction, `false` otherwise
4343
*/
44-
func hasTransactions(address: String) async throws -> Bool
44+
func hasTransactions(ethereumAddress: EthereumAddress) async throws -> Bool
4545
}
4646

4747
extension HDNode: BIP44 {
@@ -62,7 +62,7 @@ extension HDNode: BIP44 {
6262
if let searchPath = path.newPath(account: searchAccount, addressIndex: searchAddressIndex),
6363
let childNode = derive(path: searchPath, derivePrivateKey: true),
6464
let ethAddress = Utilities.publicToAddress(childNode.publicKey) {
65-
hasTransactions = try await transactionChecker.hasTransactions(address: ethAddress.address)
65+
hasTransactions = try await transactionChecker.hasTransactions(ethereumAddress: ethAddress)
6666
if hasTransactions {
6767
break
6868
}

Sources/Web3Core/KeystoreManager/EtherscanTransactionChecker.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Foundation
88
public struct EtherscanTransactionChecker: TransactionChecker {
99
private let urlSession: URLSessionProxy
1010
private let apiKey: String
11+
private let successRange = 200..<300
1112

1213
public init(urlSession: URLSession, apiKey: String) {
1314
self.urlSession = URLSessionProxyImplementation(urlSession: urlSession)
@@ -19,13 +20,16 @@ public struct EtherscanTransactionChecker: TransactionChecker {
1920
self.apiKey = apiKey
2021
}
2122

22-
public func hasTransactions(address: String) async throws -> Bool {
23-
let urlString = "https://api.etherscan.io/api?module=account&action=txlist&address=\(address)&startblock=0&page=1&offset=1&sort=asc&apikey=\(apiKey)"
23+
public func hasTransactions(ethereumAddress: EthereumAddress) async throws -> Bool {
24+
let urlString = "https://api.etherscan.io/api?module=account&action=txlist&address=\(ethereumAddress.address)&startblock=0&page=1&offset=1&sort=asc&apikey=\(apiKey)"
2425
guard let url = URL(string: urlString) else {
2526
throw EtherscanTransactionCheckerError.invalidUrl(url: urlString)
2627
}
2728
let request = URLRequest(url: url)
2829
let result = try await urlSession.data(for: request)
30+
if let httpResponse = result.1 as? HTTPURLResponse, !successRange.contains(httpResponse.statusCode) {
31+
throw EtherscanTransactionCheckerError.network(statusCode: httpResponse.statusCode)
32+
}
2933
let response = try JSONDecoder().decode(Response.self, from: result.0)
3034
return !response.result.isEmpty
3135
}
@@ -40,11 +44,14 @@ extension EtherscanTransactionChecker {
4044

4145
public enum EtherscanTransactionCheckerError: LocalizedError, Equatable {
4246
case invalidUrl(url: String)
47+
case network(statusCode: Int)
4348

4449
public var errorDescription: String? {
4550
switch self {
4651
case let .invalidUrl(url):
4752
return "Couldn't create URL(string: \(url))"
53+
case let .network(statusCode):
54+
return "Network error, statusCode: \(statusCode)"
4855
}
4956
}
5057
}

Tests/web3swiftTests/localTests/BIP44Tests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ private final class MockTransactionChecker: TransactionChecker {
192192
var addresses: [String] = .init()
193193
var results: [Bool] = .init()
194194

195-
func hasTransactions(address: String) async throws -> Bool {
196-
addresses.append(address)
195+
func hasTransactions(ethereumAddress: EthereumAddress) async throws -> Bool {
196+
addresses.append(ethereumAddress.address)
197197
return results.removeFirst()
198198
}
199199
}

Tests/web3swiftTests/remoteTests/EtherscanTransactionCheckerTests.swift

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,21 @@ import XCTest
99
final class EtherscanTransactionCheckerTests: XCTestCase {
1010
private var testApiKey: String { "4HVPVMV1PN6NGZDFXZIYKEZRP53IA41KVC" }
1111
private var vitaliksAddress: String { "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" }
12-
private var emptyAddress: String { "0x1BeY3KhtHpfATH5Yqxz9d8Z1XbqZFSXtK7" }
12+
private var emptyAddress: String { "0x3a0cd085155dc74cdddf3196f23c8cec9b217dd8" }
1313

1414
func testHasTransactions() async throws {
1515
let sut = EtherscanTransactionChecker(urlSession: URLSession.shared, apiKey: testApiKey)
16-
17-
let result = try await sut.hasTransactions(address: vitaliksAddress)
16+
17+
let result = try await sut.hasTransactions(ethereumAddress: try XCTUnwrap(EthereumAddress(vitaliksAddress)))
1818

1919
XCTAssertTrue(result)
2020
}
2121

2222
func testHasNotTransactions() async throws {
2323
let sut = EtherscanTransactionChecker(urlSession: URLSession.shared, apiKey: testApiKey)
2424

25-
let result = try await sut.hasTransactions(address: emptyAddress)
25+
let ethAddr = try XCTUnwrap(EthereumAddress(emptyAddress))
26+
let result = try await sut.hasTransactions(ethereumAddress: ethAddr)
2627

2728
XCTAssertFalse(result)
2829
}
@@ -33,31 +34,31 @@ final class EtherscanTransactionCheckerTests: XCTestCase {
3334
urlSessionMock.response = (Data(), try XCTUnwrap(HTTPURLResponse(url: try XCTUnwrap(URL(string: "https://")), statusCode: 500, httpVersion: nil, headerFields: nil)))
3435
let sut = EtherscanTransactionChecker(urlSession: urlSessionMock, apiKey: testApiKey)
3536

36-
_ = try await sut.hasTransactions(address: vitaliksAddress)
37+
_ = try await sut.hasTransactions(ethereumAddress: try XCTUnwrap(EthereumAddress(vitaliksAddress)))
3738

3839
XCTFail("Network must throw an error")
39-
} catch {
40-
XCTAssertTrue(true)
40+
} catch let EtherscanTransactionCheckerError.network(statusCode) {
41+
XCTAssertEqual(statusCode, 500)
4142
}
4243
}
4344

4445
func testInitURLError() async throws {
4546
do {
46-
let sut = EtherscanTransactionChecker(urlSession: URLSessionMock(), apiKey: testApiKey)
47+
let sut = EtherscanTransactionChecker(urlSession: URLSessionMock(), apiKey: " ")
4748

48-
_ = try await sut.hasTransactions(address: " ")
49+
_ = try await sut.hasTransactions(ethereumAddress: try XCTUnwrap(EthereumAddress(vitaliksAddress)))
4950

5051
XCTFail("URL init must throw an error")
51-
} catch {
52-
XCTAssertTrue(error is EtherscanTransactionCheckerError)
52+
} catch EtherscanTransactionCheckerError.invalidUrl {
53+
XCTAssertTrue(true)
5354
}
5455
}
5556

5657
func testWrongApiKey() async throws {
5758
do {
58-
let sut = EtherscanTransactionChecker(urlSession: URLSession.shared, apiKey: "")
59+
let sut = EtherscanTransactionChecker(urlSession: URLSession.shared, apiKey: "-")
5960

60-
_ = try await sut.hasTransactions(address: "")
61+
_ = try await sut.hasTransactions(ethereumAddress: try XCTUnwrap(EthereumAddress(vitaliksAddress)))
6162

6263
XCTFail("API not returns a valid response")
6364
} catch DecodingError.typeMismatch {

0 commit comments

Comments
 (0)