diff --git a/Features/Transfer/Sources/Services/ConfirmService.swift b/Features/Transfer/Sources/Services/ConfirmService.swift index 72db9b902..adfd7ef7c 100644 --- a/Features/Transfer/Sources/Services/ConfirmService.swift +++ b/Features/Transfer/Sources/Services/ConfirmService.swift @@ -8,6 +8,7 @@ import ChainService import AddressNameService import ActivityService import EventPresenterService +import WalletService public struct ConfirmService: Sendable { private let metadataProvider: any TransferMetadataProvidable @@ -16,6 +17,7 @@ public struct ConfirmService: Sendable { private let keystore: any Keystore private let chainService: any ChainServiceable private let explorerService: any ExplorerLinkFetchable + private let walletService: WalletService private let addressNameService: AddressNameService private let activityService: ActivityService private let eventPresenterService: EventPresenterService @@ -27,6 +29,7 @@ public struct ConfirmService: Sendable { transferExecutor: any TransferExecutable, keystore: any Keystore, chainService: any ChainServiceable, + walletService: WalletService, addressNameService: AddressNameService, activityService: ActivityService, eventPresenterService: EventPresenterService @@ -37,6 +40,7 @@ public struct ConfirmService: Sendable { self.transferExecutor = transferExecutor self.keystore = keystore self.chainService = chainService + self.walletService = walletService self.addressNameService = addressNameService self.activityService = activityService self.eventPresenterService = eventPresenterService @@ -86,6 +90,12 @@ public struct ConfirmService: Sendable { } public func getAddressName(chain: Chain, address: String) throws -> AddressName? { - try addressNameService.getAddressName(chain: chain, address: address) + if let addressName = try addressNameService.getAddressName(chain: chain, address: address) { + return addressName + } + if let wallet = try walletService.getWallet(chain: chain, address: address) { + return AddressName(chain: chain, address: address, name: wallet.name, type: .internalWallet, status: .verified) + } + return nil } } diff --git a/Features/Transfer/Sources/Services/ConfirmServiceFactory.swift b/Features/Transfer/Sources/Services/ConfirmServiceFactory.swift index e5bea51e2..95ec47557 100644 --- a/Features/Transfer/Sources/Services/ConfirmServiceFactory.swift +++ b/Features/Transfer/Sources/Services/ConfirmServiceFactory.swift @@ -14,6 +14,7 @@ import AddressNameService import ActivityService import EventPresenterService import AssetsService +import WalletService public struct ConfirmServiceFactory { public static func create( @@ -25,6 +26,7 @@ public struct ConfirmServiceFactory { assetsService: AssetsService, priceService: PriceService, transactionStateService: TransactionStateService, + walletService: WalletService, addressNameService: AddressNameService, activityService: ActivityService, eventPresenterService: EventPresenterService, @@ -47,10 +49,13 @@ public struct ConfirmServiceFactory { chainService: chainService, assetsEnabler: assetsEnabler, balanceService: balanceService, - transactionStateService: transactionStateService + transactionStateService: transactionStateService, + walletService: walletService, + addressNameService: addressNameService ), keystore: keystore, chainService: chainService, + walletService: walletService, addressNameService: addressNameService, activityService: activityService, eventPresenterService: eventPresenterService diff --git a/Features/Transfer/Sources/Services/TransferExecutor.swift b/Features/Transfer/Sources/Services/TransferExecutor.swift index e4e501890..062c8292b 100644 --- a/Features/Transfer/Sources/Services/TransferExecutor.swift +++ b/Features/Transfer/Sources/Services/TransferExecutor.swift @@ -6,6 +6,8 @@ import Foundation import Primitives import Signer import TransactionStateService +import WalletService +import AddressNameService public protocol TransferExecutable: Sendable { func execute(input: TransferConfirmationInput) async throws @@ -20,19 +22,25 @@ public struct TransferExecutor: TransferExecutable { private let assetsEnabler: any AssetsEnabler private let balanceService: BalanceService private let transactionStateService: TransactionStateService + private let walletService: WalletService + private let addressNameService: AddressNameService public init( signer: any TransactionSigneable, chainService: any ChainServiceable, assetsEnabler: any AssetsEnabler, balanceService: BalanceService, - transactionStateService: TransactionStateService + transactionStateService: TransactionStateService, + walletService: WalletService, + addressNameService: AddressNameService ) { self.signer = signer self.chainService = chainService self.assetsEnabler = assetsEnabler self.balanceService = balanceService self.transactionStateService = transactionStateService + self.walletService = walletService + self.addressNameService = addressNameService } public func execute(input: TransferConfirmationInput) async throws { @@ -87,6 +95,7 @@ extension TransferExecutor { totalTransactions: totalTransactions ) + try saveInternalWalletAddressName(input: input) try transactionStateService.addTransactions(wallet: input.wallet, transactions: transactions) Task { do { @@ -154,4 +163,23 @@ extension TransferExecutor { default: .milliseconds(500) } } + + private func saveInternalWalletAddressName(input: TransferConfirmationInput) throws { + switch input.data.type { + case .transfer, .transferNft: + break + case .swap, .tokenApprove, .stake, .account, .perpetual, .earn, .generic, .deposit, .withdrawal: + return + } + + let chain = input.data.chain + if let senderAddress = try? input.wallet.account(for: chain).address, senderAddress.isNotEmpty { + try addressNameService.addWalletAddressName(wallet: input.wallet, chain: chain, address: senderAddress) + } + + let recipientAddress = input.data.recipientData.recipient.address + if recipientAddress.isNotEmpty, let recipientWallet = try walletService.getWallet(chain: chain, address: recipientAddress) { + try addressNameService.addWalletAddressName(wallet: recipientWallet, chain: chain, address: recipientAddress) + } + } } diff --git a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift index 9b4b6f27b..02ffebb8c 100644 --- a/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift +++ b/Features/Transfer/Sources/ViewModels/ConfirmRecipientViewModel.swift @@ -46,6 +46,7 @@ extension ConfirmRecipientViewModel { private var addressNameImage: AssetImage? { switch addressName?.type { case .contact: .image(Images.System.person) + case .internalWallet: .image(Images.System.wallet) case .address, .contract, .validator, .none: nil } } diff --git a/Features/Transfer/Tests/Services/TransferExecutorTests.swift b/Features/Transfer/Tests/Services/TransferExecutorTests.swift index 9e0914e25..bde0eb959 100644 --- a/Features/Transfer/Tests/Services/TransferExecutorTests.swift +++ b/Features/Transfer/Tests/Services/TransferExecutorTests.swift @@ -10,6 +10,8 @@ import SignerTestKit import BalanceServiceTestKit import TransactionStateService import TransactionStateServiceTestKit +import AddressNameServiceTestKit +import WalletServiceTestKit import Store import StoreTestKit import ChainServiceTestKit @@ -25,7 +27,9 @@ struct TransferExecutorTests { chainService: ChainServiceMock.mock(broadcastResponses: ["hash0", "hash1", "hash2", "hash3"]), assetsEnabler: .mock(), balanceService: .mock(), - transactionStateService: .mock(transactionStore: transactionStore) + transactionStateService: .mock(transactionStore: transactionStore), + walletService: .mock(), + addressNameService: .mock() ) let input = TransferConfirmationInput( @@ -51,7 +55,9 @@ struct TransferExecutorTests { chainService: ChainServiceMock.mock(broadcastResponses: ["hash0", "hash1"]), assetsEnabler: .mock(), balanceService: .mock(), - transactionStateService: .mock(transactionStore: transactionStore) + transactionStateService: .mock(transactionStore: transactionStore), + walletService: .mock(), + addressNameService: .mock() ) let input = TransferConfirmationInput( @@ -77,7 +83,9 @@ struct TransferExecutorTests { chainService: ChainServiceMock.mock(broadcastResponses: ["hash"]), assetsEnabler: .mock(), balanceService: .mock(), - transactionStateService: .mock(transactionStore: transactionStore) + transactionStateService: .mock(transactionStore: transactionStore), + walletService: .mock(), + addressNameService: .mock() ) let input = TransferConfirmationInput( @@ -104,7 +112,9 @@ struct TransferExecutorTests { chainService: ChainServiceMock.mock(broadcastResponses: ["hash"]), assetsEnabler: .mock(), balanceService: .mock(), - transactionStateService: .mock(transactionStore: transactionStore) + transactionStateService: .mock(transactionStore: transactionStore), + walletService: .mock(), + addressNameService: .mock() ) let input = TransferConfirmationInput( diff --git a/Features/Transfer/Tests/ViewModels/ConfirmTransferSceneViewModelTests.swift b/Features/Transfer/Tests/ViewModels/ConfirmTransferSceneViewModelTests.swift index cec000aac..40ff09da6 100644 --- a/Features/Transfer/Tests/ViewModels/ConfirmTransferSceneViewModelTests.swift +++ b/Features/Transfer/Tests/ViewModels/ConfirmTransferSceneViewModelTests.swift @@ -464,6 +464,7 @@ private extension ConfirmTransferSceneViewModel { assetsService: .mock(), priceService: .mock(), transactionStateService: .mock(), + walletService: .mock(), addressNameService: addressNameService, activityService: .mock(), eventPresenterService: .mock(), diff --git a/Gem/Services/ViewModelFactory.swift b/Gem/Services/ViewModelFactory.swift index f0d573e88..d7a56dc65 100644 --- a/Gem/Services/ViewModelFactory.swift +++ b/Gem/Services/ViewModelFactory.swift @@ -109,6 +109,7 @@ public struct ViewModelFactory: Sendable { assetsService: assetsService, priceService: priceService, transactionStateService: transactionStateService, + walletService: walletService, addressNameService: addressNameService, activityService: activityService, eventPresenterService: eventPresenterService, diff --git a/Packages/FeatureServices/AddressNameService/AddressNameService.swift b/Packages/FeatureServices/AddressNameService/AddressNameService.swift index e26164ae5..e5e0d45ec 100644 --- a/Packages/FeatureServices/AddressNameService/AddressNameService.swift +++ b/Packages/FeatureServices/AddressNameService/AddressNameService.swift @@ -20,6 +20,10 @@ public struct AddressNameService: Sendable { try addressStore.getAddressName(chain: chain, address: address) } + public func addWalletAddressName(wallet: Wallet, chain: Chain, address: String) throws { + try addressStore.addWalletAddressName(wallet: wallet, chain: chain, address: address) + } + public func getAddressNames(requests: [ChainAddress]) async throws -> [ChainAddress: AddressName] { let requests = uniqueRequests(requests) guard !requests.isEmpty else { diff --git a/Packages/FeatureServices/WalletService/WalletService.swift b/Packages/FeatureServices/WalletService/WalletService.swift index b5a3a6e9c..1d5292dfa 100644 --- a/Packages/FeatureServices/WalletService/WalletService.swift +++ b/Packages/FeatureServices/WalletService/WalletService.swift @@ -56,6 +56,10 @@ public struct WalletService: Sendable { try walletSessionService.getWallet(walletId: walletId) } + public func getWallet(chain: Chain, address: String) throws -> Wallet? { + try walletStore.getWallet(chain: chain, address: address) + } + public func acceptTerms() { preferences.isAcceptTermsCompleted = true } diff --git a/Packages/Primitives/Sources/Scan.swift b/Packages/Primitives/Sources/Scan.swift index 2dce527e5..e5cf1df47 100644 --- a/Packages/Primitives/Sources/Scan.swift +++ b/Packages/Primitives/Sources/Scan.swift @@ -43,4 +43,5 @@ public enum AddressType: String, Codable, Equatable, Sendable { case contract case validator case contact + case internalWallet } diff --git a/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift b/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift index a848acdf9..3c79b7df9 100644 --- a/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift +++ b/Packages/PrimitivesComponents/Sources/ViewModels/AddressListItemViewModel.swift @@ -46,14 +46,14 @@ public struct AddressListItemViewModel { public var assetImageStyle: AssetImageView.Style? { switch account.addressType { - case .contact: AssetImageView.Style(foregroundColor: Colors.secondaryText, cornerRadius: 0) + case .contact, .internalWallet: AssetImageView.Style(foregroundColor: Colors.secondaryText, cornerRadius: 0) case .address, .contract, .validator, .none: nil } } public var assetImageSize: CGFloat { switch account.addressType { - case .contact: .list.accessory + case .contact, .internalWallet: .list.accessory case .address, .contract, .validator, .none: .list.image } } diff --git a/Packages/Store/Sources/Migrations.swift b/Packages/Store/Sources/Migrations.swift index bdf271592..00a549d57 100644 --- a/Packages/Store/Sources/Migrations.swift +++ b/Packages/Store/Sources/Migrations.swift @@ -456,6 +456,13 @@ struct Migrations { try? FiatTransactionRecord.create(db: db) } + migrator.registerMigration("Add walletId to \(AddressRecord.databaseTableName)") { db in + try? db.alter(table: AddressRecord.databaseTableName) { + $0.add(column: AddressRecord.Columns.walletId.name, .text) + .references(WalletRecord.databaseTableName, onDelete: .cascade, onUpdate: .cascade) + } + } + try migrator.migrate(dbQueue) } } diff --git a/Packages/Store/Sources/Models/AddressRecord.swift b/Packages/Store/Sources/Models/AddressRecord.swift index dae5f494e..bcc83f1f3 100644 --- a/Packages/Store/Sources/Models/AddressRecord.swift +++ b/Packages/Store/Sources/Models/AddressRecord.swift @@ -10,6 +10,7 @@ struct AddressRecord: Codable, FetchableRecord, PersistableRecord, Sendable { enum Columns { static let chain = Column("chain") static let address = Column("address") + static let walletId = Column("walletId") static let name = Column("name") static let type = Column("type") static let status = Column("status") @@ -17,6 +18,7 @@ struct AddressRecord: Codable, FetchableRecord, PersistableRecord, Sendable { let chain: Chain let address: String + let walletId: String? let name: String let type: AddressType? let status: VerificationStatus @@ -24,12 +26,14 @@ struct AddressRecord: Codable, FetchableRecord, PersistableRecord, Sendable { init( chain: Chain, address: String, + walletId: String?, name: String, type: AddressType?, status: VerificationStatus ) { self.chain = chain self.address = address + self.walletId = walletId self.name = name self.type = type self.status = status @@ -44,6 +48,8 @@ extension AddressRecord: CreateTable { .references(AssetRecord.databaseTableName, onDelete: .cascade, onUpdate: .cascade) $0.column(Columns.address.name, .text) .notNull() + $0.column(Columns.walletId.name, .text) + .references(WalletRecord.databaseTableName, onDelete: .cascade, onUpdate: .cascade) $0.column(Columns.name.name, .text) .notNull() $0.column(Columns.type.name, .text) diff --git a/Packages/Store/Sources/Stores/AddressStore.swift b/Packages/Store/Sources/Stores/AddressStore.swift index 6e0d12592..9a139e317 100644 --- a/Packages/Store/Sources/Stores/AddressStore.swift +++ b/Packages/Store/Sources/Stores/AddressStore.swift @@ -6,7 +6,7 @@ import Primitives public struct AddressStore: Sendable { - let db: DatabaseQueue + private let db: DatabaseQueue public init(db: DB) { self.db = db.dbQueue @@ -15,23 +15,22 @@ public struct AddressStore: Sendable { public func addAddressNames(_ addressNames: [AddressName]) throws { try db.write { db in for addressName in addressNames { - try AddressRecord( - chain: addressName.chain, - address: addressName.address, - name: addressName.name, - type: addressName.type, - status: addressName.status - ).save(db, onConflict: .replace) + try save(addressName: addressName, walletId: nil, db: db) } } } - - func deleteAddress(chain: Chain, address: String) throws -> Int { + + public func addWalletAddressName(wallet: Wallet, chain: Chain, address: String) throws { + let addressName = AddressName( + chain: chain, + address: address, + name: wallet.name, + type: .internalWallet, + status: .verified + ) + try db.write { db in - try AddressRecord - .filter(AddressRecord.Columns.chain == chain.rawValue) - .filter(AddressRecord.Columns.address == address) - .deleteAll(db) + try save(addressName: addressName, walletId: wallet.id, db: db) } } @@ -44,4 +43,17 @@ public struct AddressStore: Sendable { .asPrimitive() } } + + // MARK: - Private + + private func save(addressName: AddressName, walletId: String?, db: Database) throws { + try AddressRecord( + chain: addressName.chain, + address: addressName.address, + walletId: walletId, + name: addressName.name, + type: addressName.type, + status: addressName.status + ).save(db, onConflict: .replace) + } } diff --git a/Packages/Store/Sources/Stores/WalletStore.swift b/Packages/Store/Sources/Stores/WalletStore.swift index f25067112..1b69be829 100644 --- a/Packages/Store/Sources/Stores/WalletStore.swift +++ b/Packages/Store/Sources/Stores/WalletStore.swift @@ -111,6 +111,17 @@ public struct WalletStore: Sendable { public func observer() -> SubscriptionsObserver { return SubscriptionsObserver(dbQueue: db) } + + public func getWallet(chain: Chain, address: String) throws -> Wallet? { + try db.read { db in + try WalletRecord + .joining(required: WalletRecord.accounts.filter(AccountRecord.Columns.chain == chain.rawValue).filter(AccountRecord.Columns.address == address)) + .including(all: WalletRecord.accounts) + .asRequest(of: WalletRecordInfo.self) + .fetchOne(db)? + .mapToWallet() + } + } public func setWalletAvatar(_ walletId: WalletId, path: String?) throws { let _ = try db.write { db in diff --git a/core b/core index d927f3920..16549428e 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit d927f39204acbdaf9f6f9abbbf288c8903aeb8c5 +Subproject commit 16549428e0ab038bcf00b5c88c288b7b07a16733