Merge pull request #2302 from AlphaWallet/display-estimated-gas-in-actionsheet

Display estimated gas fee (and in fiat too, if available) in actionsheet for transaction confirmation
pull/2305/head
Hwee-Boon Yar 4 years ago committed by GitHub
commit d4d45a30eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      AlphaWallet/Browser/Coordinators/DappBrowserCoordinator.swift
  2. 2
      AlphaWallet/InCoordinator.swift
  3. 2
      AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift
  4. 4
      AlphaWallet/Transactions/Coordinators/TokensCardCoordinator.swift
  5. 10
      AlphaWallet/Transfer/Coordinators/TransactionConfirmationCoordinator.swift
  6. 6
      AlphaWallet/Transfer/Coordinators/TransferNFTCoordinator.swift
  7. 29
      AlphaWallet/Transfer/ViewControllers/TransactionConfirmationViewController.swift
  8. 81
      AlphaWallet/Transfer/ViewModels/TransactionConfirmationViewModel.swift

@ -40,6 +40,7 @@ final class DappBrowserCoordinator: NSObject, Coordinator {
private let sharedRealm: Realm
private let browserOnly: Bool
private let nativeCryptoCurrencyPrices: ServerDictionary<Subscribable<Double>>
private var nativeCryptoCurrencyBalanceView: NativeCryptoCurrencyBalanceView {
//Not the best implementation. Hopefully this will be unnecessary
@ -119,6 +120,7 @@ final class DappBrowserCoordinator: NSObject, Coordinator {
config: Config,
sharedRealm: Realm,
browserOnly: Bool,
nativeCryptoCurrencyPrices: ServerDictionary<Subscribable<Double>>,
analyticsCoordinator: AnalyticsCoordinator?
) {
self.navigationController = UINavigationController(navigationBarClass: DappBrowserNavigationBar.self, toolbarClass: nil)
@ -127,6 +129,7 @@ final class DappBrowserCoordinator: NSObject, Coordinator {
self.config = config
self.sharedRealm = sharedRealm
self.browserOnly = browserOnly
self.nativeCryptoCurrencyPrices = nativeCryptoCurrencyPrices
self.analyticsCoordinator = analyticsCoordinator
super.init()
@ -156,7 +159,8 @@ final class DappBrowserCoordinator: NSObject, Coordinator {
private func executeTransaction(account: AlphaWallet.Address, action: DappAction, callbackID: Int, transaction: UnconfirmedTransaction, type: ConfirmType, server: RPCServer) {
pendingTransaction = .data(callbackID: callbackID)
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .dappTransaction(confirmType: type, keystore: keystore), analyticsCoordinator: analyticsCoordinator)
let ethPrice = nativeCryptoCurrencyPrices[server]
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .dappTransaction(confirmType: type, keystore: keystore, ethPrice: ethPrice), analyticsCoordinator: analyticsCoordinator)
coordinator.delegate = self
addCoordinator(coordinator)
coordinator.start()

@ -444,7 +444,7 @@ class InCoordinator: NSObject, Coordinator {
}
private func createBrowserCoordinator(sessions: ServerDictionary<WalletSession>, realm: Realm, browserOnly: Bool, analyticsCoordinator: AnalyticsCoordinator?) -> DappBrowserCoordinator {
let coordinator = DappBrowserCoordinator(sessions: sessions, keystore: keystore, config: config, sharedRealm: realm, browserOnly: browserOnly, analyticsCoordinator: analyticsCoordinator)
let coordinator = DappBrowserCoordinator(sessions: sessions, keystore: keystore, config: config, sharedRealm: realm, browserOnly: browserOnly, nativeCryptoCurrencyPrices: nativeCryptoCurrencyPrices, analyticsCoordinator: analyticsCoordinator)
coordinator.delegate = self
coordinator.start()
coordinator.rootViewController.tabBarItem = UITabBarItem(title: R.string.localizable.browserTabbarItemTitle(), image: R.image.tab_browser(), selectedImage: nil)

@ -661,7 +661,7 @@ extension SingleChainTokenCoordinator: TokenInstanceActionViewControllerDelegate
func confirmTransactionSelected(in viewController: TokenInstanceActionViewController, tokenObject: TokenObject, contract: AlphaWallet.Address, tokenId: TokenId, values: [AttributeId: AssetInternalValue], localRefs: [AttributeId: AssetInternalValue], server: RPCServer, session: WalletSession, keystore: Keystore, transactionFunction: FunctionOrigin) {
switch transactionFunction.makeUnConfirmedTransaction(withTokenObject: tokenObject, tokenId: tokenId, attributeAndValues: values, localRefs: localRefs, server: server, session: session) {
case .success(let transaction):
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .tokenScriptTransaction(confirmType: .signThenSend, contract: contract, keystore: keystore), analyticsCoordinator: analyticsCoordinator)
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .tokenScriptTransaction(confirmType: .signThenSend, contract: contract, keystore: keystore, ethPrice: cryptoPrice), analyticsCoordinator: analyticsCoordinator)
coordinator.delegate = self
addCoordinator(coordinator)
coordinator.start()

@ -656,7 +656,7 @@ extension TokensCardCoordinator: TransferTokensCardViaWalletAddressViewControlle
switch paymentFlow {
case .send:
if case .send(let transferType) = paymentFlow {
let coordinator = TransferNFTCoordinator(navigationController: navigationController, transferType: transferType, tokenHolder: tokenHolder, recipient: recipient, keystore: keystore, session: session, analyticsCoordinator: analyticsCoordinator)
let coordinator = TransferNFTCoordinator(navigationController: navigationController, transferType: transferType, tokenHolder: tokenHolder, recipient: recipient, keystore: keystore, session: session, ethPrice: ethPrice, analyticsCoordinator: analyticsCoordinator)
addCoordinator(coordinator)
coordinator.delegate = self
coordinator.start()
@ -698,7 +698,7 @@ extension TokensCardCoordinator: TokenInstanceActionViewControllerDelegate {
func confirmTransactionSelected(in viewController: TokenInstanceActionViewController, tokenObject: TokenObject, contract: AlphaWallet.Address, tokenId: TokenId, values: [AttributeId: AssetInternalValue], localRefs: [AttributeId: AssetInternalValue], server: RPCServer, session: WalletSession, keystore: Keystore, transactionFunction: FunctionOrigin) {
switch transactionFunction.makeUnConfirmedTransaction(withTokenObject: tokenObject, tokenId: tokenId, attributeAndValues: values, localRefs: localRefs, server: server, session: session) {
case .success(let transaction):
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .tokenScriptTransaction(confirmType: .signThenSend, contract: contract, keystore: keystore), analyticsCoordinator: analyticsCoordinator)
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: .tokenScriptTransaction(confirmType: .signThenSend, contract: contract, keystore: keystore, ethPrice: ethPrice), analyticsCoordinator: analyticsCoordinator)
coordinator.delegate = self
addCoordinator(coordinator)
coordinator.start()

@ -11,22 +11,22 @@ import PromiseKit
import Result
enum TransactionConfirmationConfiguration {
case tokenScriptTransaction(confirmType: ConfirmType, contract: AlphaWallet.Address, keystore: Keystore)
case dappTransaction(confirmType: ConfirmType, keystore: Keystore)
case tokenScriptTransaction(confirmType: ConfirmType, contract: AlphaWallet.Address, keystore: Keystore, ethPrice: Subscribable<Double>)
case dappTransaction(confirmType: ConfirmType, keystore: Keystore, ethPrice: Subscribable<Double>)
case sendFungiblesTransaction(confirmType: ConfirmType, keystore: Keystore, assetDefinitionStore: AssetDefinitionStore, amount: String, ethPrice: Subscribable<Double>)
case sendNftTransaction(confirmType: ConfirmType, keystore: Keystore)
case sendNftTransaction(confirmType: ConfirmType, keystore: Keystore, ethPrice: Subscribable<Double>)
case claimPaidErc875MagicLink(confirmType: ConfirmType, keystore: Keystore, price: BigUInt, ethPrice: Subscribable<Double>, numberOfTokens: UInt)
var confirmType: ConfirmType {
switch self {
case .dappTransaction(let confirmType, _), .sendFungiblesTransaction(let confirmType, _, _, _, _), .sendNftTransaction(let confirmType, _), .tokenScriptTransaction(let confirmType, _, _), .claimPaidErc875MagicLink(let confirmType, _, _, _, _):
case .dappTransaction(let confirmType, _, _), .sendFungiblesTransaction(let confirmType, _, _, _, _), .sendNftTransaction(let confirmType, _, _), .tokenScriptTransaction(let confirmType, _, _, _), .claimPaidErc875MagicLink(let confirmType, _, _, _, _):
return confirmType
}
}
var keystore: Keystore {
switch self {
case .dappTransaction(_, let keystore), .sendFungiblesTransaction(_, let keystore, _, _, _), .sendNftTransaction(_, let keystore), .tokenScriptTransaction(_, _, let keystore), .claimPaidErc875MagicLink(_, let keystore, _, _, _):
case .dappTransaction(_, let keystore, _), .sendFungiblesTransaction(_, let keystore, _, _, _), .sendNftTransaction(_, let keystore, _), .tokenScriptTransaction(_, _, let keystore, _), .claimPaidErc875MagicLink(_, let keystore, _, _, _), .claimPaidErc875MagicLink(_, let keystore, _, _, _):
return keystore
}
}

@ -16,17 +16,19 @@ class TransferNFTCoordinator: Coordinator {
private let recipient: AlphaWallet.Address
private let keystore: Keystore
private let session: WalletSession
private let ethPrice: Subscribable<Double>
private let analyticsCoordinator: AnalyticsCoordinator?
var coordinators: [Coordinator] = []
weak var delegate: TransferNFTCoordinatorDelegate?
init(navigationController: UINavigationController, transferType: TransferType, tokenHolder: TokenHolder, recipient: AlphaWallet.Address, keystore: Keystore, session: WalletSession, analyticsCoordinator: AnalyticsCoordinator?) {
init(navigationController: UINavigationController, transferType: TransferType, tokenHolder: TokenHolder, recipient: AlphaWallet.Address, keystore: Keystore, session: WalletSession, ethPrice: Subscribable<Double>, analyticsCoordinator: AnalyticsCoordinator?) {
self.navigationController = navigationController
self.transferType = transferType
self.tokenHolder = tokenHolder
self.recipient = recipient
self.keystore = keystore
self.session = session
self.ethPrice = ethPrice
self.analyticsCoordinator = analyticsCoordinator
}
@ -40,7 +42,7 @@ class TransferNFTCoordinator: Coordinator {
tokenId: tokenHolder.tokens[0].id,
indices: tokenHolder.indices
)
let configuration: TransactionConfirmationConfiguration = .sendNftTransaction(confirmType: .signThenSend, keystore: keystore)
let configuration: TransactionConfirmationConfiguration = .sendNftTransaction(confirmType: .signThenSend, keystore: keystore, ethPrice: ethPrice)
let coordinator = TransactionConfirmationCoordinator(navigationController: navigationController, session: session, transaction: transaction, configuration: configuration, analyticsCoordinator: analyticsCoordinator)
addCoordinator(coordinator)
coordinator.delegate = self

@ -168,10 +168,18 @@ class TransactionConfirmationViewController: UIViewController {
}
switch viewModel {
case .dappTransaction:
break
case .tokenScriptTransaction:
break
case .dappTransaction(let dappTransactionViewModel):
dappTransactionViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else { return }
dappTransactionViewModel.cryptoToDollarRate = cryptoToDollarRate
strongSelf.generateSubviews()
}
case .tokenScriptTransaction(let tokenScriptTransactionViewModel):
tokenScriptTransactionViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else { return }
tokenScriptTransactionViewModel.cryptoToDollarRate = cryptoToDollarRate
strongSelf.generateSubviews()
}
case .sendFungiblesTransaction(let sendFungiblesViewModel):
sendFungiblesViewModel.recipientResolver.resolve { [weak self] in
guard let strongSelf = self else { return }
@ -185,24 +193,31 @@ class TransactionConfirmationViewController: UIViewController {
sendFungiblesViewModel.updateBalance(.nativeCryptocurrency(balanceViewModel: balanceBaseViewModel))
strongSelf.generateSubviews()
}
sendFungiblesViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else { return }
sendFungiblesViewModel.cryptoToDollarRate = cryptoToDollarRate
strongSelf.generateSubviews()
}
sendFungiblesViewModel.session.refresh(.ethBalance)
case .ERC20Token(let token, _, _):
sendFungiblesViewModel.updateBalance(.erc20(token: token))
case .ERC875Token, .ERC875TokenOrder, .ERC721Token, .ERC721ForTicketToken, .dapp, .tokenScript, .claimPaidErc875MagicLink:
break
sendFungiblesViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else { return }
sendFungiblesViewModel.cryptoToDollarRate = cryptoToDollarRate
strongSelf.generateSubviews()
}
}
case .sendNftTransaction(let sendNftViewModel):
sendNftViewModel.recipientResolver.resolve { [weak self] in
guard let strongSelf = self else { return }
strongSelf.generateSubviews()
}
sendNftViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else {return}
sendNftViewModel.cryptoToDollarRate = cryptoToDollarRate
strongSelf.generateSubviews()
}
case .claimPaidErc875MagicLink(let claimPaidErc875MagicLinkViewModel):
claimPaidErc875MagicLinkViewModel.ethPrice.subscribe { [weak self] cryptoToDollarRate in
guard let strongSelf = self else { return }

@ -12,16 +12,16 @@ enum TransactionConfirmationViewModel {
init(configurator: TransactionConfigurator, configuration: TransactionConfirmationConfiguration) {
switch configuration {
case .tokenScriptTransaction(_, let contract, _):
self = .tokenScriptTransaction(.init(address: contract))
case .dappTransaction:
self = .dappTransaction(.init(configurator: configurator))
case .tokenScriptTransaction(_, let contract, _, let ethPrice):
self = .tokenScriptTransaction(.init(address: contract, configurator: configurator, ethPrice: ethPrice))
case .dappTransaction(_, _, let ethPrice):
self = .dappTransaction(.init(configurator: configurator, ethPrice: ethPrice))
case .sendFungiblesTransaction(_, _, let assetDefinitionStore, let amount, let ethPrice):
let resolver = RecipientResolver(address: configurator.transaction.recipient)
self = .sendFungiblesTransaction(.init(configurator: configurator, assetDefinitionStore: assetDefinitionStore, recipientResolver: resolver, amount: amount, ethPrice: ethPrice))
case .sendNftTransaction:
case .sendNftTransaction(_, _, let ethPrice):
let resolver = RecipientResolver(address: configurator.transaction.recipient)
self = .sendNftTransaction(.init(configurator: configurator, recipientResolver: resolver))
self = .sendNftTransaction(.init(configurator: configurator, recipientResolver: resolver, ethPrice: ethPrice))
case .claimPaidErc875MagicLink(_, _, let price, let ethPrice, let numberOfTokens):
self = .claimPaidErc875MagicLink(.init(configurator: configurator, price: price, ethPrice: ethPrice, numberOfTokens: numberOfTokens))
}
@ -75,6 +75,19 @@ enum UpdateBalanceValue {
}
extension TransactionConfirmationViewModel {
private static func gasFeeString(withConfigurator configurator: TransactionConfigurator, cryptoToDollarRate: Double?) -> String {
let fee = configurator.currentConfiguration.gasPrice * configurator.currentConfiguration.gasLimit
let symbol = configurator.session.server.symbol
let feeString = EtherNumberFormatter.short.string(from: fee)
let cryptoToDollarSymbol = Constants.Currency.usd
if let cryptoToDollarRate = cryptoToDollarRate {
let cryptoToDollarValue = StringFormatter().currency(with: Double(fee) * cryptoToDollarRate / Double(EthereumUnit.ether.rawValue), and: cryptoToDollarSymbol)
return "< ~\(feeString) \(symbol) (\(cryptoToDollarValue) \(cryptoToDollarSymbol))"
} else {
return "< ~\(feeString) \(symbol)"
}
}
class SendFungiblesTransactionViewModel: SectionProtocol {
enum Section: Int, CaseIterable {
case balance
@ -101,9 +114,6 @@ extension TransactionConfirmationViewModel {
private var newBalance: String?
private let configurator: TransactionConfigurator
private let assetDefinitionStore: AssetDefinitionStore
private var defaultTitle: String {
R.string.localizable.tokenTransactionConfirmationDefault()
}
private var configurationTitle: String {
configurator.selectedConfigurationType.title
}
@ -169,6 +179,18 @@ extension TransactionConfirmationViewModel {
}
}
var gasFee: String {
let fee: BigInt = configurator.currentConfiguration.gasPrice * configurator.currentConfiguration.gasLimit
let feeString = EtherNumberFormatter.short.string(from: fee)
let cryptoToDollarSymbol = Constants.Currency.usd
if let cryptoToDollarRate = cryptoToDollarRate {
let cryptoToDollarValue = StringFormatter().currency(with: Double(fee) * cryptoToDollarRate / Double(EthereumUnit.ether.rawValue), and: cryptoToDollarSymbol)
return "< ~\(feeString) \(session.server.symbol) (\(cryptoToDollarValue) \(cryptoToDollarSymbol))"
} else {
return "< ~\(feeString) \(session.server.symbol)"
}
}
func isSubviewsHidden(section: Int, row: Int) -> Bool {
let isOpened = openedSections.contains(section)
@ -195,14 +217,14 @@ extension TransactionConfirmationViewModel {
section: section,
shouldHideChevron: sections[section] != .recipient
)
let placeholder = sections[section].title
switch sections[section] {
case .balance:
let title = R.string.localizable.tokenTransactionConfirmationDefault()
return .init(title: balance ?? title, placeholder: placeholder, details: newBalance, configuration: configuration)
case .gas:
return .init(title: configurationTitle, placeholder: placeholder, configuration: configuration)
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, placeholder: placeholder, details: gasFee, configuration: configuration)
case .amount:
return .init(title: formattedAmountValue, placeholder: placeholder, configuration: configuration)
case .recipient:
@ -223,21 +245,21 @@ extension TransactionConfirmationViewModel {
}
}
private let configurator: TransactionConfigurator
private var defaultTitle: String {
return R.string.localizable.tokenTransactionConfirmationDefault()
}
private var configurationTitle: String {
return configurator.selectedConfigurationType.title
}
var cryptoToDollarRate: Double?
let ethPrice: Subscribable<Double>
var openedSections = Set<Int>()
var sections: [Section] {
return Section.allCases
}
init(configurator: TransactionConfigurator) {
init(configurator: TransactionConfigurator, ethPrice: Subscribable<Double>) {
self.configurator = configurator
self.ethPrice = ethPrice
}
func isSubviewHidden(section: Int, row: Int) -> Bool {
@ -258,7 +280,8 @@ extension TransactionConfirmationViewModel {
let placeholder = sections[section].title
switch sections[section] {
case .gas:
return .init(title: configurationTitle, placeholder: placeholder, configuration: configuration)
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, placeholder: placeholder, details: gasFee, configuration: configuration)
}
}
}
@ -279,17 +302,22 @@ extension TransactionConfirmationViewModel {
}
private let address: AlphaWallet.Address
private var defaultTitle: String {
return R.string.localizable.tokenTransactionConfirmationDefault()
private let configurator: TransactionConfigurator
private var configurationTitle: String {
configurator.selectedConfigurationType.title
}
var cryptoToDollarRate: Double?
let ethPrice: Subscribable<Double>
var openedSections = Set<Int>()
var sections: [Section] {
return Section.allCases
}
init(address: AlphaWallet.Address) {
init(address: AlphaWallet.Address, configurator: TransactionConfigurator, ethPrice: Subscribable<Double>) {
self.address = address
self.configurator = configurator
self.ethPrice = ethPrice
}
func headerViewModel(section: Int) -> TransactionConfirmationHeaderViewModel {
@ -298,7 +326,8 @@ extension TransactionConfirmationViewModel {
let placeholder = sections[section].title
switch sections[section] {
case .gas:
return .init(title: defaultTitle, placeholder: placeholder, configuration: configuration)
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, placeholder: placeholder, details: gasFee, configuration: configuration)
case .contract:
return .init(title: address.truncateMiddle, placeholder: placeholder, configuration: configuration)
}
@ -331,23 +360,22 @@ extension TransactionConfirmationViewModel {
configurator.selectedConfigurationType.title
}
private var defaultTitle: String {
R.string.localizable.tokenTransactionConfirmationDefault()
}
var ensName: String? { recipientResolver.ensName }
var addressString: String? { recipientResolver.address?.eip55String }
var openedSections = Set<Int>()
let recipientResolver: RecipientResolver
var cryptoToDollarRate: Double?
let ethPrice: Subscribable<Double>
var sections: [Section] {
return Section.allCases
}
init(configurator: TransactionConfigurator, recipientResolver: RecipientResolver) {
init(configurator: TransactionConfigurator, recipientResolver: RecipientResolver, ethPrice: Subscribable<Double>) {
self.configurator = configurator
self.transferType = configurator.transaction.transferType
self.session = configurator.session
self.recipientResolver = recipientResolver
self.ethPrice = ethPrice
}
func isSubviewsHidden(section: Int, row: Int) -> Bool {
@ -379,7 +407,8 @@ extension TransactionConfirmationViewModel {
let placeholder = sections[section].title
switch sections[section] {
case .gas:
return .init(title: configurationTitle, placeholder: placeholder, configuration: configuration)
let gasFee = gasFeeString(withConfigurator: configurator, cryptoToDollarRate: cryptoToDollarRate)
return .init(title: configurationTitle, placeholder: placeholder, details: gasFee, configuration: configuration)
case .tokenId:
//TODO be good to display the token instance's name or equivalent too
let tokenId = configurator.transaction.tokenId.flatMap({ String($0) }) ?? ""

Loading…
Cancel
Save