diff --git a/Trust.xcodeproj/project.pbxproj b/Trust.xcodeproj/project.pbxproj index 39fb24875..acee36b55 100644 --- a/Trust.xcodeproj/project.pbxproj +++ b/Trust.xcodeproj/project.pbxproj @@ -40,6 +40,9 @@ 291F52BC1F6B8D0600B369AB /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291F52BB1F6B8D0600B369AB /* Account.swift */; }; 291F52BF1F6C874E00B369AB /* AccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291F52BE1F6C874E00B369AB /* AccountsViewController.swift */; }; 291F52C11F6C8A1F00B369AB /* AccountsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291F52C01F6C8A1F00B369AB /* AccountsViewModel.swift */; }; + 29282B531F7630970067F88D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B521F7630970067F88D /* Token.swift */; }; + 29282B551F7636080067F88D /* EtherScanServiceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B541F7636080067F88D /* EtherScanServiceRequest.swift */; }; + 29282B581F7636840067F88D /* GetTokensRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B571F7636840067F88D /* GetTokensRequest.swift */; }; 29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29285B411F6FB3E60044CF29 /* SendViewController.swift */; }; 29336FE71F6B245D005E3BFC /* WelcomeViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A13E271F6A903500E432A2 /* WelcomeViewModelTests.swift */; }; 293B8B411F707F4600356286 /* TransactionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293B8B401F707F4600356286 /* TransactionViewModel.swift */; }; @@ -47,6 +50,9 @@ 293B8B451F70A20200356286 /* TransactionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293B8B441F70A20200356286 /* TransactionViewCell.swift */; }; 294FE5661F72442D00754F31 /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294FE5651F72442D00754F31 /* Address.swift */; }; 295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295A59371F71C1B90092F0FC /* AccountsCoordinator.swift */; }; + 296106BF1F7639250006164B /* FetchTransactionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296106BE1F7639250006164B /* FetchTransactionsRequest.swift */; }; + 296106C21F76403A0006164B /* TokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296106C11F76403A0006164B /* TokenViewCell.swift */; }; + 296106C41F7640C50006164B /* TokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296106C31F7640C50006164B /* TokenViewCellViewModel.swift */; }; 296421951F70C1EC00EB363B /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296421941F70C1EC00EB363B /* LoadingView.swift */; }; 296421971F70C1F200EB363B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296421961F70C1F200EB363B /* ErrorView.swift */; }; 296421991F70C1F900EB363B /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296421981F70C1F900EB363B /* EmptyView.swift */; }; @@ -155,12 +161,18 @@ 291F52BB1F6B8D0600B369AB /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; 291F52BE1F6C874E00B369AB /* AccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsViewController.swift; sourceTree = ""; }; 291F52C01F6C8A1F00B369AB /* AccountsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsViewModel.swift; sourceTree = ""; }; + 29282B521F7630970067F88D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 29282B541F7636080067F88D /* EtherScanServiceRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtherScanServiceRequest.swift; sourceTree = ""; }; + 29282B571F7636840067F88D /* GetTokensRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTokensRequest.swift; sourceTree = ""; }; 29285B411F6FB3E60044CF29 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = ""; }; 293B8B401F707F4600356286 /* TransactionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewModel.swift; sourceTree = ""; }; 293B8B421F70815900356286 /* BalanceTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceTitleView.swift; sourceTree = ""; }; 293B8B441F70A20200356286 /* TransactionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewCell.swift; sourceTree = ""; }; 294FE5651F72442D00754F31 /* Address.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = ""; }; 295A59371F71C1B90092F0FC /* AccountsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsCoordinator.swift; sourceTree = ""; }; + 296106BE1F7639250006164B /* FetchTransactionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchTransactionsRequest.swift; sourceTree = ""; }; + 296106C11F76403A0006164B /* TokenViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenViewCell.swift; sourceTree = ""; }; + 296106C31F7640C50006164B /* TokenViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenViewCellViewModel.swift; sourceTree = ""; }; 296421941F70C1EC00EB363B /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 296421961F70C1F200EB363B /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 296421981F70C1F900EB363B /* EmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = ""; }; @@ -331,6 +343,7 @@ 2912CD291F6A831D00C6CBE3 /* Transactions */ = { isa = PBXGroup; children = ( + 296106C01F7640240006164B /* Tokens */, 2912CD2A1F6A833E00C6CBE3 /* TransactionsViewController.swift */, 2912CD351F6A853300C6CBE3 /* TransactionsViewModel.swift */, 29850D281F6B30E400791A49 /* Transaction.storyboard */, @@ -398,6 +411,8 @@ 291F52A01F6B6DBC00B369AB /* EtherClient */ = { isa = PBXGroup; children = ( + 296106BD1F76391B0006164B /* Etherscan */, + 29282B561F7636600067F88D /* Ethplorer */, 291F52A31F6B760A00B369AB /* Requests */, 291F52A11F6B6DCF00B369AB /* EtherClient.swift */, 291F52A41F6B762300B369AB /* EtherServiceRequest.swift */, @@ -405,6 +420,7 @@ 291F52B81F6B880F00B369AB /* EtherKeystore.swift */, 291ED08C1F6F5F0A00E7E93A /* KeyStoreError.swift */, 29FF12FD1F75EA3F00AFD326 /* Keystore.swift */, + 29282B541F7636080067F88D /* EtherScanServiceRequest.swift */, ); path = EtherClient; sourceTree = ""; @@ -451,6 +467,7 @@ 29A0E1861F706D0700BAAAED /* EthereumConverter.swift */, 29CAEB8F1F70A3DC00F7357D /* TransactionState.swift */, 294FE5651F72442D00754F31 /* Address.swift */, + 29282B521F7630970067F88D /* Token.swift */, ); path = Models; sourceTree = ""; @@ -464,6 +481,31 @@ path = Accounts; sourceTree = ""; }; + 29282B561F7636600067F88D /* Ethplorer */ = { + isa = PBXGroup; + children = ( + 29282B571F7636840067F88D /* GetTokensRequest.swift */, + ); + path = Ethplorer; + sourceTree = ""; + }; + 296106BD1F76391B0006164B /* Etherscan */ = { + isa = PBXGroup; + children = ( + 296106BE1F7639250006164B /* FetchTransactionsRequest.swift */, + ); + path = Etherscan; + sourceTree = ""; + }; + 296106C01F7640240006164B /* Tokens */ = { + isa = PBXGroup; + children = ( + 296106C11F76403A0006164B /* TokenViewCell.swift */, + 296106C31F7640C50006164B /* TokenViewCellViewModel.swift */, + ); + path = Tokens; + sourceTree = ""; + }; 2996F1441F6C9875005C33AE /* Settings */ = { isa = PBXGroup; children = ( @@ -913,6 +955,7 @@ buildActionMask = 2147483647; files = ( 29FF12FE1F75EA3F00AFD326 /* Keystore.swift in Sources */, + 296106C41F7640C50006164B /* TokenViewCellViewModel.swift in Sources */, 291EC9E21F70565A0004EDD0 /* Transaction.swift in Sources */, 291F52B11F6B814300B369AB /* MG Basic Math.swift in Sources */, 291EC9DD1F704D340004EDD0 /* TransactionDataStore.swift in Sources */, @@ -930,9 +973,12 @@ 29BE3FD21F707DC300F6BFC2 /* TransactionCoordinator.swift in Sources */, 291F52B51F6B814300B369AB /* SMP String Module.swift in Sources */, 291ED0921F6FA5D900E7E93A /* RequestViewController.swift in Sources */, + 296106C21F76403A0006164B /* TokenViewCell.swift in Sources */, 29850D251F6B27A800791A49 /* R.generated.swift in Sources */, 291ED08B1F6F5D2100E7E93A /* Bundle.swift in Sources */, + 296106BF1F7639250006164B /* FetchTransactionsRequest.swift in Sources */, 293B8B451F70A20200356286 /* TransactionViewCell.swift in Sources */, + 29282B531F7630970067F88D /* Token.swift in Sources */, 29C9F5FB1F720C050025C494 /* FloatLabelTextField.swift in Sources */, 296421951F70C1EC00EB363B /* LoadingView.swift in Sources */, 29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */, @@ -952,6 +998,8 @@ 29D72A2A1F6A8D1500CE9209 /* AppCoordinator.swift in Sources */, 29A13E331F6B1B7A00E432A2 /* AppStyle.swift in Sources */, 29FF12F81F747D6C00AFD326 /* Error.swift in Sources */, + 29282B551F7636080067F88D /* EtherScanServiceRequest.swift in Sources */, + 29282B581F7636840067F88D /* GetTokensRequest.swift in Sources */, 291F52A51F6B762300B369AB /* EtherServiceRequest.swift in Sources */, 29EB102A1F6CBD23000907A4 /* UIAlertController.swift in Sources */, 296AF9A51F736BA20058AF78 /* Config.swift in Sources */, diff --git a/Trust/EtherClient/EtherScanServiceRequest.swift b/Trust/EtherClient/EtherScanServiceRequest.swift new file mode 100644 index 000000000..b3bab7914 --- /dev/null +++ b/Trust/EtherClient/EtherScanServiceRequest.swift @@ -0,0 +1,3 @@ +// Copyright SIX DAY LLC, Inc. All rights reserved. + +import Foundation diff --git a/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift b/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift new file mode 100644 index 000000000..451c8d4bf --- /dev/null +++ b/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift @@ -0,0 +1,44 @@ +// Copyright SIX DAY LLC, Inc. All rights reserved. + +import Foundation +import APIKit + +struct FetchTransactionsRequest: APIKit.Request { + typealias Response = [Transaction] + + let address: String + + var baseURL: URL { + let config = Config() + return config.etherScanURL + } + + var method: HTTPMethod { + return .get + } + + var path: String { + return "" + } + + var parameters: Any? { + return [ + "module": "account", + "action": "txlist", + "address": address, + "startblock": "0", + "endblock": "99999999", + "sort": "asc", + "apikey": "7V8AMAVQWKNAZHZG8ARYB9SQWWKBBDA7S8", + ] + } + + func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { + if + let objectJSON = object as? [String: AnyObject], + let transactionJSON = objectJSON["result"] as? [[String: AnyObject]] { + return transactionJSON.map { .from(address: address, json: $0) } + } + return [] + } +} diff --git a/Trust/EtherClient/Ethplorer/GetTokensRequest.swift b/Trust/EtherClient/Ethplorer/GetTokensRequest.swift new file mode 100644 index 000000000..b3b913fbd --- /dev/null +++ b/Trust/EtherClient/Ethplorer/GetTokensRequest.swift @@ -0,0 +1,38 @@ +// Copyright SIX DAY LLC, Inc. All rights reserved. + +import Foundation +import APIKit + +struct GetTokensRequest: APIKit.Request { + typealias Response = [Token] + + let address: String + + var baseURL: URL { + let config = Config() + return config.ethplorerURL + } + + var method: HTTPMethod { + return .get + } + + var path: String { + return "getAddressInfo/\(address)" + } + + var parameters: Any? { + return [ + "apiKey": "freekey", + ] + } + + func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { + if + let objectJSON = object as? [String: AnyObject], + let tokensJSON = objectJSON["tokens"] as? [[String: AnyObject]] { + return tokensJSON.map { Token.from(address: address, json: $0) } + } + return [] + } +} diff --git a/Trust/Models/Token.swift b/Trust/Models/Token.swift new file mode 100644 index 000000000..b6c4fbc41 --- /dev/null +++ b/Trust/Models/Token.swift @@ -0,0 +1,24 @@ +// Copyright SIX DAY LLC, Inc. All rights reserved. + +import Foundation + +struct Token { + let address: Address + let name: String + let symbol: String + let totalSupply: String + let balance: Int64 +} + +extension Token { + static func from(address: String, json: [String: AnyObject]) -> Token { + let tokenInfo = json["tokenInfo"] as? [String: AnyObject] ?? [:] + return Token( + address: Address(address: address), + name: tokenInfo["name"] as? String ?? "", + symbol: tokenInfo["symbol"] as? String ?? "", + totalSupply: tokenInfo["symbol"] as? String ?? "", + balance: json["balance"] as? Int64 ?? 0 + ) + } +} diff --git a/Trust/Models/Transaction.swift b/Trust/Models/Transaction.swift index be4b98ee1..40d63e4a4 100644 --- a/Trust/Models/Transaction.swift +++ b/Trust/Models/Transaction.swift @@ -44,3 +44,38 @@ struct Transaction { return NSDate(timeIntervalSince1970: TimeInterval(timestamp) ?? 0) as Date } } + +extension Transaction { + 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 hex = (json["value"] as? String ?? "") + let value = BInt(hex) + return Transaction( + blockHash: blockHash, + blockNumber: blockNumber, + confirmations: confirmation, + cumulativeGasUsed: cumulativeGasUsed, + from: from, + to: to, + owner: address, + gas: gas, + gasPrice: gasPrice, + gasUsed: gasUsed, + hash: hash, + value: value, + timestamp: timestamp, + isError: isError + ) + } +} diff --git a/Trust/Settings/Config.swift b/Trust/Settings/Config.swift index dfc615c4d..bddb2de1d 100644 --- a/Trust/Settings/Config.swift +++ b/Trust/Settings/Config.swift @@ -43,4 +43,8 @@ struct Config { }() return URL(string: urlString)! } + + var ethplorerURL: URL { + return URL(string: "https://api.ethplorer.io/")! + } } diff --git a/Trust/Transactions/TransactionDataStore.swift b/Trust/Transactions/TransactionDataStore.swift index e5742a5fd..e8b9e22c0 100644 --- a/Trust/Transactions/TransactionDataStore.swift +++ b/Trust/Transactions/TransactionDataStore.swift @@ -17,101 +17,46 @@ class TransactionDataStore { let account: Account var transactions: [Transaction] = [] + var tokens: [Token] = [] init(account: Account) { self.account = account } func fetch() { - let request = FetchTransactionsRequest(address: account.address.address) - Session.send(request) { result in - switch result { - case .success(let response): - self.update(transactions: response) - case .failure(let error): - self.delegate?.didFail(with: error, viewModel: self.viewModel) - } - } + fetchTransactions() + fetchTokens() } func update(transactions: [Transaction]) { self.transactions = transactions - delegate?.didUpdate(viewModel: viewModel) } -} - -struct FetchTransactionsRequest: APIKit.Request { - typealias Response = [Transaction] - - let address: String - - var baseURL: URL { - let config = Config() - return config.etherScanURL - } - - var method: HTTPMethod { - return .get - } - var path: String { - return "" + func update(tokens: [Token]) { + self.tokens = tokens } - var parameters: Any? { - return [ - "module": "account", - "action": "txlist", - "address": address, - "startblock": "0", - "endblock": "99999999", - "sort": "asc", - "apikey": "7V8AMAVQWKNAZHZG8ARYB9SQWWKBBDA7S8", - ] + func fetchTransactions() { + let request = FetchTransactionsRequest(address: account.address.address) + Session.send(request) { result in + switch result { + case .success(let response): + self.update(transactions: response) + case .failure(let error): + self.delegate?.didFail(with: error, viewModel: self.viewModel) + } + } } - func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { - NSLog("transactions urlResponse \(urlResponse)") - if - let objectJSON = object as? [String: AnyObject], - let transactionJSON = objectJSON["result"] as? [[String: AnyObject]] { - let transactions: [Transaction] = transactionJSON.map { json in - - 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 hex = (json["value"] as? String ?? "") - let value = BInt(hex) - - return Transaction( - blockHash: blockHash, - blockNumber: blockNumber, - confirmations: confirmation, - cumulativeGasUsed: cumulativeGasUsed, - from: from, - to: to, - owner: address, - gas: gas, - gasPrice: gasPrice, - gasUsed: gasUsed, - hash: hash, - value: value, - timestamp: timestamp, - isError: isError - ) + func fetchTokens() { + let request = GetTokensRequest(address: account.address.address) + Session.send(request) { result in + switch result { + case .success(let response): + self.update(tokens: response) + case .failure(let error): + self.delegate?.didFail(with: error, viewModel: self.viewModel) } - return transactions } - return [] } }