Skip to content

Commit aa155f4

Browse files
Merge pull request #577 from yaroslavyaroslav/feature/encode-generic
Generic network layer
2 parents 6df7495 + 55732ab commit aa155f4

File tree

84 files changed

+1928
-2618
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1928
-2618
lines changed

Sources/web3swift/API/APIMethod.swift

+292
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
//
2+
// Web3+APIMethod.swift
3+
// Web3swift
4+
//
5+
// Created by Yaroslav on 24.05.2022.
6+
//
7+
8+
import Foundation
9+
import BigInt
10+
11+
public typealias Hash = String // 32 bytes hash of block (64 chars length without 0x)
12+
public typealias Receipt = Hash
13+
public typealias Address = Hash // 20 bytes (40 chars length without 0x)
14+
public typealias TransactionHash = Hash // 64 chars length without 0x
15+
16+
// FIXME: Add documentation to each method.
17+
/// Ethereum JSON RPC API Calls
18+
public enum APIRequest {
19+
// MARK: - Official API
20+
// 0 parameter in call
21+
case gasPrice
22+
case blockNumber
23+
case getNetwork
24+
case getAccounts
25+
// ??
26+
case estimateGas(TransactionParameters, BlockNumber)
27+
case sendRawTransaction(Hash)
28+
case sendTransaction(TransactionParameters)
29+
case getTransactionByHash(Hash)
30+
case getTransactionReceipt(Hash)
31+
case getLogs(EventFilterParameters)
32+
case personalSign(Address, String)
33+
case call(TransactionParameters, BlockNumber)
34+
case getTransactionCount(Address, BlockNumber)
35+
case getBalance(Address, BlockNumber)
36+
37+
/// Returns the value from a storage position at a given address.
38+
///
39+
/// - Parameters:
40+
/// - Address: Address
41+
/// - Storage: slot
42+
/// - BlockNumber: sd
43+
case getStorageAt(Address, Hash, BlockNumber)
44+
45+
case getCode(Address, BlockNumber)
46+
case getBlockByHash(Hash, Bool)
47+
case getBlockByNumber(BlockNumber, Bool)
48+
49+
/// Returns fee history with a respect to given setup
50+
///
51+
/// Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
52+
/// The transaction will not be added to the blockchain. Note that the estimate may be significantly more
53+
/// than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.
54+
///
55+
/// - Parameters:
56+
/// - UInt: Requested range of blocks. Clients will return less than the requested range if not all blocks are available.
57+
/// - BlockNumber: Highest block of the requested range.
58+
/// - [Double]: A monotonically increasing list of percentile values.
59+
/// For each block in the requested range, the transactions will be sorted in ascending order
60+
/// by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed."
61+
case feeHistory(BigUInt, BlockNumber, [Double])
62+
63+
// MARK: - Additional API
64+
/// Creates new account.
65+
///
66+
/// Note: it becomes the new current unlocked account. There can only be one unlocked account at a time.
67+
///
68+
/// - Parameters:
69+
/// - String: Password for the new account.
70+
case createAccount(String) // No in Eth API
71+
72+
/// Unlocks specified account for use.
73+
///
74+
/// If permanent unlocking is disabled (the default) then the duration argument will be ignored,
75+
/// and the account will be unlocked for a single signing.
76+
/// With permanent locking enabled, the duration sets the number of seconds to hold the account open for.
77+
/// It will default to 300 seconds. Passing 0 unlocks the account indefinitely.
78+
///
79+
/// There can only be one unlocked account at a time.
80+
///
81+
/// - Parameters:
82+
/// - Address: The address of the account to unlock.
83+
/// - String: Passphrase to unlock the account.
84+
/// - UInt?: Duration in seconds how long the account should remain unlocked for.
85+
case unlockAccount(Address, String, UInt?)
86+
case getTxPoolStatus // No in Eth API
87+
case getTxPoolContent // No in Eth API
88+
case getTxPoolInspect // No in Eth API
89+
}
90+
91+
// FIXME: This conformance should be changed to `LiteralInitiableFromString`
92+
extension Data: APIResultType { }
93+
94+
extension APIRequest {
95+
var method: REST {
96+
switch self {
97+
default: return .POST
98+
}
99+
}
100+
101+
public var responseType: APIResultType.Type {
102+
switch self {
103+
case .blockNumber: return BigUInt.self
104+
case .getAccounts: return [EthereumAddress].self
105+
case .getBalance: return BigUInt.self
106+
case .getBlockByHash: return Block.self
107+
case .getBlockByNumber: return Block.self
108+
case .gasPrice: return BigUInt.self
109+
case .feeHistory: return Web3.Oracle.FeeHistory.self
110+
case .getTransactionCount: return BigUInt.self
111+
case .getCode: return Hash.self
112+
case .getTransactionReceipt: return TransactionReceipt.self
113+
case .createAccount: return EthereumAddress.self
114+
case .unlockAccount: return Bool.self
115+
case .getTransactionByHash: return TransactionDetails.self
116+
case .sendTransaction: return Hash.self
117+
case .sendRawTransaction: return Hash.self
118+
case .estimateGas: return BigUInt.self
119+
case .call: return Data.self
120+
// FIXME: Not checked
121+
case .getNetwork: return Int.self
122+
case .personalSign: return Data.self
123+
case .getTxPoolStatus: return TxPoolStatus.self
124+
case .getTxPoolContent: return TxPoolContent.self
125+
case .getLogs: return [EventLog].self
126+
127+
// FIXME: Not implemented
128+
case .getStorageAt: return String.self
129+
case .getTxPoolInspect: return String.self
130+
}
131+
}
132+
133+
var encodedBody: Data {
134+
let request = RequestBody(method: self.call, params: self.parameters)
135+
// this is safe to force try this here
136+
// Because request must failed to compile if it not conformable with `Encodable` protocol
137+
return try! JSONEncoder().encode(request)
138+
}
139+
140+
var parameters: [RequestParameter] {
141+
switch self {
142+
case .gasPrice, .blockNumber, .getNetwork, .getAccounts, .getTxPoolStatus, .getTxPoolContent, .getTxPoolInspect:
143+
return [RequestParameter]()
144+
145+
case .estimateGas(let transactionParameters, let blockNumber):
146+
return [RequestParameter.transaction(transactionParameters), RequestParameter.string(blockNumber.stringValue)]
147+
148+
case let .sendRawTransaction(hash):
149+
return [RequestParameter.string(hash)]
150+
151+
case let .sendTransaction(transactionParameters):
152+
return [RequestParameter.transaction(transactionParameters)]
153+
154+
case .getTransactionByHash(let hash):
155+
return [RequestParameter.string(hash)]
156+
157+
case .getTransactionReceipt(let receipt):
158+
return [RequestParameter.string(receipt)]
159+
160+
case .getLogs(let eventFilterParameters):
161+
return [RequestParameter.eventFilter(eventFilterParameters)]
162+
163+
case .personalSign(let address, let string):
164+
// FIXME: Add second parameter
165+
return [RequestParameter.string(address), RequestParameter.string(string)]
166+
167+
case .call(let transactionParameters, let blockNumber):
168+
return [RequestParameter.transaction(transactionParameters), RequestParameter.string(blockNumber.stringValue)]
169+
170+
case .getTransactionCount(let address, let blockNumber):
171+
return [RequestParameter.string(address), RequestParameter.string(blockNumber.stringValue)]
172+
173+
case .getBalance(let address, let blockNumber):
174+
return [RequestParameter.string(address), RequestParameter.string(blockNumber.stringValue)]
175+
176+
case .getStorageAt(let address, let hash, let blockNumber):
177+
return [RequestParameter.string(address), RequestParameter.string(hash), RequestParameter.string(blockNumber.stringValue)]
178+
179+
case .getCode(let address, let blockNumber):
180+
return [RequestParameter.string(address), RequestParameter.string(blockNumber.stringValue)]
181+
182+
case .getBlockByHash(let hash, let bool):
183+
return [RequestParameter.string(hash), RequestParameter.bool(bool)]
184+
185+
case .getBlockByNumber(let block, let bool):
186+
return [RequestParameter.string(block.stringValue), RequestParameter.bool(bool)]
187+
188+
case .feeHistory(let uInt, let blockNumber, let array):
189+
return [RequestParameter.string(uInt.hexString), RequestParameter.string(blockNumber.stringValue), RequestParameter.doubleArray(array)]
190+
191+
case .createAccount(let string):
192+
return [RequestParameter.string(string)]
193+
194+
case .unlockAccount(let address, let string, let uInt):
195+
return [RequestParameter.string(address), RequestParameter.string(string), RequestParameter.uint(uInt ?? 0)]
196+
}
197+
}
198+
199+
var call: String {
200+
switch self {
201+
case .gasPrice: return "eth_gasPrice"
202+
case .blockNumber: return "eth_blockNumber"
203+
case .getNetwork: return "net_version"
204+
case .getAccounts: return "eth_accounts"
205+
case .sendRawTransaction: return "eth_sendRawTransaction"
206+
case .sendTransaction: return "eth_sendTransaction"
207+
case .getTransactionByHash: return "eth_getTransactionByHash"
208+
case .getTransactionReceipt: return "eth_getTransactionReceipt"
209+
case .personalSign: return "eth_sign"
210+
case .getLogs: return "eth_getLogs"
211+
case .call: return "eth_call"
212+
case .estimateGas: return "eth_estimateGas"
213+
case .getTransactionCount: return "eth_getTransactionCount"
214+
case .getBalance: return "eth_getBalance"
215+
case .getStorageAt: return "eth_getStorageAt"
216+
case .getCode: return "eth_getCode"
217+
case .getBlockByHash: return "eth_getBlockByHash"
218+
case .getBlockByNumber: return "eth_getBlockByNumber"
219+
case .feeHistory: return "eth_feeHistory"
220+
221+
case .unlockAccount: return "personal_unlockAccount"
222+
case .createAccount: return "personal_createAccount"
223+
case .getTxPoolStatus: return "txpool_status"
224+
case .getTxPoolContent: return "txpool_content"
225+
case .getTxPoolInspect: return "txpool_inspect"
226+
}
227+
}
228+
}
229+
230+
extension APIRequest {
231+
public static func sendRequest<Result>(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse<Result> {
232+
/// Don't even try to make network request if the `Result` type dosen't equal to supposed by API
233+
// FIXME: Add appropriate error thrown
234+
guard Result.self == call.responseType else { throw Web3Error.unknownError }
235+
let request = setupRequest(for: call, with: provider)
236+
return try await APIRequest.send(uRLRequest: request, with: provider.session)
237+
}
238+
239+
static func setupRequest(for call: APIRequest, with provider: Web3Provider) -> URLRequest {
240+
var urlRequest = URLRequest(url: provider.url, cachePolicy: .reloadIgnoringCacheData)
241+
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
242+
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
243+
urlRequest.httpMethod = call.method.rawValue
244+
urlRequest.httpBody = call.encodedBody
245+
return urlRequest
246+
}
247+
248+
static func send<Result>(uRLRequest: URLRequest, with session: URLSession) async throws -> APIResponse<Result> {
249+
let (data, response) = try await session.data(for: uRLRequest)
250+
251+
// FIXME: Add appropriate error thrown
252+
guard let httpResponse = response as? HTTPURLResponse,
253+
200 ..< 400 ~= httpResponse.statusCode else { throw Web3Error.connectionError }
254+
255+
// FIXME: Add throwing an error from is server fails.
256+
/// This bit of code is purposed to work with literal types that comes in Response in hexString type.
257+
/// Currently it's just any kind of Integers like `(U)Int`, `Big(U)Int`.
258+
if Result.self == Data.self || Result.self == UInt.self || Result.self == Int.self || Result.self == BigInt.self || Result.self == BigUInt.self {
259+
/// This types for sure conformed with `LiteralInitiableFromString`
260+
// FIXME: Make appropriate error
261+
guard let U = Result.self as? LiteralInitiableFromString.Type else { throw Web3Error.unknownError }
262+
let responseAsString = try! JSONDecoder().decode(APIResponse<String>.self, from: data)
263+
// FIXME: Add appropriate error thrown.
264+
guard let literalValue = U.init(from: responseAsString.result) else { throw Web3Error.unknownError }
265+
/// `U` is a APIResponseType type, which `LiteralInitiableFromString` conforms to, so it is safe to cast that.
266+
// FIXME: Make appropriate error
267+
guard let asT = literalValue as? Result else { throw Web3Error.unknownError }
268+
return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: asT)
269+
}
270+
return try JSONDecoder().decode(APIResponse<Result>.self, from: data)
271+
}
272+
}
273+
274+
enum REST: String {
275+
case POST
276+
case GET
277+
}
278+
279+
struct RequestBody: Encodable {
280+
var jsonrpc = "2.0"
281+
var id = Counter.increment()
282+
283+
var method: String
284+
var params: [RequestParameter]
285+
}
286+
287+
/// JSON RPC response structure for serialization and deserialization purposes.
288+
public struct APIResponse<Result>: Decodable where Result: APIResultType {
289+
public var id: Int
290+
public var jsonrpc = "2.0"
291+
public var result: Result
292+
}

0 commit comments

Comments
 (0)