Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 9ce78a7

Browse files
authored
Decouple Transfer from FiatConnect and auto-enable asset on buy (#1837)
* Auto-enable asset on fiat buy continue * Decouple Transfer from FiatConnect by moving navigation to app target Transfer was importing FiatConnect solely to create FiatSceneViewModel in sheet handlers, making it impossible to inject AssetsEnabler without adding package dependencies or stub types. - Move AmountNavigationView to Gem/Navigation/Transfer - Extract ConfirmTransferScene sheets into ConfirmTransferNavigationView - Remove FiatConnect dependency from Transfer - Move @entry fiatService from FiatConnect to Gem/Types/Environment * Fix walletId initialization and add missing properties * Remove redundant walletId stored property, derive from wallet * Remove unused fiatService @entry and GemAPI import
1 parent 47da8b8 commit 9ce78a7

29 files changed

Lines changed: 167 additions & 130 deletions

Features/FiatConnect/Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let package = Package(
3838
"Store",
3939
"PrimitivesComponents",
4040
.product(name: "FiatService", package: "FeatureServices"),
41+
.product(name: "BalanceService", package: "FeatureServices"),
4142
],
4243
path: "Sources"
4344
),
@@ -47,6 +48,7 @@ let package = Package(
4748
"FiatConnect",
4849
.product(name: "PrimitivesTestKit", package: "Primitives"),
4950
.product(name: "FiatServiceTestKit", package: "FeatureServices"),
51+
.product(name: "BalanceServiceTestKit", package: "FeatureServices"),
5052
],
5153
path: "Tests"
5254
),

Features/FiatConnect/Sources/ViewModels/FiatSceneViewModel.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Formatters
1212
import Validators
1313
import BigInt
1414
import FiatService
15+
import BalanceService
1516

1617
@MainActor
1718
@Observable
@@ -22,8 +23,9 @@ public final class FiatSceneViewModel {
2223
static let suggestedAmounts: [Int] = [100, 250]
2324
}
2425

25-
let walletId: WalletId
2626
let fiatService: FiatService
27+
private let wallet: Wallet
28+
private let assetsEnabler: any AssetsEnabler
2729
private let assetAddress: AssetAddress
2830
private let currencyFormatter: CurrencyFormatter
2931
private let valueFormatter = ValueFormatter(locale: .US, style: .medium)
@@ -43,28 +45,30 @@ public final class FiatSceneViewModel {
4345
fiatService: FiatService,
4446
currencyFormatter: CurrencyFormatter = CurrencyFormatter(currencyCode: Currency.usd.rawValue),
4547
assetAddress: AssetAddress,
46-
walletId: WalletId,
48+
wallet: Wallet,
49+
assetsEnabler: any AssetsEnabler,
4750
type: FiatQuoteType = .buy,
4851
amount: Int? = nil
4952
) {
5053
self.fiatService = fiatService
5154
self.currencyFormatter = currencyFormatter
5255
self.assetAddress = assetAddress
53-
self.walletId = walletId
56+
self.wallet = wallet
57+
self.assetsEnabler = assetsEnabler
5458
self.type = type
55-
self.assetQuery = ObservableQuery(AssetRequest(walletId: walletId, assetId: assetAddress.asset.id), initialValue: .with(asset: assetAddress.asset))
59+
self.assetQuery = ObservableQuery(AssetRequest(walletId: wallet.walletId, assetId: assetAddress.asset.id), initialValue: .with(asset: assetAddress.asset))
5660

5761
let buyOperation = BuyOperation(
5862
service: fiatService,
5963
asset: assetAddress.asset,
6064
currencyFormatter: currencyFormatter,
61-
walletId: walletId
65+
walletId: wallet.walletId
6266
)
6367
let sellOperation = SellOperation(
6468
service: fiatService,
6569
asset: assetAddress.asset,
6670
currencyFormatter: currencyFormatter,
67-
walletId: walletId
71+
walletId: wallet.walletId
6872
)
6973

7074
self.buyViewModel = FiatOperationViewModel(
@@ -185,12 +189,13 @@ extension FiatSceneViewModel {
185189
urlState = .loading
186190

187191
do {
188-
guard let url = try await fiatService.getQuoteUrl(walletId: walletId.id, quoteId: selectedQuote.id).redirectUrl.asURL else {
192+
guard let url = try await fiatService.getQuoteUrl(walletId: wallet.walletId.id, quoteId: selectedQuote.id).redirectUrl.asURL else {
189193
urlState = .noData
190194
return
191195
}
192196

193197
urlState = .data(())
198+
Task { await enableAsset() }
194199
await UIApplication.shared.open(url, options: [:])
195200
} catch {
196201
urlState = .error(error)
@@ -239,6 +244,16 @@ extension FiatSceneViewModel {
239244
// MARK: - Private
240245

241246
extension FiatSceneViewModel {
247+
private func enableAsset() async {
248+
do {
249+
try await assetsEnabler.enableAssets(wallet: wallet, assetIds: [asset.id], enabled: true)
250+
} catch {
251+
debugLog("FiatSceneViewModel enableAsset error: \(error)")
252+
}
253+
}
254+
255+
var walletId: WalletId { wallet.walletId }
256+
242257
private var balanceModel: BalanceViewModel {
243258
BalanceViewModel(asset: asset, balance: assetData.balance, formatter: valueFormatter)
244259
}

Features/FiatConnect/Tests/FiatSceneViewModelTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Formatters
88
import FiatService
99
import FiatServiceTestKit
1010
import BigInt
11+
import BalanceServiceTestKit
1112

1213
@testable import FiatConnect
1314

@@ -17,13 +18,14 @@ final class FiatSceneViewModelTests {
1718
fiatService: FiatService = .mock(),
1819
currencyFormatter: CurrencyFormatter = .init(locale: Locale.US, currencyCode: Currency.usd.rawValue),
1920
assetAddress: AssetAddress = .mock(),
20-
walletId: WalletId = .mock()
21+
wallet: Wallet = .mock()
2122
) -> FiatSceneViewModel {
2223
FiatSceneViewModel(
2324
fiatService: fiatService,
2425
currencyFormatter: currencyFormatter,
2526
assetAddress: assetAddress,
26-
walletId: walletId
27+
wallet: wallet,
28+
assetsEnabler: .mock()
2729
)
2830
}
2931

Features/Transfer/Package.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ let package = Package(
3434
.package(name: "Stake", path: "../Stake"),
3535
.package(name: "WalletConnector", path: "../WalletConnector"),
3636
.package(name: "InfoSheet", path: "../InfoSheet"),
37-
.package(name: "FiatConnect", path: "../FiatConnect"),
3837
.package(name: "Swap", path: "../Swap"),
3938
.package(name: "Perpetuals", path: "../Perpetuals"),
4039
.package(name: "EventPresenterService", path: "../EventPresenterService"),
@@ -63,7 +62,6 @@ let package = Package(
6362
"Stake",
6463
"WalletConnector",
6564
"InfoSheet",
66-
"FiatConnect",
6765
"Swap",
6866
"Perpetuals",
6967
"EventPresenterService",

Features/Transfer/Sources/Scenes/AmountScene.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import SwiftUI
88
import struct Stake.ValidatorViewModel
99
import struct Stake.ValidatorView
1010

11-
struct AmountScene: View {
11+
public struct AmountScene: View {
1212
@FocusState private var focusedField: Bool
1313

1414
private var model: AmountSceneViewModel
1515

16-
init(model: AmountSceneViewModel) {
16+
public init(model: AmountSceneViewModel) {
1717
self.model = model
1818
}
1919

20-
var body: some View {
20+
public var body: some View {
2121
@Bindable var model = model
2222
List {
2323
CurrencyInputValidationView(

Features/Transfer/Sources/Scenes/ConfirmTransferScene.swift

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,15 @@ import SwiftUI
44
import Components
55
import Style
66
import Localization
7-
import InfoSheet
87
import PrimitivesComponents
9-
import FiatConnect
108
import Swap
11-
import Preferences
129
import Primitives
1310

1411
public struct ConfirmTransferScene: View {
15-
@State private var model: ConfirmTransferSceneViewModel
12+
@Bindable var model: ConfirmTransferSceneViewModel
1613

1714
public init(model: ConfirmTransferSceneViewModel) {
18-
_model = State(initialValue: model)
15+
self.model = model
1916
}
2017

2118
public var body: some View {
@@ -36,56 +33,8 @@ public struct ConfirmTransferScene: View {
3633
)
3734
.taskOnce { model.fetch() }
3835
.navigationTitle(model.title)
39-
// TODO: - move to navigation view
4036
.navigationBarTitleDisplayMode(.inline)
4137
.activityIndicator(isLoading: model.confirmingState.isLoading, message: model.progressMessage)
42-
.sheet(item: $model.isPresentingSheet) {
43-
switch $0 {
44-
case .info(let type):
45-
InfoSheetScene(type: type)
46-
case .url(let url):
47-
SFSafariView(url: url)
48-
case .networkFeeSelector:
49-
NetworkFeeSheet(model: model.feeModel)
50-
case .payloadDetails:
51-
NavigationStack {
52-
SimulationPayloadDetailsScene(
53-
primaryFields: model.primaryPayloadFields,
54-
secondaryFields: model.secondaryPayloadFields,
55-
fieldViewModel: model.payloadFieldViewModel(for:),
56-
contextMenuItems: model.contextMenuItems(for:)
57-
)
58-
.presentationDetents([.large])
59-
.presentationBackground(Colors.grayBackground)
60-
}
61-
case .fiatConnect(let assetAddress, let walletId):
62-
NavigationStack {
63-
FiatConnectNavigationView(
64-
model: FiatSceneViewModel(
65-
fiatService: model.fiatService,
66-
assetAddress: assetAddress,
67-
walletId: walletId
68-
)
69-
)
70-
.navigationBarTitleDisplayMode(.inline)
71-
.toolbarDismissItem(type: .close, placement: .topBarLeading)
72-
}
73-
case .swapDetails:
74-
if case let .swapDetails(model) = model.detailsViewModel.itemModel {
75-
NavigationStack {
76-
SwapDetailsView(model: Bindable(model))
77-
.presentationDetentsForCurrentDeviceSize(expandable: true)
78-
.presentationBackground(Colors.grayBackground)
79-
}
80-
}
81-
case .perpetualDetails(let model):
82-
NavigationStack {
83-
PerpetualDetailsView(model: model)
84-
.presentationDetentsForCurrentDeviceSize(expandable: true)
85-
.presentationBackground(Colors.grayBackground)
86-
}
87-
}
88-
}
8938
.alertSheet($model.isPresentingAlertMessage)
9039
}
9140
}

Features/Transfer/Sources/Types/AmountDataProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import BigInt
44
import Primitives
55

6-
enum AmountDataProvider: AmountDataProvidable, @unchecked Sendable {
6+
public enum AmountDataProvider: AmountDataProvidable, @unchecked Sendable {
77
case transfer(AmountTransferViewModel)
88
case stake(AmountStakeViewModel)
99
case freeze(AmountFreezeViewModel)

Features/Transfer/Sources/Types/AmountSheetType.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import Components
77
import Perpetuals
88
import PrimitivesComponents
99

10-
enum AmountSheetType: Identifiable {
10+
public enum AmountSheetType: Identifiable {
1111
case infoAction(InfoSheetType)
12-
case fiatConnect(assetAddress: AssetAddress, walletId: WalletId)
12+
case fiatConnect(assetAddress: AssetAddress, wallet: Wallet)
1313
case leverageSelector(selection: SelectionState<LeverageOption>)
1414
case autoclose(AutocloseOpenData)
1515

16-
var id: String {
16+
public var id: String {
1717
switch self {
1818
case let .infoAction(type): "info-action-\(type.id)"
1919
case .fiatConnect: "fiat-connect"

Features/Transfer/Sources/Types/ConfirmTransferSheetType.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import Primitives
66
import Components
77
import PrimitivesComponents
88

9-
enum ConfirmTransferSheetType: Identifiable, Sendable {
9+
public enum ConfirmTransferSheetType: Identifiable, Sendable {
1010
case info(InfoSheetType)
1111
case networkFeeSelector
1212
case payloadDetails
1313
case url(URL)
14-
case fiatConnect(assetAddress: AssetAddress, walletId: WalletId)
14+
case fiatConnect(assetAddress: AssetAddress, wallet: Wallet)
1515
case swapDetails
1616
case perpetualDetails(PerpetualDetailsViewModel)
1717

18-
var id: String {
18+
public var id: String {
1919
switch self {
2020
case let .info(type): "info-\(type.id)"
2121
case let .url(url): "url-\(url)"

Features/Transfer/Sources/Types/SelectionState.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import Foundation
44

55
@Observable
6-
final class SelectionState<T> {
7-
let options: [T]
8-
var selected: T
9-
let isEnabled: Bool
10-
let title: String
6+
public final class SelectionState<T> {
7+
public let options: [T]
8+
public var selected: T
9+
public let isEnabled: Bool
10+
public let title: String
1111

12-
init(
12+
public init(
1313
options: [T],
1414
selected: T,
1515
isEnabled: Bool,

0 commit comments

Comments
 (0)