rraallvv/NimiqClientSwift

View on GitHub
Sources/NimiqClient/NimiqClient.swift

Summary

Maintainability
A
0 mins
Test Coverage
import Foundation

// MARK: JSONRPC Models

/// Error returned in the response for the JSONRPC the server.
struct ResponseError: Decodable {
    var code: Int
    var message: String
}

/// Used to decode the JSONRPC response returned by the server.
struct Root<T:Decodable>: Decodable {
    var jsonrpc: String
    var result: T?
    var id: Int
    var error: ResponseError?
}

/// Type of a Nimiq account.
public enum AccountType: Int, Decodable {
    /// Normal Nimiq account.
    case basic = 0
    /// Vesting contract.
    case vesting = 1
    /// Hashed Timelock Contract.
    case htlc = 2
}

/// Normal Nimiq account object returned by the server.
public struct Account: Decodable {
    /// Hex-encoded 20 byte address.
    public var id: String
    /// User friendly address (NQ-address).
    public var address: String
    /// Balance of the account (in smallest unit).
    public var balance: Int
    /// The account type associated with the account.
    public var type: AccountType
}

/// Vesting contract object returned by the server.
public struct VestingContract : Decodable {
    /// Hex-encoded 20 byte address.
    public var id: String
    /// User friendly address (NQ-address).
    public var address: String
    /// Balance of the account (in smallest unit).
    public var balance: Int
    /// The account type associated with the account.
    public var type: AccountType
    /// Hex-encoded 20 byte address of the owner of the vesting contract.
    public var owner: String
    /// User friendly address (NQ-address) of the owner of the vesting contract.
    public var ownerAddress: String
    /// The block that the vesting contracted commenced.
    public var vestingStart: Int
    /// The number of blocks after which some part of the vested funds is released.
    public var vestingStepBlocks: Int
    /// The amount (in smallest unit) released every vestingStepBlocks blocks.
    public var vestingStepAmount: Int
    /// The total amount (in smallest unit) that was provided at the contract creation.
    public var vestingTotalAmount: Int
}

/// Hashed Timelock Contract object returned by the server.
public struct HTLC : Decodable {
    /// Hex-encoded 20 byte address.
    public var id: String
    /// User friendly address (NQ-address).
    public var address: String
    /// Balance of the account (in smallest unit).
    public var balance: Int
    /// The account type associated with the account.
    public var type: AccountType
    /// Hex-encoded 20 byte address of the sender of the HTLC.
    public var sender: String
    /// User friendly address (NQ-address) of the sender of the HTLC.
    public var senderAddress: String
    /// Hex-encoded 20 byte address of the recipient of the HTLC.
    public var recipient: String
    /// User friendly address (NQ-address) of the recipient of the HTLC.
    public var recipientAddress: String
    /// Hex-encoded 32 byte hash root.
    public var hashRoot: String
    /// Hash algorithm.
    public var hashAlgorithm: Int
    /// Number of hashes this HTLC is split into.
    public var hashCount: Int
    /// Block after which the contract can only be used by the original sender to recover funds.
    public var timeout: Int
    /// The total amount (in smallest unit) that was provided at the contract creation.
    public var totalAmount: Int
}

/// Nimiq account returned by the server. The especific type is in the associated value.
enum RawAccount : Decodable {
    case account(Account)
    case vesting(VestingContract)
    case htlc(HTLC)

    var value: Any {
         switch self {
         case .account(let value):
             return value
         case .vesting(let value):
             return value
         case .htlc(let value):
             return value
         }
    }

    private enum CodingKeys: String, CodingKey {
        case account, vestingContract, hashedTimeLockedContract
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .htlc(try container.decode(HTLC.self))
        } catch {
            do {
                self = .vesting(try container.decode(VestingContract.self))
            } catch {
                self = .account(try container.decode(Account.self))
            }
        }
    }
}

/// Consensus state returned by the server.
public enum ConsensusState: String, Decodable {
    /// Connecting.
    case connecting
    /// Syncing blocks.
    case syncing
    /// Consensus established.
    case established
}

/// Nimiq wallet returned by the server.
public struct Wallet: Decodable {
    /// Hex-encoded 20 byte address.
    public var id: String
    /// User friendly address (NQ-address).
    public var address: String
    /// Hex-encoded 32 byte Ed25519 public key.
    public var publicKey: String
    /// Hex-encoded 32 byte Ed25519 private key.
    public var privateKey: String?
}

/// Can be both a hexadecimal representation or a human readable address.
public typealias Address = String

/// Used to pass the data to send transaccions.
public struct OutgoingTransaction {
    /// The address the transaction is send from.
    public var from: Address
    /// The account type at the given address.
    public var fromType: AccountType? = .basic
    /// The address the transaction is directed to.
    public var to: Address
    /// The account type at the given address.
    public var toType: AccountType? = .basic
    /// Integer of the value (in smallest unit) sent with this transaction.
    public var value: Int
    /// Integer of the fee (in smallest unit) for this transaction.
    public var fee: Int
    /// Hex-encoded contract parameters or a message.
    public var data: String? = nil

    /// OutgoingTransaction initialization.
    /// - Parameter from: The address the transaction is send from.
    /// - Parameter fromType: The account type at the given address.
    /// - Parameter to: The address the transaction is directed to.
    /// - Parameter toType: The account type at the given address.
    /// - Parameter value: Integer of the value (in smallest unit) sent with this transaction.
    /// - Parameter fee: Integer of the fee (in smallest unit) for this transaction.
    /// - Parameter data: Hex-encoded contract parameters or a message.
    public init(from: Address, fromType: AccountType? = .basic, to: Address, toType: AccountType? = .basic, value: Int, fee: Int, data: String? = nil) {
        self.from = from
        self.fromType = fromType
        self.to = to
        self.toType = toType
        self.value = value
        self.fee = fee
        self.data = data
    }
}

/// Hexadecimal string containing a hash value.
public typealias Hash = String

/// Transaction returned by the server.
public struct Transaction : Decodable {
    /// Hex-encoded hash of the transaction.
    public var hash: Hash
    /// Hex-encoded hash of the block containing the transaction.
    public var blockHash: Hash?
    /// Height of the block containing the transaction.
    public var blockNumber: Int?
    /// UNIX timestamp of the block containing the transaction.
    public var timestamp: Int?
    /// Number of confirmations of the block containing the transaction.
    public var confirmations: Int? = 0
    /// Index of the transaction in the block.
    public var transactionIndex: Int?
    /// Hex-encoded address of the sending account.
    public var from: String
    /// Nimiq user friendly address (NQ-address) of the sending account.
    public var fromAddress: Address
    /// Hex-encoded address of the recipient account.
    public var to: String
    /// Nimiq user friendly address (NQ-address) of the recipient account.
    public var toAddress: Address
    /// Integer of the value (in smallest unit) sent with this transaction.
    public var value: Int
    /// Integer of the fee (in smallest unit) for this transaction.
    public var fee: Int
    /// Hex-encoded contract parameters or a message.
    public var data: String? = nil
    /// Bit-encoded transaction flags.
    public var flags: Int
}

/// Block returned by the server.
public struct Block : Decodable {
    /// Height of the block.
    public var number: Int
    /// Hex-encoded 32-byte hash of the block.
    public var hash: Hash
    /// Hex-encoded 32-byte Proof-of-Work hash of the block.
    public var pow: Hash
    /// Hex-encoded 32-byte hash of the predecessor block.
    public var parentHash: Hash
    /// The nonce of the block used to fulfill the Proof-of-Work.
    public var nonce: Int
    /// Hex-encoded 32-byte hash of the block body Merkle root.
    public var bodyHash: Hash
    /// Hex-encoded 32-byte hash of the accounts tree root.
    public var accountsHash: Hash
    /// Block difficulty, encoded as decimal number in string.
    public var difficulty: String
    /// UNIX timestamp of the block
    public var timestamp: Int
    /// Number of confirmations for this transaction (number of blocks on top of the block where this transaction was in).
    public var confirmations: Int
    /// Hex-encoded 20 byte address of the miner of the block.
    public var miner: String
    /// User friendly address (NQ-address) of the miner of the block.
    public var minerAddress: Address
    /// Hex-encoded value of the extra data field, maximum of 255 bytes.
    public var extraData: String
    /// Block size in byte.
    public var size: Int
    /// Array of transactions. Either represented by the transaction hash or a Transaction object.
    public var transactions: [Any]

    private enum CodingKeys: String, CodingKey {
        case number, hash, pow, parentHash, nonce, bodyHash, accountsHash, difficulty, timestamp, confirmations, miner, minerAddress, extraData, size, transactions
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        number = try container.decode(Int.self, forKey: .number)
        hash = try container.decode(Hash.self, forKey: .hash)
        pow = try container.decode(Hash.self, forKey: .pow)
        parentHash = try container.decode(Hash.self, forKey: .parentHash)
        nonce = try container.decode(Int.self, forKey: .nonce)
        bodyHash = try container.decode(Hash.self, forKey: .bodyHash)
        accountsHash = try container.decode(Hash.self, forKey: .accountsHash)
        difficulty = try container.decode(String.self, forKey: .difficulty)
        timestamp = try container.decode(Int.self, forKey: .timestamp)
        confirmations = try container.decode(Int.self, forKey: .confirmations)
        miner = try container.decode(String.self, forKey: .miner)
        minerAddress = try container.decode(Address.self, forKey: .minerAddress)
        extraData = try container.decode(String.self, forKey: .extraData)
        size = try container.decode(Int.self, forKey: .size)
        do {
            transactions = try container.decode([Transaction].self, forKey: .transactions)
        } catch {
            transactions = try container.decode([Hash].self, forKey: .transactions)
        }
    }
}

/// Block template header returned by the server.
public struct BlockTemplateHeader : Decodable {
    /// Version in block header.
    public var version: Int
    /// 32-byte hex-encoded hash of the previous block.
    public var prevHash: Hash
    /// 32-byte hex-encoded hash of the interlink.
    public var interlinkHash: Hash
    /// 32-byte hex-encoded hash of the accounts tree.
    public var accountsHash: Hash
    /// Compact form of the hash target for this block.
    public var nBits: Int
    /// Height of the block in the block chain (also known as block number).
    public var height: Int
}

/// Block template body returned by the server.
public struct BlockTemplateBody : Decodable {
    /// 32-byte hex-encoded hash of the block body.
    public var hash: Hash
    /// 20-byte hex-encoded miner address.
    public var minerAddr: String
    /// Hex-encoded value of the extra data field.
    public var extraData: String
    /// Array of hex-encoded transactions for this block.
    public var transactions: [String]
    /// Array of hex-encoded pruned accounts for this block.
    public var prunedAccounts: [String]
    /// Array of hex-encoded hashes that verify the path of the miner address in the merkle tree.
    /// This can be used to change the miner address easily.
    public var merkleHashes: [Hash]
}

/// Block template returned by the server.
public struct BlockTemplate : Decodable {
    /// Block template header returned by the server.
    public var header: BlockTemplateHeader
    /// Hex-encoded interlink.
    public var interlink: String
    /// Block template body returned by the server.
    public var body: BlockTemplateBody
    /// Compact form of the hash target to submit a block to this client.
    public var target: Int
}

/// Transaction receipt returned by the server.
public struct TransactionReceipt : Decodable {
    /// Hex-encoded hash of the transaction.
    public var transactionHash: Hash
    /// Integer of the transactions index position in the block.
    public var transactionIndex: Int
    /// Hex-encoded hash of the block where this transaction was in.
    public var blockHash: Hash
    /// Block number where this transaction was in.
    public var blockNumber: Int
    /// Number of confirmations for this transaction (number of blocks on top of the block where this transaction was in).
    public var confirmations: Int
    /// Timestamp of the block where this transaction was in.
    public var timestamp: Int
}

/// Work instructions receipt returned by the server.
public struct WorkInstructions : Decodable {
    /// Hex-encoded block header. This is what should be passed through the hash function.
    /// The last 4 bytes describe the nonce, the 4 bytes before are the current timestamp.
    /// Most implementations allow the miner to arbitrarily choose the nonce and to update the timestamp without requesting new work instructions.
    public var data: String
    /// Hex-encoded block without the header. When passing a mining result to submitBlock, append the suffix to the data string with selected nonce.
    public var suffix: String
    /// Compact form of the hash target to submit a block to this client.
    public var target: Int
    /// Field to describe the algorithm used to mine the block. Always nimiq-argon2 for now.
    public var algorithm: String
}

/// Used to set the log level in the JSONRPC server.
public enum LogLevel : String {
    /// Trace level log.
    case trace
    /// Verbose level log.
    case verbose
    /// Debugging level log.
    case debug
    /// Info level log.
    case info
    /// Warning level log.
    case warn
    /// Error level log.
    case error
    /// Assertions level log.
    case assert
}

/// Mempool information returned by the server.
public struct MempoolInfo : Decodable {
    /// Total number of pending transactions in mempool.
    public var total: Int
    /// Array containing a subset of fee per byte buckets from [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1, 0] that currently have more than one transaction.
    public var buckets: [Int]
    /// Number of transaction in the bucket. A transaction is assigned to the highest bucket of a value lower than its fee per byte value.
    public var transactionsPerBucket: [Int:Int]

    private enum CodingKeys: String, CodingKey {
        case total, buckets
        case bucket10000 = "10000"
        case bucket5000 = "5000"
        case bucket2000 = "2000"
        case bucket1000 = "1000"
        case bucket500 = "500"
        case bucket200 = "200"
        case bucket100 = "100"
        case bucket50 = "50"
        case bucket20 = "20"
        case bucket10 = "10"
        case bucket5 = "5"
        case bucket2 = "2"
        case bucket1 = "1"
        case bucket0 = "0"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        total = try container.decode(Int.self, forKey: .total)
        buckets = try container.decode([Int].self, forKey: .buckets)
        transactionsPerBucket = [Int:Int]()
        for key in container.allKeys {
            guard let intKey = Int(key.stringValue) else {
                continue
            }
            transactionsPerBucket[intKey] = try container.decode(Int.self, forKey: key)
        }
    }
}

/// Transaction returned by the server. The especific type is in the associated value.
enum HashOrTransaction : Decodable {
    case hash(Hash)
    case transaction(Transaction)

    var value: Any {
         switch self {
         case .hash(let value):
             return value
         case .transaction(let value):
             return value
         }
    }

    private enum CodingKeys: String, CodingKey {
        case hash, transaction
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .transaction(try container.decode(Transaction.self))
        } catch {
            self = .hash(try container.decode(Hash.self))
        }
    }
}

/// Peer address state returned by the server.
public enum PeerAddressState : Int, Decodable {
    /// New peer.
    case new = 1
    /// Established peer.
    case established = 2
    /// Already tried peer.
    case tried = 3
    /// Peer failed.
    case failed = 4
    /// Balled peer.
    case banned = 5
}

/// Peer connection state returned by the server.
public enum PeerConnectionState : Int, Decodable {
    /// New connection.
    case new = 1
    /// Connecting.
    case connecting = 2
    /// Connected.
    case connected = 3
    /// Negotiating connection.
    case negotiating = 4
    /// Connection established.
    case established = 5
    /// Connection closed.
    case closed = 6
}

/// Peer information returned by the server.
public struct Peer : Decodable {
    /// Peer id.
    public var id: String
    /// Peer address.
    public var address: String
    /// Peer address state.
    public var addressState: PeerAddressState
    /// Peer connection state.
    public var connectionState: PeerConnectionState?
    /// Node version the peer is running.
    public var version: Int?
    /// Time offset with the peer (in miliseconds).
    public var timeOffset: Int?
    /// Hash of the head block of the peer.
    public var headHash: Hash?
    /// Latency to the peer.
    public var latency: Int?
    /// Received bytes.
    public var rx: Int?
    /// Sent bytes.
    public var tx: Int?
}

/// Commands to change the state of a peer.
public enum PeerStateCommand : String {
    /// Connect.
    case connect
    /// Disconnect.
    case disconnect
    /// Ban.
    case ban
    /// Unban.
    case unban
}

/// Pool connection state information returned by the server.
public enum PoolConnectionState : Int, Decodable {
    /// Connected.
    case connected = 0
    /// Connecting.
    case connecting = 1
    /// Closed.
    case closed = 2
}

/// Syncing status returned by the server.
public struct SyncStatus : Decodable {
    /// The block at which the import started (will only be reset, after the sync reached his head).
    public var startingBlock: Int
    /// The current block, same as blockNumber.
    public var currentBlock: Int
    /// The estimated highest block.
    public var highestBlock: Int
}

/// Syncing status returned by the server. The especific type is in the associated value.
enum SyncStatusOrBool : Decodable {
    case syncStatus(SyncStatus)
    case bool(Bool)

    var value: Any {
         switch self {
         case .syncStatus(let value):
             return value
         case .bool(let value):
             return value
         }
    }

    private enum CodingKeys: String, CodingKey {
        case syncStatus, bool
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .syncStatus(try container.decode(SyncStatus.self))
        } catch {
            self = .bool(try container.decode(Bool.self))
        }
    }
}

// MARK: -
// MARK: JSONRPC Client

/// Used in convenience initializer in the NimiqClient class.
public struct Config {
    /// Protocol squeme, `"http"` or `"https"`.
    public var scheme: String
    /// Authorized user.
    public var user: String
    /// Password for the authorized user.
    public var password: String
    /// Host IP address.
    public var host: String
    /// Host port.
    public var port: Int

    /// Config initialization.
    /// - Parameter scheme: Protocol squeme, `"http"` or `"https"`.
    /// - Parameter user: Authorized user.
    /// - Parameter password: Password for the authorized user.
    /// - Parameter host: Host IP address.
    /// - Parameter port: Host port.
    public init(scheme: String = "http", user: String = "", password: String = "", host: String = "127.0.0.1", port: Int = 8648) {
        self.scheme = scheme
        self.host = host
        self.port = port
        self.user = user
        self.password = password
    }
}

/// Thrown when something when wrong with the JSONRPC request.
public enum Error: Swift.Error, Equatable {
    /// Internal error during a JSON RPC request.
    case internalError(_ message: String)
    /// Exception on the remote server.
    case remoteError(_ message: String)
    /// Error with connection.
    case connectionError(_ message: String)
}

/// Nimiq JSONRPC Client
public class NimiqClient {

    /// Number in the sequence for the next request.
    public var id: Int = 0

    /// URL of the JSONRPC server.
    private let url: String
    
    /// Base64 string containing authentication parameters.
    private let auth: String

    /// URLSession used for HTTP requests sent to the JSONRPC server.
    private let session: URLSession

    /// Client initialization from a Config structure using shared URLSession.
    /// When no parameter is given, it uses de default configuration in the server (`http://:@127.0.0.1:8648`).
    /// - Parameter config: Options used for the configuration.
    public convenience init(config: Config? = nil) {
        if config != nil {
            self.init(scheme: config!.scheme, user: config!.user, password: config!.password, host: config!.host, port: config!.port)
        } else {
            self.init(scheme: "http", user: "", password: "", host: "127.0.0.1", port: 8648)
        }
    }

    /// Client initialization.
    /// - Parameter scheme: Protocol squeme, `"http"` or `"https"`.
    /// - Parameter user: Authorized user.
    /// - Parameter password: Password for the authorized user.
    /// - Parameter host: Host IP address.
    /// - Parameter port: Host port.
    /// - Parameter session: Used to make all requests. If ommited the shared URLSession is used.
    public init(scheme: String = "http", user: String = "", password: String = "", host: String = "127.0.0.1", port: Int = 8648, session: URLSession? = nil){
        self.url = "\(scheme)://\(host):\(port)"
        self.auth = "\(user):\(password)".data(using: String.Encoding.utf8)!.base64EncodedString()
        if session != nil {
            self.session = session!
        } else {
            self.session = URLSession.shared
        }
    }

    /// Used in all JSONRPC requests to fetch the data.
    /// - Parameter method: JSONRPC method.
    /// - Parameter params: Parameters used by the request.
    /// - Returns: If succesfull, returns the model reperestation of the result, `nil` otherwise.
    private func call<T:Decodable>(method: String, params: [Any]) throws -> T? {
        var responseObject: Root<T>? = nil
        var clientError: Error? = nil

        // make JSON object to send to the server
        let callObject:[String:Any] = [
            "jsonrpc": "2.0",
            "method": method,
            "params": params,
            "id": id
        ]

        // prepare the request
        let data = try JSONSerialization.data(withJSONObject: callObject, options: [])
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = "POST"
        request.httpBody = data
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue("Basic \(auth)", forHTTPHeaderField: "Authorization")
        // TODO: find a better way to fix the error when the server terminates the connection prematurely
        request.addValue("close", forHTTPHeaderField: "Connection")

        let semaphore = DispatchSemaphore(value: 0)

        // send the request
        let task = session.dataTask(with: request, completionHandler: { data, response, error in
            if error == nil {
                // serialize the data into an object
                do {
                    responseObject = try JSONDecoder().decode(Root<T>.self, from: data! )

                } catch {
                    clientError = Error.internalError(error.localizedDescription)
                }
            } else {
                clientError = Error.connectionError(error!.localizedDescription)
            }

            // signal that the request was completed
            semaphore.signal()
        })
        task.resume()

        // wait for the response
        semaphore.wait()

        // throw if there are any errors
        if clientError != nil {
            throw clientError!
        }

        if let error = responseObject?.error {
            throw Error.remoteError("\(error.message) (Code: \(error.code)")
        }

        // increase the JSONRPC client request id for the next request
        self.id = self.id + 1

        return responseObject?.result
    }

    /// Returns a list of addresses owned by client.
    /// - Returns: Array of Accounts owned by the client.
    public func accounts() throws -> [Any]? {
        let result: [RawAccount] = try call(method: "accounts", params: [])!
        var converted: [Any] = [Any]()
        for rawAccount in result {
            converted.append(rawAccount.value)
        }
        return converted
    }

    /// Returns the height of most recent block.
    /// - Returns: The current block height the client is on.
    public func blockNumber() throws -> Int? {
        return try call(method: "blockNumber", params: [])
    }

    /// Returns information on the current consensus state.
    /// - Returns: Consensus state. `established` is the value for a good state, other values indicate bad.
    public func consensus() throws -> ConsensusState? {
        return try call(method: "consensus", params: [])
    }

    /// Returns or overrides a constant value.
    /// When no parameter is given, it returns the value of the constant. When giving a value as parameter,
    /// it sets the constant to the given value. To reset the constant use `resetConstant()` instead.
    /// - Parameter string: The class and name of the constant (format should be `Class.CONSTANT`).
    /// - Parameter value: The new value of the constant.
    /// - Returns: The value of the constant.
    public func constant(_ constant: String, value: Int? = nil) throws -> Int? {
        var params:[Any] = [constant]
        if value != nil {
            params.append(value!)
        }
        return try call(method: "constant", params: params)
    }

    /// Creates a new account and stores its private key in the client store.
    /// - Returns: Information on the wallet that was created using the command.
    public func createAccount() throws -> Wallet? {
        return try call(method: "createAccount", params: [])
    }

    /// Creates and signs a transaction without sending it. The transaction can then be send via `sendRawTransaction()` without accidentally replaying it.
    /// - Parameter transaction: The transaction object.
    /// - Returns: Hex-encoded transaction.
    public func createRawTransaction(_ transaction: OutgoingTransaction) throws -> String? {
        let params:[String:Any?] = [
            "from": transaction.from,
            "fromType": transaction.fromType?.rawValue,
            "to": transaction.to,
            "toType": transaction.toType?.rawValue,
            "value": transaction.value,
            "fee": transaction.fee,
            "data": transaction.data
        ]
        return try call(method: "createRawTransaction", params: [params])
    }

    /// Returns details for the account of given address.
    /// - Parameter address: Address to get account details.
    /// - Returns: Details about the account. Returns the default empty basic account for non-existing accounts.
    public func getAccount(address: Address) throws -> Any? {
        let result: RawAccount = try call(method: "getAccount", params: [address])!
        return result.value
    }

    /// Returns the balance of the account of given address.
    /// - Parameter address: Address to check for balance.
    /// - Returns: The current balance at the specified address (in smalest unit).
    public func getBalance(address: Address) throws -> Int? {
        return try call(method: "getBalance", params: [address])
    }

    /// Returns information about a block by hash.
    /// - Parameter hash: Hash of the block to gather information on.
    /// - Parameter fullTransactions: If `true` it returns the full transaction objects, if `false` only the hashes of the transactions.
    /// - Returns: A block object or `nil` when no block was found.
    public func getBlockByHash(_ hash: Hash, fullTransactions: Bool = false) throws -> Block? {
        return try call(method: "getBlockByHash", params: [hash, fullTransactions])
    }

    /// Returns information about a block by block number.
    /// - Parameter height: The height of the block to gather information on.
    /// - Parameter fullTransactions: If `true` it returns the full transaction objects, if `false` only the hashes of the transactions.
    /// - Returns: A block object or `nil` when no block was found.
    public func getBlockByNumber(height: Int, fullTransactions: Bool = false) throws -> Block? {
        return try call(method: "getBlockByNumber", params: [height, fullTransactions])
    }

    /// Returns a template to build the next block for mining. This will consider pool instructions when connected to a pool.
    /// If `address` and `extraData` are provided the values are overriden.
    /// - Parameter address: The address to use as a miner for this block. This overrides the address provided during startup or from the pool.
    /// - Parameter extraData: Hex-encoded value for the extra data field. This overrides the extra data provided during startup or from the pool.
    /// - Returns: A block template object.
    public func getBlockTemplate(address: Address? = nil, extraData: String = "") throws -> BlockTemplate? {
        var params: [Any] = [Any]()
        if address != nil {
            params.append(address!)
            params.append(extraData)
        }
        return try call(method: "getBlockTemplate", params: params)

    }

    /// Returns the number of transactions in a block from a block matching the given block hash.
    /// - Parameter hash: Hash of the block.
    /// - Returns: Number of transactions in the block found, or `nil`, when no block was found.
    public func getBlockTransactionCountByHash(_ hash: Hash) throws -> Int? {
        return try call(method: "getBlockTransactionCountByHash", params: [hash])
    }

    /// Returns the number of transactions in a block matching the given block number.
    /// - Parameter height: Height of the block.
    /// - Returns: Number of transactions in the block found, or `nil`, when no block was found.
    public func getBlockTransactionCountByNumber(height: Int) throws -> Int? {
        return try call(method: "getBlockTransactionCountByNumber", params: [height])
    }

    /// Returns information about a transaction by block hash and transaction index position.
    /// - Parameter hash: Hash of the block containing the transaction.
    /// - Parameter index: Index of the transaction in the block.
    /// - Returns: A transaction object or `nil` when no transaction was found.
    public func getTransactionByBlockHashAndIndex(hash: Hash, index: Int) throws -> Transaction? {
        return try call(method: "getTransactionByBlockHashAndIndex", params: [hash, index])
    }

    /// Returns information about a transaction by block number and transaction index position.
    /// - Parameter height: Height of the block containing the transaction.
    /// - Parameter index: Index of the transaction in the block.
    /// - Returns: A transaction object or `nil` when no transaction was found.
    public func getTransactionByBlockNumberAndIndex(height: Int, index: Int) throws -> Transaction? {
        return try call(method: "getTransactionByBlockNumberAndIndex", params: [height, index])
    }

    /// Returns the information about a transaction requested by transaction hash.
    /// - Parameter hash: Hash of a transaction.
    /// - Returns: A transaction object or `nil` when no transaction was found.
    public func getTransactionByHash(_ hash: Hash) throws -> Transaction? {
        return try call(method: "getTransactionByHash", params: [hash])
    }

    /// Returns the receipt of a transaction by transaction hash.
    /// - Parameter hash: Hash of a transaction.
    /// - Returns: A transaction receipt object, or `nil` when no receipt was found.
    public func getTransactionReceipt(hash: Hash) throws -> TransactionReceipt? {
        return try call(method: "getTransactionReceipt", params: [hash])
    }

    /// Returns the latest transactions successfully performed by or for an address.
    /// Note that this information might change when blocks are rewinded on the local state due to forks.
    /// - Parameter address: Address of which transactions should be gathered.
    /// - Parameter numberOfTransactions: Number of transactions that shall be returned.
    /// - Returns: Array of transactions linked to the requested address.
    public func getTransactionsByAddress(_ address: Address, numberOfTransactions: Int = 1000) throws -> [Transaction]? {
        return try call(method: "getTransactionsByAddress", params: [address, numberOfTransactions])
    }

    /// Returns instructions to mine the next block. This will consider pool instructions when connected to a pool.
    /// - Parameter address: The address to use as a miner for this block. This overrides the address provided during startup or from the pool.
    /// - Parameter extraData: Hex-encoded value for the extra data field. This overrides the extra data provided during startup or from the pool.
    /// - Returns: Mining work instructions.
    public func getWork(address: Address? = nil, extraData: String = "") throws -> WorkInstructions? {
        var params: [Any] = [Any]()
        if address != nil {
            params.append(address!)
            params.append(extraData)
        }
        return try call(method: "getWork", params: params)
    }

    /// Returns the number of hashes per second that the node is mining with.
    /// - Returns: Number of hashes per second.
    public func hashrate() throws -> Float? {
        return try call(method: "hashrate", params: [])
    }

    /// Sets the log level of the node.
    /// - Parameter tag: Tag: If `"*"` the log level is set globally, otherwise the log level is applied only on this tag.
    /// - Parameter level: Minimum log level to display.
    /// - Returns: `true` if the log level was changed, `false` otherwise.
    public func log(tag: String, level: LogLevel) throws -> Bool? {
        return try call(method: "log", params: [tag, level.rawValue])
    }

    /// Returns information on the current mempool situation. This will provide an overview of the number of transactions sorted into buckets based on their fee per byte (in smallest unit).
    /// - Returns: Mempool information.
    public func mempool() throws -> MempoolInfo? {
        return try call(method: "mempool", params: [])
    }

    /// Returns transactions that are currently in the mempool.
    /// - Parameter fullTransactions: If `true` includes full transactions, if `false` includes only transaction hashes.
    /// - Returns: Array of transactions (either represented by the transaction hash or a transaction object).
    public func mempoolContent(fullTransactions: Bool = false) throws -> [Any]? {
        let result: [HashOrTransaction] = try call(method: "mempoolContent", params: [fullTransactions])!
        var converted: [Any] = [Any]()
        for transaction in result {
            converted.append(transaction.value)
        }
        return converted
    }

    /// Returns the miner address.
    /// - Returns: The miner address configured on the node.
    public func minerAddress() throws -> String? {
        return try call(method: "minerAddress", params: [])
    }

    /// Returns or sets the number of CPU threads for the miner.
    /// When no parameter is given, it returns the current number of miner threads.
    /// When a value is given as parameter, it sets the number of miner threads to that value.
    /// - Parameter threads: The number of threads to allocate for mining.
    /// - Returns: The number of threads allocated for mining.
    public func minerThreads(_ threads: Int? = nil) throws -> Int? {
        var params: [Int] = [Int]()
        if threads != nil {
            params.append(threads!)
        }
        return try call(method: "minerThreads", params: params)
    }

    /// Returns or sets the minimum fee per byte.
    /// When no parameter is given, it returns the current minimum fee per byte.
    /// When a value is given as parameter, it sets the minimum fee per byte to that value.
    /// - Parameter fee: The new minimum fee per byte.
    /// - Returns: The new minimum fee per byte.
    public func minFeePerByte(fee: Int? = nil) throws -> Int? {
        var params: [Int] = [Int]()
        if fee != nil {
            params.append(fee!)
        }
        return try call(method: "minFeePerByte", params: params)
    }

    /// Returns true if client is actively mining new blocks.
    /// When no parameter is given, it returns the current state.
    /// When a value is given as parameter, it sets the current state to that value.
    /// - Parameter state: The state to be set.
    /// - Returns: `true` if the client is mining, otherwise `false`.
    public func mining(state: Bool? = nil) throws -> Bool? {
        var params: [Bool] = [Bool]()
        if state != nil {
            params.append(state!)
        }
        return try call(method: "mining", params: params)
    }

    /// Returns number of peers currently connected to the client.
    /// - Returns: Number of connected peers.
    public func peerCount() throws -> Int? {
        return try call(method: "peerCount", params: [])
    }

    /// Returns list of peers known to the client.
    /// - Returns: The list of peers.
    public func peerList() throws -> [Peer]? {
        return try call(method: "peerList", params: [])
    }

    /// Returns the state of the peer.
    /// When no command is given, it returns peer state.
    /// When a value is given for command, it sets the peer state to that value.
    /// - Parameter address: The address of the peer.
    /// - Parameter command: The command to send.
    /// - Returns: The current state of the peer.
    public func peerState(address: String, command: PeerStateCommand? = nil) throws -> Peer? {
        var params: [Any] = [Any]()
        params.append(address)
        if let commandString = command?.rawValue  {
            params.append(commandString)
        }
        return try call(method: "peerState", params: params)
    }

    /// Returns or sets the mining pool.
    /// When no parameter is given, it returns the current mining pool.
    /// When a value is given as parameter, it sets the mining pool to that value.
    /// - Parameter address: The mining pool connection string (`url:port`) or boolean to enable/disable pool mining.
    /// - Returns: The mining pool connection string, or `nil` if not enabled.
    public func pool(address: Any? = nil) throws -> String? {
        var params: [Any] = [Any]()
        if let addressString = address as? String {
            params.append(addressString)
        } else if let addressBool = address as? Bool {
            params.append(addressBool)
        }
        return try call(method: "pool", params: params)
    }

    /// Returns the confirmed mining pool balance.
    /// - Returns: The confirmed mining pool balance (in smallest unit).
    public func poolConfirmedBalance() throws -> Int? {
        return try call(method: "poolConfirmedBalance", params: [])
    }

    /// Returns the connection state to mining pool.
    /// - Returns: The mining pool connection state.
    public func poolConnectionState() throws -> PoolConnectionState? {
        return try call(method: "poolConnectionState", params: [])
    }

    /// Sends a signed message call transaction or a contract creation, if the data field contains code.
    /// - Parameter transaction: The hex encoded signed transaction
    /// - Returns: The Hex-encoded transaction hash.
    public func sendRawTransaction(_ transaction: String) throws -> Hash? {
        return try call(method: "sendRawTransaction", params: [transaction])
    }

    /// Creates new message call transaction or a contract creation, if the data field contains code.
    /// - Parameter transaction: The hex encoded signed transaction
    /// - Returns: The Hex-encoded transaction hash.
    public func sendTransaction(_ transaction: OutgoingTransaction) throws -> Hash? {
        let params:[String:Any?] = [
            "from": transaction.from,
            "fromType": transaction.fromType?.rawValue,
            "to": transaction.to,
            "toType": transaction.toType?.rawValue,
            "value": transaction.value,
            "fee": transaction.fee,
            "data": transaction.data
        ]
        return try call(method: "sendTransaction", params: [params])
    }

    /// Submits a block to the node. When the block is valid, the node will forward it to other nodes in the network.
    /// - Parameter block: Hex-encoded full block (including header, interlink and body). When submitting work from getWork, remember to include the suffix.
    /// - Returns: Always `nil`.
    @discardableResult public func submitBlock(_ block: String) throws -> String? {
        return try call(method: "submitBlock", params: [block])
    }

    /// Returns an object with data about the sync status or `false`.
    /// - Returns: An object with sync status data or `false`, when not syncing.
    public func syncing() throws -> Any? {
        let result: SyncStatusOrBool = try call(method: "syncing", params: [])!
        return result.value
    }

    /// Deserializes hex-encoded transaction and returns a transaction object.
    /// - Parameter transaction: The hex encoded signed transaction.
    /// - Returns: The transaction object.
    public func getRawTransactionInfo(transaction: String) throws -> Transaction? {
        return try call(method: "getRawTransactionInfo", params: [transaction])
    }

    /// Resets the constant to default value.
    /// - Parameter constant: Name of the constant.
    /// - Returns: The new value of the constant.
    public func resetConstant(_ constant: String) throws -> Int? {
        return try call(method: "constant", params: [constant, "reset"])
    }
}