commit
fbfd9ad6cd
|
@ -1,6 +1,8 @@
|
|||
/// Steem RPC requests and responses.
|
||||
/// - Author: Johan Nordberg <johan@steemit.com>
|
||||
/// - Author: Iain Maitland <imaitland@steemit.com>
|
||||
|
||||
import AnyCodable
|
||||
import Foundation
|
||||
|
||||
/// Steem RPC API request- and response-types.
|
||||
|
@ -27,17 +29,73 @@ public struct API {
|
|||
public struct DynamicGlobalProperties: Decodable {
|
||||
public let headBlockNumber: UInt32
|
||||
public let headBlockId: BlockId
|
||||
public let currentWitness: String
|
||||
public let numPowWitnesses: UInt32
|
||||
public let virtualSupply: Asset
|
||||
public let currentSupply: Asset
|
||||
public let confidentialSupply: Asset
|
||||
public let currentSbdSupply: Asset
|
||||
public let confidentialSbdSupply: Asset
|
||||
public let totalVestingFundSteem: Asset
|
||||
public let totalVestingShares: Asset
|
||||
public let totalRewardFundSteem: Asset
|
||||
public let totalRewardShares2: String
|
||||
public let pendingRewardedVestingShares: Asset
|
||||
public let pendingRewardedVestingSteem: Asset
|
||||
public let sbdInterestRate: UInt32
|
||||
public let sbdPrintRate: UInt32
|
||||
public let currentAslot: UInt32
|
||||
public let recentSlotsFilled: String
|
||||
public let participationCount: UInt32
|
||||
public let lastIrreversibleBlockNum: UInt32
|
||||
public let votePowerReserveRate: UInt32
|
||||
public let time: Date
|
||||
}
|
||||
|
||||
public struct GetDynamicGlobalProperties: Request {
|
||||
public typealias Response = DynamicGlobalProperties
|
||||
public let method = "get_dynamic_global_properties"
|
||||
public let method = "condenser_api.get_dynamic_global_properties"
|
||||
public let params: RequestParams<[String]>? = RequestParams([])
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct SteemPrices: Decodable {
|
||||
public let steemSbd: Float32
|
||||
public let steemUsd: Float32
|
||||
public let steemVest: Float32
|
||||
}
|
||||
|
||||
public struct GetPrices: Request {
|
||||
public typealias Response = SteemPrices
|
||||
public let method = "conveyor.get_prices"
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct FeedHistory: Decodable {
|
||||
public let currentMedianHistory: Price
|
||||
public let priceHistory: [Price]
|
||||
}
|
||||
|
||||
public struct GetFeedHistory: Request {
|
||||
public typealias Response = FeedHistory
|
||||
public let method = "get_feed_history"
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct OrderBook: Decodable {
|
||||
public let bids: [Order]
|
||||
public let asks: [Order]
|
||||
}
|
||||
|
||||
public struct GetOrderBook: Request {
|
||||
public typealias Response = OrderBook
|
||||
public let method = "get_order_book"
|
||||
public let params: RequestParams<Int>?
|
||||
public init(count: Int) {
|
||||
self.params = RequestParams([count])
|
||||
}
|
||||
}
|
||||
|
||||
public struct TransactionConfirmation: Decodable {
|
||||
public let id: Data
|
||||
public let blockNum: Int32
|
||||
|
@ -50,7 +108,7 @@ public struct API {
|
|||
public let method = "call"
|
||||
public let params: CallParams<SignedTransaction>?
|
||||
public init(transaction: SignedTransaction) {
|
||||
self.params = CallParams("network_broadcast_api", "broadcast_transaction_synchronous", [transaction])
|
||||
self.params = CallParams("condenser_api", "broadcast_transaction_synchronous", [transaction])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,10 +190,68 @@ public struct API {
|
|||
/// Fetch accounts.
|
||||
public struct GetAccounts: Request {
|
||||
public typealias Response = [ExtendedAccount]
|
||||
public let method = "get_accounts"
|
||||
public let method = "condenser_api.get_accounts"
|
||||
public let params: RequestParams<[String]>?
|
||||
public init(names: [String]) {
|
||||
self.params = RequestParams([names])
|
||||
}
|
||||
}
|
||||
|
||||
public struct OperationObject: Decodable {
|
||||
public let trxId: Data
|
||||
public let block: UInt32
|
||||
public let trxInBlock: UInt32
|
||||
public let opInTrx: UInt32
|
||||
public let virtualOp: UInt32
|
||||
public let timestamp: Date
|
||||
private let op: AnyOperation
|
||||
public var operation: OperationType {
|
||||
return self.op.operation
|
||||
}
|
||||
}
|
||||
|
||||
public struct AccountHistoryObject: Decodable {
|
||||
public let id: UInt32
|
||||
public let value: OperationObject
|
||||
public init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
self.id = try container.decode(UInt32.self)
|
||||
self.value = try container.decode(OperationObject.self)
|
||||
}
|
||||
}
|
||||
|
||||
public struct GetAccountHistory: Request, Encodable {
|
||||
public typealias Response = [AccountHistoryObject]
|
||||
public let method = "condenser_api.get_account_history"
|
||||
public var params: RequestParams<AnyEncodable>? {
|
||||
return RequestParams([AnyEncodable(self.account), AnyEncodable(self.from), AnyEncodable(self.limit)])
|
||||
}
|
||||
|
||||
public var account: String
|
||||
public var from: Int
|
||||
public var limit: Int
|
||||
public init(account: String, from: Int = -1, limit: Int = 100) {
|
||||
self.account = account
|
||||
self.from = from
|
||||
self.limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
/// Type representing the order book for the internal STEEM market
|
||||
public struct Order: Equatable, SteemEncodable, Decodable {
|
||||
/// The order price
|
||||
public var orderPrice: Price
|
||||
|
||||
/// The real price
|
||||
public var realPrice: String
|
||||
|
||||
/// The STEEM price
|
||||
public var steem: UInt32
|
||||
|
||||
/// The SBD price
|
||||
public var sbd: UInt32
|
||||
|
||||
/// Created
|
||||
public var created: Date
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/// Steem token types.
|
||||
/// - Author: Johan Nordberg <johan@steemit.com>
|
||||
/// - Author: Iain Maitland <imaitland@steemit.com>
|
||||
|
||||
import Foundation
|
||||
|
||||
|
@ -17,7 +18,7 @@ public struct Asset: Equatable {
|
|||
case custom(name: String, precision: UInt8)
|
||||
|
||||
/// Number of decimal points represented.
|
||||
var precision: UInt8 {
|
||||
public var precision: UInt8 {
|
||||
switch self {
|
||||
case .steem, .sbd:
|
||||
return 3
|
||||
|
@ -47,7 +48,6 @@ public struct Asset: Equatable {
|
|||
public let symbol: Symbol
|
||||
|
||||
internal let amount: Int64
|
||||
|
||||
/// Create a new `Asset`.
|
||||
/// - Parameter value: Amount of tokens.
|
||||
/// - Parameter symbol: Token symbol.
|
||||
|
@ -83,17 +83,46 @@ public struct Asset: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
enum PriceError: Error {
|
||||
case cannotConvert(asset: Asset, usingPrice: Price)
|
||||
}
|
||||
|
||||
/// Type representing a quotation of the relative value of asset against another asset.
|
||||
public struct Price: Equatable, SteemEncodable, Decodable {
|
||||
/// The base asset.
|
||||
public var base: Asset
|
||||
/// The quote asset.
|
||||
public var quote: Asset
|
||||
/// Use the Price base and quote Assets to convert between Asset amounts.
|
||||
public func convert(asset: Asset) throws -> Asset {
|
||||
if asset.symbol == self.base.symbol {
|
||||
assert(self.base.resolvedAmount > 0)
|
||||
return Asset(asset.resolvedAmount * self.quote.resolvedAmount / self.base.resolvedAmount, self.quote.symbol)
|
||||
} else if asset.symbol == self.quote.symbol {
|
||||
assert(self.quote.resolvedAmount > 0)
|
||||
return Asset(asset.resolvedAmount * self.base.resolvedAmount / self.quote.resolvedAmount, self.base.symbol)
|
||||
} else {
|
||||
throw PriceError.cannotConvert(asset: asset, usingPrice: self)
|
||||
}
|
||||
}
|
||||
|
||||
public init(base: Asset, quote: Asset) {
|
||||
self.base = base
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
extension Asset {
|
||||
/// The amount of the token, based on symbol precision.
|
||||
public var resolvedAmount: Double {
|
||||
return Double(self.amount) / pow(10, Double(self.symbol.precision))
|
||||
}
|
||||
}
|
||||
|
||||
extension Asset: LosslessStringConvertible {
|
||||
/// The amount of the token and its symbol.
|
||||
public var description: String {
|
||||
let value = Double(self.amount) / pow(10, Double(self.symbol.precision))
|
||||
let value = self.resolvedAmount
|
||||
let formatter = NumberFormatter()
|
||||
formatter.decimalSeparator = "."
|
||||
formatter.usesGroupingSeparator = false
|
||||
|
|
|
@ -17,7 +17,7 @@ internal extension Data {
|
|||
/**
|
||||
Creates a new data buffer from a Base58Check-encoded string.
|
||||
- note: Returns nil if the check fails or if the string decodes to more than 128 bytes.
|
||||
*/
|
||||
*/
|
||||
init?(base58CheckEncoded str: String, options: Base58CheckOptions = []) {
|
||||
let len = str.count
|
||||
let data = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
|
||||
|
|
|
@ -11,10 +11,18 @@ public enum ChainId: Equatable {
|
|||
case testNet
|
||||
/// Custom chain id.
|
||||
case custom(Data)
|
||||
public init(string: String) {
|
||||
let data = Data(hexEncoded: string)
|
||||
switch data {
|
||||
case mainNetId: self = .mainNet
|
||||
case testNetId: self = .testNet
|
||||
default: self = .custom(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let mainNetId = Data(hexEncoded: "0000000000000000000000000000000000000000000000000000000000000000")
|
||||
fileprivate let testNetId = Data(hexEncoded: "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc")
|
||||
fileprivate let testNetId = Data(hexEncoded: "46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32")
|
||||
|
||||
extension ChainId {
|
||||
/// The 32-byte chain id.
|
||||
|
|
|
@ -285,7 +285,7 @@ public class Client {
|
|||
/// JSON Coding helpers.
|
||||
extension Client {
|
||||
/// Steem-style date formatter (ISO 8601 minus Z at the end).
|
||||
static let dateFormatter: DateFormatter = {
|
||||
public static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.calendar = Calendar(identifier: .iso8601)
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
@ -327,7 +327,7 @@ extension Client {
|
|||
#endif
|
||||
return decoder
|
||||
}
|
||||
|
||||
|
||||
/// Returns a JSONEncoder instance configured for the Steem JSON format.
|
||||
public static func JSONEncoder() -> Foundation.JSONEncoder {
|
||||
let encoder = Foundation.JSONEncoder()
|
||||
|
|
|
@ -8,3 +8,59 @@ public typealias SteemCodable = SteemEncodable & Decodable
|
|||
|
||||
/// Placeholder type for future extensions.
|
||||
public struct FutureExtensions: SteemCodable, Equatable {}
|
||||
|
||||
/// Type representing an optional JSON string.
|
||||
public struct JSONString: Equatable {
|
||||
/// The JSON string value, an empty string denotes a nil object.
|
||||
public var value: String
|
||||
|
||||
/// The decoded JSON object.
|
||||
public var object: [String: Any]? {
|
||||
get { return decodeMeta(self.value) }
|
||||
set { self.value = encodeMeta(newValue) }
|
||||
}
|
||||
|
||||
public init(jsonString: String) {
|
||||
self.value = jsonString
|
||||
}
|
||||
|
||||
public init(jsonObject: [String: Any]) {
|
||||
self.value = encodeMeta(jsonObject)
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONString: SteemCodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.value = try container.decode(String.self)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONString: ExpressibleByStringLiteral {
|
||||
public typealias StringLiteralType = String
|
||||
public init(stringLiteral: String) {
|
||||
self.value = stringLiteral
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func encodeMeta(_ value: [String: Any]?) -> String {
|
||||
if let object = value,
|
||||
let encoded = try? JSONSerialization.data(withJSONObject: object, options: []) {
|
||||
return String(bytes: encoded, encoding: .utf8) ?? ""
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func decodeMeta(_ value: String) -> [String: Any]? {
|
||||
guard let data = value.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
let decoded = try? JSONSerialization.jsonObject(with: data, options: [])
|
||||
return decoded as? [String: Any]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
import Foundation
|
||||
|
||||
/// A type that represents a operation on the Steem blockchain.
|
||||
public protocol OperationType: SteemCodable {}
|
||||
public protocol OperationType: SteemCodable {
|
||||
/// Whether the operation is virtual or not.
|
||||
var isVirtual: Bool { get }
|
||||
}
|
||||
|
||||
extension OperationType {
|
||||
public var isVirtual: Bool { return false }
|
||||
}
|
||||
|
||||
/// Namespace for all available Steem operations.
|
||||
public struct Operation {
|
||||
|
@ -47,7 +54,7 @@ public struct Operation {
|
|||
/// The content body.
|
||||
public var body: String
|
||||
/// Additional content metadata.
|
||||
public var jsonMetadata: String
|
||||
public var jsonMetadata: JSONString
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
|
@ -56,7 +63,7 @@ public struct Operation {
|
|||
permlink: String,
|
||||
parentAuthor: String = "",
|
||||
parentPermlink: String = "",
|
||||
jsonMetadata: String = ""
|
||||
jsonMetadata: JSONString = ""
|
||||
) {
|
||||
self.parentAuthor = parentAuthor
|
||||
self.parentPermlink = parentPermlink
|
||||
|
@ -69,8 +76,8 @@ public struct Operation {
|
|||
|
||||
/// Content metadata.
|
||||
var metadata: [String: Any]? {
|
||||
set { self.jsonMetadata = encodeMeta(newValue) }
|
||||
get { return decodeMeta(self.jsonMetadata) }
|
||||
set { self.jsonMetadata.object = newValue }
|
||||
get { return self.jsonMetadata.object }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +199,7 @@ public struct Operation {
|
|||
public var active: Authority
|
||||
public var posting: Authority
|
||||
public var memoKey: PublicKey
|
||||
public var jsonMetadata: String
|
||||
public var jsonMetadata: JSONString
|
||||
|
||||
public init(
|
||||
fee: Asset,
|
||||
|
@ -202,7 +209,7 @@ public struct Operation {
|
|||
active: Authority,
|
||||
posting: Authority,
|
||||
memoKey: PublicKey,
|
||||
jsonMetadata: String = ""
|
||||
jsonMetadata: JSONString = ""
|
||||
) {
|
||||
self.fee = fee
|
||||
self.creator = creator
|
||||
|
@ -216,8 +223,8 @@ public struct Operation {
|
|||
|
||||
/// Account metadata.
|
||||
var metadata: [String: Any]? {
|
||||
set { self.jsonMetadata = encodeMeta(newValue) }
|
||||
get { return decodeMeta(self.jsonMetadata) }
|
||||
set { self.jsonMetadata.object = newValue }
|
||||
get { return self.jsonMetadata.object }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,24 +361,19 @@ public struct Operation {
|
|||
public var requiredAuths: [String]
|
||||
public var requiredPostingAuths: [String]
|
||||
public var id: String
|
||||
public var json: String
|
||||
public var json: JSONString
|
||||
|
||||
public init(
|
||||
requiredAuths: [String],
|
||||
requiredPostingAuths: [String],
|
||||
id: String,
|
||||
json: String
|
||||
json: JSONString
|
||||
) {
|
||||
self.requiredAuths = requiredAuths
|
||||
self.requiredPostingAuths = requiredPostingAuths
|
||||
self.id = id
|
||||
self.json = json
|
||||
}
|
||||
|
||||
var jsonObject: [String: Any]? {
|
||||
set { self.json = encodeMeta(newValue) }
|
||||
get { return decodeMeta(self.json) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets comment options.
|
||||
|
@ -555,7 +557,7 @@ public struct Operation {
|
|||
public var fee: Asset
|
||||
public var ratificationDeadline: Date
|
||||
public var escrowExpiration: Date
|
||||
public var jsonMeta: String
|
||||
public var jsonMeta: JSONString
|
||||
|
||||
public init(
|
||||
from: String,
|
||||
|
@ -567,7 +569,7 @@ public struct Operation {
|
|||
fee: Asset,
|
||||
ratificationDeadline: Date,
|
||||
escrowExpiration: Date,
|
||||
jsonMeta: String = ""
|
||||
jsonMeta: JSONString = ""
|
||||
) {
|
||||
self.from = from
|
||||
self.to = to
|
||||
|
@ -583,8 +585,8 @@ public struct Operation {
|
|||
|
||||
/// Metadata.
|
||||
var metadata: [String: Any]? {
|
||||
set { self.jsonMeta = encodeMeta(newValue) }
|
||||
get { return decodeMeta(self.jsonMeta) }
|
||||
set { self.jsonMeta.object = newValue }
|
||||
get { return self.jsonMeta.object }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -837,7 +839,7 @@ public struct Operation {
|
|||
public var active: Authority
|
||||
public var posting: Authority
|
||||
public var memoKey: PublicKey
|
||||
public var jsonMetadata: String
|
||||
public var jsonMetadata: JSONString
|
||||
public var extensions: [FutureExtensions]
|
||||
|
||||
public init(
|
||||
|
@ -849,7 +851,7 @@ public struct Operation {
|
|||
active: Authority,
|
||||
posting: Authority,
|
||||
memoKey: PublicKey,
|
||||
jsonMetadata: String = "",
|
||||
jsonMetadata: JSONString = "",
|
||||
extensions: [FutureExtensions] = []
|
||||
) {
|
||||
self.fee = fee
|
||||
|
@ -866,11 +868,120 @@ public struct Operation {
|
|||
|
||||
/// Account metadata.
|
||||
var metadata: [String: Any]? {
|
||||
set { self.jsonMetadata = encodeMeta(newValue) }
|
||||
get { return decodeMeta(self.jsonMetadata) }
|
||||
set { self.jsonMetadata.object = newValue }
|
||||
get { return self.jsonMetadata.object }
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual operations.
|
||||
|
||||
public struct AuthorReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let author: String
|
||||
public let permlink: String
|
||||
public let sbdPayout: Asset
|
||||
public let steemPayout: Asset
|
||||
public let vestingPayout: Asset
|
||||
}
|
||||
|
||||
public struct CurationReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let curator: String
|
||||
public let reward: Asset
|
||||
public let commentAuthor: String
|
||||
public let commentPermlink: String
|
||||
}
|
||||
|
||||
public struct CommentReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let author: String
|
||||
public let permlink: String
|
||||
public let payout: Asset
|
||||
}
|
||||
|
||||
public struct LiquidityReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let owner: String
|
||||
public let payout: Asset
|
||||
}
|
||||
|
||||
public struct Interest: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let owner: String
|
||||
public let interest: Asset
|
||||
}
|
||||
|
||||
public struct FillConvertRequest: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let owner: String
|
||||
public let requestid: UInt32
|
||||
public let amountIn: Asset
|
||||
public let amountOut: Asset
|
||||
}
|
||||
|
||||
public struct FillVestingWithdraw: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let fromAccount: String
|
||||
public let toAccount: String
|
||||
public let withdrawn: Asset
|
||||
public let deposited: Asset
|
||||
}
|
||||
|
||||
public struct ShutdownWitness: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let owner: String
|
||||
}
|
||||
|
||||
public struct FillOrder: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let currentOwner: String
|
||||
public let currentOrderid: UInt32
|
||||
public let currentPays: Asset
|
||||
public let openOwner: String
|
||||
public let openOrderid: UInt32
|
||||
public let openPays: Asset
|
||||
}
|
||||
|
||||
public struct FillTransferFromSavings: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let from: String
|
||||
public let to: String
|
||||
public let amount: Asset
|
||||
public let requestId: UInt32
|
||||
public let memo: String
|
||||
}
|
||||
|
||||
public struct Hardfork: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let hardforkId: UInt32
|
||||
}
|
||||
|
||||
public struct CommentPayoutUpdate: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let author: String
|
||||
public let permlink: String
|
||||
}
|
||||
|
||||
public struct ReturnVestingDelegation: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let account: String
|
||||
public let vestingShares: Asset
|
||||
}
|
||||
|
||||
public struct CommentBenefactorReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let benefactor: String
|
||||
public let author: String
|
||||
public let permlink: String
|
||||
public let reward: Asset
|
||||
}
|
||||
|
||||
public struct ProducerReward: OperationType, Equatable {
|
||||
public var isVirtual: Bool { return true }
|
||||
public let producer: String
|
||||
public let vestingShares: Asset
|
||||
}
|
||||
|
||||
/// Unknown operation, seen if the decoder encounters operation which has no type defined.
|
||||
/// - Note: Not encodable, the encoder will throw if encountering this operation.
|
||||
public struct Unknown: OperationType, Equatable {}
|
||||
|
@ -922,6 +1033,22 @@ fileprivate enum OperationId: UInt8, SteemEncodable, Decodable {
|
|||
case claim_reward_balance = 39
|
||||
case delegate_vesting_shares = 40
|
||||
case account_create_with_delegation = 41
|
||||
// Virtual operations
|
||||
case fill_convert_request
|
||||
case author_reward
|
||||
case curation_reward
|
||||
case comment_reward
|
||||
case liquidity_reward
|
||||
case interest
|
||||
case fill_vesting_withdraw
|
||||
case fill_order
|
||||
case shutdown_witness
|
||||
case fill_transfer_from_savings
|
||||
case hardfork
|
||||
case comment_payout_update
|
||||
case return_vesting_delegation
|
||||
case comment_benefactor_reward
|
||||
case producer_reward
|
||||
case unknown = 255
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
|
@ -970,6 +1097,21 @@ fileprivate enum OperationId: UInt8, SteemEncodable, Decodable {
|
|||
case "claim_reward_balance": self = .claim_reward_balance
|
||||
case "delegate_vesting_shares": self = .delegate_vesting_shares
|
||||
case "account_create_with_delegation": self = .account_create_with_delegation
|
||||
case "fill_convert_request": self = .fill_convert_request
|
||||
case "author_reward": self = .author_reward
|
||||
case "curation_reward": self = .curation_reward
|
||||
case "comment_reward": self = .comment_reward
|
||||
case "liquidity_reward": self = .liquidity_reward
|
||||
case "interest": self = .interest
|
||||
case "fill_vesting_withdraw": self = .fill_vesting_withdraw
|
||||
case "fill_order": self = .fill_order
|
||||
case "shutdown_witness": self = .shutdown_witness
|
||||
case "fill_transfer_from_savings": self = .fill_transfer_from_savings
|
||||
case "hardfork": self = .hardfork
|
||||
case "comment_payout_update": self = .comment_payout_update
|
||||
case "return_vesting_delegation": self = .return_vesting_delegation
|
||||
case "comment_benefactor_reward": self = .comment_benefactor_reward
|
||||
case "producer_reward": self = .producer_reward
|
||||
default: self = .unknown
|
||||
}
|
||||
}
|
||||
|
@ -1044,6 +1186,21 @@ internal struct AnyOperation: SteemEncodable, Decodable {
|
|||
case .claim_reward_balance: op = try container.decode(Operation.ClaimRewardBalance.self)
|
||||
case .delegate_vesting_shares: op = try container.decode(Operation.DelegateVestingShares.self)
|
||||
case .account_create_with_delegation: op = try container.decode(Operation.AccountCreateWithDelegation.self)
|
||||
case .fill_convert_request: op = try container.decode(Operation.FillConvertRequest.self)
|
||||
case .author_reward: op = try container.decode(Operation.AuthorReward.self)
|
||||
case .curation_reward: op = try container.decode(Operation.CurationReward.self)
|
||||
case .comment_reward: op = try container.decode(Operation.CommentReward.self)
|
||||
case .liquidity_reward: op = try container.decode(Operation.LiquidityReward.self)
|
||||
case .interest: op = try container.decode(Operation.Interest.self)
|
||||
case .fill_vesting_withdraw: op = try container.decode(Operation.FillVestingWithdraw.self)
|
||||
case .fill_order: op = try container.decode(Operation.FillOrder.self)
|
||||
case .shutdown_witness: op = try container.decode(Operation.ShutdownWitness.self)
|
||||
case .fill_transfer_from_savings: op = try container.decode(Operation.FillTransferFromSavings.self)
|
||||
case .hardfork: op = try container.decode(Operation.Hardfork.self)
|
||||
case .comment_payout_update: op = try container.decode(Operation.CommentPayoutUpdate.self)
|
||||
case .return_vesting_delegation: op = try container.decode(Operation.ReturnVestingDelegation.self)
|
||||
case .comment_benefactor_reward: op = try container.decode(Operation.CommentBenefactorReward.self)
|
||||
case .producer_reward: op = try container.decode(Operation.ProducerReward.self)
|
||||
case .unknown: op = Operation.Unknown()
|
||||
}
|
||||
self.operation = op
|
||||
|
@ -1180,7 +1337,8 @@ internal struct AnyOperation: SteemEncodable, Decodable {
|
|||
try container.encode(op)
|
||||
default:
|
||||
throw EncodingError.invalidValue(self.operation, EncodingError.Context(
|
||||
codingPath: container.codingPath, debugDescription: "Encountered unknown operation type"))
|
||||
codingPath: container.codingPath, debugDescription: "Encountered unknown operation type"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1213,20 +1371,3 @@ extension Operation.CommentOptions.Extension {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func encodeMeta(_ value: [String: Any]?) -> String {
|
||||
if let object = value,
|
||||
let encoded = try? JSONSerialization.data(withJSONObject: object, options: []) {
|
||||
return String(bytes: encoded, encoding: .utf8) ?? ""
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func decodeMeta(_ value: String) -> [String: Any]? {
|
||||
guard let data = value.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
let decoded = try? JSONSerialization.jsonObject(with: data, options: [])
|
||||
return decoded as? [String: Any]
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ internal class Secp256k1Context {
|
|||
return key.withUnsafeBytes { (keyPtr: UnsafePointer<UInt8>) -> Bool in
|
||||
if let ndata = ndata {
|
||||
return ndata.withUnsafeBytes { (ndataPtr: UnsafePointer<UInt8>) -> Bool in
|
||||
return secp256k1_ecdsa_sign_recoverable(self.ctx, sig, msgPtr, keyPtr, nil, ndataPtr) == 1
|
||||
secp256k1_ecdsa_sign_recoverable(self.ctx, sig, msgPtr, keyPtr, nil, ndataPtr) == 1
|
||||
}
|
||||
} else {
|
||||
return secp256k1_ecdsa_sign_recoverable(self.ctx, sig, msgPtr, keyPtr, nil, nil) == 1
|
||||
|
|
|
@ -9,8 +9,8 @@ struct HelloRequest: Request {
|
|||
|
||||
let client = Steem.Client(address: URL(string: "https://api.steemit.com")!)
|
||||
|
||||
let testnetClient = Steem.Client(address: URL(string: "https://testnet.steem.vc")!)
|
||||
let testnetId = ChainId.custom(Data(hexEncoded: "79276aea5d4877d9a25892eaa01b0adf019d3e5cb12a97478df3298ccdd01673"))
|
||||
let testnetClient = Steem.Client(address: URL(string: "https://testnet.steemitdev.com")!)
|
||||
let testnetId = ChainId.custom(Data(hexEncoded: "46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32"))
|
||||
|
||||
class ClientTest: XCTestCase {
|
||||
func testNani() {
|
||||
|
@ -47,6 +47,56 @@ class ClientTest: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testFeedHistory() {
|
||||
let test = expectation(description: "Response")
|
||||
let req = API.GetFeedHistory()
|
||||
client.send(req) { res, error in
|
||||
XCTAssertNil(error)
|
||||
XCTAssertNotNil(res)
|
||||
XCTAssertEqual(res?.currentMedianHistory.quote.resolvedAmount, 1.000)
|
||||
XCTAssertEqual(res?.currentMedianHistory.quote.symbol, .steem)
|
||||
XCTAssertEqual(res?.priceHistory.first?.quote.resolvedAmount, 1.000)
|
||||
XCTAssertEqual(res?.priceHistory.first?.quote.symbol, .steem)
|
||||
test.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 5) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetOrderBook() {
|
||||
let test = expectation(description: "Response")
|
||||
let req = API.GetOrderBook(count: 1)
|
||||
client.send(req) { res, error in
|
||||
XCTAssertNil(error)
|
||||
XCTAssertNotNil(res)
|
||||
test.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 5) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetPrices() {
|
||||
let test = expectation(description: "Response")
|
||||
let req = API.GetPrices()
|
||||
client.send(req) { res, error in
|
||||
XCTAssertNil(error)
|
||||
XCTAssertNotNil(res)
|
||||
test.fulfill()
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 5) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetBlock() {
|
||||
let test = expectation(description: "Response")
|
||||
let req = API.GetBlock(blockNum: 12_345_678)
|
||||
|
@ -65,15 +115,15 @@ class ClientTest: XCTestCase {
|
|||
|
||||
func testBroadcast() {
|
||||
let test = expectation(description: "Response")
|
||||
let key = PrivateKey("5JQzF7rejVDDFYqCtm4ypcNHhP8Zru6hY1bpALxjNWKtm2yBqC9")!
|
||||
let key = PrivateKey("5KS8eoAGLrCg2w3ytqSQXsmHuDTdvb2NLjJLpxgaiVJDXaGpcGT")!
|
||||
var comment = Operation.Comment(
|
||||
title: "Hello from Swift",
|
||||
body: "The time is \(Date()) and I'm running tests.",
|
||||
author: "swift",
|
||||
author: "test19",
|
||||
permlink: "hey-eveyone-im-running-swift-tests-and-the-time-is-\(UInt32(Date().timeIntervalSinceReferenceDate))"
|
||||
)
|
||||
comment.parentPermlink = "test"
|
||||
let vote = Operation.Vote(voter: "swift", author: "swift", permlink: comment.permlink)
|
||||
let vote = Operation.Vote(voter: "test19", author: "test19", permlink: comment.permlink)
|
||||
testnetClient.send(API.GetDynamicGlobalProperties()) { props, error in
|
||||
XCTAssertNil(error)
|
||||
guard let props = props else {
|
||||
|
@ -84,7 +134,52 @@ class ClientTest: XCTestCase {
|
|||
refBlockNum: UInt16(props.headBlockNumber & 0xFFFF),
|
||||
refBlockPrefix: props.headBlockId.prefix,
|
||||
expiration: expiry,
|
||||
operations: [comment, vote])
|
||||
operations: [comment, vote]
|
||||
)
|
||||
|
||||
guard let stx = try? tx.sign(usingKey: key, forChain: testnetId) else {
|
||||
return XCTFail("Unable to sign tx")
|
||||
}
|
||||
testnetClient.send(API.BroadcastTransaction(transaction: stx)) { res, error in
|
||||
XCTAssertNil(error)
|
||||
if let res = res {
|
||||
XCTAssertFalse(res.expired)
|
||||
XCTAssert(res.blockNum > props.headBlockId.num)
|
||||
} else {
|
||||
XCTFail("No response")
|
||||
}
|
||||
test.fulfill()
|
||||
}
|
||||
}
|
||||
waitForExpectations(timeout: 10) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTransferBroadcast() {
|
||||
let test = expectation(description: "Response")
|
||||
let key = PrivateKey("5KS8eoAGLrCg2w3ytqSQXsmHuDTdvb2NLjJLpxgaiVJDXaGpcGT")!
|
||||
|
||||
|
||||
|
||||
let transfer = Operation.Transfer.init(from: "test19", to: "maitland", amount: Asset(1, .custom(name: "TESTS", precision: 3)), memo: "Gulliver's travels.")
|
||||
|
||||
testnetClient.send(API.GetDynamicGlobalProperties()) { props, error in
|
||||
XCTAssertNil(error)
|
||||
guard let props = props else {
|
||||
return XCTFail("Unable to get props")
|
||||
}
|
||||
let expiry = props.time.addingTimeInterval(60)
|
||||
|
||||
let tx = Transaction(
|
||||
refBlockNum: UInt16(props.headBlockNumber & 0xFFFF),
|
||||
refBlockPrefix: props.headBlockId.prefix,
|
||||
expiration: expiry,
|
||||
operations: [transfer]
|
||||
)
|
||||
|
||||
guard let stx = try? tx.sign(usingKey: key, forChain: testnetId) else {
|
||||
return XCTFail("Unable to sign tx")
|
||||
}
|
||||
|
@ -116,4 +211,50 @@ class ClientTest: XCTestCase {
|
|||
XCTAssertEqual(account.name, "almost-digital")
|
||||
XCTAssertEqual(account.created, Date(timeIntervalSince1970: 1_496_691_060))
|
||||
}
|
||||
|
||||
func testTestnetGetAccount() throws {
|
||||
let result = try testnetClient.sendSynchronous(API.GetAccounts(names: ["almost-digital"]))
|
||||
guard let account = result?.first else {
|
||||
XCTFail("No account returned")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(account.id, 40413)
|
||||
XCTAssertEqual(account.name, "almost-digital")
|
||||
}
|
||||
|
||||
func testGetAccountHistory() throws {
|
||||
let req = API.GetAccountHistory(account: "almost-digital", from: 1, limit: 1)
|
||||
let result = try client.sendSynchronous(req)
|
||||
guard let r = result?.first else {
|
||||
XCTFail("No results returned")
|
||||
return
|
||||
}
|
||||
let createOp = r.value.operation as? Steem.Operation.AccountCreateWithDelegation
|
||||
XCTAssertEqual(createOp?.newAccountName, "almost-digital")
|
||||
}
|
||||
|
||||
func testTestnetGetAccountHistory() throws {
|
||||
let req = API.GetAccountHistory(account: "almost-digital", from: 1, limit: 1)
|
||||
let result = try testnetClient.sendSynchronous(req)
|
||||
guard let r = result?.first else {
|
||||
XCTFail("No results returned")
|
||||
return
|
||||
}
|
||||
let createOp = r.value.operation as? Steem.Operation.AccountCreate
|
||||
XCTAssertEqual(createOp?.newAccountName, "almost-digital")
|
||||
}
|
||||
|
||||
func testGetAccountHistoryVirtual() throws {
|
||||
let req = API.GetAccountHistory(account: "almost-digital", from: 476, limit: 0)
|
||||
let result = try client.sendSynchronous(req)
|
||||
guard let r = result?.first else {
|
||||
XCTFail("No results returned")
|
||||
return
|
||||
}
|
||||
let op = r.value.operation as? Steem.Operation.AuthorReward
|
||||
XCTAssertEqual(op?.isVirtual, true)
|
||||
XCTAssertEqual(op?.author, "almost-digital")
|
||||
XCTAssertEqual(op?.vestingPayout, Asset(113.868270, .vests))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,36 @@ class AssetTest: XCTestCase {
|
|||
AssertEncodes(Asset(0.001, .sbd), "0.001 SBD")
|
||||
}
|
||||
|
||||
func testProperties() {
|
||||
let mockAsset = Asset(0.001, .sbd)
|
||||
XCTAssertEqual(mockAsset.description, "0.001 SBD")
|
||||
XCTAssertEqual(mockAsset.amount, 1)
|
||||
XCTAssertEqual(mockAsset.symbol, Asset.Symbol.sbd)
|
||||
XCTAssertEqual(mockAsset.resolvedAmount, 0.001)
|
||||
}
|
||||
|
||||
func testEquateable() {
|
||||
let mockAsset = Asset(0.1, .sbd)
|
||||
let mockAsset2 = Asset(0.1, .steem)
|
||||
let mockAsset3 = Asset(0.1, .sbd)
|
||||
let mockAsset4 = Asset(0.2, .sbd)
|
||||
XCTAssertFalse(mockAsset == mockAsset2)
|
||||
XCTAssertTrue(mockAsset == mockAsset3)
|
||||
XCTAssertFalse(mockAsset == mockAsset4)
|
||||
}
|
||||
|
||||
func testPrice() throws {
|
||||
let mockPrice: Price = Price(base: Asset(0.842, .sbd), quote: Asset(1.000, .steem))
|
||||
let inputAsset: Asset = Asset(666, .steem)
|
||||
let actualConversion: Asset = try mockPrice.convert(asset: inputAsset)
|
||||
let reverseConversion: Asset = try mockPrice.convert(asset: actualConversion)
|
||||
let expectedConversion: Asset = Asset(560.772, .sbd)
|
||||
XCTAssertEqual(expectedConversion, actualConversion)
|
||||
XCTAssertEqual(inputAsset, reverseConversion)
|
||||
let invalidInputAsset: Asset = Asset(666, .custom(name: "magicBeans", precision: UInt8(3)))
|
||||
XCTAssertThrowsError(try mockPrice.convert(asset: invalidInputAsset))
|
||||
}
|
||||
|
||||
func testDecodable() throws {
|
||||
AssertDecodes(string: "10.000 STEEM", Asset(10, .steem))
|
||||
AssertDecodes(string: "0.001 SBD", Asset(0.001, .sbd))
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// ChainIdTests.swift
|
||||
// SteemTests
|
||||
//
|
||||
// Created by im on 10/12/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import Steem
|
||||
|
||||
class ChainIdTest: XCTestCase {
|
||||
func testEncodeCustomChainId() {
|
||||
let mockChainId = ChainId(string: "11223344")
|
||||
XCTAssertEqual(mockChainId, ChainId.custom(Data(hexEncoded: "11223344")))
|
||||
}
|
||||
func testTestnetId() {
|
||||
let mockChainId = ChainId.testNet.data
|
||||
XCTAssertEqual(mockChainId, Data(hexEncoded: "46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32"))
|
||||
}
|
||||
func testMainnetId() {
|
||||
let mockChainId = ChainId.mainNet.data
|
||||
XCTAssertEqual(mockChainId, Data(hexEncoded: "0000000000000000000000000000000000000000000000000000000000000000"))
|
||||
}
|
||||
}
|
|
@ -47,5 +47,13 @@ class OperationTest: XCTestCase {
|
|||
AssertDecodes(json: transfer.1, transfer.0)
|
||||
AssertDecodes(json: commentOptions.1, commentOptions.0)
|
||||
AssertDecodes(json: account_create.1, account_create.0)
|
||||
XCTAssert(vote.0.isVirtual == false)
|
||||
}
|
||||
|
||||
func testVirtual() {
|
||||
let opJson = "{\"curator\":\"foo\",\"reward\":\"0.010366 VESTS\",\"comment_author\":\"foo\",\"comment_permlink\":\"foo\"}"
|
||||
let op = Operation.CurationReward(curator: "foo", reward: Asset(0.010366, .vests), commentAuthor: "foo", commentPermlink: "foo")
|
||||
AssertDecodes(json: opJson, op)
|
||||
XCTAssert(op.isVirtual)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,32 @@ class TransactionTest: XCTestCase {
|
|||
override class func tearDown() {
|
||||
PrivateKey.determenisticSignatures = false
|
||||
}
|
||||
|
||||
func testInitWithOp() throws {
|
||||
let transferOp = Steem.Operation.Transfer(from: "foo", to: "bar", amount: Asset(10, .steem), memo: "baz")
|
||||
let expiration = Date(timeIntervalSince1970: 0)
|
||||
var initTx = Steem.Transaction.init(refBlockNum: 12345, refBlockPrefix: 1122334455, expiration: expiration, operations:[transferOp] )
|
||||
XCTAssertEqual(initTx.refBlockNum, 12345)
|
||||
XCTAssertEqual(initTx.refBlockPrefix, 1_122_334_455)
|
||||
XCTAssertEqual(initTx.expiration, Date(timeIntervalSince1970: 0))
|
||||
XCTAssertEqual(initTx.operations.count, 1)
|
||||
let transfer = initTx.operations.first as? Steem.Operation.Transfer
|
||||
XCTAssertEqual(transfer, Steem.Operation.Transfer(from: "foo", to: "bar", amount: Asset(10, .steem), memo: "baz"))
|
||||
}
|
||||
|
||||
func testAppend() throws {
|
||||
let transferOp = Steem.Operation.Transfer(from: "foo", to: "bar", amount: Asset(10, .steem), memo: "baz")
|
||||
let expiration = Date(timeIntervalSince1970: 0)
|
||||
var initTx = Steem.Transaction.init(refBlockNum: 12345, refBlockPrefix: 1122334455, expiration: expiration)
|
||||
XCTAssertEqual(initTx.refBlockNum, 12345)
|
||||
XCTAssertEqual(initTx.refBlockPrefix, 1_122_334_455)
|
||||
XCTAssertEqual(initTx.expiration, Date(timeIntervalSince1970: 0))
|
||||
XCTAssertEqual(initTx.operations.count, 0)
|
||||
initTx.append(operation: transferOp)
|
||||
XCTAssertEqual(initTx.operations.count, 1)
|
||||
let transfer = initTx.operations.first as? Steem.Operation.Transfer
|
||||
XCTAssertEqual(transfer, Steem.Operation.Transfer(from: "foo", to: "bar", amount: Asset(10, .steem), memo: "baz"))
|
||||
}
|
||||
|
||||
func testDecodable() throws {
|
||||
let tx = try TestDecode(Transaction.self, json: txJson)
|
||||
|
|
Loading…
Reference in New Issue