Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c53c4e4
Delete untyped decode methods.
yaroslavyaroslav Apr 28, 2022
e4012bf
Add generic encodeHex methods to `(Un)KeyedEncodingContainer`
yaroslavyaroslav May 3, 2022
d86583b
Update `Encodable+Extensions` to be generic.
yaroslavyaroslav May 10, 2022
24dc3a8
Making memos to methods.
yaroslavyaroslav May 13, 2022
cd751ee
`JSONRPCParam` enum implementation.
yaroslavyaroslav May 14, 2022
fb4c10d
`JSONRPCparams` replaced with `[JSONRPCParameter]` in outgoing API ca…
yaroslavyaroslav May 15, 2022
88c84bb
To create `JSONRPCrequest` parameters should conforms `JSONRPCParamet…
yaroslavyaroslav May 17, 2022
72e7ad9
Move JSONRPCParameter to separate file.
yaroslavyaroslav May 18, 2022
9f79702
Update UInt64 to UInt since this accuracy isn't required.
yaroslavyaroslav May 19, 2022
d7953dc
Change UInt64 to UInt.
yaroslavyaroslav May 22, 2022
e439b04
Remove commented out piece of code
yaroslavyaroslav May 22, 2022
1a7734e
Make Counter.increment() static method mode clear
yaroslavyaroslav May 22, 2022
8398c5d
Fix feeHistory logic bug
yaroslavyaroslav May 22, 2022
8c0e119
Make async/await internal API consistent
yaroslavyaroslav May 22, 2022
dfe4f72
Delete duplicate BlockNumber enum in FeeHistory.
yaroslavyaroslav May 22, 2022
e4fcc1d
Add EthJSONRPC enum to encapsulate REST logic.
yaroslavyaroslav May 23, 2022
f1ab07d
Method `sendRequest<U>(with call: EthJSONRPC) async throws -> EthResp…
yaroslavyaroslav May 24, 2022
9e0343c
Rename request types
yaroslavyaroslav May 24, 2022
5db2272
Implement most of required protocols to make decoding responses to ge…
yaroslavyaroslav May 25, 2022
245cc0d
Make `APIRequestParameterType` internal.
yaroslavyaroslav May 25, 2022
2a54eec
Refactor API a bit.
yaroslavyaroslav May 26, 2022
4e69483
Update for new Network layer:
yaroslavyaroslav May 26, 2022
635009a
Update for network layer:
yaroslavyaroslav May 27, 2022
27a28e2
Update clients code for changes made
yaroslavyaroslav May 27, 2022
351b671
Update for network layer:
yaroslavyaroslav May 27, 2022
7c5126a
Refactor code a bit.
yaroslavyaroslav May 27, 2022
412972b
Update for network layer:
yaroslavyaroslav May 27, 2022
2de0d21
Revert `BigUInt` to `UInt` type changes.
yaroslavyaroslav May 27, 2022
8207727
Rename API calls files and folder
yaroslavyaroslav May 27, 2022
216686e
Encapsulate request methods into APIRequest enum.
yaroslavyaroslav May 31, 2022
dddf1f2
Rename `getTransactionCount(address:, onBlock:)` to `getTransactionCo…
yaroslavyaroslav May 31, 2022
65c5a53
Add TxPool and getLog implementation
yaroslavyaroslav May 31, 2022
b24948f
Temporary disable Websocket provider.
yaroslavyaroslav May 31, 2022
0bc450b
Delete old HTTP(S) network code.
yaroslavyaroslav May 31, 2022
983d084
Temporary comment out not failed to build tests.
yaroslavyaroslav May 31, 2022
bbe64ef
Comment out some duplicating code.
yaroslavyaroslav May 31, 2022
16d6f1c
Delete `Web3+Eth.swift` since its purpose were to make Promise method…
yaroslavyaroslav May 31, 2022
6611999
Rename deleted methods in tests and BrowserFunctions to new one.
yaroslavyaroslav May 31, 2022
fec0bc0
Little code formatting
yaroslavyaroslav Jun 1, 2022
78301bd
Fix wrong sendTransaction request bug.
yaroslavyaroslav Jun 2, 2022
41ac98f
Fix `gasLimit` and `gasPrice` request bug.
yaroslavyaroslav Jun 2, 2022
23ef94a
Add Data type support by conforming `LiteralInitiableFromString` prot…
yaroslavyaroslav Jun 2, 2022
fa80d43
Implement `GetLogs` call.
yaroslavyaroslav Jun 3, 2022
90e2061
Update Web3HTTPProvider
yaroslavyaroslav Jun 5, 2022
55732ab
Delete unused `URLSession.data` method method.
yaroslavyaroslav Jun 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
292 changes: 292 additions & 0 deletions Sources/web3swift/API/APIMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
//
// Web3+APIMethod.swift
// Web3swift
//
// Created by Yaroslav on 24.05.2022.
//

import Foundation
import BigInt

public typealias Hash = String // 32 bytes hash of block (64 chars length without 0x)
public typealias Receipt = Hash
public typealias Address = Hash // 20 bytes (40 chars length without 0x)
public typealias TransactionHash = Hash // 64 chars length without 0x

// FIXME: Add documentation to each method.
/// Ethereum JSON RPC API Calls
public enum APIRequest {
// MARK: - Official API
// 0 parameter in call
case gasPrice
case blockNumber
case getNetwork
case getAccounts
// ??
case estimateGas(TransactionParameters, BlockNumber)
case sendRawTransaction(Hash)
case sendTransaction(TransactionParameters)
case getTransactionByHash(Hash)
case getTransactionReceipt(Hash)
case getLogs(EventFilterParameters)
case personalSign(Address, String)
case call(TransactionParameters, BlockNumber)
case getTransactionCount(Address, BlockNumber)
case getBalance(Address, BlockNumber)

/// Returns the value from a storage position at a given address.
///
/// - Parameters:
/// - Address: Address
/// - Storage: slot
/// - BlockNumber: sd
case getStorageAt(Address, Hash, BlockNumber)

case getCode(Address, BlockNumber)
case getBlockByHash(Hash, Bool)
case getBlockByNumber(BlockNumber, Bool)

/// Returns fee history with a respect to given setup
///
/// Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
/// The transaction will not be added to the blockchain. Note that the estimate may be significantly more
/// than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.
///
/// - Parameters:
/// - UInt: Requested range of blocks. Clients will return less than the requested range if not all blocks are available.
/// - BlockNumber: Highest block of the requested range.
/// - [Double]: A monotonically increasing list of percentile values.
/// For each block in the requested range, the transactions will be sorted in ascending order
/// by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed."
case feeHistory(BigUInt, BlockNumber, [Double])

// MARK: - Additional API
/// Creates new account.
///
/// Note: it becomes the new current unlocked account. There can only be one unlocked account at a time.
///
/// - Parameters:
/// - String: Password for the new account.
case createAccount(String) // No in Eth API

/// Unlocks specified account for use.
///
/// If permanent unlocking is disabled (the default) then the duration argument will be ignored,
/// and the account will be unlocked for a single signing.
/// With permanent locking enabled, the duration sets the number of seconds to hold the account open for.
/// It will default to 300 seconds. Passing 0 unlocks the account indefinitely.
///
/// There can only be one unlocked account at a time.
///
/// - Parameters:
/// - Address: The address of the account to unlock.
/// - String: Passphrase to unlock the account.
/// - UInt?: Duration in seconds how long the account should remain unlocked for.
case unlockAccount(Address, String, UInt?)
case getTxPoolStatus // No in Eth API
case getTxPoolContent // No in Eth API
case getTxPoolInspect // No in Eth API
}

// FIXME: This conformance should be changed to `LiteralInitiableFromString`
extension Data: APIResultType { }

extension APIRequest {
var method: REST {
switch self {
default: return .POST
}
}

public var responseType: APIResultType.Type {
switch self {
case .blockNumber: return BigUInt.self
case .getAccounts: return [EthereumAddress].self
case .getBalance: return BigUInt.self
case .getBlockByHash: return Block.self
case .getBlockByNumber: return Block.self
case .gasPrice: return BigUInt.self
case .feeHistory: return Web3.Oracle.FeeHistory.self
case .getTransactionCount: return BigUInt.self
case .getCode: return Hash.self
case .getTransactionReceipt: return TransactionReceipt.self
case .createAccount: return EthereumAddress.self
case .unlockAccount: return Bool.self
case .getTransactionByHash: return TransactionDetails.self
case .sendTransaction: return Hash.self
case .sendRawTransaction: return Hash.self
case .estimateGas: return BigUInt.self
case .call: return Data.self
// FIXME: Not checked
case .getNetwork: return Int.self
case .personalSign: return Data.self
case .getTxPoolStatus: return TxPoolStatus.self
case .getTxPoolContent: return TxPoolContent.self
case .getLogs: return [EventLog].self

// FIXME: Not implemented
case .getStorageAt: return String.self
case .getTxPoolInspect: return String.self
}
}

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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suggest removing force try

Copy link
Collaborator

Choose a reason for hiding this comment

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

in this PR and new code for 3.0 I suggest we don't open PR's with force unwraps/for try etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Generally i agreed with you, but in some case force try is reasonable, like that one which were used in gas prediction PR, so this rule should not be carved in stone.

But here i'm agreed with you, it's unnecessary here, and i'll fix it soon.

}

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

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

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

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

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

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

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

case .personalSign(let address, let string):
// FIXME: Add second parameter
return [RequestParameter.string(address), RequestParameter.string(string)]

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

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

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

case .getStorageAt(let address, let hash, let blockNumber):
return [RequestParameter.string(address), RequestParameter.string(hash), RequestParameter.string(blockNumber.stringValue)]

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

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

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

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

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

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

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"
}
}
}

extension APIRequest {
public static func sendRequest<Result>(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse<Result> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we have this not be a static

Copy link
Collaborator Author

@yaroslavyaroslav yaroslavyaroslav Jun 1, 2022

Choose a reason for hiding this comment

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

I doubt it.

I've considered to move this method to web3httpprovider class, but it's complicated because then you've should to make the whole provider struct generic and then make protocol that it conforms with associated type. Which leads to you need to rewrite almost every method in whole lib, so i refused it.

Making this method non static within this enum makes API kinda wired. It'll be looks like this:

let request: APIRequest = .gasPrice
let response: APIResponse<BigUInt> = request.send(with: web3.provider)

So i've refused that either.

/// Don't even try to make network request if the `Result` type dosen't equal to supposed by API
// FIXME: Add appropriate error thrown
guard Result.self == call.responseType else { throw Web3Error.unknownError }
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
}

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

// FIXME: Add appropriate error thrown
guard let httpResponse = response as? HTTPURLResponse,
200 ..< 400 ~= httpResponse.statusCode else { throw Web3Error.connectionError }

// FIXME: Add throwing an error from is server fails.
/// This bit of code is purposed to work with literal types that comes in Response in hexString type.
/// Currently it's just any kind of Integers like `(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 {
/// This types for sure conformed with `LiteralInitiableFromString`
// FIXME: Make appropriate error
guard let U = Result.self as? LiteralInitiableFromString.Type else { throw Web3Error.unknownError }
let responseAsString = try! JSONDecoder().decode(APIResponse<String>.self, from: data)
// FIXME: Add appropriate error thrown.
guard let literalValue = U.init(from: responseAsString.result) else { throw Web3Error.unknownError }
/// `U` is a APIResponseType type, which `LiteralInitiableFromString` conforms to, so it is safe to cast that.
// FIXME: Make appropriate error
guard let asT = literalValue as? Result else { throw Web3Error.unknownError }
return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: asT)
}
return try JSONDecoder().decode(APIResponse<Result>.self, from: data)
}
}

enum REST: String {
case POST
case GET
}

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

var method: String
var params: [RequestParameter]
}

/// 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
}
Loading