From ba6db3cfdd28ad2549a44e4866151fb88e91530a Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 25 Jan 2024 17:23:15 +0100 Subject: [PATCH 1/5] add preferred collators for parachain staking --- novawallet.xcodeproj/project.pbxproj | 4 + .../Helpers/CancellableCallHelper.swift | 27 ++++ novawallet/Common/Model/KnownChainIds.swift | 1 + .../Staking/Model/StakingConstants.swift | 3 +- .../ParaStkPreferredCollatorFactory.swift | 120 ++++++++++++++++++ .../Parachain/Model/CollatorsSortType.swift | 41 ++++-- .../ParaStkSelectCollatorsPresenter.swift | 5 +- .../ParaStkSelectCollatorsViewFactory.swift | 3 + .../ParaStkStakeSetupInteractor.swift | 53 ++++++-- .../ParaStkStakeSetupPresenter.swift | 22 +++- .../ParaStkStakeSetupProtocols.swift | 1 + .../ParaStkStakeSetupViewFactory.swift | 25 +++- 12 files changed, 278 insertions(+), 27 deletions(-) create mode 100644 novawallet/Modules/Staking/Operations/ParachainStaking/ParaStkPreferredCollatorFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 112e399f41..6294eb6e88 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -357,6 +357,7 @@ 0CCA245D2AC6918800AEF23D /* XcmV3Junction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA245C2AC6918800AEF23D /* XcmV3Junction.swift */; }; 0CCA245F2AC6974200AEF23D /* XcmV3Multilocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA245E2AC6974200AEF23D /* XcmV3Multilocation.swift */; }; 0CCA24652AC6B51200AEF23D /* AssetHubSwapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA24642AC6B51200AEF23D /* AssetHubSwapTests.swift */; }; + 0CCCDF742B62AA3400473D42 /* ParaStkPreferredCollatorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCCDF732B62AA3400473D42 /* ParaStkPreferredCollatorFactory.swift */; }; 0CCE25212A44306200286709 /* TransactionHistoryPhishingFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCE25202A44306200286709 /* TransactionHistoryPhishingFilter.swift */; }; 0CD1F4D100ED82D137AB9834 /* ParaStkStakeSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2F1EEBF48485F02BF690A4 /* ParaStkStakeSetupViewController.swift */; }; 0CD352932ACAD7A500B3E446 /* AssetHubExtrinsicService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD352922ACAD7A500B3E446 /* AssetHubExtrinsicService.swift */; }; @@ -4591,6 +4592,7 @@ 0CCA245C2AC6918800AEF23D /* XcmV3Junction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcmV3Junction.swift; sourceTree = ""; }; 0CCA245E2AC6974200AEF23D /* XcmV3Multilocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcmV3Multilocation.swift; sourceTree = ""; }; 0CCA24642AC6B51200AEF23D /* AssetHubSwapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetHubSwapTests.swift; sourceTree = ""; }; + 0CCCDF732B62AA3400473D42 /* ParaStkPreferredCollatorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParaStkPreferredCollatorFactory.swift; sourceTree = ""; }; 0CCE25202A44306200286709 /* TransactionHistoryPhishingFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryPhishingFilter.swift; sourceTree = ""; }; 0CD352922ACAD7A500B3E446 /* AssetHubExtrinsicService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetHubExtrinsicService.swift; sourceTree = ""; }; 0CD352942ACAF59900B3E446 /* BigRational.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigRational.swift; sourceTree = ""; }; @@ -16204,6 +16206,7 @@ 841E5558282E6A7700C8438F /* ParaStkDurationOperationFactory.swift */, 84C3420628314D9600156569 /* ParaStkScheduledRequestsQueryFactory.swift */, 84C11F162840BD46007F7C05 /* ParaStkCollatorsOperationFactory.swift */, + 0CCCDF732B62AA3400473D42 /* ParaStkPreferredCollatorFactory.swift */, ); path = ParachainStaking; sourceTree = ""; @@ -22833,6 +22836,7 @@ 8493D0E326FF571D00A28008 /* PriceProviderFactory.swift in Sources */, 8858917629A4BC7D00320896 /* VotesContentView.swift in Sources */, F7EB8F835CFA7FC949EF4C22 /* YourValidatorListWireframe.swift in Sources */, + 0CCCDF742B62AA3400473D42 /* ParaStkPreferredCollatorFactory.swift in Sources */, AEA0C8AC267B6B5200F9666F /* SelectedValidatorListViewFactory.swift in Sources */, 84ED6BE428698D2E00B3C558 /* TransferCrossChainConfirmInteractor.swift in Sources */, DBA436B3B1C90965FE8F9B79 /* YourValidatorListPresenter.swift in Sources */, diff --git a/novawallet/Common/Helpers/CancellableCallHelper.swift b/novawallet/Common/Helpers/CancellableCallHelper.swift index 10fa5864ba..33399cedab 100644 --- a/novawallet/Common/Helpers/CancellableCallHelper.swift +++ b/novawallet/Common/Helpers/CancellableCallHelper.swift @@ -100,3 +100,30 @@ func execute( operationQueue.addOperations([operation], waitUntilFinished: false) } + +func execute( + operation: BaseOperation, + inOperationQueue operationQueue: OperationQueue, + backingCallIn callStore: CancellableCallStore, + runningCallbackIn callbackQueue: DispatchQueue?, + callbackClosure: @escaping (Result) -> Void +) { + operation.completionBlock = { + dispatchInQueueWhenPossible(callbackQueue) { + guard callStore.clearIfMatches(call: operation) else { + return + } + + do { + let value = try operation.extractNoCancellableResultData() + callbackClosure(.success(value)) + } catch { + callbackClosure(.failure(error)) + } + } + } + + callStore.store(call: operation) + + operationQueue.addOperations([operation], waitUntilFinished: false) +} diff --git a/novawallet/Common/Model/KnownChainIds.swift b/novawallet/Common/Model/KnownChainIds.swift index e530b57b21..d0b60ff307 100644 --- a/novawallet/Common/Model/KnownChainIds.swift +++ b/novawallet/Common/Model/KnownChainIds.swift @@ -26,6 +26,7 @@ enum KnowChainId { static let rococo = "a84b46a3e602245284bb9a72c4abd58ee979aa7a5d7f8c4dfdddfaaf0665a4ae" static let westend = "e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e" static let westmint = "67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9" + static let polimec = "7eb9354488318e7549c722669dcbdcdc526f1fef1420e7944667212f3601fdbd" static var kiltOnEnviroment: String { #if F_DEV diff --git a/novawallet/Modules/Staking/Model/StakingConstants.swift b/novawallet/Modules/Staking/Model/StakingConstants.swift index ff44ce60c8..0763a8a6f3 100644 --- a/novawallet/Modules/Staking/Model/StakingConstants.swift +++ b/novawallet/Modules/Staking/Model/StakingConstants.swift @@ -17,7 +17,8 @@ struct StakingConstants { "DhK6qU2U5kDWeJKvPRtmnWRs8ETUGZ9S9QmNmQFuzrNoKm4", "EtETk1FbrDg7FoAfkREuXT7xHxCjbEf28sBvWf6zfB5wFyV" ], - KnowChainId.alephZero: ["5DBhSX89qijHkzUt9gcqsq9RiXxDfbjxyma1z78JSCdt4SoU"] + KnowChainId.alephZero: ["5DBhSX89qijHkzUt9gcqsq9RiXxDfbjxyma1z78JSCdt4SoU"], + KnowChainId.polimec: ["5A5Qgq3wn6JeH8Qtu7rakxULpBhtyqyX8iNj1XV8WFg3U58T"] ] static func preferredValidatorIds(for chain: ChainModel) -> [AccountId] { diff --git a/novawallet/Modules/Staking/Operations/ParachainStaking/ParaStkPreferredCollatorFactory.swift b/novawallet/Modules/Staking/Operations/ParachainStaking/ParaStkPreferredCollatorFactory.swift new file mode 100644 index 0000000000..3a861e861b --- /dev/null +++ b/novawallet/Modules/Staking/Operations/ParachainStaking/ParaStkPreferredCollatorFactory.swift @@ -0,0 +1,120 @@ +import Foundation +import SubstrateSdk +import RobinHood + +protocol ParaStkPreferredCollatorFactoryProtocol { + func createPreferredCollatorWrapper() -> CompoundOperationWrapper +} + +final class ParaStkPreferredCollatorFactory { + let chain: ChainModel + let connection: JSONRPCEngine + let runtimeService: RuntimeCodingServiceProtocol + let identityOperationFactory: IdentityOperationFactoryProtocol + let collatorService: ParachainStakingCollatorServiceProtocol + let rewardService: ParaStakingRewardCalculatorServiceProtocol + let operationQueue: OperationQueue + + init( + chain: ChainModel, + connection: JSONRPCEngine, + runtimeService: RuntimeCodingServiceProtocol, + collatorService: ParachainStakingCollatorServiceProtocol, + rewardService: ParaStakingRewardCalculatorServiceProtocol, + identityOperationFactory: IdentityOperationFactoryProtocol, + operationQueue: OperationQueue + ) { + self.chain = chain + self.connection = connection + self.runtimeService = runtimeService + self.rewardService = rewardService + self.collatorService = collatorService + self.identityOperationFactory = identityOperationFactory + self.operationQueue = operationQueue + } + + private func createResultWrapper( + dependingOn mergeOperation: BaseOperation + ) -> CompoundOperationWrapper { + OperationCombiningService.compoundNonOptionalWrapper( + operationManager: OperationManager(operationQueue: operationQueue) + ) { + let optAccountId = try mergeOperation.extractNoCancellableResultData() + + guard let accountId = optAccountId else { + return CompoundOperationWrapper.createWithResult(nil) + } + + let identityWrapper = self.identityOperationFactory.createIdentityWrapper( + for: { [accountId] }, + engine: self.connection, + runtimeService: self.runtimeService, + chainFormat: self.chain.chainFormat + ) + + let mappingOperation = ClosureOperation { + let identities = try identityWrapper.targetOperation.extractNoCancellableResultData() + let address = try accountId.toAddress(using: self.chain.chainFormat) + let name = identities[address]?.displayName + + return DisplayAddress(address: address, username: name ?? "") + } + + mappingOperation.addDependency(identityWrapper.targetOperation) + + return CompoundOperationWrapper( + targetOperation: mappingOperation, + dependencies: identityWrapper.allOperations + ) + } + } +} + +extension ParaStkPreferredCollatorFactory: ParaStkPreferredCollatorFactoryProtocol { + func createPreferredCollatorWrapper() -> CompoundOperationWrapper { + let preferredCollators = StakingConstants.preferredValidatorIds(for: chain) + + guard !preferredCollators.isEmpty else { + return CompoundOperationWrapper.createWithResult(nil) + } + + let collatorsSet = Set(preferredCollators) + + let collatorsOperation = collatorService.fetchInfoOperation() + let rewardOperation = rewardService.fetchCalculatorOperation() + + let mergeOperation = ClosureOperation { + let collators = try collatorsOperation.extractNoCancellableResultData().collators + let rewardsCalculator = try rewardOperation.extractNoCancellableResultData() + + let optCollator = collators + .filter { collatorsSet.contains($0.accountId) } + .sorted { col1, col2 in + let optApr1 = try? rewardsCalculator.calculateAPR(for: col1.accountId) + let optApr2 = try? rewardsCalculator.calculateAPR(for: col2.accountId) + + if let apr1 = optApr1, let apr2 = optApr2 { + return apr1 > apr2 + } else if optApr1 != nil { + return true + } else { + return false + } + } + .first + + return optCollator?.accountId + } + + mergeOperation.addDependency(collatorsOperation) + mergeOperation.addDependency(rewardOperation) + + let resultWrapper = createResultWrapper(dependingOn: mergeOperation) + resultWrapper.addDependency(operations: [mergeOperation]) + + let dependencies = [collatorsOperation, rewardOperation] + [mergeOperation] + + resultWrapper.dependencies + + return CompoundOperationWrapper(targetOperation: resultWrapper.targetOperation, dependencies: dependencies) + } +} diff --git a/novawallet/Modules/Staking/Parachain/Model/CollatorsSortType.swift b/novawallet/Modules/Staking/Parachain/Model/CollatorsSortType.swift index e52b5debf3..79209dfc96 100644 --- a/novawallet/Modules/Staking/Parachain/Model/CollatorsSortType.swift +++ b/novawallet/Modules/Staking/Parachain/Model/CollatorsSortType.swift @@ -10,16 +10,37 @@ enum CollatorsSortType: Equatable { } extension Array where Element == CollatorSelectionInfo { - func sortedByType(_ type: CollatorsSortType) -> [CollatorSelectionInfo] { - switch type { - case .rewards: - return sorted { ($0.apr ?? 0) > ($1.apr ?? 0) } - case .minStake: - return sorted { $0.minRewardableStake < $1.minRewardableStake } - case .totalStake: - return sorted { $0.totalStake > $1.totalStake } - case .ownStake: - return sorted { $0.ownStake > $1.ownStake } + func sortedByType( + _ type: CollatorsSortType, + preferredCollators: Set + ) -> [CollatorSelectionInfo] { + sorted { item1, item2 in + CompoundComparator.compare(list: [{ + let isItem1Pref = preferredCollators.contains(item1.accountId) + let isItem2Pref = preferredCollators.contains(item2.accountId) + + if isItem1Pref, !isItem2Pref { + return .orderedAscending + } else if !isItem1Pref, isItem2Pref { + return .orderedDescending + } else { + return .orderedSame + } + }, { + switch type { + case .rewards: + return (item1.apr ?? 0) > (item2.apr ?? 0) ? .orderedAscending : .orderedDescending + case .minStake: + return item1.minRewardableStake < item2.minRewardableStake ? .orderedAscending : + .orderedDescending + case .totalStake: + return item1.totalStake > item2.totalStake ? .orderedAscending : + .orderedDescending + case .ownStake: + return item1.ownStake > item2.ownStake ? .orderedAscending : + .orderedDescending + } + }]) } } } diff --git a/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsPresenter.swift b/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsPresenter.swift index c04bccee4e..b90303f6c0 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsPresenter.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsPresenter.swift @@ -20,12 +20,14 @@ final class ParaStkSelectCollatorsPresenter { let chainAsset: ChainAsset let balanceViewModelFactory: BalanceViewModelFactoryProtocol + let preferredCollators: Set let logger: LoggerProtocol init( interactor: ParaStkSelectCollatorsInteractorInputProtocol, wireframe: ParaStkSelectCollatorsWireframeProtocol, delegate: ParaStkSelectCollatorsDelegate, + preferredCollators: Set, chainAsset: ChainAsset, balanceViewModelFactory: BalanceViewModelFactoryProtocol, localizationManager: LocalizationManagerProtocol, @@ -34,6 +36,7 @@ final class ParaStkSelectCollatorsPresenter { self.interactor = interactor self.wireframe = wireframe self.delegate = delegate + self.preferredCollators = preferredCollators self.chainAsset = chainAsset self.balanceViewModelFactory = balanceViewModelFactory self.logger = logger @@ -206,7 +209,7 @@ final class ParaStkSelectCollatorsPresenter { private func applySortingAndSaveResult(_ result: Result<[CollatorSelectionInfo], Error>) { collatorsInfoResult = result.map { - $0.sortedByType(sorting) + $0.sortedByType(sorting, preferredCollators: preferredCollators) } } } diff --git a/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsViewFactory.swift b/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsViewFactory.swift index 272d46eeea..2e382a9186 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsViewFactory.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkSelectCollators/ParaStkSelectCollatorsViewFactory.swift @@ -26,10 +26,13 @@ struct ParaStkSelectCollatorsViewFactory { let localizationManager = LocalizationManager.shared + let preferredCollators = Set(StakingConstants.preferredValidatorIds(for: chainAsset.chain)) + let presenter = ParaStkSelectCollatorsPresenter( interactor: interactor, wireframe: wireframe, delegate: delegate, + preferredCollators: preferredCollators, chainAsset: chainAsset, balanceViewModelFactory: balanceViewModelFactory, localizationManager: localizationManager, diff --git a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupInteractor.swift b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupInteractor.swift index c62bb33490..6d5fd5a1c9 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupInteractor.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupInteractor.swift @@ -10,7 +10,7 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { let selectedAccount: MetaChainAccountResponse let walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol let priceLocalSubscriptionFactory: PriceProviderFactoryProtocol - let collatorService: ParachainStakingCollatorServiceProtocol + let preferredCollatorFactory: ParaStkPreferredCollatorFactoryProtocol? let rewardService: ParaStakingRewardCalculatorServiceProtocol let extrinsicService: ExtrinsicServiceProtocol let feeProxy: ExtrinsicFeeProxyProtocol @@ -26,6 +26,8 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { private var collatorSubscription: CallbackStorageSubscription? private var delegatorProvider: AnyDataProvider? private var scheduledRequestsProvider: StreamableProvider? + private var collatorsCancellable = CancellableCallStore() + private var delegatorIdentityCancellable = CancellableCallStore() private lazy var localKeyFactory = LocalStorageKeyFactory() @@ -35,7 +37,7 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { stakingLocalSubscriptionFactory: ParachainStakingLocalSubscriptionFactoryProtocol, walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, - collatorService: ParachainStakingCollatorServiceProtocol, + preferredCollatorFactory: ParaStkPreferredCollatorFactoryProtocol?, rewardService: ParaStakingRewardCalculatorServiceProtocol, extrinsicService: ExtrinsicServiceProtocol, feeProxy: ExtrinsicFeeProxyProtocol, @@ -51,7 +53,7 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { self.stakingLocalSubscriptionFactory = stakingLocalSubscriptionFactory self.walletLocalSubscriptionFactory = walletLocalSubscriptionFactory self.priceLocalSubscriptionFactory = priceLocalSubscriptionFactory - self.collatorService = collatorService + self.preferredCollatorFactory = preferredCollatorFactory self.rewardService = rewardService self.extrinsicService = extrinsicService self.feeProxy = feeProxy @@ -65,6 +67,9 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { deinit { self.collatorSubscription = nil + + collatorsCancellable.cancel() + delegatorIdentityCancellable.cancel() } private func provideRewardCalculator() { @@ -202,10 +207,15 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { chainFormat: chainAsset.chain.chainFormat ) - wrapper.targetOperation.completionBlock = { [weak self] in - DispatchQueue.main.async { + executeCancellable( + wrapper: wrapper, + inOperationQueue: operationQueue, + backingCallIn: delegatorIdentityCancellable, + runningCallbackIn: .main + ) { [weak self] result in + switch result { + case let .success(identities): do { - let identities = try wrapper.targetOperation.extractNoCancellableResultData() let identitiesByAccountId = try identities.reduce( into: [AccountId: AccountIdentity]() ) { result, keyValue in @@ -214,11 +224,36 @@ final class ParaStkStakeSetupInteractor: RuntimeConstantFetching { } self?.presenter?.didReceiveDelegationIdentities(identitiesByAccountId) - } catch {} + } catch { + self?.presenter?.didReceiveError(error) + } + case let .failure(error): + self?.presenter?.didReceiveError(error) } } + } - operationQueue.addOperations(wrapper.allOperations, waitUntilFinished: false) + private func providePreferredCollator() { + guard let operationFactory = preferredCollatorFactory else { + presenter?.didReceivePreferredCollator(nil) + return + } + + let wrapper = operationFactory.createPreferredCollatorWrapper() + + executeCancellable( + wrapper: wrapper, + inOperationQueue: operationQueue, + backingCallIn: collatorsCancellable, + runningCallbackIn: .main + ) { [weak self] result in + switch result { + case let .success(optCollator): + self?.presenter?.didReceivePreferredCollator(optCollator) + case let .failure(error): + self?.presenter?.didReceiveError(error) + } + } } } @@ -232,6 +267,8 @@ extension ParaStkStakeSetupInteractor: ParaStkStakeSetupInteractorInputProtocol feeProxy.delegate = self + providePreferredCollator() + provideMinTechStake() provideMinDelegationAmount() provideMaxDelegationsPerDelegator() diff --git a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupPresenter.swift b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupPresenter.swift index 3bdd823760..4332e92d51 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupPresenter.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupPresenter.swift @@ -270,14 +270,14 @@ final class ParaStkStakeSetupPresenter { } } - private func changeCollator(with collatorId: AccountId, name: String?) { + func changeCollator(with newAddress: DisplayAddress?) { guard - let newAddress = try? collatorId.toAddress(using: chainAsset.chain.chainFormat), - newAddress != collatorDisplayAddress?.address else { + let collatorId = try? newAddress?.address.toAccountId(using: chainAsset.chain.chainFormat), + newAddress?.address != collatorDisplayAddress?.address else { return } - collatorDisplayAddress = DisplayAddress(address: newAddress, username: name ?? "") + collatorDisplayAddress = newAddress collatorMetadata = nil @@ -287,6 +287,14 @@ final class ParaStkStakeSetupPresenter { interactor.applyCollator(with: collatorId) } + + func changeCollator(with collatorId: AccountId, name: String?) { + guard let newAddress = try? collatorId.toAddress(using: chainAsset.chain.chainFormat) else { + return + } + + changeCollator(with: DisplayAddress(address: newAddress, username: name ?? "")) + } } extension ParaStkStakeSetupPresenter: ParaStkStakeSetupPresenterProtocol { @@ -453,6 +461,12 @@ extension ParaStkStakeSetupPresenter: ParaStkStakeSetupInteractorOutputProtocol self.maxDelegations = maxDelegations } + func didReceivePreferredCollator(_ collator: DisplayAddress?) { + if collator != nil, collatorDisplayAddress == nil { + changeCollator(with: collator) + } + } + func didReceiveError(_ error: Error) { _ = wireframe.present(error: error, from: view, locale: selectedLocale) diff --git a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupProtocols.swift b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupProtocols.swift index 8350a0a5ee..5284c4915c 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupProtocols.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupProtocols.swift @@ -38,6 +38,7 @@ protocol ParaStkStakeSetupInteractorOutputProtocol: AnyObject { func didReceiveDelegator(_ delegator: ParachainStaking.Delegator?) func didReceiveDelegationIdentities(_ identities: [AccountId: AccountIdentity]?) func didReceiveScheduledRequests(_ scheduledRequests: [ParachainStaking.DelegatorScheduledRequest]?) + func didReceivePreferredCollator(_ collator: DisplayAddress?) func didReceiveError(_ error: Error) } diff --git a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupViewFactory.swift b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupViewFactory.swift index 085e98ff34..0c9b2e7098 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupViewFactory.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkStakeSetup/ParaStkStakeSetupViewFactory.swift @@ -13,7 +13,7 @@ struct ParaStkStakeSetupViewFactory { guard let currencyManager = CurrencyManager.shared, - let interactor = createInteractor(from: state) else { + let interactor = createInteractor(from: state, initialDelegator: initialDelegator) else { return nil } @@ -88,7 +88,8 @@ struct ParaStkStakeSetupViewFactory { } private static func createInteractor( - from state: ParachainStakingSharedStateProtocol + from state: ParachainStakingSharedStateProtocol, + initialDelegator: ParachainStaking.Delegator? ) -> ParaStkStakeSetupInteractor? { let optMetaAccount = SelectedWalletSettings.shared.value let chainRegistry = state.chainRegistry @@ -125,13 +126,31 @@ struct ParaStkStakeSetupViewFactory { let identityOperationFactory = IdentityOperationFactory(requestFactory: requestFactory) + let preferredCollatorFactory: ParaStkPreferredCollatorFactory? + + if initialDelegator == nil { + // add pref collators only for first staking + + preferredCollatorFactory = ParaStkPreferredCollatorFactory( + chain: chainAsset.chain, + connection: connection, + runtimeService: runtimeProvider, + collatorService: collatorService, + rewardService: rewardService, + identityOperationFactory: identityOperationFactory, + operationQueue: OperationManagerFacade.sharedDefaultQueue + ) + } else { + preferredCollatorFactory = nil + } + return ParaStkStakeSetupInteractor( chainAsset: chainAsset, selectedAccount: selectedAccount, stakingLocalSubscriptionFactory: state.stakingLocalSubscriptionFactory, walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, priceLocalSubscriptionFactory: PriceProviderFactory.shared, - collatorService: collatorService, + preferredCollatorFactory: preferredCollatorFactory, rewardService: rewardService, extrinsicService: extrinsicService, feeProxy: ExtrinsicFeeProxy(), From 8d6289ecf1cfc99723773b7610bca6be26734b4e Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 26 Jan 2024 14:31:01 +0100 Subject: [PATCH 2/5] separate sender fee from crosschain fee --- novawallet/Common/Model/Xcm/XcmFeeModel.swift | 11 ++-- .../CrossChainTransferPresenter.swift | 41 ++++++++++++--- .../TransferCrossChainConfirmPresenter.swift | 9 ++-- .../CrossChainTransferSetupPresenter.swift | 27 +++++++--- .../TransferDataValidatorFactory.swift | 50 ++++++++++++++++--- 5 files changed, 108 insertions(+), 30 deletions(-) diff --git a/novawallet/Common/Model/Xcm/XcmFeeModel.swift b/novawallet/Common/Model/Xcm/XcmFeeModel.swift index c0c7d92205..041fd322f3 100644 --- a/novawallet/Common/Model/Xcm/XcmFeeModel.swift +++ b/novawallet/Common/Model/Xcm/XcmFeeModel.swift @@ -7,15 +7,14 @@ protocol XcmFeeModelProtocol { var weightLimit: BigUInt { get } } -extension XcmFeeModelProtocol { - var total: BigUInt { - senderPart + holdingPart - } -} - struct XcmFeeModel: XcmFeeModelProtocol { + // fee paid in native token in the origin chain let senderPart: BigUInt + + // total fee paid in sending token in reserve and destination chains let holdingPart: BigUInt + + // limit of the xcm let weightLimit: BigUInt } diff --git a/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift b/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift index 4dd4f2bf7c..46b09ea305 100644 --- a/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift +++ b/novawallet/Modules/Transfer/BaseTransfer/CrossChain/CrossChainTransferPresenter.swift @@ -31,7 +31,7 @@ class CrossChainTransferPresenter { private(set) lazy var iconGenerator = PolkadotIconGenerator() - private(set) var originFee: ExtrinsicFeeProtocol? + private(set) var networkFee: ExtrinsicFeeProtocol? private(set) var crossChainFee: XcmFeeModelProtocol? let networkViewModelFactory: NetworkViewModelFactoryProtocol @@ -50,6 +50,22 @@ class CrossChainTransferPresenter { destinationChainAsset.chain.utilityAssets().first?.assetId == destinationChainAsset.asset.assetId } + var displayOriginFee: BigUInt? { + // this is paid in the native token + + if networkFee != nil { + return (networkFee?.amountForCurrentAccount ?? 0) + (crossChainFee?.senderPart ?? 0) + } else { + return nil + } + } + + var displayCrosschainFee: BigUInt? { + // this is paid in the sending token + + crossChainFee.map(\.holdingPart) + } + init( originChainAsset: ChainAsset, destinationChainAsset: ChainAsset, @@ -77,7 +93,7 @@ class CrossChainTransferPresenter { } func updateOriginFee(_ newValue: ExtrinsicFeeProtocol?) { - originFee = newValue + networkFee = newValue } func refreshCrossChainFee() { @@ -112,7 +128,7 @@ class CrossChainTransferPresenter { locale: selectedLocale ), - dataValidatingFactory.has(fee: originFee, locale: selectedLocale) { [weak self] in + dataValidatingFactory.has(fee: networkFee, locale: selectedLocale) { [weak self] in self?.refreshOriginFee() return }, @@ -133,23 +149,34 @@ class CrossChainTransferPresenter { dataValidatingFactory.canPayFeeSpendingAmountInPlank( balance: senderUtilityAssetTransferable, - fee: originFee, + fee: networkFee, spendingAmount: isOriginUtilityTransfer ? sendingAmount : nil, asset: utilityAssetInfo, locale: selectedLocale ), dataValidatingFactory.notViolatingMinBalancePaying( - fee: originFee, + fee: networkFee, total: senderUtilityBalanceCountingEd, minBalance: isOriginUtilityTransfer ? originSendingMinBalance : originUtilityMinBalance, locale: selectedLocale ), + dataValidatingFactory.canPayOriginDeliveryFee( + for: isOriginUtilityTransfer ? sendingAmount : 0, + networkFee: networkFee, + crosschainFee: crossChainFee, + transferable: senderUtilityAssetTransferable, + locale: selectedLocale + ), + // check whether cross chain fee can be paid after sending amount and paying origin fee dataValidatingFactory.canPayCrossChainFee( for: sendingAmount, - fee: (origin: isOriginUtilityTransfer ? originFee : nil, crossChain: crossChainFee), + fee: ( + origin: isOriginUtilityTransfer ? displayOriginFee : nil, + crossChain: displayCrosschainFee + ), transferable: senderSendingAssetBalance?.transferable, destinationAsset: destinationChainAsset.assetDisplayInfo, locale: selectedLocale @@ -201,7 +228,7 @@ class CrossChainTransferPresenter { func didReceiveOriginFee(result: Result) { switch result { case let .success(fee): - originFee = fee + networkFee = fee case let .failure(error): logger?.error("Origin fee error: \(error)") diff --git a/novawallet/Modules/Transfer/TransferConfirm/CrossChain/TransferCrossChainConfirmPresenter.swift b/novawallet/Modules/Transfer/TransferConfirm/CrossChain/TransferCrossChainConfirmPresenter.swift index a6bd3739b3..7ec0a377d3 100644 --- a/novawallet/Modules/Transfer/TransferConfirm/CrossChain/TransferCrossChainConfirmPresenter.swift +++ b/novawallet/Modules/Transfer/TransferConfirm/CrossChain/TransferCrossChainConfirmPresenter.swift @@ -96,9 +96,9 @@ final class TransferCrossChainConfirmPresenter: CrossChainTransferPresenter { private func provideOriginFeeViewModel() { let optAssetInfo = originChainAsset.chain.utilityAssets().first?.displayInfo - if let fee = originFee, let assetInfo = optAssetInfo { + if let fee = displayOriginFee, let assetInfo = optAssetInfo { let feeDecimal = Decimal.fromSubstrateAmount( - fee.amount, + fee, precision: assetInfo.assetPrecision ) ?? 0.0 @@ -116,7 +116,7 @@ final class TransferCrossChainConfirmPresenter: CrossChainTransferPresenter { private func provideCrossChainFeeViewModel() { let assetInfo = originChainAsset.assetDisplayInfo - if let fee = crossChainFee?.total { + if let fee = displayCrosschainFee { let feeDecimal = Decimal.fromSubstrateAmount( fee, precision: assetInfo.assetPrecision @@ -206,6 +206,7 @@ final class TransferCrossChainConfirmPresenter: CrossChainTransferPresenter { super.didReceiveCrossChainFee(result: result) if case .success = result { + provideOriginFeeViewModel() provideCrossChainFeeViewModel() refreshOriginFee() } @@ -306,7 +307,7 @@ extension TransferCrossChainConfirmPresenter: TransferConfirmPresenterProtocol { amount: amountInPlank + crossChainFee.holdingPart, recepient: strongSelf.recepientAccountAddress, weightLimit: crossChainFee.weightLimit, - originFee: strongSelf.originFee + originFee: strongSelf.networkFee ) } } diff --git a/novawallet/Modules/Transfer/TransferSetup/CrossChain/CrossChainTransferSetupPresenter.swift b/novawallet/Modules/Transfer/TransferSetup/CrossChain/CrossChainTransferSetupPresenter.swift index 8e03c72abd..23f6b92754 100644 --- a/novawallet/Modules/Transfer/TransferSetup/CrossChain/CrossChainTransferSetupPresenter.swift +++ b/novawallet/Modules/Transfer/TransferSetup/CrossChain/CrossChainTransferSetupPresenter.swift @@ -104,9 +104,9 @@ final class CrossChainTransferSetupPresenter: CrossChainTransferPresenter, private func updateOriginFeeView() { let optAssetInfo = originChainAsset.chain.utilityAssets().first?.displayInfo - if let fee = originFee, let assetInfo = optAssetInfo { + if let fee = displayOriginFee, let assetInfo = optAssetInfo { let feeDecimal = Decimal.fromSubstrateAmount( - fee.amount, + fee, precision: assetInfo.assetPrecision ) ?? 0.0 @@ -126,7 +126,7 @@ final class CrossChainTransferSetupPresenter: CrossChainTransferPresenter, private func updateCrossChainFeeView() { let assetInfo = originChainAsset.assetDisplayInfo - if let fee = crossChainFee?.total { + if let fee = displayCrosschainFee { let feeDecimal = Decimal.fromSubstrateAmount( fee, precision: assetInfo.assetPrecision @@ -184,8 +184,8 @@ final class CrossChainTransferSetupPresenter: CrossChainTransferPresenter, private func balanceMinusFee() -> Decimal { let balanceValue = senderSendingAssetBalance?.transferable ?? 0 - let originFeeValue = isOriginUtilityTransfer ? (originFee?.amountForCurrentAccount ?? 0) : 0 - let crossChainFeeValue = crossChainFee?.total ?? 0 + let originFeeValue = isOriginUtilityTransfer ? displayOriginFee ?? 0 : 0 + let crossChainFeeValue = displayCrosschainFee ?? 0 let precision = originChainAsset.assetDisplayInfo.assetPrecision @@ -287,6 +287,7 @@ final class CrossChainTransferSetupPresenter: CrossChainTransferPresenter, super.didReceiveCrossChainFee(result: result) if case .success = result { + updateOriginFeeView() updateCrossChainFeeView() provideAmountInputViewModelIfRate() updateAmountPriceView() @@ -388,6 +389,18 @@ extension CrossChainTransferSetupPresenter: TransferSetupChildPresenterProtocol let utilityAssetInfo = ChainAsset(chain: originChainAsset.chain, asset: utilityAsset).assetDisplayInfo let sendingAmount = inputResult?.absoluteValue(from: balanceMinusFee()) + + let originDeliveryFeeSpending = isOriginUtilityTransfer ? + crossChainFee?.senderPart.decimal(assetInfo: utilityAssetInfo) : + nil + + let crosschainFeeSpending = crossChainFee?.holdingPart.decimal( + assetInfo: originChainAsset.assetDisplayInfo + ) + + let totalSpending = (sendingAmount ?? 0) + (originDeliveryFeeSpending ?? 0) + + (crosschainFeeSpending ?? 0) + var validators: [DataValidating] = baseValidators( for: sendingAmount, recepientAddress: partialRecepientAddress, @@ -402,8 +415,8 @@ extension CrossChainTransferSetupPresenter: TransferSetupChildPresenterProtocol ), dataValidatingFactory.willBeReaped( - amount: sendingAmount, - fee: isOriginUtilityTransfer ? originFee : nil, + amount: totalSpending, + fee: isOriginUtilityTransfer ? networkFee : nil, totalAmount: senderSendingAssetBalance?.balanceCountingEd, minBalance: originSendingMinBalance, locale: selectedLocale diff --git a/novawallet/Modules/Transfer/Validation/TransferDataValidatorFactory.swift b/novawallet/Modules/Transfer/Validation/TransferDataValidatorFactory.swift index 4577a80790..7fb6560fd9 100644 --- a/novawallet/Modules/Transfer/Validation/TransferDataValidatorFactory.swift +++ b/novawallet/Modules/Transfer/Validation/TransferDataValidatorFactory.swift @@ -2,7 +2,7 @@ import Foundation import BigInt import SoraFoundation -typealias CrossChainValidationFee = (origin: ExtrinsicFeeProtocol?, crossChain: XcmFeeModelProtocol?) +typealias CrossChainValidationFee = (origin: BigUInt?, crossChain: BigUInt?) protocol TransferDataValidatorFactoryProtocol: BaseDataValidatingFactoryProtocol { func willBeReaped( @@ -42,6 +42,14 @@ protocol TransferDataValidatorFactoryProtocol: BaseDataValidatingFactoryProtocol func receiverNotBlocked(_ isBlocked: Bool?, locale: Locale) -> DataValidating + func canPayOriginDeliveryFee( + for amount: Decimal?, + networkFee: ExtrinsicFeeProtocol?, + crosschainFee: XcmFeeModelProtocol?, + transferable: BigUInt?, + locale: Locale + ) -> DataValidating + func canPayCrossChainFee( for amount: Decimal?, fee: CrossChainValidationFee?, @@ -236,6 +244,36 @@ final class TransferDataValidatorFactory: TransferDataValidatorFactoryProtocol { }) } + func canPayOriginDeliveryFee( + for amount: Decimal?, + networkFee: ExtrinsicFeeProtocol?, + crosschainFee: XcmFeeModelProtocol?, + transferable: BigUInt?, + locale: Locale + ) -> DataValidating { + let feeAmountInPlank = (networkFee?.amountForCurrentAccount ?? 0) + (crosschainFee?.senderPart ?? 0) + let feeDecimal = feeAmountInPlank.decimal(assetInfo: assetDisplayInfo) + let balanceDecimal = transferable?.decimal(assetInfo: assetDisplayInfo) ?? 0 + let amountDecimal = amount ?? 0 + + return ErrorConditionViolation(onError: { [weak self] in + guard let view = self?.view, let assetInfo = self?.assetDisplayInfo else { + return + } + + let tokenFormatter = AssetBalanceFormatterFactory().createTokenFormatter(for: assetInfo) + + let balanceAfterOperation = balanceDecimal >= amountDecimal ? balanceDecimal - amountDecimal : 0 + let balanceString = tokenFormatter.value(for: locale).stringFromDecimal(balanceAfterOperation) ?? "" + let feeString = tokenFormatter.value(for: locale).stringFromDecimal(feeDecimal) ?? "" + + self?.basePresentable.presentFeeTooHigh(from: view, balance: balanceString, fee: feeString, locale: locale) + + }, preservesCondition: { + feeDecimal + amountDecimal <= balanceDecimal + }) + } + func canPayCrossChainFee( for amount: Decimal?, fee: CrossChainValidationFee?, @@ -265,7 +303,7 @@ final class TransferDataValidatorFactory: TransferDataValidatorFactoryProtocol { ) let crossChainFeeDecimal = Decimal.fromSubstrateAmount( - fee?.crossChain?.total ?? 0, + fee?.crossChain ?? 0, precision: destinationAsset.assetPrecision ) ?? 0 @@ -279,12 +317,12 @@ final class TransferDataValidatorFactory: TransferDataValidatorFactoryProtocol { ) ?? 0 let originFeeDecimal = Decimal.fromSubstrateAmount( - fee?.origin?.amountForCurrentAccount ?? 0, + fee?.origin ?? 0, precision: originAsset.assetPrecision ) ?? 0 let remainingString = originBalanceViewModelFactory.amountFromValue( - transferableDecimal - sendingAmountDecimal - originFeeDecimal + max(transferableDecimal - sendingAmountDecimal - originFeeDecimal, 0) ).value(for: locale) self?.presentable.presentCantPayCrossChainFee( @@ -296,8 +334,8 @@ final class TransferDataValidatorFactory: TransferDataValidatorFactoryProtocol { }, preservesCondition: { if let sendingAmount = sendingAmount, let transferable = transferable { - let originFeeAmount = fee?.origin?.amountForCurrentAccount ?? 0 - let crosschainFeeAmount = fee?.crossChain?.total ?? 0 + let originFeeAmount = fee?.origin ?? 0 + let crosschainFeeAmount = fee?.crossChain ?? 0 return sendingAmount + originFeeAmount + crosschainFeeAmount <= transferable } else { return false From 32b47da37eac591fa57416903269d162d60b81a7 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 26 Jan 2024 17:09:37 +0100 Subject: [PATCH 3/5] fix polkadot config --- .../InflationCurveRewardConfig.swift | 49 ++++++++++++++++--- .../RewardCalculatorEngineFactory.swift | 3 +- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/InflationCurveRewardConfig.swift b/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/InflationCurveRewardConfig.swift index adc3538d06..30aa429cc0 100644 --- a/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/InflationCurveRewardConfig.swift +++ b/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/InflationCurveRewardConfig.swift @@ -1,20 +1,53 @@ import Foundation struct InflationCurveRewardConfig { - let fallof: Decimal = 0.05 - let minAnnualInflation: Decimal = 0.025 - let maxAnnualInflation: Decimal = 0.1 - let maxParachainsCount: Int = 60 - let parachainsReserve: Decimal = 0.3 + let fallof: Decimal + let minAnnualInflation: Decimal + let maxAnnualInflation: Decimal + let maxParachainsCount: Int + let parachainsReserve: Decimal + let idealStake: Decimal - func idealStakePortion(for parachainsCount: Int) -> Decimal { - // 30% reserved for up to 60 slots + init( + idealStake: Decimal = 0.75, + fallof: Decimal = 0.05, + minAnnualInflation: Decimal = 0.025, + maxAnnualInflation: Decimal = 0.1, + maxParachainsCount: Int = 60, + parachainsReserve: Decimal = 0.3 + ) { + self.idealStake = idealStake + self.fallof = fallof + self.minAnnualInflation = minAnnualInflation + self.maxAnnualInflation = maxAnnualInflation + self.maxParachainsCount = maxParachainsCount + self.parachainsReserve = parachainsReserve + } + func idealStakePortion(for parachainsCount: Int) -> Decimal { let cappedParachains = min(parachainsCount, maxParachainsCount) let auctionPortion = Decimal(cappedParachains) / Decimal(maxParachainsCount) * parachainsReserve // Therefore the ideal amount at stake (as a percentage of total issuance) is 75% less the // amount that we expect to be taken up with auctions. - return 0.75 - auctionPortion + return idealStake - auctionPortion + } +} + +extension InflationCurveRewardConfig { + static func config(for chainId: ChainModel.Id) -> InflationCurveRewardConfig { + switch chainId { + case KnowChainId.polkadot: + return InflationCurveRewardConfig( + idealStake: 0.75, + fallof: 0.05, + minAnnualInflation: 0.025, + maxAnnualInflation: 0.1, + maxParachainsCount: 60, + parachainsReserve: 0.2 + ) + default: + return InflationCurveRewardConfig() + } } } diff --git a/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/RewardCalculatorEngineFactory.swift b/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/RewardCalculatorEngineFactory.swift index 46eb508b7c..f71e766d30 100644 --- a/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/RewardCalculatorEngineFactory.swift +++ b/novawallet/Modules/Staking/Services/RewardCalculatorService/RelayChain/RewardCalculatorEngineFactory.swift @@ -43,13 +43,14 @@ final class RewardCalculatorEngineFactory: RewardCalculatorEngineFactoryProtocol treasuryPercentage: treasuryPercentage ) default: + let config = InflationCurveRewardConfig.config(for: chainId) return InflationCurveRewardEngine( chainId: chainId, assetPrecision: assetPrecision, totalIssuance: totalIssuance, validators: validators, eraDurationInSeconds: eraDurationInSeconds, - config: InflationCurveRewardConfig(), + config: config, parachainsCount: params.parachainsCount ?? 0 ) } From d90ea3b1340075c3129eca9a71c28f4d5efdd7d7 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 26 Jan 2024 17:16:32 +0100 Subject: [PATCH 4/5] fix polkadot and parity signer links --- novawallet/Common/Configs/ApplicationConfigs.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index c0ac83ac87..c4c6119e10 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -208,12 +208,12 @@ extension ApplicationConfig: ApplicationConfigProtocol { var paritySignerTroubleshoutingURL: URL { // swiftlint:disable:next line_length - URL(string: "https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/hardware-wallets/parity-signer/troubleshooting")! + URL(string: "https://docs.novawallet.io/nova-wallet-wiki/wallet-management/hardware-wallets/parity-signer/troubleshooting")! } var polkadotVaultTroubleshoutingURL: URL { // swiftlint:disable:next line_length - URL(string: "https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/hardware-wallets/polkadot-vault/troubleshooting")! + URL(string: "https://docs.novawallet.io/nova-wallet-wiki/wallet-management/hardware-wallets/polkadot-vault/troubleshooting")! } var ledgerGuideURL: URL { From c12fe360d653176234e20c1dcafce03b4868871b Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 26 Jan 2024 17:50:59 +0100 Subject: [PATCH 5/5] fix amount display for gov referendums --- .../Action/Gov2ActionOperationFactory.swift | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/novawallet/Modules/Vote/Governance/Operation/Action/Gov2ActionOperationFactory.swift b/novawallet/Modules/Vote/Governance/Operation/Action/Gov2ActionOperationFactory.swift index 566ef66384..44acd69bf7 100644 --- a/novawallet/Modules/Vote/Governance/Operation/Action/Gov2ActionOperationFactory.swift +++ b/novawallet/Modules/Vote/Governance/Operation/Action/Gov2ActionOperationFactory.swift @@ -8,27 +8,51 @@ final class Gov2ActionOperationFactory: GovernanceActionOperationFactory { requestFactory: StorageRequestFactoryProtocol, connection: JSONRPCEngine, codingFactoryOperation: BaseOperation - ) -> CompoundOperationWrapper<[StorageResponse]> { - OperationCombiningService.compoundNonOptionalWrapper( + ) -> CompoundOperationWrapper { + let fetchOperation = OperationCombiningService( operationManager: OperationManager(operationQueue: operationQueue) ) { let codingFactory = try codingFactoryOperation.extractNoCancellableResultData() - let storagePath: StorageCodingPath + var wrappers: [CompoundOperationWrapper<[StorageResponse]>] = [] if codingFactory.hasStorage(for: Preimage.requestStatusForStoragePath) { - storagePath = Preimage.requestStatusForStoragePath - } else { - storagePath = Preimage.statusForStoragePath + wrappers.append( + requestFactory.queryItems( + engine: connection, + keyParams: { [BytesCodable(wrappedValue: hash)] }, + factory: { codingFactory }, + storagePath: Preimage.requestStatusForStoragePath + ) + ) } - return requestFactory.queryItems( - engine: connection, - keyParams: { [BytesCodable(wrappedValue: hash)] }, - factory: { codingFactory }, - storagePath: storagePath - ) + if codingFactory.hasStorage(for: Preimage.statusForStoragePath) { + wrappers.append( + requestFactory.queryItems( + engine: connection, + keyParams: { [BytesCodable(wrappedValue: hash)] }, + factory: { codingFactory }, + storagePath: Preimage.statusForStoragePath + ) + ) + } + + return wrappers + }.longrunOperation() + + let mappingOperation = ClosureOperation { + let results = try fetchOperation.extractNoCancellableResultData() + + return results + .flatMap { $0 } + .first { $0.value != nil }? + .value } + + mappingOperation.addDependency(fetchOperation) + + return CompoundOperationWrapper(targetOperation: mappingOperation, dependencies: [fetchOperation]) } override func fetchCall( @@ -44,7 +68,7 @@ final class Gov2ActionOperationFactory: GovernanceActionOperationFactory { ) let callKeyParams: () throws -> [Preimage.PreimageKey] = { - let status = try statusFetchWrapper.targetOperation.extractNoCancellableResultData().first?.value + let status = try statusFetchWrapper.targetOperation.extractNoCancellableResultData() guard let length = status?.length, length <= Self.maxFetchCallSize else { return [] @@ -66,7 +90,7 @@ final class Gov2ActionOperationFactory: GovernanceActionOperationFactory { let callKeys = try callKeyParams() guard !callKeys.isEmpty else { - let optStatus = try statusFetchWrapper.targetOperation.extractNoCancellableResultData().first?.value + let optStatus = try statusFetchWrapper.targetOperation.extractNoCancellableResultData() if let length = optStatus?.length { return length > Self.maxFetchCallSize ? .tooLong : nil