Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split library into modules #589

Merged
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ let package = Package(
products: [
.library(name: "web3swift", targets: ["web3swift"])
],

dependencies: [
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0"),
.package(url: "https://github.com/daltoniam/Starscream.git", from: "4.0.4"),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.5.1")
],
targets: [
.target(name: "secp256k1"),
.target(
name: "Core",
dependencies: ["BigInt", "secp256k1", "CryptoSwift"]
),
.target(
name: "web3swift",
dependencies: ["BigInt", "secp256k1", "Starscream", "CryptoSwift"],
dependencies: ["Core", "BigInt", "secp256k1", "Starscream"],
exclude: excludeFiles,
resources: [
.copy("./Browser/browser.js"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ extension EthereumAddress {

}

extension EthereumAddress: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
self.init(stringValue)!
}

public func encode(to encoder: Encoder) throws {
let value = self.address.lowercased()
var signleValuedCont = encoder.singleValueContainer()
try signleValuedCont.encode(value)
}
}

extension EthereumAddress: Hashable { }

extension EthereumAddress: APIResultType { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// APIRequest+ComputedProperties.swift
//
//
// Created by Yaroslav Yashin on 12.07.2022.
//

import Foundation

extension APIRequest {
var method: REST {
switch self {
default: return .POST
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a weird computed variable.
Is it correct that it always returns .POST?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually yes, since none of Eth API uses any other REST method. But i'd either think that it's redundant, and could be just dropped in a favor of hardcoding this method in URLSession configuration.

}
}

public var encodedBody: Data {
let request = RequestBody(method: self.call, params: self.parameters)
// this is safe to force try this here
// Because request must failed to compile if it not conformable with `Encodable` protocol
return try! JSONEncoder().encode(request)
}

var parameters: [RequestParameter] {
switch self {
case .gasPrice, .blockNumber, .getNetwork, .getAccounts, .getTxPoolStatus, .getTxPoolContent, .getTxPoolInspect:
return [RequestParameter]()

case .estimateGas(let transactionParameters, let blockNumber):
return [.transaction(transactionParameters), .string(blockNumber.stringValue)]

case let .sendRawTransaction(hash):
return [.string(hash)]

case let .sendTransaction(transactionParameters):
return [.transaction(transactionParameters)]

case .getTransactionByHash(let hash):
return [.string(hash)]

case .getTransactionReceipt(let receipt):
return [.string(receipt)]

case .getLogs(let eventFilterParameters):
return [.eventFilter(eventFilterParameters)]

case .personalSign(let address, let string):
return [.string(address), .string(string)]

case .call(let transactionParameters, let blockNumber):
return [.transaction(transactionParameters), .string(blockNumber.stringValue)]

case .getTransactionCount(let address, let blockNumber):
return [.string(address), .string(blockNumber.stringValue)]

case .getBalance(let address, let blockNumber):
return [.string(address), .string(blockNumber.stringValue)]

case .getStorageAt(let address, let bigUInt, let blockNumber):
return [.string(address), .string(bigUInt.hexString), .string(blockNumber.stringValue)]

case .getCode(let address, let blockNumber):
return [.string(address), .string(blockNumber.stringValue)]

case .getBlockByHash(let hash, let bool):
return [.string(hash), .bool(bool)]

case .getBlockByNumber(let block, let bool):
return [.string(block.stringValue), .bool(bool)]

case .feeHistory(let uInt, let blockNumber, let array):
return [.string(uInt.hexString), .string(blockNumber.stringValue), .doubleArray(array)]

case .createAccount(let string):
return [.string(string)]

case .unlockAccount(let address, let string, let uInt):
return [.string(address), .string(string), .uint(uInt ?? 0)]
}
}

public var call: String {
switch self {
case .gasPrice: return "eth_gasPrice"
case .blockNumber: return "eth_blockNumber"
case .getNetwork: return "net_version"
case .getAccounts: return "eth_accounts"
case .sendRawTransaction: return "eth_sendRawTransaction"
case .sendTransaction: return "eth_sendTransaction"
case .getTransactionByHash: return "eth_getTransactionByHash"
case .getTransactionReceipt: return "eth_getTransactionReceipt"
case .personalSign: return "eth_sign"
case .getLogs: return "eth_getLogs"
case .call: return "eth_call"
case .estimateGas: return "eth_estimateGas"
case .getTransactionCount: return "eth_getTransactionCount"
case .getBalance: return "eth_getBalance"
case .getStorageAt: return "eth_getStorageAt"
case .getCode: return "eth_getCode"
case .getBlockByHash: return "eth_getBlockByHash"
case .getBlockByNumber: return "eth_getBlockByNumber"
case .feeHistory: return "eth_feeHistory"

case .unlockAccount: return "personal_unlockAccount"
case .createAccount: return "personal_createAccount"
case .getTxPoolStatus: return "txpool_status"
case .getTxPoolContent: return "txpool_content"
case .getTxPoolInspect: return "txpool_inspect"
}
}
}
49 changes: 49 additions & 0 deletions Sources/Core/EthereumNetwork/Request/APIRequest+Methods.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// APIRequest+Methods.swift
//
//
// Created by Yaroslav Yashin on 12.07.2022.
//

import Foundation
import BigInt

extension APIRequest {
public static func sendRequest<Result>(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse<Result> {
let request = setupRequest(for: call, with: provider)
return try await APIRequest.send(uRLRequest: request, with: provider.session)
}

static func setupRequest(for call: APIRequest, with provider: Web3Provider) -> URLRequest {
var urlRequest = URLRequest(url: provider.url, cachePolicy: .reloadIgnoringCacheData)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpMethod = call.method.rawValue
urlRequest.httpBody = call.encodedBody
return urlRequest
}

public static func send<Result>(uRLRequest: URLRequest, with session: URLSession) async throws -> APIResponse<Result> {
let (data, response) = try await session.data(for: uRLRequest)

guard 200 ..< 400 ~= response.statusCode else {
if 400 ..< 500 ~= response.statusCode {
throw Web3Error.clientError(code: response.statusCode)
} else {
throw Web3Error.serverError(code: response.statusCode)
}
}

/// This bit of code is purposed to work with literal types that comes in Response in hexString type.
/// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`.
if Result.self == Data.self || Result.self == UInt.self || Result.self == Int.self || Result.self == BigInt.self || Result.self == BigUInt.self {
guard let LiteralType = Result.self as? LiteralInitiableFromString.Type else { throw Web3Error.typeError }
guard let responseAsString = try? JSONDecoder().decode(APIResponse<String>.self, from: data) else { throw Web3Error.dataError }
guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError }
/// `Literal` conforming `LiteralInitiableFromString`, that conforming to an `APIResponseType` type, so it's never fails.
guard let result = literalValue as? Result else { throw Web3Error.typeError }
return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result)
}
return try JSONDecoder().decode(APIResponse<Result>.self, from: data)
}
}
28 changes: 28 additions & 0 deletions Sources/Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// APIRequest+UtilityTypes.swift
//
//
// Created by Yaroslav Yashin on 12.07.2022.
//

import Foundation

/// JSON RPC response structure for serialization and deserialization purposes.
public struct APIResponse<Result>: Decodable where Result: APIResultType {
public var id: Int
public var jsonrpc = "2.0"
public var result: Result
}

enum REST: String {
case POST
case GET
}

struct RequestBody: Encodable {
var jsonrpc = "2.0"
var id = Counter.increment()

var method: String
var params: [RequestParameter]
}
Loading