From 8c751071b846be0454b27dac20958b05077de6c5 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:25:08 +0200 Subject: [PATCH 01/21] Integrate earn balance updates into services Introduce EarnBalanceUpdatable protocol and have BalanceService conform to it so earn balances can be updated via a shared interface. EarnService now accepts an EarnBalanceUpdatable dependency and triggers an earn balance refresh after updating positions. ServicesFactory is updated to inject the balance service into EarnService, and Package.swift adds the BalanceService dependency for the EarnService target. BalanceStore gains hasEarnBalance(...) to gate earn updates, the TransactionStateService testkit adds an EarnBalanceUpdaterMock and updated mock constructor, and a small UI label change for earn deposit was applied. Also bumps core submodule commit. --- Gem/Services/ServicesFactory.swift | 9 +++--- .../BalanceService/BalanceService.swift | 28 ++++++++++++------- .../Protocols/EarnBalanceUpdatable.swift | 9 ++++++ .../EarnService/EarnService.swift | 8 ++++-- Packages/FeatureServices/Package.swift | 1 + .../TransactionStateService+TestKit.swift | 9 ++++-- .../ViewModels/TransactionViewModel.swift | 2 +- .../Store/Sources/Stores/BalanceStore.swift | 10 +++++++ core | 2 +- 9 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift diff --git a/Gem/Services/ServicesFactory.swift b/Gem/Services/ServicesFactory.swift index 385a1f28b..fbda0a4d9 100644 --- a/Gem/Services/ServicesFactory.swift +++ b/Gem/Services/ServicesFactory.swift @@ -99,15 +99,16 @@ struct ServicesFactory { walletStore: storeManager.walletStore, avatarService: avatarService ) - let earnService = EarnService( - store: storeManager.stakeStore, - gatewayService: gatewayService - ) let balanceService = Self.makeBalanceService( balanceStore: storeManager.balanceStore, assetsService: assetsService, chainFactory: chainServiceFactory ) + let earnService = EarnService( + store: storeManager.stakeStore, + gatewayService: gatewayService, + earnBalanceUpdater: balanceService + ) let stakeService = Self.makeStakeService( stakeStore: storeManager.stakeStore, addressStore: storeManager.addressStore, diff --git a/Packages/FeatureServices/BalanceService/BalanceService.swift b/Packages/FeatureServices/BalanceService/BalanceService.swift index 3f71efbf8..d3cd61b45 100644 --- a/Packages/FeatureServices/BalanceService/BalanceService.swift +++ b/Packages/FeatureServices/BalanceService/BalanceService.swift @@ -70,6 +70,10 @@ extension BalanceService: BalanceUpdater { group.addTask { await updateCoinStakeBalance(walletId: walletId, asset: chain.assetId, address: address) } + } + + // earn balance + if (try? balanceStore.hasEarnBalance(walletId: walletId, chain: chain)) == true { group.addTask { await updateEarnBalance(walletId: walletId, chain: chain, address: address) } @@ -149,16 +153,6 @@ extension BalanceService { ) } - @discardableResult - private func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { - await updateBalanceAsync( - walletId: walletId, - chain: chain, - fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address) }, - mapBalance: { $0.earnChange } - ) - } - @discardableResult private func updateTokenBalances(walletId: WalletId, chain: Chain, tokenIds: [AssetId], address: String) async -> [AssetBalanceChange] { await updateBalanceAsync( @@ -299,3 +293,17 @@ extension BalanceService { } } } + +// MARK: - EarnBalanceUpdatable + +extension BalanceService: EarnBalanceUpdatable { + @discardableResult + public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { + await updateBalanceAsync( + walletId: walletId, + chain: chain, + fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address) }, + mapBalance: { $0.earnChange } + ) + } +} diff --git a/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift b/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift new file mode 100644 index 000000000..0eadbcd35 --- /dev/null +++ b/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift @@ -0,0 +1,9 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import Primitives + +public protocol EarnBalanceUpdatable: Sendable { + @discardableResult + func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] +} diff --git a/Packages/FeatureServices/EarnService/EarnService.swift b/Packages/FeatureServices/EarnService/EarnService.swift index 95202a19a..4e540b1f4 100644 --- a/Packages/FeatureServices/EarnService/EarnService.swift +++ b/Packages/FeatureServices/EarnService/EarnService.swift @@ -1,5 +1,6 @@ // Copyright (c). Gem Wallet. All rights reserved. +import BalanceService import Blockchain import Foundation import Primitives @@ -12,10 +13,12 @@ public protocol EarnDataProvidable: Sendable { public struct EarnService: Sendable { private let store: StakeStore private let gatewayService: GatewayService + private let earnBalanceUpdater: any EarnBalanceUpdatable - public init(store: StakeStore, gatewayService: GatewayService) { + public init(store: StakeStore, gatewayService: GatewayService, earnBalanceUpdater: any EarnBalanceUpdatable) { self.store = store self.gatewayService = gatewayService + self.earnBalanceUpdater = earnBalanceUpdater } public func update(walletId: WalletId, assetId: AssetId, address: String) async throws { @@ -23,8 +26,9 @@ public struct EarnService: Sendable { try store.updateValidators(providers) let positions = try await gatewayService.earnPositions(chain: assetId.chain, address: address, assetIds: [assetId]) - try updatePositions(walletId: walletId, assetId: assetId, positions: positions) + + await earnBalanceUpdater.updateEarnBalance(walletId: walletId, chain: assetId.chain, address: address) } private func updatePositions(walletId: WalletId, assetId: AssetId, positions: [DelegationBase]) throws { diff --git a/Packages/FeatureServices/Package.swift b/Packages/FeatureServices/Package.swift index d3e9a9ec8..80bde2650 100644 --- a/Packages/FeatureServices/Package.swift +++ b/Packages/FeatureServices/Package.swift @@ -620,6 +620,7 @@ let package = Package( "Primitives", "Store", "Blockchain", + "BalanceService", ], path: "EarnService", exclude: ["TestKit"] diff --git a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift index 197424914..5c1fa26ce 100644 --- a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift +++ b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift @@ -42,9 +42,14 @@ public extension TransactionStateService { public extension EarnService { static func mock( - store: StakeStore = .mock() + store: StakeStore = .mock(), + earnBalanceUpdater: any EarnBalanceUpdatable = EarnBalanceUpdaterMock() ) -> EarnService { let provider = NativeProvider(url: Constants.apiURL, requestInterceptor: EmptyRequestInterceptor()) - return EarnService(store: store, gatewayService: GatewayService(provider: provider)) + return EarnService(store: store, gatewayService: GatewayService(provider: provider), earnBalanceUpdater: earnBalanceUpdater) } } + +public struct EarnBalanceUpdaterMock: EarnBalanceUpdatable { + public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { [] } +} diff --git a/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift b/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift index 488b874f1..2f8a5ca3e 100644 --- a/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift +++ b/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift @@ -137,7 +137,7 @@ public struct TransactionViewModel: Sendable { case .perpetualModifyPosition: return .empty case .earnDeposit: - return Localized.Transfer.Stake.title + return Localized.Common.earn case .earnWithdraw: return Localized.Transfer.Withdraw.title } diff --git a/Packages/Store/Sources/Stores/BalanceStore.swift b/Packages/Store/Sources/Stores/BalanceStore.swift index 209b7cb03..442ba9cb9 100644 --- a/Packages/Store/Sources/Stores/BalanceStore.swift +++ b/Packages/Store/Sources/Stores/BalanceStore.swift @@ -97,6 +97,16 @@ public struct BalanceStore: Sendable { } } + public func hasEarnBalance(walletId: WalletId, chain: Chain) throws -> Bool { + try db.read { db in + try BalanceRecord + .filter(BalanceRecord.Columns.walletId == walletId.id) + .filter(BalanceRecord.Columns.assetId.like("\(chain.rawValue)%")) + .filter(BalanceRecord.Columns.earnAmount > 0) + .fetchCount(db) > 0 + } + } + @discardableResult public func getBalance(walletId: WalletId, assetId: AssetId) throws -> Balance? { try db.read { db in diff --git a/core b/core index 68d6a8a46..cb40d5c06 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 68d6a8a46473454bfa040dceb45404e9217b64ad +Subproject commit cb40d5c06a5bd8c916bb969160b44d94e2328403 From ed410d4847d2d7ed31b16cbe173921d824003364 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:47:55 +0200 Subject: [PATCH 02/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index cb40d5c06..4b4753a5b 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit cb40d5c06a5bd8c916bb969160b44d94e2328403 +Subproject commit 4b4753a5b5fc284eb8475df61acaea2d6caac8c9 From 732b153564ddece76623951eb02776761e888104 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:25:58 +0200 Subject: [PATCH 03/21] Handle earn provider labels and views Show provider/earn-specific labels and capitalize names across stake/transaction/transfer views. Changes include: capitalizing validator name for .earn; adding earnProviderItemModel and routing .earnDeposit/.earnWithdraw to it in TransactionParticipantViewModel (and using Localized.Common.provider as the label); and updating ConfirmRecipientViewModel to use the provider title for .earn and make recipient input visible for earn flows. Files updated: ValidatorViewModel.swift, TransactionParticipantViewModel.swift, ConfirmRecipientViewModel.swift. --- .../Sources/ViewModels/ValidatorViewModel.swift | 2 +- .../TransactionParticipantViewModel.swift | 17 +++++++++++++++-- .../ViewModels/ConfirmRecipientViewModel.swift | 7 ++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift b/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift index 82aa0dadb..de24626d5 100644 --- a/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift +++ b/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift @@ -32,7 +32,7 @@ public struct ValidatorViewModel { } return validator.name case .earn: - return validator.name + return validator.name.capitalized } } diff --git a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift index ba0055ea9..3fb81a590 100644 --- a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift +++ b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift @@ -20,8 +20,9 @@ extension TransactionParticipantViewModel: ItemModelProvidable { var itemModel: TransactionItemModel { switch transactionViewModel.transaction.transaction.type { case .stakeFreeze, .stakeUnfreeze: resourceItemModel + case .earnDeposit, .earnWithdraw: earnProviderItemModel case .transfer, .transferNFT, .tokenApproval, .smartContractCall, .stakeDelegate: participantItemModel - case .swap, .stakeUndelegate, .stakeRedelegate, .stakeRewards, .stakeWithdraw, .assetActivation, .perpetualOpenPosition, .perpetualClosePosition, .perpetualModifyPosition, .earnDeposit, .earnWithdraw: .empty + case .swap, .stakeUndelegate, .stakeRedelegate, .stakeRewards, .stakeWithdraw, .assetActivation, .perpetualOpenPosition, .perpetualClosePosition, .perpetualModifyPosition: .empty } } } @@ -56,6 +57,16 @@ extension TransactionParticipantViewModel { ) } + private var earnProviderItemModel: TransactionItemModel { + let address = transactionViewModel.participant + let addressName = transactionViewModel.getAddressName(address: address) + let name = (addressName?.name ?? address).capitalized + return .listItem(ListItemModel( + title: Localized.Common.provider, + subtitle: name + )) + } + private var resourceItemModel: TransactionItemModel { guard let resourceType = transactionViewModel.transaction.transaction.metadata?.decode(TransactionResourceTypeMetadata.self)?.resourceType else { return .empty @@ -82,8 +93,10 @@ extension TransactionParticipantViewModel { Localized.Stake.validator case .stakeFreeze, .stakeUnfreeze: Localized.Stake.resource + case .earnDeposit, .earnWithdraw: + Localized.Common.provider case .swap, .stakeUndelegate, .stakeRedelegate, .stakeRewards, .stakeWithdraw, - .assetActivation, .perpetualOpenPosition, .perpetualClosePosition, .perpetualModifyPosition, .earnDeposit, .earnWithdraw: nil + .assetActivation, .perpetualOpenPosition, .perpetualClosePosition, .perpetualModifyPosition: nil } } } diff --git a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift index 33335dd0b..aecd03873 100644 --- a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift +++ b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift @@ -63,7 +63,8 @@ extension ConfirmRecipientViewModel { case .sign: Localized.Asset.contract case .send: Localized.Transfer.Recipient.title } - case .transfer, .deposit, .withdrawal, .transferNft, .tokenApprove, .account, .perpetual, .earn: Localized.Transfer.Recipient.title + case .earn: Localized.Common.provider + case .transfer, .deposit, .withdrawal, .transferNft, .tokenApprove, .account, .perpetual: Localized.Transfer.Recipient.title } } @@ -79,12 +80,12 @@ extension ConfirmRecipientViewModel { } case .account, .swap, - .perpetual, - .earn: false + .perpetual: false case .transfer, .transferNft, .deposit, .withdrawal, + .earn, .generic, .tokenApprove: true } From a3569b452cbcf2dbeb42e67b54eac3282f416c05 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:17:26 +0200 Subject: [PATCH 04/21] Fix tests --- .../SwapService/TestKit/Types+Mock/SwaperRoute+TestKit.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Packages/FeatureServices/SwapService/TestKit/Types+Mock/SwaperRoute+TestKit.swift b/Packages/FeatureServices/SwapService/TestKit/Types+Mock/SwaperRoute+TestKit.swift index 582ca262f..9276d67ac 100644 --- a/Packages/FeatureServices/SwapService/TestKit/Types+Mock/SwaperRoute+TestKit.swift +++ b/Packages/FeatureServices/SwapService/TestKit/Types+Mock/SwaperRoute+TestKit.swift @@ -9,8 +9,7 @@ extension SwapperRoute { SwapperRoute( input: "ethereum_0x0000000000000000000000000000000000000000", output: "ethereum_0xdac17f958d2ee523a2206206994597c13d831ec7", - routeData: "0x", - gasLimit: "150000" + routeData: "0x" ) } } From 8a722e29e414bf514d2095fa2ce021e804a3fc27 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:34:01 +0200 Subject: [PATCH 05/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 4b4753a5b..36a876cf9 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 4b4753a5b5fc284eb8475df61acaea2d6caac8c9 +Subproject commit 36a876cf9607d7c58d657012ca7c0b27bb6feb1f From d6de5111494669435e8a6cfcd762a1afbdc0a2de Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:40:18 +0200 Subject: [PATCH 06/21] Delete TransactionStateService+TestKit.swift --- .../TransactionStateService+TestKit.swift | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift diff --git a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift deleted file mode 100644 index 5c1fa26ce..000000000 --- a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c). Gem Wallet. All rights reserved. - -import Foundation -import Gemstone -import Primitives -import TransactionStateService -import Store -import StakeService -import EarnService -import Blockchain -import NFTService -import ChainService -import BalanceService -import NativeProviderService -import StoreTestKit -import StakeServiceTestKit -import NFTServiceTestKit -import ChainServiceTestKit -import BalanceServiceTestKit -import SwapServiceTestKit - -public extension TransactionStateService { - static func mock( - transactionStore: TransactionStore = .mock(), - swapper: any GemSwapperProtocol = GemSwapperMock(), - stakeService: StakeService = .mock(), - earnService: EarnService = .mock(), - nftService: NFTService = .mock(), - chainServiceFactory: any ChainServiceFactorable = ChainServiceFactoryMock() - ) -> TransactionStateService { - TransactionStateService( - transactionStore: transactionStore, - swapper: swapper, - stakeService: stakeService, - earnService: earnService, - nftService: nftService, - chainServiceFactory: chainServiceFactory, - balanceUpdater: .mock() - ) - } -} - -public extension EarnService { - static func mock( - store: StakeStore = .mock(), - earnBalanceUpdater: any EarnBalanceUpdatable = EarnBalanceUpdaterMock() - ) -> EarnService { - let provider = NativeProvider(url: Constants.apiURL, requestInterceptor: EmptyRequestInterceptor()) - return EarnService(store: store, gatewayService: GatewayService(provider: provider), earnBalanceUpdater: earnBalanceUpdater) - } -} - -public struct EarnBalanceUpdaterMock: EarnBalanceUpdatable { - public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { [] } -} From d5879a03ed6e4f94743df94517ce1106ba5396df Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:08:09 +0200 Subject: [PATCH 07/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 36a876cf9..7ca679138 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 36a876cf9607d7c58d657012ca7c0b27bb6feb1f +Subproject commit 7ca679138737fc8c199271b37b371fd76d4b3f8e From 2ae445ca0609ed69ac19392dc9783e447dcbfaff Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:15:27 +0200 Subject: [PATCH 08/21] undo TransactionStateService --- .../TestKit/EarnBalanceUpdater+TestKit.swift | 16 ++++++ .../TransactionStateService+TestKit.swift | 50 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift create mode 100644 Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift diff --git a/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift b/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift new file mode 100644 index 000000000..15cd3b8b2 --- /dev/null +++ b/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift @@ -0,0 +1,16 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import BalanceService +import Primitives + +public struct EarnBalanceUpdaterMock: EarnBalanceUpdatable { + public init() {} + public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { [] } +} + +public extension EarnBalanceUpdatable where Self == EarnBalanceUpdaterMock { + static func mock() -> EarnBalanceUpdaterMock { + EarnBalanceUpdaterMock() + } +} diff --git a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift new file mode 100644 index 000000000..886b109b5 --- /dev/null +++ b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift @@ -0,0 +1,50 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import Gemstone +import Primitives +import TransactionStateService +import Store +import StakeService +import EarnService +import Blockchain +import NFTService +import ChainService +import BalanceService +import NativeProviderService +import StoreTestKit +import StakeServiceTestKit +import NFTServiceTestKit +import ChainServiceTestKit +import BalanceServiceTestKit +import SwapServiceTestKit + +public extension TransactionStateService { + static func mock( + transactionStore: TransactionStore = .mock(), + swapper: any GemSwapperProtocol = GemSwapperMock(), + stakeService: StakeService = .mock(), + earnService: EarnService = .mock(), + nftService: NFTService = .mock(), + chainServiceFactory: any ChainServiceFactorable = ChainServiceFactoryMock() + ) -> TransactionStateService { + TransactionStateService( + transactionStore: transactionStore, + swapper: swapper, + stakeService: stakeService, + earnService: earnService, + nftService: nftService, + chainServiceFactory: chainServiceFactory, + balanceUpdater: .mock() + ) + } +} + +public extension EarnService { + static func mock( + store: StakeStore = .mock() + ) -> EarnService { + let provider = NativeProvider(url: Constants.apiURL, requestInterceptor: EmptyRequestInterceptor()) + return EarnService(store: store, gatewayService: GatewayService(provider: provider), earnBalanceUpdater: .mock()) + } +} From 954d776d78cdce12b8bb2040f577069c0d2dddc3 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:26:14 +0200 Subject: [PATCH 09/21] updates core --- Packages/Blockchain/Sources/Gateway/GatewayService.swift | 9 ++++----- Packages/FeatureServices/EarnService/EarnService.swift | 4 ++-- core | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Packages/Blockchain/Sources/Gateway/GatewayService.swift b/Packages/Blockchain/Sources/Gateway/GatewayService.swift index b2fc422b9..41f14b6ee 100644 --- a/Packages/Blockchain/Sources/Gateway/GatewayService.swift +++ b/Packages/Blockchain/Sources/Gateway/GatewayService.swift @@ -162,13 +162,12 @@ extension GatewayService { // MARK: - Earn extension GatewayService { - public func earnProviders(assetId: Primitives.AssetId) -> [DelegationValidator] { - gateway.getEarnProviders(assetId: assetId.identifier).compactMap { try? $0.map() } + public func earnProviders(assetId: Primitives.AssetId) throws -> [DelegationValidator] { + try gateway.getEarnProviders(assetId: assetId.identifier).map { try $0.map() } } - public func earnPositions(chain: Primitives.Chain, address: String, assetIds: [Primitives.AssetId]) async throws -> [DelegationBase] { - try await gateway.getEarnPositions(chain: chain.rawValue, address: address, assetIds: assetIds.ids) - .map { try $0.map() } + public func earnPositions(address: String, assetId: Primitives.AssetId) async throws -> [DelegationBase] { + try await gateway.getEarnPositions(address: address, assetId: assetId.identifier).map { try $0.map() } } public func getEarnData( diff --git a/Packages/FeatureServices/EarnService/EarnService.swift b/Packages/FeatureServices/EarnService/EarnService.swift index 4e540b1f4..9c7137603 100644 --- a/Packages/FeatureServices/EarnService/EarnService.swift +++ b/Packages/FeatureServices/EarnService/EarnService.swift @@ -22,10 +22,10 @@ public struct EarnService: Sendable { } public func update(walletId: WalletId, assetId: AssetId, address: String) async throws { - let providers = await gatewayService.earnProviders(assetId: assetId) + let providers = try await gatewayService.earnProviders(assetId: assetId) try store.updateValidators(providers) - let positions = try await gatewayService.earnPositions(chain: assetId.chain, address: address, assetIds: [assetId]) + let positions = try await gatewayService.earnPositions(address: address, assetId: assetId) try updatePositions(walletId: walletId, assetId: assetId, positions: positions) await earnBalanceUpdater.updateEarnBalance(walletId: walletId, chain: assetId.chain, address: address) diff --git a/core b/core index 7ca679138..8a0e4851c 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 7ca679138737fc8c199271b37b371fd76d4b3f8e +Subproject commit 8a0e4851c987d0b7c8650e0d19d67f0a125b908f From 303e6a7593a138c48489afff8b7fab1bb133bb1a Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:02:45 +0200 Subject: [PATCH 10/21] Removed uneeded checks --- .../ViewModels/ValidatorViewModel.swift | 2 +- .../TransactionParticipantViewModel.swift | 2 +- Gem/Services/ServicesFactory.swift | 3 +- .../BalanceService/BalanceFetcher.swift | 4 ++- .../BalanceService/BalanceService.swift | 30 ++++++++----------- .../Protocols/EarnBalanceUpdatable.swift | 9 ------ .../TestKit/EarnBalanceUpdater+TestKit.swift | 16 ---------- .../EarnService/EarnService.swift | 7 +---- .../TransactionStateService+TestKit.swift | 2 +- .../Store/Sources/Stores/BalanceStore.swift | 10 ------- core | 2 +- 11 files changed, 21 insertions(+), 66 deletions(-) delete mode 100644 Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift delete mode 100644 Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift diff --git a/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift b/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift index de24626d5..82aa0dadb 100644 --- a/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift +++ b/Features/Stake/Sources/ViewModels/ValidatorViewModel.swift @@ -32,7 +32,7 @@ public struct ValidatorViewModel { } return validator.name case .earn: - return validator.name.capitalized + return validator.name } } diff --git a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift index 3fb81a590..9962888dd 100644 --- a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift +++ b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift @@ -60,7 +60,7 @@ extension TransactionParticipantViewModel { private var earnProviderItemModel: TransactionItemModel { let address = transactionViewModel.participant let addressName = transactionViewModel.getAddressName(address: address) - let name = (addressName?.name ?? address).capitalized + let name = addressName?.name ?? address return .listItem(ListItemModel( title: Localized.Common.provider, subtitle: name diff --git a/Gem/Services/ServicesFactory.swift b/Gem/Services/ServicesFactory.swift index d321281b0..63a6f0b91 100644 --- a/Gem/Services/ServicesFactory.swift +++ b/Gem/Services/ServicesFactory.swift @@ -106,8 +106,7 @@ struct ServicesFactory { ) let earnService = EarnService( store: storeManager.stakeStore, - gatewayService: gatewayService, - earnBalanceUpdater: balanceService + gatewayService: gatewayService ) let stakeService = Self.makeStakeService( stakeStore: storeManager.stakeStore, diff --git a/Packages/FeatureServices/BalanceService/BalanceFetcher.swift b/Packages/FeatureServices/BalanceService/BalanceFetcher.swift index a9fd9259e..eb2f33db0 100644 --- a/Packages/FeatureServices/BalanceService/BalanceFetcher.swift +++ b/Packages/FeatureServices/BalanceService/BalanceFetcher.swift @@ -30,7 +30,9 @@ struct BalanceFetcher: Sendable { } func getEarnBalance(chain: Chain, address: String) async throws -> [AssetBalance] { - try await chainServiceFactory.service(for: chain).getEarnBalance(for: address) + try await chainServiceFactory + .service(for: chain) + .getEarnBalance(for: address) } func getTokenBalance( diff --git a/Packages/FeatureServices/BalanceService/BalanceService.swift b/Packages/FeatureServices/BalanceService/BalanceService.swift index 5f7235c8c..a6ac5bf67 100644 --- a/Packages/FeatureServices/BalanceService/BalanceService.swift +++ b/Packages/FeatureServices/BalanceService/BalanceService.swift @@ -63,10 +63,8 @@ extension BalanceService: BalanceUpdater { } // earn balance - if (try? balanceStore.hasEarnBalance(walletId: walletId, chain: chain)) == true { - group.addTask { - await updateEarnBalance(walletId: walletId, chain: chain, address: address) - } + group.addTask { + await updateEarnBalance(walletId: walletId, chain: chain, address: address) } // token balance @@ -135,6 +133,16 @@ extension BalanceService { ) } + @discardableResult + private func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { + await updateBalanceAsync( + walletId: walletId, + chain: chain, + fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address) }, + mapBalance: { $0.earnChange } + ) + } + @discardableResult private func updateTokenBalances(walletId: WalletId, chain: Chain, tokenIds: [AssetId], address: String) async -> [AssetBalanceChange] { await updateBalanceAsync( @@ -241,17 +249,3 @@ extension BalanceService { } } } - -// MARK: - EarnBalanceUpdatable - -extension BalanceService: EarnBalanceUpdatable { - @discardableResult - public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { - await updateBalanceAsync( - walletId: walletId, - chain: chain, - fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address) }, - mapBalance: { $0.earnChange } - ) - } -} diff --git a/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift b/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift deleted file mode 100644 index 0eadbcd35..000000000 --- a/Packages/FeatureServices/BalanceService/Protocols/EarnBalanceUpdatable.swift +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c). Gem Wallet. All rights reserved. - -import Foundation -import Primitives - -public protocol EarnBalanceUpdatable: Sendable { - @discardableResult - func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] -} diff --git a/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift b/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift deleted file mode 100644 index 15cd3b8b2..000000000 --- a/Packages/FeatureServices/BalanceService/TestKit/EarnBalanceUpdater+TestKit.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c). Gem Wallet. All rights reserved. - -import Foundation -import BalanceService -import Primitives - -public struct EarnBalanceUpdaterMock: EarnBalanceUpdatable { - public init() {} - public func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { [] } -} - -public extension EarnBalanceUpdatable where Self == EarnBalanceUpdaterMock { - static func mock() -> EarnBalanceUpdaterMock { - EarnBalanceUpdaterMock() - } -} diff --git a/Packages/FeatureServices/EarnService/EarnService.swift b/Packages/FeatureServices/EarnService/EarnService.swift index 9c7137603..30cf765e7 100644 --- a/Packages/FeatureServices/EarnService/EarnService.swift +++ b/Packages/FeatureServices/EarnService/EarnService.swift @@ -1,6 +1,5 @@ // Copyright (c). Gem Wallet. All rights reserved. -import BalanceService import Blockchain import Foundation import Primitives @@ -13,12 +12,10 @@ public protocol EarnDataProvidable: Sendable { public struct EarnService: Sendable { private let store: StakeStore private let gatewayService: GatewayService - private let earnBalanceUpdater: any EarnBalanceUpdatable - public init(store: StakeStore, gatewayService: GatewayService, earnBalanceUpdater: any EarnBalanceUpdatable) { + public init(store: StakeStore, gatewayService: GatewayService) { self.store = store self.gatewayService = gatewayService - self.earnBalanceUpdater = earnBalanceUpdater } public func update(walletId: WalletId, assetId: AssetId, address: String) async throws { @@ -27,8 +24,6 @@ public struct EarnService: Sendable { let positions = try await gatewayService.earnPositions(address: address, assetId: assetId) try updatePositions(walletId: walletId, assetId: assetId, positions: positions) - - await earnBalanceUpdater.updateEarnBalance(walletId: walletId, chain: assetId.chain, address: address) } private func updatePositions(walletId: WalletId, assetId: AssetId, positions: [DelegationBase]) throws { diff --git a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift index 886b109b5..197424914 100644 --- a/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift +++ b/Packages/FeatureServices/TransactionStateService/TestKit/TransactionStateService+TestKit.swift @@ -45,6 +45,6 @@ public extension EarnService { store: StakeStore = .mock() ) -> EarnService { let provider = NativeProvider(url: Constants.apiURL, requestInterceptor: EmptyRequestInterceptor()) - return EarnService(store: store, gatewayService: GatewayService(provider: provider), earnBalanceUpdater: .mock()) + return EarnService(store: store, gatewayService: GatewayService(provider: provider)) } } diff --git a/Packages/Store/Sources/Stores/BalanceStore.swift b/Packages/Store/Sources/Stores/BalanceStore.swift index 0e194d7af..0e8499734 100644 --- a/Packages/Store/Sources/Stores/BalanceStore.swift +++ b/Packages/Store/Sources/Stores/BalanceStore.swift @@ -97,16 +97,6 @@ public struct BalanceStore: Sendable { } } - public func hasEarnBalance(walletId: WalletId, chain: Chain) throws -> Bool { - try db.read { db in - try BalanceRecord - .filter(BalanceRecord.Columns.walletId == walletId.id) - .filter(BalanceRecord.Columns.assetId.like("\(chain.rawValue)%")) - .filter(BalanceRecord.Columns.earnAmount > 0) - .fetchCount(db) > 0 - } - } - @discardableResult public func getBalance(walletId: WalletId, assetId: AssetId) throws -> Balance? { try db.read { db in diff --git a/core b/core index 8a0e4851c..ef48d1882 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 8a0e4851c987d0b7c8650e0d19d67f0a125b908f +Subproject commit ef48d1882b5f35044f01be4002eb63166fd1940a From 0ffc887ad207de31636ea487a1a73ff8e8f66a5f Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:09:03 +0200 Subject: [PATCH 11/21] Pass token IDs to earn balance fetch for targeted RPC calls Update getEarnBalance to accept [AssetId] tokenIds, consistent with tokenBalance pattern. BalanceService passes wallet's token IDs so only chains with matching earn assets trigger RPC calls. --- .../Sources/Gateway/GatewayChainService.swift | 4 ++-- .../Blockchain/Sources/Gateway/GatewayService.swift | 4 ++-- .../Sources/Protocols/ChainServiceable.swift | 4 ++-- Packages/Blockchain/TestKit/ChainServiceMock.swift | 2 +- .../BalanceService/BalanceFetcher.swift | 4 ++-- .../BalanceService/BalanceService.swift | 12 +++++------- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Packages/Blockchain/Sources/Gateway/GatewayChainService.swift b/Packages/Blockchain/Sources/Gateway/GatewayChainService.swift index 09c66a06e..eaf9e387c 100644 --- a/Packages/Blockchain/Sources/Gateway/GatewayChainService.swift +++ b/Packages/Blockchain/Sources/Gateway/GatewayChainService.swift @@ -35,8 +35,8 @@ extension GatewayChainService: ChainBalanceable { try await gateway.getStakeBalance(chain: chain, address: address) } - public func getEarnBalance(for address: String) async throws -> [AssetBalance] { - try await gateway.getEarnBalance(chain: chain, address: address) + public func getEarnBalance(for address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] { + try await gateway.getEarnBalance(chain: chain, address: address, tokenIds: tokenIds) } } diff --git a/Packages/Blockchain/Sources/Gateway/GatewayService.swift b/Packages/Blockchain/Sources/Gateway/GatewayService.swift index 41f14b6ee..3ed66cc7d 100644 --- a/Packages/Blockchain/Sources/Gateway/GatewayService.swift +++ b/Packages/Blockchain/Sources/Gateway/GatewayService.swift @@ -50,8 +50,8 @@ extension GatewayService { try await gateway.getBalanceStaking(chain: chain.rawValue, address: address)?.map() } - public func getEarnBalance(chain: Primitives.Chain, address: String) async throws -> [AssetBalance] { - try await gateway.getBalanceEarn(chain: chain.rawValue, address: address) + public func getEarnBalance(chain: Primitives.Chain, address: String, tokenIds: [Primitives.AssetId]) async throws -> [AssetBalance] { + try await gateway.getBalanceEarn(chain: chain.rawValue, address: address, tokenIds: tokenIds.compactMap(\.tokenId)) .map { try $0.map() } } } diff --git a/Packages/Blockchain/Sources/Protocols/ChainServiceable.swift b/Packages/Blockchain/Sources/Protocols/ChainServiceable.swift index 698e57a83..5f9fb7009 100644 --- a/Packages/Blockchain/Sources/Protocols/ChainServiceable.swift +++ b/Packages/Blockchain/Sources/Protocols/ChainServiceable.swift @@ -22,7 +22,7 @@ public protocol ChainBalanceable: Sendable { func coinBalance(for address: String) async throws -> AssetBalance func tokenBalance(for address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] func getStakeBalance(for address: String) async throws -> AssetBalance? - func getEarnBalance(for address: String) async throws -> [AssetBalance] + func getEarnBalance(for address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] } public protocol ChainFeeRateFetchable: Sendable { @@ -75,7 +75,7 @@ public protocol ChainNodeStatusFetchable: Sendable { protocol ChainFeePriorityPreference: Sendable {} public extension ChainBalanceable { - func getEarnBalance(for address: String) async throws -> [AssetBalance] { + func getEarnBalance(for address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] { return [] } } diff --git a/Packages/Blockchain/TestKit/ChainServiceMock.swift b/Packages/Blockchain/TestKit/ChainServiceMock.swift index 13228bc17..e46926600 100644 --- a/Packages/Blockchain/TestKit/ChainServiceMock.swift +++ b/Packages/Blockchain/TestKit/ChainServiceMock.swift @@ -46,7 +46,7 @@ extension ChainServiceMock { stakeBalance } - public func getEarnBalance(for address: String) async throws -> [AssetBalance] { + public func getEarnBalance(for address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] { [] } diff --git a/Packages/FeatureServices/BalanceService/BalanceFetcher.swift b/Packages/FeatureServices/BalanceService/BalanceFetcher.swift index eb2f33db0..a0c4b57f8 100644 --- a/Packages/FeatureServices/BalanceService/BalanceFetcher.swift +++ b/Packages/FeatureServices/BalanceService/BalanceFetcher.swift @@ -29,10 +29,10 @@ struct BalanceFetcher: Sendable { .getStakeBalance(for: address) } - func getEarnBalance(chain: Chain, address: String) async throws -> [AssetBalance] { + func getEarnBalance(chain: Chain, address: String, tokenIds: [AssetId]) async throws -> [AssetBalance] { try await chainServiceFactory .service(for: chain) - .getEarnBalance(for: address) + .getEarnBalance(for: address, tokenIds: tokenIds) } func getTokenBalance( diff --git a/Packages/FeatureServices/BalanceService/BalanceService.swift b/Packages/FeatureServices/BalanceService/BalanceService.swift index a6ac5bf67..3ef6f8c5d 100644 --- a/Packages/FeatureServices/BalanceService/BalanceService.swift +++ b/Packages/FeatureServices/BalanceService/BalanceService.swift @@ -62,16 +62,14 @@ extension BalanceService: BalanceUpdater { } } - // earn balance - group.addTask { - await updateEarnBalance(walletId: walletId, chain: chain, address: address) - } - // token balance if !tokenIds.isEmpty { group.addTask { await updateTokenBalances(walletId: walletId, chain: chain, tokenIds: tokenIds, address: address) } + group.addTask { + await updateEarnBalance(walletId: walletId, chain: chain, address: address, tokenIds: tokenIds) + } } } @@ -134,11 +132,11 @@ extension BalanceService { } @discardableResult - private func updateEarnBalance(walletId: WalletId, chain: Chain, address: String) async -> [AssetBalanceChange] { + private func updateEarnBalance(walletId: WalletId, chain: Chain, address: String, tokenIds: [AssetId]) async -> [AssetBalanceChange] { await updateBalanceAsync( walletId: walletId, chain: chain, - fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address) }, + fetchBalance: { try await fetcher.getEarnBalance(chain: chain, address: address, tokenIds: tokenIds) }, mapBalance: { $0.earnChange } ) } From 788cbb2c22da8685340fcb4e474c2daadc3f3558 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:09:09 +0200 Subject: [PATCH 12/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index ef48d1882..ee874a38d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit ef48d1882b5f35044f01be4002eb63166fd1940a +Subproject commit ee874a38d02325e35fdb89fc6d60e0f829edee71 From a5e8579b2e4aea3bcb9d962f74bf8a585dbf79fc Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 02:22:40 +0200 Subject: [PATCH 13/21] Update core , added update for earn apr --- Packages/FeatureServices/EarnService/EarnService.swift | 2 ++ Packages/Store/Sources/Stores/StakeStore.swift | 9 +++++++++ core | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Packages/FeatureServices/EarnService/EarnService.swift b/Packages/FeatureServices/EarnService/EarnService.swift index 30cf765e7..f2f122407 100644 --- a/Packages/FeatureServices/EarnService/EarnService.swift +++ b/Packages/FeatureServices/EarnService/EarnService.swift @@ -19,7 +19,9 @@ public struct EarnService: Sendable { } public func update(walletId: WalletId, assetId: AssetId, address: String) async throws { + let apr = try store.getEarnApr(assetId: assetId) ?? 0 let providers = try await gatewayService.earnProviders(assetId: assetId) + .map { DelegationValidator(chain: $0.chain, id: $0.id, name: $0.name, isActive: $0.isActive, commission: $0.commission, apr: apr, providerType: $0.providerType) } try store.updateValidators(providers) let positions = try await gatewayService.earnPositions(address: address, assetId: assetId) diff --git a/Packages/Store/Sources/Stores/StakeStore.swift b/Packages/Store/Sources/Stores/StakeStore.swift index e2a0df37c..3062d16cc 100644 --- a/Packages/Store/Sources/Stores/StakeStore.swift +++ b/Packages/Store/Sources/Stores/StakeStore.swift @@ -20,6 +20,15 @@ public struct StakeStore: Sendable { .map { $0.stakingApr } ?? .none } } + + public func getEarnApr(assetId: AssetId) throws -> Double? { + try db.read { db in + try AssetRecord + .filter(key: assetId.identifier) + .fetchOne(db) + .map { $0.earnApr } ?? .none + } + } public func updateDelegations(walletId: WalletId, delegations: [DelegationBase]) throws { try db.write { db in diff --git a/core b/core index ee874a38d..34f941577 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit ee874a38d02325e35fdb89fc6d60e0f829edee71 +Subproject commit 34f9415775275bd3545d5bc839be086760811111 From b5017fbc756e80d83b61257627e245b6c8f45c06 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:55:50 +0200 Subject: [PATCH 14/21] Persist earnApr from API to asset store --- Packages/Store/Sources/Stores/AssetStore.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packages/Store/Sources/Stores/AssetStore.swift b/Packages/Store/Sources/Stores/AssetStore.swift index 329e3be3e..854f30c72 100644 --- a/Packages/Store/Sources/Stores/AssetStore.swift +++ b/Packages/Store/Sources/Stores/AssetStore.swift @@ -38,7 +38,8 @@ public struct AssetStore: Sendable { AssetRecord.Columns.isSellable.set(to: asset.properties.isSellable), AssetRecord.Columns.isSwappable.set(to: asset.properties.isSwapable), AssetRecord.Columns.isStakeable.set(to: asset.properties.isStakeable), - AssetRecord.Columns.stakingApr.set(to: asset.properties.stakingApr) + AssetRecord.Columns.stakingApr.set(to: asset.properties.stakingApr), + AssetRecord.Columns.earnApr.set(to: asset.properties.earnApr) ) } } From 4a1a9b151e27b89eee3b198f8ab99557543b2635 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:55:57 +0200 Subject: [PATCH 15/21] Show earn provider in confirm and transaction history Use contract address as recipient so provider is shown with explorer link, matching staking validator display pattern --- .../TransactionParticipantViewModel.swift | 13 +------------ .../Sources/ViewModels/AmountEarnViewModel.swift | 11 +++++------ .../ViewModels/ConfirmRecipientViewModel.swift | 4 ++-- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift index 9962888dd..368dca8c2 100644 --- a/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift +++ b/Features/Transactions/Sources/ViewModels/TransactionParticipantViewModel.swift @@ -20,8 +20,7 @@ extension TransactionParticipantViewModel: ItemModelProvidable { var itemModel: TransactionItemModel { switch transactionViewModel.transaction.transaction.type { case .stakeFreeze, .stakeUnfreeze: resourceItemModel - case .earnDeposit, .earnWithdraw: earnProviderItemModel - case .transfer, .transferNFT, .tokenApproval, .smartContractCall, .stakeDelegate: participantItemModel + case .earnDeposit, .earnWithdraw, .transfer, .transferNFT, .tokenApproval, .smartContractCall, .stakeDelegate: participantItemModel case .swap, .stakeUndelegate, .stakeRedelegate, .stakeRewards, .stakeWithdraw, .assetActivation, .perpetualOpenPosition, .perpetualClosePosition, .perpetualModifyPosition: .empty } } @@ -57,16 +56,6 @@ extension TransactionParticipantViewModel { ) } - private var earnProviderItemModel: TransactionItemModel { - let address = transactionViewModel.participant - let addressName = transactionViewModel.getAddressName(address: address) - let name = addressName?.name ?? address - return .listItem(ListItemModel( - title: Localized.Common.provider, - subtitle: name - )) - } - private var resourceItemModel: TransactionItemModel { guard let resourceType = transactionViewModel.transaction.transaction.metadata?.decode(TransactionResourceTypeMetadata.self)?.resourceType else { return .empty diff --git a/Features/Transfer/Sources/ViewModels/AmountEarnViewModel.swift b/Features/Transfer/Sources/ViewModels/AmountEarnViewModel.swift index 9e7734e08..70806c94a 100644 --- a/Features/Transfer/Sources/ViewModels/AmountEarnViewModel.swift +++ b/Features/Transfer/Sources/ViewModels/AmountEarnViewModel.swift @@ -64,11 +64,7 @@ final class AmountEarnViewModel: AmountDataProvidable { } func recipientData() -> RecipientData { - let provider = switch action { - case .deposit(let provider): provider - case .withdraw(let delegation): delegation.validator - } - return RecipientData( + RecipientData( recipient: Recipient(name: provider.name, address: provider.id, memo: nil), amount: nil ) @@ -84,7 +80,10 @@ final class AmountEarnViewModel: AmountDataProvidable { ) return TransferData( type: .earn(asset, action, earnData), - recipientData: recipientData(), + recipientData: RecipientData( + recipient: Recipient(name: provider.name, address: earnData.contractAddress, memo: nil), + amount: nil + ), value: value, canChangeValue: canChangeValue ) diff --git a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift index b0b74a29f..089cc753c 100644 --- a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift +++ b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift @@ -80,8 +80,8 @@ extension ConfirmRecipientViewModel { } case .account, .swap, - .perpetual, - .earn: false + .perpetual: false + case .earn: true case .generic: switch model.type.outputAction { case .sign: false From a3c8b6da3ff053e756671cbfaac2a4362b0ec73b Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:13:32 +0200 Subject: [PATCH 16/21] Update TransactionViewModel.swift --- .../Sources/ViewModels/TransactionViewModel.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift b/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift index 2f8a5ca3e..f60d185c6 100644 --- a/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift +++ b/Packages/PrimitivesComponents/Sources/ViewModels/TransactionViewModel.swift @@ -212,15 +212,17 @@ public struct TransactionViewModel: Sendable { case .stakeUnfreeze: guard let title = getResourceTitle() else { return .none } return String(format: "%@ %@", Localized.Transfer.from, title) + case .earnDeposit: + return String(format: "%@ %@", Localized.Transfer.to, getDisplayName(address: transaction.transaction.to, chain: chain)) + case .earnWithdraw: + return String(format: "%@ %@", Localized.Transfer.from, getDisplayName(address: transaction.transaction.to, chain: chain)) case .swap, .stakeRewards, .stakeWithdraw, .assetActivation, .perpetualModifyPosition, .perpetualOpenPosition, - .perpetualClosePosition, - .earnDeposit, - .earnWithdraw: + .perpetualClosePosition: guard let metadata = transaction.transaction.metadata?.decode(TransactionPerpetualMetadata.self) else { return .none } From 4603ecddfb283b4ddb77022c43ea1f2563eb80e9 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:54:55 +0200 Subject: [PATCH 17/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 34f941577..a3662e3dc 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 34f9415775275bd3545d5bc839be086760811111 +Subproject commit a3662e3dcc214d3b1c425ad3c8a24029117426fe From 9bd0b870bdbe65ee94b1c17fbe2384f8ce3c2288 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:00:18 +0200 Subject: [PATCH 18/21] Added missed isEarnable --- Packages/Store/Sources/Stores/AssetStore.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Packages/Store/Sources/Stores/AssetStore.swift b/Packages/Store/Sources/Stores/AssetStore.swift index 854f30c72..3a4670851 100644 --- a/Packages/Store/Sources/Stores/AssetStore.swift +++ b/Packages/Store/Sources/Stores/AssetStore.swift @@ -38,6 +38,7 @@ public struct AssetStore: Sendable { AssetRecord.Columns.isSellable.set(to: asset.properties.isSellable), AssetRecord.Columns.isSwappable.set(to: asset.properties.isSwapable), AssetRecord.Columns.isStakeable.set(to: asset.properties.isStakeable), + AssetRecord.Columns.isEarnable.set(to: asset.properties.isEarnable), AssetRecord.Columns.stakingApr.set(to: asset.properties.stakingApr), AssetRecord.Columns.earnApr.set(to: asset.properties.earnApr) ) From f5badb09dfa4a38c1e286f575923ab88d21a3dab Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:23:43 +0200 Subject: [PATCH 19/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index a3662e3dc..6dd74c2b1 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit a3662e3dcc214d3b1c425ad3c8a24029117426fe +Subproject commit 6dd74c2b1eac52e460c56335bb05f5795cb091f7 From daf9d3d2b66711926ecc41a6c19c78acd7546e82 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:11:22 +0200 Subject: [PATCH 20/21] Update core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 6dd74c2b1..edcca4ee6 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 6dd74c2b1eac52e460c56335bb05f5795cb091f7 +Subproject commit edcca4ee6b1302f6d26914ced0314576d99a933c From c8b199100fd16fa69a0d0dfe56fbd188fa174bf3 Mon Sep 17 00:00:00 2001 From: gemdev111 <171273137+gemdev111@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:23:33 +0200 Subject: [PATCH 21/21] Cleanup --- Features/Stake/Sources/ViewModels/EarnSceneViewModel.swift | 2 -- Gem/Navigation/Stake/EarnNavigationView.swift | 1 - Packages/FeatureServices/EarnService/EarnService.swift | 1 - .../EarnService/TestKit/EarnService+TestKit.swift | 1 - 4 files changed, 5 deletions(-) diff --git a/Features/Stake/Sources/ViewModels/EarnSceneViewModel.swift b/Features/Stake/Sources/ViewModels/EarnSceneViewModel.swift index a51d8fed4..068c07dba 100644 --- a/Features/Stake/Sources/ViewModels/EarnSceneViewModel.swift +++ b/Features/Stake/Sources/ViewModels/EarnSceneViewModel.swift @@ -6,7 +6,6 @@ import Foundation import Localization import Primitives import Store -import Style import EarnService import PrimitivesComponents @@ -52,7 +51,6 @@ public final class EarnSceneViewModel { var title: String { Localized.Common.earn } var assetTitle: String { AssetViewModel(asset: asset).title } - private var apr: Double? { providers.first.map(\.apr).flatMap { $0 > 0 ? $0 : nil } ?? assetData.metadata.earnApr diff --git a/Gem/Navigation/Stake/EarnNavigationView.swift b/Gem/Navigation/Stake/EarnNavigationView.swift index 311de39ad..0a06a7215 100644 --- a/Gem/Navigation/Stake/EarnNavigationView.swift +++ b/Gem/Navigation/Stake/EarnNavigationView.swift @@ -1,6 +1,5 @@ // Copyright (c). Gem Wallet. All rights reserved. -import Foundation import SwiftUI import Primitives import Stake diff --git a/Packages/FeatureServices/EarnService/EarnService.swift b/Packages/FeatureServices/EarnService/EarnService.swift index f2f122407..2366d1e86 100644 --- a/Packages/FeatureServices/EarnService/EarnService.swift +++ b/Packages/FeatureServices/EarnService/EarnService.swift @@ -1,7 +1,6 @@ // Copyright (c). Gem Wallet. All rights reserved. import Blockchain -import Foundation import Primitives import Store diff --git a/Packages/FeatureServices/EarnService/TestKit/EarnService+TestKit.swift b/Packages/FeatureServices/EarnService/TestKit/EarnService+TestKit.swift index c652f5ee0..7ea7f75ec 100644 --- a/Packages/FeatureServices/EarnService/TestKit/EarnService+TestKit.swift +++ b/Packages/FeatureServices/EarnService/TestKit/EarnService+TestKit.swift @@ -1,6 +1,5 @@ // Copyright (c). Gem Wallet. All rights reserved. -import Foundation import Primitives import PrimitivesTestKit import EarnService