Skip to content

Commit

Permalink
Add minimum fee rate to BTC fee settings
Browse files Browse the repository at this point in the history
  • Loading branch information
esen committed Aug 7, 2023
1 parent 6744f52 commit a02ea5d
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 84 deletions.
14 changes: 7 additions & 7 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,7 @@
2FA5D1897F90F77E6D9076AB /* LegacyEvmFeeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D735675BD26B75B7FFC7 /* LegacyEvmFeeDataSource.swift */; };
2FA5D18A57B386FD3A4384BA /* Eip1559EvmFeeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DEDF215D171796AA34FE /* Eip1559EvmFeeViewModel.swift */; };
2FA5D1A368FEA07EA308A94A /* BitcoinIncomingTransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DE3FC4EA4B42BC982C9A /* BitcoinIncomingTransactionRecord.swift */; };
2FA5D1E33A714D64DDAFA119 /* SendFeeWarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5CBE81863EDADA1D47D /* SendFeeWarningViewModel.swift */; };
2FA5D1E33A714D64DDAFA119 /* SendFeeCautionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5CBE81863EDADA1D47D /* SendFeeCautionViewModel.swift */; };
2FA5D1F6979A345597788DE9 /* NonceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D64286A249D6D106DDFC /* NonceService.swift */; };
2FA5D201AED8FB83968A5220 /* StepperAmountInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5A6EDE4A729F3DFF626 /* StepperAmountInputView.swift */; };
2FA5D20BC28280786A0FC3FE /* DropDownListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DB591688EEA6116C8323 /* DropDownListCell.swift */; };
Expand All @@ -1467,7 +1467,7 @@
2FA5D2FA6FE5298D991A7248 /* FeeRateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D96AE7C5E97B6E609CA5 /* FeeRateService.swift */; };
2FA5D2FE81DA1FB5A63C2D7C /* BitcoinCore+Hodler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D33978B54432713B047C /* BitcoinCore+Hodler.swift */; };
2FA5D342449885CF8D58B704 /* HdWalletExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DA265C9608D0BCF88F17 /* HdWalletExtensions.swift */; };
2FA5D37B3AD06F8249FA7959 /* SendFeeWarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5CBE81863EDADA1D47D /* SendFeeWarningViewModel.swift */; };
2FA5D37B3AD06F8249FA7959 /* SendFeeCautionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5CBE81863EDADA1D47D /* SendFeeCautionViewModel.swift */; };
2FA5D3893C3B6F2270F857C7 /* SendEvmCautionsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DD57DA8B88537341459E /* SendEvmCautionsFactory.swift */; };
2FA5D3AFFBE0E51DD79236EF /* LegacyEvmFeeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D735675BD26B75B7FFC7 /* LegacyEvmFeeDataSource.swift */; };
2FA5D3C3D3AB41202B592640 /* Eip1559EvmFeeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5BC7249DDB680769ADD /* Eip1559EvmFeeDataSource.swift */; };
Expand Down Expand Up @@ -3384,7 +3384,7 @@
2FA5D5A6EDE4A729F3DFF626 /* StepperAmountInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepperAmountInputView.swift; sourceTree = "<group>"; };
2FA5D5B2E57E92B6EF057356 /* FeeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeeCell.swift; sourceTree = "<group>"; };
2FA5D5BC7249DDB680769ADD /* Eip1559EvmFeeDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Eip1559EvmFeeDataSource.swift; sourceTree = "<group>"; };
2FA5D5CBE81863EDADA1D47D /* SendFeeWarningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendFeeWarningViewModel.swift; sourceTree = "<group>"; };
2FA5D5CBE81863EDADA1D47D /* SendFeeCautionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendFeeCautionViewModel.swift; sourceTree = "<group>"; };
2FA5D64286A249D6D106DDFC /* NonceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonceService.swift; sourceTree = "<group>"; };
2FA5D6522540716BACE4AD09 /* InputOutputOrderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputOutputOrderViewModel.swift; sourceTree = "<group>"; };
2FA5D657FD86FE9B675B475D /* TransactionInfoViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInfoViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5994,7 +5994,7 @@
2FA5D752137F74F62C01383E /* FeeWarning */ = {
isa = PBXGroup;
children = (
2FA5D5CBE81863EDADA1D47D /* SendFeeWarningViewModel.swift */,
2FA5D5CBE81863EDADA1D47D /* SendFeeCautionViewModel.swift */,
2FA5D8546E264BEEE654D17B /* SendFeeSettingsAmountCautionViewModel.swift */,
);
path = FeeWarning;
Expand Down Expand Up @@ -8808,7 +8808,7 @@
2FA5DF42271E59C1CE40B5DB /* FeeRateViewModel.swift in Sources */,
2FA5D2FA6FE5298D991A7248 /* FeeRateService.swift in Sources */,
2FA5DF8F364E6B5BED14C0B4 /* SendSettingsViewController.swift in Sources */,
2FA5D1E33A714D64DDAFA119 /* SendFeeWarningViewModel.swift in Sources */,
2FA5D1E33A714D64DDAFA119 /* SendFeeCautionViewModel.swift in Sources */,
2FA5DCCE2273577F8F12004F /* SendFeeSettingsAmountCautionViewModel.swift in Sources */,
2FA5D7C2BEF5739328BA8239 /* TimeLockDataSource.swift in Sources */,
2FA5DE46C61524144F406FDB /* TimeLockService.swift in Sources */,
Expand Down Expand Up @@ -10065,7 +10065,7 @@
2FA5DF43F20E03E94403555D /* FeeRateViewModel.swift in Sources */,
2FA5DF7570C7D9E33922CB03 /* FeeRateService.swift in Sources */,
2FA5DD05ECC5501DC33050C6 /* SendSettingsViewController.swift in Sources */,
2FA5D37B3AD06F8249FA7959 /* SendFeeWarningViewModel.swift in Sources */,
2FA5D37B3AD06F8249FA7959 /* SendFeeCautionViewModel.swift in Sources */,
2FA5D7CC1617FDF8D2EAFB56 /* SendFeeSettingsAmountCautionViewModel.swift in Sources */,
2FA5DA3E8B501D515A25E8A2 /* TimeLockDataSource.swift in Sources */,
2FA5D4AB0DEF37A103E1E130 /* TimeLockService.swift in Sources */,
Expand Down Expand Up @@ -10718,7 +10718,7 @@
repositoryURL = "https://github.com/horizontalsystems/FeeRateKit.Swift";
requirement = {
kind = exactVersion;
version = 2.0.0;
version = 2.1.0;
};
};
D3604E8328F03CDC0066C366 /* XCRemoteSwiftPackageReference "BinanceChainKit.Swift" */ = {
Expand Down
7 changes: 1 addition & 6 deletions UnstoppableWallet/UnstoppableWallet/Core/Protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,7 @@ protocol INftEventProvider {
}

protocol IFeeRateProvider {
func recommendedFeeRate() async throws -> Int
}

protocol ICustomRangedFeeRateProvider: IFeeRateProvider {
var customFeeRange: ClosedRange<Int> { get }
var step: Int { get }
func feeRates() async throws -> FeeRateProvider.FeeRates
}

protocol IAppManager {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct AppConfig {
static let appTwitterAccount = "unstoppablebyhs"
static let appTelegramAccount = "unstoppable_announcements"
static let appRedditAccount = "UNSTOPPABLEWallet"
static let btcCoreRpcUrl = "https://btc.blocksdecoded.com/rpc"
static let mempoolSpaceUrl = "https://mempool.space"
static let guidesIndexUrl = URL(string: "https://raw.githubusercontent.com/horizontalsystems/blockchain-crypto-guides/v1.2/index.json")!
static let faqIndexUrl = URL(string: "https://raw.githubusercontent.com/horizontalsystems/unstoppable-wallet-website/master/src/faq.json")!

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ class FeeRateProvider {
ethEvmUrl: FeeProviderConfig.infuraUrl(projectId: AppConfig.infuraCredentials.id),
ethEvmAuth: AppConfig.infuraCredentials.secret,
bscEvmUrl: FeeProviderConfig.defaultBscEvmUrl,
btcCoreRpcUrl: AppConfig.btcCoreRpcUrl,
btcCoreRpcUser: nil,
btcCoreRpcPassword: nil
mempoolSpaceUrl: AppConfig.mempoolSpaceUrl
)
feeRateKit = FeeRateKit.Kit.instance(providerConfig: providerConfig, minLogLevel: .error)
}
Expand All @@ -27,38 +25,43 @@ class FeeRateProvider {
// feeRateKit.binanceSmartChain
// }

var litecoinFeeRate: Int {
fileprivate var litecoinFeeRate: Int {
feeRateKit.litecoin
}

var bitcoinCashFeeRate: Int {
fileprivate var bitcoinCashFeeRate: Int {
feeRateKit.bitcoinCash
}

var dashFeeRate: Int {
fileprivate var dashFeeRate: Int {
feeRateKit.dash
}

func bitcoinFeeRate(blockCount: Int) async throws -> Int {
try await feeRateKit.bitcoin(blockCount: blockCount)
fileprivate func bitcoinFeeRate() async throws -> MempoolSpaceProvider.RecommendedFees {
try await feeRateKit.bitcoin()
}

}

class BitcoinFeeRateProvider: ICustomRangedFeeRateProvider {
static let defaultFeeRange: ClosedRange<Int> = 1...200
let customFeeRange: ClosedRange<Int> = BitcoinFeeRateProvider.defaultFeeRange
let step: Int = 1
extension FeeRateProvider {

struct FeeRates {
let recommended: Int
let minimum: Int
}

}

class BitcoinFeeRateProvider: IFeeRateProvider {
private let feeRateProvider: FeeRateProvider
private let recommendedBlockCount = 2

init(feeRateProvider: FeeRateProvider) {
self.feeRateProvider = feeRateProvider
}

func recommendedFeeRate() async throws -> Int {
try await feeRateProvider.bitcoinFeeRate(blockCount: recommendedBlockCount)
func feeRates() async throws -> FeeRateProvider.FeeRates {
let rates = try await feeRateProvider.bitcoinFeeRate()
return .init(recommended: rates.economyFee, minimum: rates.minimumFee)
}

}
Expand All @@ -70,8 +73,8 @@ class LitecoinFeeRateProvider: IFeeRateProvider {
self.feeRateProvider = feeRateProvider
}

func recommendedFeeRate() async throws -> Int {
feeRateProvider.litecoinFeeRate
func feeRates() async throws -> FeeRateProvider.FeeRates {
.init(recommended: feeRateProvider.litecoinFeeRate, minimum: 0)
}

}
Expand All @@ -83,16 +86,16 @@ class BitcoinCashFeeRateProvider: IFeeRateProvider {
self.feeRateProvider = feeRateProvider
}

func recommendedFeeRate() async throws -> Int {
feeRateProvider.bitcoinCashFeeRate
func feeRates() async throws -> FeeRateProvider.FeeRates {
.init(recommended: feeRateProvider.bitcoinCashFeeRate, minimum: 0)
}

}

class ECashFeeRateProvider: IFeeRateProvider {

func recommendedFeeRate() async throws -> Int {
1
func feeRates() async throws -> FeeRateProvider.FeeRates {
.init(recommended: 1, minimum: 0)
}

}
Expand All @@ -104,8 +107,8 @@ class DashFeeRateProvider: IFeeRateProvider {
self.feeRateProvider = feeRateProvider
}

func recommendedFeeRate() async throws -> Int {
feeRateProvider.dashFeeRate
func feeRates() async throws -> FeeRateProvider.FeeRates {
.init(recommended: feeRateProvider.dashFeeRate, minimum: 0)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import Foundation
import RxSwift
import RxRelay
import RxCocoa

class SendFeeWarningViewModel {

class SendFeeCautionViewModel {
private let disposeBag = DisposeBag()
private let service: FeeRateService
private let cautionTitle: String
private let cautionText: String

private let cautionRelay = BehaviorRelay<TitledCaution?>(value: nil)
var caution: TitledCaution? {
Expand All @@ -18,26 +16,33 @@ class SendFeeWarningViewModel {
}
}

init(service: FeeRateService, cautionTitle: String = "send.fee_settings.stuck_warning.title".localized, cautionText: String = "send.fee_settings.stuck_warning".localized) {
init(service: FeeRateService) {
self.service = service
self.cautionTitle = cautionTitle
self.cautionText = cautionText

subscribe(disposeBag, service.statusObservable) { [weak self] _ in self?.sync() }
sync()
}

private func sync() {
guard service.feeRateAvailble else {
caution = TitledCaution(title: "send.fee_settings.fee_error.title".localized, text: "send.fee_settings.fee_rate_unavailable".localized, type: .error)
return
}

if case let .completed(feeRate) = service.status, service.recommendedFeeRate > feeRate {
caution = TitledCaution(title: cautionTitle, text: cautionText, type: .warning)
if service.minimumFeeRate <= feeRate {
caution = TitledCaution(title: "send.fee_settings.stuck_warning.title".localized, text: "send.fee_settings.stuck_warning".localized, type: .warning)
} else {
caution = TitledCaution(title: "send.fee_settings.fee_error.title".localized, text: "send.fee_settings.too_low".localized, type: .error)
}
} else {
caution = nil
}
}

}

extension SendFeeWarningViewModel: ITitledCautionViewModel {
extension SendFeeCautionViewModel: ITitledCautionViewModel {

var cautionDriver: Driver<TitledCaution?> {
cautionRelay.asDriver()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ class SendBitcoinAdapterService {
}

private func sync(feeRate: DataStatus<Int>? = nil, updatedFrom: UpdatedField = .feeRate) {
let feeRate = feeRate ?? feeRateService.status
let feeRateStatus = feeRate ?? feeRateService.status
let amount = amountInputService.amount
var feeRate = 0

switch feeRate {
switch feeRateStatus {
case .loading:
guard !amount.isZero else { // force update fee for bitcoin, when clear amount to zero value
feeState = .completed(0)
Expand All @@ -116,9 +117,11 @@ class SendBitcoinAdapterService {
feeState = .loading
case .failed(let error):
feeState = .failed(error)
case .completed(let feeRate):
update(feeRate: feeRate, amount: amount, address: addressService.state.address?.raw, pluginData: pluginData, updatedFrom: updatedFrom)
case .completed(let _feeRate):
feeRate = _feeRate
}

update(feeRate: feeRate, amount: amount, address: addressService.state.address?.raw, pluginData: pluginData, updatedFrom: updatedFrom)
}

private func update(feeRate: Int, amount: Decimal, address: String?, pluginData: [UInt8: IBitcoinPluginData], updatedFrom: UpdatedField) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,10 @@ class SendBitcoinFactory: BaseSendFactory {
private let addressService: AddressService
private let timeLockService: TimeLockService?
private let adapterService: SendBitcoinAdapterService
private let customFeeRateProvider: ICustomRangedFeeRateProvider?
private let logger: Logger
private let token: Token

init(fiatService: FiatService, amountCautionService: SendAmountCautionService, addressService: AddressService, feeFiatService: FiatService, feeService: SendFeeService, feeRateService: FeeRateService, timeLockService: TimeLockService?, adapterService: SendBitcoinAdapterService, customFeeRateProvider: ICustomRangedFeeRateProvider?, logger: Logger, token: Token) {
init(fiatService: FiatService, amountCautionService: SendAmountCautionService, addressService: AddressService, feeFiatService: FiatService, feeService: SendFeeService, feeRateService: FeeRateService, timeLockService: TimeLockService?, adapterService: SendBitcoinAdapterService, logger: Logger, token: Token) {
self.fiatService = fiatService
self.amountCautionService = amountCautionService
self.feeFiatService = feeFiatService
Expand All @@ -85,7 +84,6 @@ class SendBitcoinFactory: BaseSendFactory {
self.addressService = addressService
self.timeLockService = timeLockService
self.adapterService = adapterService
self.customFeeRateProvider = customFeeRateProvider
self.logger = logger
self.token = token
}
Expand Down Expand Up @@ -133,7 +131,7 @@ extension SendBitcoinFactory: ISendFeeSettingsFactory {
var dataSources: [ISendSettingsDataSource] = []

let feeViewModel = SendFeeViewModel(service: feeService)
let feeCautionViewModel = SendFeeWarningViewModel(service: feeRateService)
let feeCautionViewModel = SendFeeCautionViewModel(service: feeRateService)
let amountCautionViewModel = SendFeeSettingsAmountCautionViewModel(service: amountCautionService, feeToken: token)
let feeRateViewModel = FeeRateViewModel(service: feeRateService, feeCautionViewModel: feeCautionViewModel, amountCautionViewModel: amountCautionViewModel)
if token.blockchainType == .bitcoin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import RxCocoa
class SendBitcoinViewController: BaseSendViewController {
private let disposeBag = DisposeBag()

private let feeWarningViewModel: ITitledCautionViewModel
private let feeCautionViewModel: ITitledCautionViewModel

private let feeCell: FeeCell
private let feeCautionCell = TitledHighlightedDescriptionCell()
Expand All @@ -21,10 +21,10 @@ class SendBitcoinViewController: BaseSendViewController {
amountCautionViewModel: SendAmountCautionViewModel,
recipientViewModel: RecipientAddressViewModel,
feeViewModel: SendFeeViewModel,
feeWarningViewModel: ITitledCautionViewModel
feeCautionViewModel: ITitledCautionViewModel
) {

self.feeWarningViewModel = feeWarningViewModel
self.feeCautionViewModel = feeCautionViewModel

feeCell = FeeCell(viewModel: feeViewModel, title: "fee_settings.fee".localized)

Expand All @@ -50,7 +50,7 @@ class SendBitcoinViewController: BaseSendViewController {
self?.openInfo(title: "fee_settings.fee".localized, description: "fee_settings.fee.info".localized)
}

subscribe(disposeBag, feeWarningViewModel.cautionDriver) { [weak self] in
subscribe(disposeBag, feeCautionViewModel.cautionDriver) { [weak self] in
self?.handle(caution: $0)
}

Expand Down Expand Up @@ -86,14 +86,14 @@ class SendBitcoinViewController: BaseSendViewController {
)
}

var feeWarningSection: SectionProtocol {
var feeCautionSection: SectionProtocol {
Section(
id: "fee-warning",
id: "fee-caution",
headerState: .margin(height: .margin12),
rows: [
StaticRow(
cell: feeCautionCell,
id: "fee-warning",
id: "fee-caution",
dynamicHeight: { [weak self] containerWidth in
self?.feeCautionCell.cellHeight(containerWidth: containerWidth) ?? 0
}
Expand All @@ -104,7 +104,7 @@ class SendBitcoinViewController: BaseSendViewController {

override func buildSections() -> [SectionProtocol] {
var sections = [availableBalanceSection, amountSection, recipientSection, feeSection]
sections.append(contentsOf: [feeWarningSection, buttonSection])
sections.append(contentsOf: [feeCautionSection, buttonSection])

return sections
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,7 @@ class SendModule {

// Fee
let feeViewModel = SendFeeViewModel(service: feeService)
let feeWarningViewModel = SendFeeWarningViewModel(service: feeRateService)

// Confirmation and Settings
let customRangedFeeRateProvider = feeRateProvider as? ICustomRangedFeeRateProvider
let feeCautionViewModel = SendFeeCautionViewModel(service: feeRateService)

let sendFactory = SendBitcoinFactory(
fiatService: fiatService,
Expand All @@ -141,7 +138,6 @@ class SendModule {
feeRateService: feeRateService,
timeLockService: timeLockService,
adapterService: bitcoinAdapterService,
customFeeRateProvider: customRangedFeeRateProvider,
logger: App.shared.logger,
token: token
)
Expand All @@ -155,7 +151,7 @@ class SendModule {
amountCautionViewModel: amountCautionViewModel,
recipientViewModel: recipientViewModel,
feeViewModel: feeViewModel,
feeWarningViewModel: feeWarningViewModel
feeCautionViewModel: feeCautionViewModel
)

return ThemeNavigationController(rootViewController: viewController)
Expand Down
Loading

0 comments on commit a02ea5d

Please sign in to comment.