Merge pull request #2 from hiveuprss/more-api

More api
This commit is contained in:
Hive Trending 2020-07-11 21:52:11 -07:00 committed by GitHub
commit fbfd9ad6cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 639 additions and 59 deletions

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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()

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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))

View File

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

View File

@ -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)
}
}

View File

@ -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)