Add support for caching transactions

pull/2/head
Michael Scoff 7 years ago committed by Michael Scoff
parent fe39124869
commit 3f7c6eca2f
  1. 30
      Trust.xcodeproj/project.pbxproj
  2. 103
      Trust/AppCoordinator.swift
  3. 2
      Trust/AppDelegate.swift
  4. 2
      Trust/EtherClient/EtherKeystore.swift
  5. 71
      Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift
  6. 28
      Trust/EtherClient/Requests/TransactionByHashRequest.swift
  7. 28
      Trust/EtherClient/Requests/TransactionReceiptRequest.swift
  8. 8
      Trust/Models/Address.swift
  9. 5
      Trust/Models/Balance.swift
  10. 21
      Trust/Models/Block.swift
  11. 49
      Trust/Models/ParsedTransaction.swift
  12. 4
      Trust/Tokens/Types/TokensDataStore.swift
  13. 220
      Trust/Transactions/Coordinators/TransactionCoordinator.swift
  14. 155
      Trust/Transactions/Coordinators/TransactionDataCoordinator.swift
  15. 3
      Trust/Transactions/Storage/Transaction.swift
  16. 14
      Trust/Transactions/ViewControllers/TransactionViewController.swift
  17. 22
      Trust/Transactions/ViewControllers/TransactionsViewController.swift
  18. 2
      Trust/Transactions/ViewModels/TransactionViewCellViewModel.swift
  19. 6
      Trust/Transactions/ViewModels/TransactionsViewModel.swift
  20. 17
      Trust/UI/Coordinator.swift
  21. 47
      TrustTests/Coordinators/AppCoordinatorTests.swift
  22. 40
      TrustTests/Coordinators/TransactionCoordinatorTests.swift
  23. 2
      TrustTests/EtherClient/EtherKeystoreTests.swift
  24. 10
      TrustTests/Factories/FakeNavigationController.swift
  25. 2
      TrustTests/Factories/Transaction.swift

@ -73,6 +73,7 @@
296AF9AB1F7380920058AF78 /* GetTransactionCountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */; };
2977CAE01F7DEEB0009682A0 /* FakeEtherKeystore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2977CADF1F7DEEB0009682A0 /* FakeEtherKeystore.swift */; };
297800521F71FDCF003185C1 /* FormAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297800511F71FDCF003185C1 /* FormAppearance.swift */; };
2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */; };
29850D251F6B27A800791A49 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29850D241F6B27A800791A49 /* R.generated.swift */; };
29850D2B1F6B30FF00791A49 /* TransactionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29850D2A1F6B30FF00791A49 /* TransactionViewController.swift */; };
2996F1431F6C96FF005C33AE /* ImportWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2996F1421F6C96FF005C33AE /* ImportWalletViewModel.swift */; };
@ -95,7 +96,7 @@
29BBB36B1F7BCEDD006BC91B /* GethBigInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BBB36A1F7BCEDD006BC91B /* GethBigInt.swift */; };
29BE3FCE1F706D8800F6BFC2 /* EthereumConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FCD1F706D8800F6BFC2 /* EthereumConverterTests.swift */; };
29BE3FD01F7071A200F6BFC2 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FCF1F7071A200F6BFC2 /* UIColor.swift */; };
29BE3FD21F707DC300F6BFC2 /* TransactionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */; };
29BE3FD21F707DC300F6BFC2 /* TransactionDataCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */; };
29C9F5F91F720BD30025C494 /* FloatLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */; };
29C9F5FB1F720C050025C494 /* FloatLabelTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */; };
29CA4B771F6FBBFB0032313D /* RequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CA4B761F6FBBFB0032313D /* RequestViewModel.swift */; };
@ -112,6 +113,10 @@
29E2E33E1F7A2423000CF94A /* TransactionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */; };
29E2E3411F7B1585000CF94A /* ActionButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E3401F7B1585000CF94A /* ActionButtonRow.swift */; };
29EB102A1F6CBD23000907A4 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EB10291F6CBD23000907A4 /* UIAlertController.swift */; };
29FC0CB11F81CED10036089F /* TransactionByHashRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */; };
29FC0CB31F81D2640036089F /* TransactionReceiptRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */; };
29FC0CB61F8298820036089F /* TransactionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB51F8298820036089F /* TransactionCoordinator.swift */; };
29FC0CB81F8299510036089F /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB71F8299510036089F /* Coordinator.swift */; };
29FF12F61F74799D00AFD326 /* NSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12F51F74799D00AFD326 /* NSAttributedString.swift */; };
29FF12F81F747D6C00AFD326 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12F71F747D6C00AFD326 /* Error.swift */; };
29FF12FB1F74CC8200AFD326 /* EthereumAddressRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12FA1F74CC8200AFD326 /* EthereumAddressRule.swift */; };
@ -214,6 +219,7 @@
296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTransactionCountRequest.swift; sourceTree = "<group>"; };
2977CADF1F7DEEB0009682A0 /* FakeEtherKeystore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeEtherKeystore.swift; sourceTree = "<group>"; };
297800511F71FDCF003185C1 /* FormAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormAppearance.swift; sourceTree = "<group>"; };
2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinatorTests.swift; sourceTree = "<group>"; };
29850D241F6B27A800791A49 /* R.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = "<group>"; };
29850D2A1F6B30FF00791A49 /* TransactionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewController.swift; sourceTree = "<group>"; };
2996F1421F6C96FF005C33AE /* ImportWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWalletViewModel.swift; sourceTree = "<group>"; };
@ -237,7 +243,7 @@
29BBB36A1F7BCEDD006BC91B /* GethBigInt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GethBigInt.swift; sourceTree = "<group>"; };
29BE3FCD1F706D8800F6BFC2 /* EthereumConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumConverterTests.swift; sourceTree = "<group>"; };
29BE3FCF1F7071A200F6BFC2 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinator.swift; sourceTree = "<group>"; };
29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDataCoordinator.swift; sourceTree = "<group>"; };
29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatLabelCell.swift; sourceTree = "<group>"; };
29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatLabelTextField.swift; sourceTree = "<group>"; };
29CA4B761F6FBBFB0032313D /* RequestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = "<group>"; };
@ -254,6 +260,10 @@
29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHeaderView.swift; sourceTree = "<group>"; };
29E2E3401F7B1585000CF94A /* ActionButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonRow.swift; sourceTree = "<group>"; };
29EB10291F6CBD23000907A4 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionByHashRequest.swift; sourceTree = "<group>"; };
29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionReceiptRequest.swift; sourceTree = "<group>"; };
29FC0CB51F8298820036089F /* TransactionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinator.swift; sourceTree = "<group>"; };
29FC0CB71F8299510036089F /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
29FF12F51F74799D00AFD326 /* NSAttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedString.swift; sourceTree = "<group>"; };
29FF12F71F747D6C00AFD326 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
29FF12FA1F74CC8200AFD326 /* EthereumAddressRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumAddressRule.swift; sourceTree = "<group>"; };
@ -389,11 +399,11 @@
2912CD291F6A831D00C6CBE3 /* Transactions */ = {
isa = PBXGroup;
children = (
2977CAE31F7E0B3F009682A0 /* Coordinators */,
29E14FCF1F7F456A00185568 /* Storage */,
2977CAED1F7E0BEC009682A0 /* Views */,
2977CAEC1F7E0BE2009682A0 /* ViewControllers */,
2977CAEB1F7E0BC3009682A0 /* ViewModels */,
2977CAE31F7E0B3F009682A0 /* Coordinators */,
29B6AECF1F7C881100EC6DE3 /* Types */,
);
path = Transactions;
@ -429,6 +439,7 @@
297800511F71FDCF003185C1 /* FormAppearance.swift */,
29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */,
29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */,
29FC0CB71F8299510036089F /* Coordinator.swift */,
);
path = UI;
sourceTree = "<group>";
@ -468,6 +479,8 @@
296AF9A81F737F6F0058AF78 /* SendRawTransactionRequest.swift */,
296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */,
290FD8BA1F7BFE7400548896 /* GetBlockByNumberRequest.swift */,
29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */,
29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */,
);
path = Requests;
sourceTree = "<group>";
@ -563,7 +576,8 @@
2977CAE31F7E0B3F009682A0 /* Coordinators */ = {
isa = PBXGroup;
children = (
29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */,
29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */,
29FC0CB51F8298820036089F /* TransactionCoordinator.swift */,
);
path = Coordinators;
sourceTree = "<group>";
@ -856,6 +870,7 @@
children = (
29FF13071F75F0AE00AFD326 /* AppCoordinatorTests.swift */,
296106CB1F776FD00006164B /* WalletCoordinatorTests.swift */,
2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */,
);
path = Coordinators;
sourceTree = "<group>";
@ -1236,6 +1251,7 @@
291F52B11F6B814300B369AB /* MG Basic Math.swift in Sources */,
29E2E33A1F7A008C000CF94A /* UIView.swift in Sources */,
291F52B21F6B814300B369AB /* MG Benchmark Tools.swift in Sources */,
29FC0CB11F81CED10036089F /* TransactionByHashRequest.swift in Sources */,
291ED08F1F6F613200E7E93A /* GetTransactionRequest.swift in Sources */,
295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */,
296421971F70C1F200EB363B /* ErrorView.swift in Sources */,
@ -1250,10 +1266,11 @@
296106C81F7646590006164B /* TokensViewModel.swift in Sources */,
29E2E3411F7B1585000CF94A /* ActionButtonRow.swift in Sources */,
291F52B41F6B814300B369AB /* SMP Core.swift in Sources */,
29FC0CB61F8298820036089F /* TransactionCoordinator.swift in Sources */,
29B6AECB1F7C5FA900EC6DE3 /* SendAndRequestViewContainer.swift in Sources */,
2996F14A1F6C9D10005C33AE /* ExportCoordinator.swift in Sources */,
291D73C61F7F500D00A8AB56 /* TransactionItemState.swift in Sources */,
29BE3FD21F707DC300F6BFC2 /* TransactionCoordinator.swift in Sources */,
29BE3FD21F707DC300F6BFC2 /* TransactionDataCoordinator.swift in Sources */,
291F52B51F6B814300B369AB /* SMP String Module.swift in Sources */,
291ED0921F6FA5D900E7E93A /* RequestViewController.swift in Sources */,
296106C21F76403A0006164B /* TokenViewCell.swift in Sources */,
@ -1262,6 +1279,7 @@
296106BF1F7639250006164B /* FetchTransactionsRequest.swift in Sources */,
293B8B451F70A20200356286 /* TransactionViewCell.swift in Sources */,
293E66421F8026F10052973D /* Block.swift in Sources */,
29FC0CB81F8299510036089F /* Coordinator.swift in Sources */,
296106C61F7645CC0006164B /* TokensViewController.swift in Sources */,
29282B531F7630970067F88D /* Token.swift in Sources */,
29C9F5FB1F720C050025C494 /* FloatLabelTextField.swift in Sources */,
@ -1282,6 +1300,7 @@
29CA4B791F6FBFD50032313D /* Balance.swift in Sources */,
291F52A71F6B766100B369AB /* BalanceRequest.swift in Sources */,
29D72A2A1F6A8D1500CE9209 /* AppCoordinator.swift in Sources */,
29FC0CB31F81D2640036089F /* TransactionReceiptRequest.swift in Sources */,
29A13E331F6B1B7A00E432A2 /* AppStyle.swift in Sources */,
29FF12F81F747D6C00AFD326 /* Error.swift in Sources */,
29282B581F7636840067F88D /* GetTokensRequest.swift in Sources */,
@ -1345,6 +1364,7 @@
2977CAE01F7DEEB0009682A0 /* FakeEtherKeystore.swift in Sources */,
29E14FD51F7F470C00185568 /* TransactionsStorageTests.swift in Sources */,
29FF130A1F75F67200AFD326 /* Address.swift in Sources */,
2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -3,7 +3,7 @@
import Foundation
import UIKit
class AppCoordinator: NSObject {
class AppCoordinator: NSObject, Coordinator {
let rootNavigationController: UINavigationController
@ -17,16 +17,10 @@ class AppCoordinator: NSObject {
return WalletCoordinator(rootNavigationController: self.rootNavigationController)
}()
lazy var settingsCoordinator: SettingsCoordinator = {
return SettingsCoordinator(navigationController: self.rootNavigationController)
}()
lazy var accountsCoordinator: AccountsCoordinator = {
return AccountsCoordinator(navigationController: self.rootNavigationController)
}()
private var keystore: Keystore
var coordinators: [Coordinator] = []
init(
window: UIWindow,
keystore: Keystore = EtherKeystore(),
@ -48,8 +42,14 @@ class AppCoordinator: NSObject {
}
func showTransactions(for account: Account) {
let controller = makeTransactionsController(with: account)
rootNavigationController.viewControllers = [controller]
let coordinator = TransactionCoordinator(
account: account,
rootNavigationController: rootNavigationController
)
coordinator.delegate = self
rootNavigationController.viewControllers = [coordinator.rootViewController]
addCoordinator(coordinator)
keystore.recentlyUsedAccount = account
}
@ -58,36 +58,10 @@ class AppCoordinator: NSObject {
walletCoordinator.delegate = self
}
private func makeTransactionsController(with account: Account) -> TransactionsViewController {
let controller = TransactionsViewController(account: account)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(image: R.image.settings_icon(), landscapeImagePhone: R.image.settings_icon(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showSettings))
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.accountsSwitch(), landscapeImagePhone: R.image.accountsSwitch(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showAccounts))
controller.delegate = self
return controller
}
@objc func dismiss() {
rootNavigationController.dismiss(animated: true, completion: nil)
}
@objc func reset() {
coordinators.removeAll()
rootNavigationController.viewControllers = [welcomeViewController]
}
@objc func showAccounts() {
accountsCoordinator.start()
accountsCoordinator.delegate = self
}
@objc func showSettings() {
settingsCoordinator.start()
settingsCoordinator.delegate = self
}
func showTokens(for account: Account) {
let controller = TokensViewController(account: account)
rootNavigationController.pushViewController(controller, animated: true)
}
}
extension AppCoordinator: WelcomeViewControllerDelegate {
@ -96,9 +70,15 @@ extension AppCoordinator: WelcomeViewControllerDelegate {
}
}
extension AppCoordinator: SettingsCoordinatorDelegate {
func didCancel(in coordinator: SettingsCoordinator) {
extension AppCoordinator: TransactionCoordinatorDelegate {
func didCancel(in coordinator: TransactionCoordinator) {
removeCoordinator(coordinator)
reset()
}
func didChangeAccount(to account: Account, in coordinator: TransactionCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
showTransactions(for: account)
}
}
@ -116,46 +96,3 @@ extension AppCoordinator: WalletCoordinatorDelegate {
coordinator.navigationViewController.dismiss(animated: true, completion: nil)
}
}
extension AppCoordinator: AccountsCoordinatorDelegate {
func didCancel(in coordinator: AccountsCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
}
func didSelectAccount(account: Account, in coordinator: AccountsCoordinator) {
showTransactions(for: account)
coordinator.navigationController.dismiss(animated: true, completion: nil)
}
func didDeleteAccount(account: Account, in coordinator: AccountsCoordinator) {
guard !coordinator.accountsViewController.hasAccounts else { return }
coordinator.navigationController.dismiss(animated: true, completion: nil)
rootNavigationController.dismiss(animated: true, completion: nil)
reset()
}
}
extension AppCoordinator: TransactionsViewControllerDelegate {
func didPressSend(for account: Account, in viewController: TransactionsViewController) {
let controller = SendAndRequestViewContainer(flow: .send, account: account)
let nav = NavigationController(rootViewController: controller)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
rootNavigationController.present(nav, animated: true, completion: nil)
}
func didPressRequest(for account: Account, in viewController: TransactionsViewController) {
let controller = SendAndRequestViewContainer(flow: .request, account: account)
let nav = NavigationController(rootViewController: controller)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
rootNavigationController.present(nav, animated: true, completion: nil)
}
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) {
let controller = TransactionViewController(transaction: transaction)
rootNavigationController.pushViewController(controller, animated: true)
}
func didPressTokens(for account: Account, in viewController: TransactionsViewController) {
showTokens(for: account)
}
}

@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
self.window = window
let config = Realm.Configuration(
schemaVersion: 3,
schemaVersion: 4,
migrationBlock: { _, _ in }
)
Realm.Configuration.defaultConfiguration = config

@ -151,7 +151,7 @@ class EtherKeystore: Keystore {
}
func getGethAccount(for address: Address) -> GethAccount {
return gethAccounts.filter { $0.getAddress().getHex() == address.address }.first!
return gethAccounts.filter { Address(address: $0.getAddress().getHex()) == address }.first!
}
}

@ -4,9 +4,17 @@ import Foundation
import APIKit
struct FetchTransactionsRequest: APIKit.Request {
typealias Response = [ParsedTransaction]
typealias Response = [EtherScanTransaction]
let address: String
let startBlock: String
let endBlock: String
init(address: String, startBlock: String = "0", endBlock: String = "99999999") {
self.address = address
self.startBlock = startBlock
self.endBlock = endBlock
}
var baseURL: URL {
let config = Config()
@ -26,8 +34,8 @@ struct FetchTransactionsRequest: APIKit.Request {
"module": "account",
"action": "txlist",
"address": address,
"startblock": "0",
"endblock": "99999999",
"startblock": startBlock,
"endblock": endBlock,
"sort": "asc",
"apikey": "7V8AMAVQWKNAZHZG8ARYB9SQWWKBBDA7S8",
]
@ -37,8 +45,63 @@ struct FetchTransactionsRequest: APIKit.Request {
if
let objectJSON = object as? [String: AnyObject],
let transactionJSON = objectJSON["result"] as? [[String: AnyObject]] {
return transactionJSON.map { .from(address: address.lowercased(), json: $0) }
return transactionJSON.map { .from(json: $0) }
}
return []
}
}
struct EtherScanTransaction {
let blockHash: String
let blockNumber: String
let transactionIndex: String
let confirmations: String
let cumulativeGasUsed: String
let from: String
let to: String
let gas: String
let gasPrice: String
let gasUsed: String
let hash: String
let value: String
let nonce: String
let timestamp: String
let isError: Bool
}
extension EtherScanTransaction {
static func from(json: [String: AnyObject]) -> EtherScanTransaction {
let blockHash = json["blockHash"] as? String ?? ""
let blockNumber = json["blockNumber"] as? String ?? ""
let transactionIndex = json["transactionIndex"] as? String ?? ""
let confirmation = json["confirmations"] as? String ?? ""
let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? ""
let from = json["from"] as? String ?? ""
let to = json["to"] as? String ?? ""
let gas = json["gas"] as? String ?? ""
let gasPrice = json["gasPrice"] as? String ?? ""
let gasUsed = json["gasUsed"] as? String ?? ""
let hash = json["hash"] as? String ?? ""
let isError = Bool(json["isError"] as? String ?? "") ?? false
let timestamp = (json["timeStamp"] as? String ?? "")
let value = (json["value"] as? String ?? "")
let nonce = (json["nonce"] as? String ?? "")
return EtherScanTransaction(
blockHash: blockHash,
blockNumber: blockNumber,
transactionIndex: transactionIndex,
confirmations: confirmation,
cumulativeGasUsed: cumulativeGasUsed,
from: from,
to: to,
gas: gas,
gasPrice: gasPrice,
gasUsed: gasUsed,
hash: hash,
value: value,
nonce: nonce,
timestamp: timestamp,
isError: isError
)
}
}

@ -0,0 +1,28 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import JSONRPCKit
struct TransactionByHashRequest: JSONRPCKit.Request {
typealias Response = ParsedTransaction
let hash: String
var method: String {
return "eth_getTransactionByHash"
}
var parameters: Any? {
return [
hash,
]
}
func response(from resultObject: Any) throws -> Response {
if let response = resultObject as? [String: AnyObject] {
return ParsedTransaction.from(json: response)
} else {
throw CastError(actualValue: resultObject, expectedType: Response.self)
}
}
}

@ -0,0 +1,28 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import JSONRPCKit
struct TransactionReceiptRequest: JSONRPCKit.Request {
typealias Response = ParsedTransaction
let hash: String
var method: String {
return "eth_getTransactionReceipt"
}
var parameters: Any? {
return [
hash,
]
}
func response(from resultObject: Any) throws -> Response {
if let response = resultObject as? [String: AnyObject] {
return ParsedTransaction.from(json: response)
} else {
throw CastError(actualValue: resultObject, expectedType: Response.self)
}
}
}

@ -7,6 +7,12 @@ struct Address {
let address: String
init(address: String) {
self.address = address
self.address = address.lowercased()
}
}
extension Address: Equatable {
static func == (lhs: Address, rhs: Address) -> Bool {
return lhs.address == rhs.address
}
}

@ -25,6 +25,9 @@ struct Balance {
extension String {
var drop0x: String {
return String(self.characters.dropFirst(2))
if self.count > 2 && self.substring(with: 0..<2) == "0x" {
return String(self.characters.dropFirst(2))
}
return self
}
}

@ -2,8 +2,14 @@
import Foundation
struct ParsedBlock {
let number: String
let timestamp: String
}
struct Block {
let number: String
let timestamp: String
let transactions: [ParsedTransaction]
}
@ -11,9 +17,20 @@ extension Block {
static func from(json: [String: AnyObject]) -> Block? {
if
let transactionsJSON = json["transactions"] as? [[String: AnyObject]] {
let transactions: [ParsedTransaction] = transactionsJSON.map({ .from(json: $0) })
let number = json["number"] as? String ?? ""
let timestamp = json["timestamp"] as? String ?? ""
let block = ParsedBlock(
number: number,
timestamp: timestamp
)
let transactions: [ParsedTransaction] = transactionsJSON.map {.from(block: block, transaction: $0)}
return Block(
number: "1",
number: number,
timestamp: timestamp,
transactions: transactions
)
}

@ -8,14 +8,13 @@ enum TransactionDirection {
}
struct ParsedTransaction {
let blockHash: String
let blockNumber: String
let transactionIndex: String
let confirmations: String
let cumulativeGasUsed: String
let from: String
let to: String
let owner: String
let gas: String
let gasPrice: String
let gasUsed: String
@ -24,16 +23,13 @@ struct ParsedTransaction {
let nonce: String
let timestamp: String
let isError: Bool
var time: Date {
return NSDate(timeIntervalSince1970: TimeInterval(timestamp) ?? 0) as Date
}
}
extension ParsedTransaction {
static func from(address: String = "", json: [String: AnyObject]) -> ParsedTransaction {
static func from(json: [String: AnyObject]) -> ParsedTransaction {
let blockHash = json["blockHash"] as? String ?? ""
let blockNumber = json["blockNumber"] as? String ?? ""
let transactionIndex = json["transactionIndex"] as? String ?? ""
let confirmation = json["confirmations"] as? String ?? ""
let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? ""
let from = json["from"] as? String ?? ""
@ -49,11 +45,11 @@ extension ParsedTransaction {
return ParsedTransaction(
blockHash: blockHash,
blockNumber: blockNumber,
transactionIndex: transactionIndex,
confirmations: confirmation,
cumulativeGasUsed: cumulativeGasUsed,
from: from,
to: to,
owner: address,
gas: gas,
gasPrice: gasPrice,
gasUsed: gasUsed,
@ -65,3 +61,40 @@ extension ParsedTransaction {
)
}
}
extension ParsedTransaction {
static func from(block: ParsedBlock, transaction: [String: AnyObject]) -> ParsedTransaction {
let blockHash = transaction["blockHash"] as? String ?? ""
let blockNumber = transaction["blockNumber"] as? String ?? ""
let transactionIndex = transaction["transactionIndex"] as? String ?? "0"
let confirmation = transaction["confirmations"] as? String ?? "0"
let cumulativeGasUsed = transaction["cumulativeGasUsed"] as? String ?? "0"
let from = transaction["from"] as? String ?? ""
let to = transaction["to"] as? String ?? ""
let gas = transaction["gas"] as? String ?? "0"
let gasPrice = transaction["gasPrice"] as? String ?? "0"
let gasUsed = transaction["gasUsed"] as? String ?? "0"
let hash = transaction["hash"] as? String ?? ""
let isError = Bool(transaction["isError"] as? String ?? "") ?? false
let timestamp = block.timestamp
let value = transaction["value"] as? String ?? "0"
let nonce = transaction["nonce"] as? String ?? "0"
return ParsedTransaction(
blockHash: blockHash,
blockNumber: BInt(hex: blockNumber.drop0x).dec,
transactionIndex: BInt(hex: transactionIndex.drop0x).dec,
confirmations: confirmation,
cumulativeGasUsed: BInt(hex: cumulativeGasUsed.drop0x).dec,
from: from,
to: to,
gas: BInt(hex: gas.drop0x).dec,
gasPrice: BInt(hex: gasPrice.drop0x).dec,
gasUsed: BInt(hex: gasUsed.drop0x).dec,
hash: hash,
value: BInt(hex: value.drop0x).dec,
nonce: BInt(hex: nonce.drop0x).dec,
timestamp: BInt(hex: timestamp.drop0x).dec,
isError: isError
)
}
}

@ -4,6 +4,10 @@ import Foundation
import Result
import APIKit
enum TokenError: Error {
case failedToFetch
}
protocol TokensDataStoreDelegate: class {
func didUpdate(result: Result<TokensViewModel, TokenError>)
}

@ -2,165 +2,133 @@
import Foundation
import UIKit
import JSONRPCKit
import APIKit
import RealmSwift
import Result
enum TransactionError: Error {
case failedToFetch
}
enum TokenError: Error {
case failedToFetch
}
protocol TransactionCoordinatorDelegate: class {
func didUpdate(result: Result<[Transaction], TransactionError>)
func didCancel(in coordinator: TransactionCoordinator)
func didChangeAccount(to account: Account, in coordinator: TransactionCoordinator)
}
class TransactionCoordinator {
class TransactionCoordinator: Coordinator {
let storage = TransactionsStorage()
let account: Account
private let keystore: Keystore
var viewModel: TransactionsViewModel {
return .init(transactions: self.storage.objects)
}
lazy var rootViewController: TransactionsViewController = {
let controller = self.makeTransactionsController(with: self.account)
return controller
}()
lazy var dataCoordinator: TransactionDataCoordinator = {
let coordinator = TransactionDataCoordinator(account: self.account)
return coordinator
}()
weak var delegate: TransactionCoordinatorDelegate?
var timer: Timer?
//let notificationToken: NotificationToken
init(account: Account) {
lazy var settingsCoordinator: SettingsCoordinator = {
return SettingsCoordinator(navigationController: self.navigationController)
}()
lazy var accountsCoordinator: AccountsCoordinator = {
return AccountsCoordinator(navigationController: self.navigationController)
}()
let account: Account
let navigationController: UINavigationController
var coordinators: [Coordinator] = []
init(
account: Account,
rootNavigationController: UINavigationController
) {
self.account = account
self.keystore = EtherKeystore()
self.navigationController = rootNavigationController
}
storage.deleteAll()
private func makeTransactionsController(with account: Account) -> TransactionsViewController {
let controller = TransactionsViewController(account: account, dataCoordinator: dataCoordinator)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(image: R.image.settings_icon(), landscapeImagePhone: R.image.settings_icon(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showSettings))
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.accountsSwitch(), landscapeImagePhone: R.image.accountsSwitch(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showAccounts))
controller.delegate = self
return controller
}
// notificationToken = storage.realm.addNotificationBlock { item, _ in
// switch item {
// case .didChange: break
// case .refreshRequired: break
// }
// }
@objc func showAccounts() {
accountsCoordinator.start()
accountsCoordinator.delegate = self
}
@objc func showSettings() {
settingsCoordinator.start()
settingsCoordinator.delegate = self
}
func start() {
fetchTransactions()
fetchPendingTransactions()
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.timerTrigger), userInfo: nil, repeats: true)
func showTokens(for account: Account) {
let controller = TokensViewController(account: account)
navigationController.pushViewController(controller, animated: true)
}
func fetch() {
fetchPendingTransactions()
@objc func dismiss() {
navigationController.dismiss(animated: true, completion: nil)
}
}
func fetchTransactions() {
let request = FetchTransactionsRequest(address: account.address.address)
Session.send(request) { result in
switch result {
case .success(let response):
self.update(owner: self.account.address, items: response)
case .failure:
break
}
}
extension TransactionCoordinator: SettingsCoordinatorDelegate {
func didCancel(in coordinator: SettingsCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
}
}
func fetchPendingTransactions() {
Session.send(EtherServiceRequest(batch: BatchFactory().create(GetBlockByNumberRequest(block: "pending")))) { result in
switch result {
case .success(let block):
//NSLog("transactions \(block)")
for item in block.transactions {
NSLog("item \(item.hash)")
if item.to == self.account.address.address.lowercased() || item.from == self.account.address.address.lowercased() {
self.update(owner: self.account.address, items: [item])
} else {
self.update(owner: self.account.address, items: [])
}
}
case .failure:
break
}
}
extension TransactionCoordinator: TransactionsViewControllerDelegate {
func didPressSend(for account: Account, in viewController: TransactionsViewController) {
let controller = SendAndRequestViewContainer(flow: .send, account: account)
let nav = NavigationController(rootViewController: controller)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
navigationController.present(nav, animated: true, completion: nil)
}
@objc func timerTrigger() {
fetchPendingTransactions()
func didPressRequest(for account: Account, in viewController: TransactionsViewController) {
let controller = SendAndRequestViewContainer(flow: .request, account: account)
let nav = NavigationController(rootViewController: controller)
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
navigationController.present(nav, animated: true, completion: nil)
}
func update(owner: Address, items: [ParsedTransaction]) {
let transactionItems: [Transaction] = items.map { .from(owner: owner, transaction: $0) }
storage.add(transactionItems)
print()
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) {
let controller = TransactionViewController(
transaction: transaction
)
navigationController.pushViewController(controller, animated: true)
}
delegate?.didUpdate(result: .success(self.storage.objects))
func didPressTokens(for account: Account, in viewController: TransactionsViewController) {
showTokens(for: account)
}
func print() {
NSLog("objects \(storage.count)")
func reset() {
clean()
delegate?.didCancel(in: self)
}
func stop() {
//notificationToken.stop()
func clean() {
dataCoordinator.storage.deleteAll()
}
}
extension Transaction {
static func from(owner: Address, transaction: ParsedTransaction) -> Transaction {
let state: TransactionState = {
return .pending
}()
return Transaction(
id: transaction.hash,
owner: owner.address, //TODO
state: state,
blockNumber: transaction.blockNumber,
from: transaction.from,
to: transaction.to,
value: transaction.value, //TODO
gas: transaction.gas,
gasPrice: transaction.gasPrice,
gasUsed: transaction.gasUsed,
confirmations: Int64(transaction.confirmations) ?? 0,
nonce: "0", // TODO
date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date
)
extension TransactionCoordinator: AccountsCoordinatorDelegate {
func didCancel(in coordinator: AccountsCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
}
// static func from(address: String, json: [String: AnyObject]) -> Transaction {
// let blockHash = json["blockHash"] as? String ?? ""
// let blockNumber = json["blockNumber"] as? String ?? ""
// let confirmation = json["confirmations"] as? String ?? ""
// let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? ""
// let from = json["from"] as? String ?? ""
// let to = json["to"] as? String ?? ""
// let gas = json["gas"] as? String ?? ""
// let gasPrice = json["gasPrice"] as? String ?? ""
// let gasUsed = json["gasUsed"] as? String ?? ""
// let hash = json["hash"] as? String ?? ""
// let isError = Bool(json["isError"] as? String ?? "") ?? false
// let timestamp = (json["timeStamp"] as? String ?? "")
// let value = (json["value"] as? String ?? "")
//
// let state: TransactionState = {
// return .pending
// }()
//
// return Transaction(
// id: hash,
// state: state,
// blockNumber: blockNumber,
// from: from,
// to: to,
// value: "0x0", //TODO
// gas: gas,
// gasPrice: gasPrice,
// confirmations: Int64(confirmations) ?? 0,
// nonce: "0x0", // TODO
// date: NSDate(timeIntervalSince1970: TimeInterval(timestamp) ?? 0) as Date
// )
// }
func didSelectAccount(account: Account, in coordinator: AccountsCoordinator) {
delegate?.didChangeAccount(to: account, in: self)
}
func didDeleteAccount(account: Account, in coordinator: AccountsCoordinator) {
guard !coordinator.accountsViewController.hasAccounts else { return }
coordinator.navigationController.dismiss(animated: true, completion: nil)
clean()
reset()
}
}

@ -0,0 +1,155 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import UIKit
import JSONRPCKit
import APIKit
import RealmSwift
import Result
enum TransactionError: Error {
case failedToFetch
}
protocol TransactionDataCoordinatorDelegate: class {
func didUpdate(result: Result<[Transaction], TransactionError>)
}
class TransactionDataCoordinator {
let storage = TransactionsStorage()
let account: Account
var viewModel: TransactionsViewModel {
return .init(transactions: self.storage.objects)
}
var timer: Timer?
weak var delegate: TransactionDataCoordinatorDelegate?
init(account: Account) {
self.account = account
}
func start() {
fetchTransactions()
fetchPendingTransactions()
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(fetchPending), userInfo: nil, repeats: true)
}
func fetch() {
fetchTransactions()
}
func fetchTransactions() {
let startBlock: String = {
guard let transction = storage.objects.first else { return "0" }
return String(Int(transction.blockNumber) ?? 0 - 20)
}()
let request = FetchTransactionsRequest(address: account.address.address, startBlock: startBlock)
Session.send(request) { result in
switch result {
case .success(let response):
let transactions: [Transaction] = response.map { .from(owner: self.account.address, transaction: $0) }
self.update(owner: self.account.address, items: transactions)
case .failure(let error):
self.handleError(error: error)
}
}
}
func fetchPendingTransactions() {
Session.send(EtherServiceRequest(batch: BatchFactory().create(GetBlockByNumberRequest(block: "pending")))) { result in
switch result {
case .success(let block):
for item in block.transactions {
if item.to == self.account.address.address || item.from == self.account.address.address {
self.update(owner: self.account.address, items: [item])
}
}
case .failure(let error):
self.handleError(error: error)
}
}
}
@objc func fetchPending() {
fetchPendingTransactions()
}
@objc func fetchLatest() {
fetchTransactions()
}
func update(owner: Address, items: [ParsedTransaction]) {
let transactionItems: [Transaction] = items.map { .from(owner: owner, transaction: $0) }
update(owner: owner, items: transactionItems)
}
func update(owner: Address, items: [Transaction]) {
storage.add(items)
handleUpdateItems()
}
func handleError(error: Error) {
delegate?.didUpdate(result: .failure(TransactionError.failedToFetch))
}
func handleUpdateItems() {
delegate?.didUpdate(result: .success(self.storage.objects))
}
func stop() {
timer?.invalidate()
timer = nil
}
}
extension Transaction {
static func from(owner: Address, transaction: ParsedTransaction) -> Transaction {
let state: TransactionState = {
return .pending
}()
return Transaction(
id: transaction.hash,
owner: owner.address,
state: state,
blockNumber: transaction.blockNumber,
transactionIndex: transaction.transactionIndex,
from: transaction.from,
to: transaction.to,
value: transaction.value,
gas: transaction.gas,
gasPrice: transaction.gasPrice,
gasUsed: transaction.gasUsed,
confirmations: Int64(transaction.confirmations) ?? 0,
nonce: transaction.nonce,
date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date
)
}
}
extension Transaction {
static func from(owner: Address, transaction: EtherScanTransaction) -> Transaction {
let state: TransactionState = {
return .pending
}()
return Transaction(
id: transaction.hash,
owner: owner.address,
state: state,
blockNumber: transaction.blockNumber,
transactionIndex: transaction.transactionIndex,
from: transaction.from,
to: transaction.to,
value: transaction.value,
gas: transaction.gas,
gasPrice: transaction.gasPrice,
gasUsed: transaction.gasUsed,
confirmations: Int64(transaction.confirmations) ?? 0,
nonce: transaction.nonce,
date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date
)
}
}

@ -8,6 +8,7 @@ class Transaction: Object {
@objc dynamic var owner: String = ""
@objc dynamic var state: Int = TransactionState.pending.rawValue
@objc dynamic var blockNumber = ""
@objc dynamic var transactionIndex = ""
@objc dynamic var from = ""
@objc dynamic var to = ""
@objc dynamic var value = ""
@ -23,6 +24,7 @@ class Transaction: Object {
owner: String,
state: TransactionState,
blockNumber: String,
transactionIndex: String,
from: String,
to: String,
value: String,
@ -38,6 +40,7 @@ class Transaction: Object {
self.owner = owner
self.state = state.rawValue
self.blockNumber = blockNumber
self.transactionIndex = transactionIndex
self.from = from
self.to = to
self.value = value

@ -2,6 +2,7 @@
import UIKit
import StackViewController
import Result
class TransactionViewController: UIViewController {
@ -11,7 +12,6 @@ class TransactionViewController: UIViewController {
let stackViewController = StackViewController()
let transaction: Transaction
let refreshControl = UIRefreshControl()
init(transaction: Transaction) {
self.transaction = transaction
@ -52,9 +52,6 @@ class TransactionViewController: UIViewController {
for item in items {
stackViewController.addItem(item)
}
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
stackViewController.scrollView.addSubview(refreshControl)
}
override func viewDidLoad() {
@ -69,15 +66,6 @@ class TransactionViewController: UIViewController {
stackViewController.didMove(toParentViewController: self)
}
func pullToRefresh() {
fetch()
}
func fetch() {
refreshControl.beginRefreshing()
refreshControl.endRefreshing()
}
private func item(title: String, subTitle: String) -> UIView {
let titleLabel = UILabel(frame: .zero)
titleLabel.translatesAutoresizingMaskIntoConstraints = false

@ -30,15 +30,11 @@ class TransactionsViewController: UIViewController {
}()
weak var delegate: TransactionsViewControllerDelegate?
let dataCoordinator: TransactionDataCoordinator
lazy var transactionCoordinator: TransactionCoordinator = {
let coordinator = TransactionCoordinator(account: self.account)
coordinator.delegate = self
return coordinator
}()
init(account: Account) {
init(account: Account, dataCoordinator: TransactionDataCoordinator) {
self.account = account
self.dataCoordinator = dataCoordinator
tableView = UITableView(frame: .zero, style: .plain)
sendButton = Button(size: .extraLarge, style: .squared)
requestButton = Button(size: .extraLarge, style: .squared)
@ -93,6 +89,9 @@ class TransactionsViewController: UIViewController {
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
dataCoordinator.delegate = self
dataCoordinator.start()
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
tableView.addSubview(refreshControl)
@ -116,8 +115,6 @@ class TransactionsViewController: UIViewController {
super.viewWillAppear(animated)
fetch()
transactionCoordinator.start()
}
override func viewDidAppear(_ animated: Bool) {
@ -139,11 +136,12 @@ class TransactionsViewController: UIViewController {
}
func fetch() {
transactionCoordinator.fetch()
startLoading()
fetchBalance()
dataCoordinator.fetch()
}
func fetchBalance() {
let request = EtherServiceRequest(batch: BatchFactory().create(BalanceRequest(address: account.address.address)))
Session.send(request) { [weak self] result in
switch result {
@ -189,7 +187,7 @@ extension TransactionsViewController: UITableViewDelegate {
}
}
extension TransactionsViewController: TransactionCoordinatorDelegate {
extension TransactionsViewController: TransactionDataCoordinatorDelegate {
func didUpdate(result: Result<[Transaction], TransactionError>) {
switch result {
case .success(let items):

@ -57,7 +57,7 @@ struct TransactionViewCellViewModel {
case .error, .completed:
return .white
case .pending:
return Colors.lightGray
return UIColor(hex: "f4f4f4")
}
}
}

@ -24,9 +24,11 @@ struct TransactionsViewModel {
currentItems.insert(transaction, at: 0)
newItems[date] = currentItems
}
//TODO. IMPROVE perfomance
let tuple = newItems.map { (key, values) in return (date: key, transactions: values) }
items = tuple.sorted { (object1, object2) -> Bool in return object1.date > object2.date }
items = tuple.sorted { (object1, object2) -> Bool in
return TransactionsViewModel.formatter.date(from: object1.date)! > TransactionsViewModel.formatter.date(from: object2.date)!
}
}
var title: String {

@ -0,0 +1,17 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
protocol Coordinator: class {
var coordinators: [Coordinator] {get set}
}
extension Coordinator {
func addCoordinator(_ coordinator: Coordinator) {
coordinators.append(coordinator)
}
func removeCoordinator(_ coordinator: Coordinator) {
coordinators = coordinators.filter { $0 !== coordinator }
}
}

@ -42,37 +42,7 @@ class AppCoordinatorTests: XCTestCase {
XCTAssertTrue(coordinator.rootNavigationController.viewControllers[0] is WelcomeViewController)
}
func testShowAccounts() {
let coordinator = AppCoordinator(
window: UIWindow(),
keystore: FakeKeystore(
accounts: [.make()]
),
rootNavigationController: FakeNavigationController()
)
coordinator.start()
coordinator.showAccounts()
XCTAssertTrue((coordinator.rootNavigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is AccountsViewController)
}
func testShowSettings() {
let coordinator = AppCoordinator(
window: UIWindow(),
keystore: FakeKeystore(
accounts: [.make()]
),
rootNavigationController: FakeNavigationController()
)
coordinator.start()
coordinator.showSettings()
XCTAssertTrue((coordinator.rootNavigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is SettingsViewController)
}
func testStartWalletCoordinator() {
let coordinator = AppCoordinator(
window: UIWindow(),
@ -102,19 +72,4 @@ class AppCoordinatorTests: XCTestCase {
XCTAssertTrue(coordinator.rootNavigationController.viewControllers[0] is TransactionsViewController)
}
func testShowTokens() {
let coordinator = AppCoordinator(
window: UIWindow(),
keystore: FakeKeystore(
accounts: [.make()]
),
rootNavigationController: FakeNavigationController()
)
coordinator.start()
coordinator.showTokens(for: .make())
XCTAssertTrue(coordinator.rootNavigationController.viewControllers[1] is TokensViewController)
}
}

@ -0,0 +1,40 @@
// Copyright SIX DAY LLC. All rights reserved.
import XCTest
@testable import Trust
class TransactionCoordinatorTests: XCTestCase {
func testShowTokens() {
let coordinator = TransactionCoordinator(
account: .make(),
rootNavigationController: FakeNavigationController()
)
coordinator.showTokens(for: .make())
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is TokensViewController)
}
func testShowAccounts() {
let coordinator = TransactionCoordinator(
account: .make(),
rootNavigationController: FakeNavigationController()
)
coordinator.showAccounts()
XCTAssertTrue((coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is AccountsViewController)
}
func testShowSettings() {
let coordinator = TransactionCoordinator(
account: .make(),
rootNavigationController: FakeNavigationController()
)
coordinator.showSettings()
XCTAssertTrue((coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is SettingsViewController)
}
}

@ -49,7 +49,7 @@ class EtherKeystoreTests: XCTestCase {
return XCTFail()
}
XCTAssertEqual("0x5E9c27156a612a2D516C74c7a80af107856F8539", account.address.address)
XCTAssertEqual("0x5e9c27156a612a2d516c74c7a80af107856f8539", account.address.address)
XCTAssertEqual(1, keystore.accounts.count)
}

@ -4,12 +4,18 @@ import Foundation
import UIKit
class FakeNavigationController: UINavigationController {
private var _presentedViewController: UIViewController?
override var presentedViewController: UIViewController? {
return _presentedViewController
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: false)
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
super.present(viewControllerToPresent, animated: false, completion: completion)
_presentedViewController = viewControllerToPresent
}
}

@ -9,6 +9,7 @@ extension Transaction {
owner: String = "0x1",
state: TransactionState = .pending,
blockNumber: String = "0x1",
transactionIndex: String = "0x1",
from: String = "0x1",
to: String = "0x1",
value: String = "1",
@ -24,6 +25,7 @@ extension Transaction {
owner: owner,
state: state,
blockNumber: blockNumber,
transactionIndex: transactionIndex,
from: from,
to: to,
value: value,

Loading…
Cancel
Save