-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #863 from novasamatech/feature/swaps-confirm-base
Swaps base interactor
- Loading branch information
Showing
25 changed files
with
892 additions
and
249 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
12 changes: 12 additions & 0 deletions
12
novawallet/Assets.xcassets/iconForward.imageset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"filename" : "arrow-forward.pdf", | ||
"idiom" : "universal" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
import UIKit | ||
import RobinHood | ||
import BigInt | ||
|
||
class SwapBaseInteractor: AnyCancellableCleaning, AnyProviderAutoCleaning, SwapBaseInteractorInputProtocol { | ||
weak var basePresenter: SwapSetupInteractorOutputProtocol? | ||
let assetConversionOperationFactory: AssetConversionOperationFactoryProtocol | ||
let assetConversionExtrinsicService: AssetConversionExtrinsicServiceProtocol | ||
let runtimeService: RuntimeProviderProtocol | ||
let feeProxy: ExtrinsicFeeProxyProtocol | ||
let extrinsicServiceFactory: ExtrinsicServiceFactoryProtocol | ||
let priceLocalSubscriptionFactory: PriceProviderFactoryProtocol | ||
let walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol | ||
let currencyManager: CurrencyManagerProtocol | ||
let selectedAccount: MetaAccountModel | ||
|
||
private let operationQueue: OperationQueue | ||
private var quoteCall: CancellableCall? | ||
private var runtimeOperationCall: CancellableCall? | ||
private var extrinsicService: ExtrinsicServiceProtocol? | ||
|
||
private var priceProviders: [ChainAssetId: StreamableProvider<PriceData>] = [:] | ||
private var assetBalanceProviders: [ChainAssetId: StreamableProvider<AssetBalance>] = [:] | ||
|
||
init( | ||
assetConversionOperationFactory: AssetConversionOperationFactoryProtocol, | ||
assetConversionExtrinsicService: AssetConversionExtrinsicServiceProtocol, | ||
runtimeService: RuntimeProviderProtocol, | ||
feeProxy: ExtrinsicFeeProxyProtocol, | ||
extrinsicServiceFactory: ExtrinsicServiceFactoryProtocol, | ||
priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, | ||
walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, | ||
currencyManager: CurrencyManagerProtocol, | ||
selectedAccount: MetaAccountModel, | ||
operationQueue: OperationQueue | ||
) { | ||
self.assetConversionOperationFactory = assetConversionOperationFactory | ||
self.assetConversionExtrinsicService = assetConversionExtrinsicService | ||
self.runtimeService = runtimeService | ||
self.feeProxy = feeProxy | ||
self.extrinsicServiceFactory = extrinsicServiceFactory | ||
self.priceLocalSubscriptionFactory = priceLocalSubscriptionFactory | ||
self.walletLocalSubscriptionFactory = walletLocalSubscriptionFactory | ||
self.currencyManager = currencyManager | ||
self.selectedAccount = selectedAccount | ||
self.operationQueue = operationQueue | ||
} | ||
|
||
func updateSubscriptions(activeChainAssets: Set<ChainAssetId>) { | ||
priceProviders = clear(providers: priceProviders, activeChainAssets: activeChainAssets) | ||
assetBalanceProviders = clear(providers: assetBalanceProviders, activeChainAssets: activeChainAssets) | ||
} | ||
|
||
func clear<T>( | ||
providers: [ChainAssetId: StreamableProvider<T>], | ||
activeChainAssets: Set<ChainAssetId> | ||
) -> [ChainAssetId: StreamableProvider<T>] { | ||
providers.reduce(into: [ChainAssetId: StreamableProvider<T>]()) { | ||
if !activeChainAssets.contains($1.key) { | ||
$1.value.removeObserver(self) | ||
} else { | ||
$0[$1.key] = $1.value | ||
} | ||
} | ||
} | ||
|
||
func priceSubscription(chainAsset: ChainAsset) -> StreamableProvider<PriceData>? { | ||
guard let priceId = chainAsset.asset.priceId else { | ||
return nil | ||
} | ||
|
||
return priceProviders[chainAsset.chainAssetId] ?? subscribeToPrice( | ||
for: priceId, | ||
currency: currencyManager.selectedCurrency | ||
) | ||
} | ||
|
||
func assetBalanceSubscription(chainAsset: ChainAsset) -> StreamableProvider<AssetBalance>? { | ||
guard let accountId = chainAccountResponse(for: chainAsset)?.accountId else { | ||
return nil | ||
} | ||
let chainAssetId = chainAsset.chainAssetId | ||
return assetBalanceProviders[chainAssetId] ?? subscribeToAssetBalanceProvider( | ||
for: accountId, | ||
chainId: chainAssetId.chainId, | ||
assetId: chainAssetId.assetId | ||
) | ||
} | ||
|
||
func quote(args: AssetConversion.QuoteArgs) { | ||
clear(cancellable: "eCall) | ||
|
||
let wrapper = assetConversionOperationFactory.quote(for: args) | ||
wrapper.targetOperation.completionBlock = { [weak self, args] in | ||
DispatchQueue.main.async { | ||
guard self?.quoteCall === wrapper else { | ||
return | ||
} | ||
do { | ||
let result = try wrapper.targetOperation.extractNoCancellableResultData() | ||
|
||
self?.basePresenter?.didReceive(quote: result, for: args) | ||
} catch { | ||
self?.basePresenter?.didReceive(error: .quote(error, args)) | ||
} | ||
} | ||
} | ||
|
||
quoteCall = wrapper | ||
operationQueue.addOperations(wrapper.allOperations, waitUntilFinished: false) | ||
} | ||
|
||
func fee(args: AssetConversion.CallArgs) { | ||
clear(cancellable: &runtimeOperationCall) | ||
guard let extrinsicService = extrinsicService else { | ||
return | ||
} | ||
|
||
let runtimeCoderFactoryOperation = runtimeService.fetchCoderFactoryOperation() | ||
|
||
runtimeCoderFactoryOperation.completionBlock = { [weak self] in | ||
guard let self = self else { | ||
return | ||
} | ||
do { | ||
let runtimeCoderFactory = try runtimeCoderFactoryOperation.extractNoCancellableResultData() | ||
let builder = self.assetConversionExtrinsicService.fetchExtrinsicBuilderClosure( | ||
for: args, | ||
codingFactory: runtimeCoderFactory | ||
) | ||
self.feeProxy.estimateFee( | ||
using: extrinsicService, | ||
reuseIdentifier: args.identifier, | ||
setupBy: builder | ||
) | ||
} catch { | ||
DispatchQueue.main.async { | ||
self.basePresenter?.didReceive(error: .fetchFeeFailed(error, args.identifier)) | ||
} | ||
} | ||
} | ||
|
||
runtimeOperationCall = runtimeCoderFactoryOperation | ||
operationQueue.addOperation(runtimeCoderFactoryOperation) | ||
} | ||
|
||
func chainAccountResponse(for chainAsset: ChainAsset) -> ChainAccountResponse? { | ||
let metaChainAccountResponse = selectedAccount.fetchMetaChainAccount(for: chainAsset.chain.accountRequest()) | ||
return metaChainAccountResponse?.chainAccount | ||
} | ||
|
||
func set(receiveChainAsset chainAsset: ChainAsset) { | ||
priceProviders[chainAsset.chainAssetId] = priceSubscription(chainAsset: chainAsset) | ||
} | ||
|
||
func set(payChainAsset chainAsset: ChainAsset) { | ||
guard let chainAccount = chainAccountResponse(for: chainAsset) else { | ||
extrinsicService = nil | ||
basePresenter?.didReceive(payAccountId: nil) | ||
return | ||
} | ||
priceProviders[chainAsset.chainAssetId] = priceSubscription(chainAsset: chainAsset) | ||
assetBalanceProviders[chainAsset.chainAssetId] = assetBalanceSubscription(chainAsset: chainAsset) | ||
|
||
extrinsicService = extrinsicServiceFactory.createService( | ||
account: chainAccount, | ||
chain: chainAsset.chain | ||
) | ||
basePresenter?.didReceive(payAccountId: chainAccount.accountId) | ||
} | ||
|
||
func set(feeChainAsset chainAsset: ChainAsset) { | ||
priceProviders[chainAsset.chainAssetId] = priceSubscription(chainAsset: chainAsset) | ||
assetBalanceProviders[chainAsset.chainAssetId] = assetBalanceSubscription(chainAsset: chainAsset) | ||
} | ||
|
||
// MARK: - SwapBaseInteractorInputProtocol | ||
|
||
func setup() { | ||
feeProxy.delegate = self | ||
} | ||
|
||
func calculateQuote(for args: AssetConversion.QuoteArgs) { | ||
quote(args: args) | ||
} | ||
|
||
func calculateFee( | ||
args: AssetConversion.CallArgs | ||
) { | ||
fee(args: args) | ||
} | ||
|
||
func remakePriceSubscription(for chainAsset: ChainAsset) { | ||
priceProviders[chainAsset.chainAssetId] = priceSubscription(chainAsset: chainAsset) | ||
} | ||
} | ||
|
||
extension SwapBaseInteractor: ExtrinsicFeeProxyDelegate { | ||
func didReceiveFee(result: Result<RuntimeDispatchInfo, Error>, for transactionId: TransactionFeeId) { | ||
DispatchQueue.main.async { | ||
switch result { | ||
case let .success(dispatchInfo): | ||
let fee = BigUInt(dispatchInfo.fee) | ||
self.basePresenter?.didReceive(fee: fee, transactionId: transactionId) | ||
case let .failure(error): | ||
self.basePresenter?.didReceive(error: .fetchFeeFailed(error, transactionId)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension SwapBaseInteractor: PriceLocalStorageSubscriber, PriceLocalSubscriptionHandler { | ||
func handlePrice(result: Result<PriceData?, Error>, priceId: AssetModel.PriceId) { | ||
switch result { | ||
case let .success(priceData): | ||
basePresenter?.didReceive(price: priceData, priceId: priceId) | ||
case let .failure(error): | ||
basePresenter?.didReceive(error: .price(error, priceId)) | ||
} | ||
} | ||
} | ||
|
||
extension SwapBaseInteractor: WalletLocalStorageSubscriber, WalletLocalSubscriptionHandler { | ||
func handleAssetBalance( | ||
result: Result<AssetBalance?, Error>, | ||
accountId: AccountId, | ||
chainId: ChainModel.Id, | ||
assetId: AssetModel.Id | ||
) { | ||
let chainAssetId = ChainAssetId(chainId: chainId, assetId: assetId) | ||
switch result { | ||
case let .success(balance): | ||
let balance = balance ?? .createZero( | ||
for: .init(chainId: chainId, assetId: assetId), | ||
accountId: accountId | ||
) | ||
basePresenter?.didReceive( | ||
balance: balance, | ||
for: chainAssetId, | ||
accountId: accountId | ||
) | ||
case let .failure(error): | ||
basePresenter?.didReceive(error: .assetBalance(error, chainAssetId, accountId)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import BigInt | ||
|
||
protocol SwapBaseInteractorInputProtocol: AnyObject { | ||
func setup() | ||
func calculateQuote(for args: AssetConversion.QuoteArgs) | ||
func calculateFee(args: AssetConversion.CallArgs) | ||
func remakePriceSubscription(for chainAsset: ChainAsset) | ||
} | ||
|
||
protocol SwapBaseInteractorOutputProtocol: AnyObject { | ||
func didReceive(quote: AssetConversion.Quote, for quoteArgs: AssetConversion.QuoteArgs) | ||
func didReceive(fee: BigUInt?, transactionId: TransactionFeeId) | ||
func didReceive(error: SwapSetupError) | ||
func didReceive(price: PriceData?, priceId: AssetModel.PriceId) | ||
func didReceive(payAccountId: AccountId?) | ||
} |
File renamed without changes.
11 changes: 11 additions & 0 deletions
11
novawallet/Modules/Swaps/Base/View/SwapNetworkFeeViewCell.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import SoraUI | ||
|
||
final class SwapNetworkFeeViewCell: RowView<SwapNetworkFeeView>, StackTableViewCellProtocol { | ||
var titleButton: RoundedButton { rowContentView.titleView } | ||
var valueTopButton: RoundedButton { rowContentView.valueView.fView } | ||
var valueBottomLabel: UILabel { rowContentView.valueView.sView } | ||
|
||
func bind(loadableViewModel: LoadableViewModelState<SwapFeeViewModel>) { | ||
rowContentView.bind(loadableViewModel: loadableViewModel) | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import SoraUI | ||
|
||
final class SwapRateViewCell: RowView<SwapRateView>, StackTableViewCellProtocol { | ||
var titleButton: RoundedButton { rowContentView.titleView } | ||
var valueLabel: UILabel { rowContentView.valueView } | ||
|
||
func bind(loadableViewModel: LoadableViewModelState<String>) { | ||
rowContentView.bind(loadableViewModel: loadableViewModel) | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
novawallet/Modules/Swaps/Confirm/SwapConfirmInteractor.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import UIKit | ||
|
||
final class SwapConfirmInteractor: SwapBaseInteractor { | ||
weak var presenter: SwapConfirmInteractorOutputProtocol? | ||
let payChainAsset: ChainAsset | ||
let receiveChainAsset: ChainAsset | ||
let feeChainAsset: ChainAsset | ||
let slippage: BigRational | ||
|
||
init( | ||
payChainAsset: ChainAsset, | ||
receiveChainAsset: ChainAsset, | ||
feeChainAsset: ChainAsset, | ||
slippage: BigRational, | ||
assetConversionOperationFactory: AssetConversionOperationFactoryProtocol, | ||
assetConversionExtrinsicService: AssetConversionExtrinsicServiceProtocol, | ||
runtimeService: RuntimeProviderProtocol, | ||
feeProxy: ExtrinsicFeeProxyProtocol, | ||
extrinsicServiceFactory: ExtrinsicServiceFactoryProtocol, | ||
priceLocalSubscriptionFactory: PriceProviderFactoryProtocol, | ||
walletLocalSubscriptionFactory: WalletLocalSubscriptionFactoryProtocol, | ||
currencyManager: CurrencyManagerProtocol, | ||
selectedAccount: MetaAccountModel, | ||
operationQueue: OperationQueue | ||
) { | ||
self.payChainAsset = payChainAsset | ||
self.receiveChainAsset = receiveChainAsset | ||
self.feeChainAsset = feeChainAsset | ||
self.slippage = slippage | ||
|
||
super.init( | ||
assetConversionOperationFactory: assetConversionOperationFactory, | ||
assetConversionExtrinsicService: assetConversionExtrinsicService, | ||
runtimeService: runtimeService, | ||
feeProxy: feeProxy, | ||
extrinsicServiceFactory: extrinsicServiceFactory, | ||
priceLocalSubscriptionFactory: priceLocalSubscriptionFactory, | ||
walletLocalSubscriptionFactory: walletLocalSubscriptionFactory, | ||
currencyManager: currencyManager, | ||
selectedAccount: selectedAccount, | ||
operationQueue: operationQueue | ||
) | ||
} | ||
|
||
override func setup() { | ||
super.setup() | ||
|
||
set(payChainAsset: payChainAsset) | ||
set(receiveChainAsset: receiveChainAsset) | ||
set(feeChainAsset: feeChainAsset) | ||
} | ||
} | ||
|
||
extension SwapConfirmInteractor: SwapConfirmInteractorInputProtocol {} |
34 changes: 34 additions & 0 deletions
34
novawallet/Modules/Swaps/Confirm/SwapConfirmPresenter.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import Foundation | ||
import BigInt | ||
|
||
final class SwapConfirmPresenter { | ||
weak var view: SwapConfirmViewProtocol? | ||
let wireframe: SwapConfirmWireframeProtocol | ||
let interactor: SwapConfirmInteractorInputProtocol | ||
|
||
init( | ||
interactor: SwapConfirmInteractorInputProtocol, | ||
wireframe: SwapConfirmWireframeProtocol | ||
) { | ||
self.interactor = interactor | ||
self.wireframe = wireframe | ||
} | ||
} | ||
|
||
extension SwapConfirmPresenter: SwapConfirmPresenterProtocol { | ||
func setup() { | ||
interactor.setup() | ||
} | ||
} | ||
|
||
extension SwapConfirmPresenter: SwapConfirmInteractorOutputProtocol { | ||
func didReceive(quote _: AssetConversion.Quote, for _: AssetConversion.QuoteArgs) {} | ||
|
||
func didReceive(fee _: BigUInt?, transactionId _: TransactionFeeId) {} | ||
|
||
func didReceive(error _: SwapSetupError) {} | ||
|
||
func didReceive(price _: PriceData?, priceId _: AssetModel.PriceId) {} | ||
|
||
func didReceive(payAccountId _: AccountId?) {} | ||
} |
Oops, something went wrong.