Skip to content

Commit

Permalink
- add staking operations to tzkt transaction fetching
Browse files Browse the repository at this point in the history
- add new constants and types
- update tests and stubs
  • Loading branch information
simonmcl committed Jun 18, 2024
1 parent b329721 commit cccb9a2
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Sources/KukaiCoreSwift/Clients/TzKTClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ public class TzKTClient {
// Main transactions
var urlMain = config.tzktURL
urlMain.appendPathComponent("v1/accounts/\(address)/operations")
urlMain.appendQueryItem(name: "type", value: "delegation,origination,transaction")
urlMain.appendQueryItem(name: "type", value: "delegation,origination,transaction,staking")
urlMain.appendQueryItem(name: "micheline", value: 1)
urlMain.appendQueryItem(name: "limit", value: limit)

Expand Down
25 changes: 20 additions & 5 deletions Sources/KukaiCoreSwift/Models/BakingBad/TzKTTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
case delegation
case origination
case transaction
case staking
case reveal
case batch
case unknown
Expand All @@ -42,6 +43,9 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
case reveal
case exchange
case contractCall
case stake
case unstake
case finaliseUnstake
case batch
case unknown
}
Expand Down Expand Up @@ -74,6 +78,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
public let hasInternals: Bool
public let tokenTransfersCount: Decimal?
public let errors: [TransactionError]?
public let kind: String?

public let date: Date?
public var tzktTokenTransfer: TzKTTokenTransfer? = nil {
Expand Down Expand Up @@ -110,11 +115,11 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
// MARK: - Codable Protocol

public enum CodingKeys: String, CodingKey {
case type, id, level, timestamp, hash, counter, initiater, sender, bakerFee, storageFee, allocationFee, target, prevDelegate, newDelegate, amount, parameter, status, subType, entrypointCalled, primaryToken, hasInternals, tokenTransfersCount, errors
case type, id, level, timestamp, hash, counter, initiater, sender, bakerFee, storageFee, allocationFee, target, prevDelegate, newDelegate, amount, parameter, status, subType, entrypointCalled, primaryToken, hasInternals, tokenTransfersCount, errors, kind
}

/// Manually init a `TzKTTransaction`
public init(type: TransactionType, id: Decimal, level: Decimal, timestamp: String, hash: String, counter: Decimal, initiater: TzKTAddress?, sender: TzKTAddress, bakerFee: XTZAmount, storageFee: XTZAmount, allocationFee: XTZAmount, target: TzKTAddress?, prevDelegate: TzKTAddress?, newDelegate: TzKTAddress?, amount: TokenAmount, parameter: [String: String]?, status: TransactionStatus, hasInternals: Bool, tokenTransfersCount: Decimal?, errors: [TransactionError]?) {
public init(type: TransactionType, id: Decimal, level: Decimal, timestamp: String, hash: String, counter: Decimal, initiater: TzKTAddress?, sender: TzKTAddress, bakerFee: XTZAmount, storageFee: XTZAmount, allocationFee: XTZAmount, target: TzKTAddress?, prevDelegate: TzKTAddress?, newDelegate: TzKTAddress?, amount: TokenAmount, parameter: [String: String]?, status: TransactionStatus, hasInternals: Bool, tokenTransfersCount: Decimal?, errors: [TransactionError]?, kind: String?) {

self.type = type
self.id = id
Expand All @@ -138,6 +143,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident

self.date = TzKTTransaction.dateFormatter.date(from: timestamp)
self.errors = errors
self.kind = kind
}

/// Convert a `TzKTTokenTransfer` into a `TzKTTransaction`
Expand Down Expand Up @@ -168,6 +174,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident

self.tzktTokenTransfer = from
self.errors = nil
self.kind = nil
}

public init(from decoder: Decoder) throws {
Expand Down Expand Up @@ -221,6 +228,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
}

self.errors = try? container.decodeIfPresent([TransactionError].self, forKey: .errors)
self.kind = try? container.decodeIfPresent(String.self, forKey: .kind)
}

public func encode(to encoder: Encoder) throws {
Expand Down Expand Up @@ -249,14 +257,15 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
try container.encodeIfPresent(subType, forKey: .subType)
try container.encodeIfPresent(entrypointCalled, forKey: .entrypointCalled)
try container.encodeIfPresent(primaryToken, forKey: .primaryToken)
try container.encodeIfPresent(kind, forKey: .kind)
}

/// Used for creating "Pending" transactions
public static func placeholder(withStatus status: TransactionStatus, id: Decimal, opHash: String, type: TransactionType, counter: Decimal, fromWallet: WalletMetadata, destination: TzKTAddress, xtzAmount: TokenAmount, parameters: [String: String]?, primaryToken: Token?) -> TzKTTransaction {
public static func placeholder(withStatus status: TransactionStatus, id: Decimal, opHash: String, type: TransactionType, counter: Decimal, fromWallet: WalletMetadata, destination: TzKTAddress, xtzAmount: TokenAmount, parameters: [String: String]?, primaryToken: Token?, kind: String?) -> TzKTTransaction {
let timestamp = TzKTTransaction.dateFormatter.string(from: Date())
let sender = TzKTAddress(alias: fromWallet.walletNickname ?? fromWallet.socialUsername ?? fromWallet.address, address: fromWallet.address)

var transaction = TzKTTransaction(type: .transaction, id: id, level: id, timestamp: timestamp, hash: opHash, counter: counter, initiater: nil, sender: sender, bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: destination, prevDelegate: nil, newDelegate: nil, amount: xtzAmount, parameter: parameters, status: status, hasInternals: false, tokenTransfersCount: nil, errors: nil)
var transaction = TzKTTransaction(type: .transaction, id: id, level: id, timestamp: timestamp, hash: opHash, counter: counter, initiater: nil, sender: sender, bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: destination, prevDelegate: nil, newDelegate: nil, amount: xtzAmount, parameter: parameters, status: status, hasInternals: false, tokenTransfersCount: nil, errors: nil, kind: kind)
transaction.processAdditionalData(withCurrentWalletAddress: fromWallet.address)

if let pToken = primaryToken {
Expand All @@ -271,7 +280,7 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
let timestamp = TzKTTransaction.dateFormatter.string(from: Date())
let sender = TzKTAddress(alias: fromWallet.walletNickname ?? fromWallet.socialUsername ?? fromWallet.address, address: fromWallet.address)

var transaction = TzKTTransaction(type: .delegation, id: id, level: id, timestamp: timestamp, hash: opHash, counter: counter, initiater: nil, sender: sender, bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: nil, prevDelegate: nil, newDelegate: newDelegate, amount: .zero(), parameter: nil, status: status, hasInternals: false, tokenTransfersCount: nil, errors: nil)
var transaction = TzKTTransaction(type: .delegation, id: id, level: id, timestamp: timestamp, hash: opHash, counter: counter, initiater: nil, sender: sender, bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: nil, prevDelegate: nil, newDelegate: newDelegate, amount: .zero(), parameter: nil, status: status, hasInternals: false, tokenTransfersCount: nil, errors: nil, kind: nil)
transaction.processAdditionalData(withCurrentWalletAddress: fromWallet.address)

return transaction
Expand Down Expand Up @@ -327,6 +336,12 @@ public struct TzKTTransaction: Codable, CustomStringConvertible, Hashable, Ident
self.subType = .contractCall
self.entrypointCalled = entrypoint

} else if self.kind == "stake" {
self.subType = .stake

} else if self.kind == "unstake" {
self.subType = .unstake

} else {
if self.type == .delegation {
self.subType = .delegate
Expand Down
16 changes: 15 additions & 1 deletion Tests/KukaiCoreSwiftTests/Clients/TzKTClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class TzKTClientTests: XCTestCase {
MockConstants.shared.tzktClient.fetchTransactions(forAddress: MockConstants.defaultHdWallet.address) { transactions in
let groups = MockConstants.shared.tzktClient.groupTransactions(transactions: transactions, currentWalletAddress: MockConstants.defaultHdWallet.address)

XCTAssert(groups.count == 19, "\(groups.count)")
XCTAssert(groups.count == 21, "\(groups.count)")

for (index, group) in groups.enumerated() {

Expand Down Expand Up @@ -228,6 +228,20 @@ class TzKTClientTests: XCTestCase {
XCTAssert(group.hash == "opC815T6zqTUtzQktPBBeLAB1eRnvuR5ETZDoLPGgAb3698wwFK", group.hash)
XCTAssert(group.status == .failed, group.status.rawValue)

case 19:
XCTAssert(group.groupType == .unstake, group.groupType.rawValue)
XCTAssert(group.transactions.count == 1, group.transactions.count.description)
XCTAssert(group.transactions.first?.amount.description == "1", group.transactions.first?.amount.description ?? "-")
XCTAssert(group.hash == "ooyVR1r5vt3K4JGoVnH2XLQjwAVpoZaAkfdG1PssCPPovi7m1FL", group.hash)
XCTAssert(group.status == .applied, group.status.rawValue)

case 20:
XCTAssert(group.groupType == .stake, group.groupType.rawValue)
XCTAssert(group.transactions.count == 1, group.transactions.count.description)
XCTAssert(group.transactions.first?.amount.description == "10", group.transactions.first?.amount.description ?? "-")
XCTAssert(group.hash == "opPGcuZ459ZGR11RXaL2rRDtKnHFC9o5JQdyBHj3Qua4BMBkAsi", group.hash)
XCTAssert(group.status == .applied, group.status.rawValue)

default:
XCTFail("Missing test for transaction")
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/KukaiCoreSwiftTests/MockConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public struct MockConstants {
bcdTokenBalanceURL.appendQueryItem(name: "hide_empty", value: "true")

var tzktHistoryMainURL = tzktURL.appendingPathComponent("v1/accounts/tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss/operations")
tzktHistoryMainURL.appendQueryItem(name: "type", value: "delegation,origination,transaction")
tzktHistoryMainURL.appendQueryItem(name: "type", value: "delegation,origination,transaction,staking")
tzktHistoryMainURL.appendQueryItem(name: "micheline", value: "1")
tzktHistoryMainURL.appendQueryItem(name: "limit", value: "50")

Expand Down
12 changes: 6 additions & 6 deletions Tests/KukaiCoreSwiftTests/Models/TzKTTransactionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class TzKTTransactionTests: XCTestCase {
"value": "{\"to\": \"tz1eDdsp1d27kortm5LuoRqD6aKrdxmTiTSQ\", \"from\": \"tz1Z8E2gMHLXFcGGzW68B5e1Vk49FpHQd7xc\", \"value\": \"500000000000000000\"}"
]

var transaction = TzKTTransaction(type: .transaction, id: 123, level: 123, timestamp: "", hash: "", counter: 123, initiater: TzKTAddress(alias: nil, address: "tz1abc"), sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "KT1GG8Zd5rUp1XV8nMPRBY2tSyVn6NR5F4Q1"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: transferTokenParameters, status: .applied, hasInternals: false, tokenTransfersCount: 0, errors: nil)
var transaction = TzKTTransaction(type: .transaction, id: 123, level: 123, timestamp: "", hash: "", counter: 123, initiater: TzKTAddress(alias: nil, address: "tz1abc"), sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "KT1GG8Zd5rUp1XV8nMPRBY2tSyVn6NR5F4Q1"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: transferTokenParameters, status: .applied, hasInternals: false, tokenTransfersCount: 0, errors: nil, kind: nil)
transaction.processAdditionalData(withCurrentWalletAddress: "tz1abc")

let token = transaction.getFaTokenTransferData()
Expand All @@ -42,7 +42,7 @@ final class TzKTTransactionTests: XCTestCase {
"entrypoint": "transfer",
"value": "{\"to\": \"tz1eDdsp1d27kortm5LuoRqD6aKrdxmTiTSQ\", \"from\": \"tz1Z8E2gMHLXFcGGzW68B5e1Vk49FpHQd7xc\", \"value\": \"500000000000000000\"}"
]
let transaction1 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: dictParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil)
let transaction1 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: dictParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil, kind: nil)
let dict = transaction1.parameterValueAsDict()
let dictValue1 = dict?["to"] as? String
XCTAssert(dictValue1 == "tz1eDdsp1d27kortm5LuoRqD6aKrdxmTiTSQ", dictValue1 ?? "-")
Expand All @@ -53,7 +53,7 @@ final class TzKTTransactionTests: XCTestCase {
"entrypoint": "transfer",
"value": "[\"test\", 123, 14.7]"
]
let transaction2 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: arrayParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil)
let transaction2 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: arrayParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil, kind: nil)
let array = transaction2.parameterValueAsArray()
let arrayValue1 = array?[0] as? String
let arrayValue2 = array?[1] as? Int
Expand All @@ -67,7 +67,7 @@ final class TzKTTransactionTests: XCTestCase {
"entrypoint": "transfer",
"value": "[{\"key\": \"value\"}, {\"key\": 123}, {\"key\": 14.7}]"
]
let transaction3 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: arrayOfDictParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil)
let transaction3 = TzKTTransaction(type: .transaction, id: 1, level: 1, timestamp: "", hash: "", counter: 1, initiater: nil, sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "tz1abc"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: arrayOfDictParams, status: .confirmed, hasInternals: false, tokenTransfersCount: nil, errors: nil, kind: nil)
let arryOfDict = transaction3.parameterValueAsArrayOfDictionary()
let arryOfDictValue1 = (arryOfDict?[0] as? [String: String])?["key"] as? String
let arryOfDictValue2 = (arryOfDict?[1] as? [String: Int])?["key"] as? Int
Expand All @@ -81,7 +81,7 @@ final class TzKTTransactionTests: XCTestCase {
let source = WalletMetadata(address: "tz1abc", hdWalletGroupName: nil, type: .hd, children: [], isChild: false, isWatchOnly: false, bas58EncodedPublicKey: "", backedUp: true)
let placeholder1 = TzKTTransaction.placeholder(withStatus: .unconfirmed, id: 567, opHash: "abc123", type: .transaction, counter: 0, fromWallet: source, newDelegate: TzKTAddress(alias: "Baking Benjamins", address: "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD"))

let placeholder2 = TzKTTransaction.placeholder(withStatus: .unconfirmed, id: 456, opHash: "def456", type: .transaction, counter: 1, fromWallet: source, destination: TzKTAddress(alias: nil, address: "tz1def"), xtzAmount: .init(fromNormalisedAmount: 4.17, decimalPlaces: 6), parameters: nil, primaryToken: nil)
let placeholder2 = TzKTTransaction.placeholder(withStatus: .unconfirmed, id: 456, opHash: "def456", type: .transaction, counter: 1, fromWallet: source, destination: TzKTAddress(alias: nil, address: "tz1def"), xtzAmount: .init(fromNormalisedAmount: 4.17, decimalPlaces: 6), parameters: nil, primaryToken: nil, kind: nil)

XCTAssert(placeholder1.newDelegate?.address == "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD", placeholder1.newDelegate?.address ?? "-")
XCTAssert(placeholder2.amount.description == "4.17", placeholder2.amount.description)
Expand All @@ -93,7 +93,7 @@ final class TzKTTransactionTests: XCTestCase {
"value": "{\"to\": \"tz1eDdsp1d27kortm5LuoRqD6aKrdxmTiTSQ\", \"from\": \"tz1Z8E2gMHLXFcGGzW68B5e1Vk49FpHQd7xc\", \"value\": \"500000000000000000\"}"
]

var transaction = TzKTTransaction(type: .transaction, id: 123, level: 123, timestamp: "", hash: "", counter: 123, initiater: TzKTAddress(alias: nil, address: "tz1abc"), sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "KT1GG8Zd5rUp1XV8nMPRBY2tSyVn6NR5F4Q1"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: transferTokenParameters, status: .applied, hasInternals: false, tokenTransfersCount: 0, errors: nil)
var transaction = TzKTTransaction(type: .transaction, id: 123, level: 123, timestamp: "", hash: "", counter: 123, initiater: TzKTAddress(alias: nil, address: "tz1abc"), sender: TzKTAddress(alias: nil, address: "tz1abc"), bakerFee: .zero(), storageFee: .zero(), allocationFee: .zero(), target: TzKTAddress(alias: nil, address: "KT1GG8Zd5rUp1XV8nMPRBY2tSyVn6NR5F4Q1"), prevDelegate: nil, newDelegate: nil, amount: .zero(), parameter: transferTokenParameters, status: .applied, hasInternals: false, tokenTransfersCount: 0, errors: nil, kind: nil)
transaction.processAdditionalData(withCurrentWalletAddress: "tz1abc")

if let json = try? JSONEncoder().encode(transaction), let jsonString = String(data: json, encoding: .utf8) {
Expand Down
50 changes: 50 additions & 0 deletions Tests/KukaiCoreSwiftTests/Stubs/tzkt_transactions-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -991,5 +991,55 @@
}
],
"hasInternals": false
},
{
"type": "staking",
"id": 314516181090304,
"level": 1672990,
"timestamp": "2024-06-18T12:03:40Z",
"hash": "ooyVR1r5vt3K4JGoVnH2XLQjwAVpoZaAkfdG1PssCPPovi7m1FL",
"sender": {
"address": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss"
},
"counter": 392658,
"gasLimit": 4335,
"gasUsed": 4249,
"storageLimit": 0,
"bakerFee": 705,
"action": "unstake",
"requestedAmount": 1000000,
"amount": 1000000,
"baker": {
"alias": "Baking Benjamins",
"address": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD"
},
"stakingUpdatesCount": 1,
"status": "applied",
"kind": "unstake"
},
{
"type": "staking",
"id": 314486743367680,
"level": 1672989,
"timestamp": "2024-06-18T10:41:46Z",
"hash": "opPGcuZ459ZGR11RXaL2rRDtKnHFC9o5JQdyBHj3Qua4BMBkAsi",
"sender": {
"address": "tz1Ue76bLW7boAcJEZf2kSGcamdBKVi4Kpss"
},
"counter": 392657,
"gasLimit": 3907,
"gasUsed": 3830,
"storageLimit": 0,
"bakerFee": 663,
"action": "stake",
"requestedAmount": 10000000,
"amount": 10000000,
"baker": {
"alias": "Baking Benjamins",
"address": "tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD"
},
"stakingUpdatesCount": 2,
"status": "applied",
"kind": "stake"
}
]

0 comments on commit cccb9a2

Please sign in to comment.