From cc7a1f54cecf581853a8d23c0886325fd89f2968 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sat, 6 Jan 2024 18:42:23 +0300 Subject: [PATCH 001/169] add pairingExpirationPublisher --- .../Wallet/Wallet/WalletPresenter.swift | 9 +++++---- Sources/WalletConnectPairing/PairingClient.swift | 3 +++ .../Services/Common/ExpirationService.swift | 9 +++++++++ Sources/Web3Wallet/Web3WalletClient.swift | 4 ++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 59d5c1fc2..9b69a567d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -110,7 +110,7 @@ extension WalletPresenter { Task.detached(priority: .high) { @MainActor [unowned self] in do { self.showPairingLoading = true - self.removePairingIndicator() + self.setUpPairingIndicatorRemoval(topic: uri.topic) try await self.interactor.pair(uri: uri) } catch { self.showPairingLoading = false @@ -127,10 +127,11 @@ extension WalletPresenter { pair(uri: uri) } - private func removePairingIndicator() { - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + private func setUpPairingIndicatorRemoval(topic: String) { + Pair.instance.pairingExpirationPublisher.sink { pairing in + guard pairing.topic == topic else {return} self.showPairingLoading = false - } + }.store(in: &disposeBag) } } diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 16d8f0076..b7506e915 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -10,6 +10,9 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient } public let socketConnectionStatusPublisher: AnyPublisher + public let pairingExpirationPublisher: AnyPublisher { + return expirationService.pairingExpirationPublisher + } private let pairingStorage: WCPairingStorage private let walletPairService: WalletPairService diff --git a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift index 4a8850cbe..92d749e28 100644 --- a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift +++ b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift @@ -1,9 +1,14 @@ import Foundation +import Combine final class ExpirationService { private let pairingStorage: WCPairingStorage private let networkInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol + private let pairingExpirationPublisherSubject: PassthroughSubject = .init() + var pairingExpirationPublisher: AnyPublisher { + pairingExpirationPublisherSubject.eraseToAnyPublisher() + } init(pairingStorage: WCPairingStorage, networkInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol) { self.pairingStorage = pairingStorage @@ -15,6 +20,10 @@ final class ExpirationService { pairingStorage.onPairingExpiration = { [weak self] pairing in self?.kms.deleteSymmetricKey(for: pairing.topic) self?.networkInteractor.unsubscribe(topic: pairing.topic) + + DispatchQueue.main.async { + pairingExpirationPublisherSubject.send(Pairing(pairing)) + } } } } diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 5b70a374a..85e660d42 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -67,6 +67,10 @@ public class Web3WalletClient { pairingClient.pairingDeletePublisher } + public let pairingExpirationPublisher: AnyPublisher { + return pairingClient.pairingExpirationPublisher + } + public var logsPublisher: AnyPublisher { return signClient.logsPublisher .merge(with: pairingClient.logsPublisher) From 0f3a29d35271cba1f3980f1e6b17dbb7dd9f70fe Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sat, 6 Jan 2024 18:46:37 +0300 Subject: [PATCH 002/169] fix build --- .../PresentationLayer/Wallet/Wallet/WalletPresenter.swift | 4 ++-- Sources/WalletConnectPairing/PairingClient.swift | 2 +- Sources/WalletConnectPairing/PairingClientProtocol.swift | 1 + .../Services/Common/ExpirationService.swift | 2 +- Sources/Web3Wallet/Web3WalletClient.swift | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 9b69a567d..df8f27eb7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -40,7 +40,7 @@ final class WalletPresenter: ObservableObject { func onAppear() { showPairingLoading = app.requestSent - removePairingIndicator() + showPairingLoading = false let pendingRequests = interactor.getPendingRequests() if let request = pendingRequests.first(where: { $0.context != nil }) { @@ -128,7 +128,7 @@ extension WalletPresenter { } private func setUpPairingIndicatorRemoval(topic: String) { - Pair.instance.pairingExpirationPublisher.sink { pairing in + Web3Wallet.instance.pairingExpirationPublisher.sink { pairing in guard pairing.topic == topic else {return} self.showPairingLoading = false }.store(in: &disposeBag) diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index b7506e915..20ee1cfce 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -10,7 +10,7 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient } public let socketConnectionStatusPublisher: AnyPublisher - public let pairingExpirationPublisher: AnyPublisher { + public var pairingExpirationPublisher: AnyPublisher { return expirationService.pairingExpirationPublisher } diff --git a/Sources/WalletConnectPairing/PairingClientProtocol.swift b/Sources/WalletConnectPairing/PairingClientProtocol.swift index 7edd05b30..5c5e539c2 100644 --- a/Sources/WalletConnectPairing/PairingClientProtocol.swift +++ b/Sources/WalletConnectPairing/PairingClientProtocol.swift @@ -3,6 +3,7 @@ import Combine public protocol PairingClientProtocol { var logsPublisher: AnyPublisher {get} var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> {get} + var pairingExpirationPublisher: AnyPublisher {get} func pair(uri: WalletConnectURI) async throws func disconnect(topic: String) async throws func getPairings() -> [Pairing] diff --git a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift index 92d749e28..0df4caf7e 100644 --- a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift +++ b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift @@ -22,7 +22,7 @@ final class ExpirationService { self?.networkInteractor.unsubscribe(topic: pairing.topic) DispatchQueue.main.async { - pairingExpirationPublisherSubject.send(Pairing(pairing)) + self?.pairingExpirationPublisherSubject.send(Pairing(pairing)) } } } diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 85e660d42..72f1ec45a 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -67,7 +67,7 @@ public class Web3WalletClient { pairingClient.pairingDeletePublisher } - public let pairingExpirationPublisher: AnyPublisher { + public var pairingExpirationPublisher: AnyPublisher { return pairingClient.pairingExpirationPublisher } From 77c7472b03e35246855d11c7f3249a2a188ee0ca Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sat, 6 Jan 2024 18:52:15 +0300 Subject: [PATCH 003/169] remove loader on session request --- .../PresentationLayer/Wallet/Wallet/WalletPresenter.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index df8f27eb7..e74cf5b68 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -126,12 +126,16 @@ extension WalletPresenter { } pair(uri: uri) } - + private func setUpPairingIndicatorRemoval(topic: String) { Web3Wallet.instance.pairingExpirationPublisher.sink { pairing in guard pairing.topic == topic else {return} self.showPairingLoading = false }.store(in: &disposeBag) + + Web3Wallet.instance.sessionRequestPublisher.sink { _ in + self.showPairingLoading = false + }.store(in: &disposeBag) } } From 300ecea0e2635942169da0d55b3c5d1b1c0e94d4 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sat, 6 Jan 2024 18:56:45 +0300 Subject: [PATCH 004/169] remove loader on session proposal --- .../PresentationLayer/Wallet/Wallet/WalletPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index e74cf5b68..3e73fa8aa 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -133,7 +133,7 @@ extension WalletPresenter { self.showPairingLoading = false }.store(in: &disposeBag) - Web3Wallet.instance.sessionRequestPublisher.sink { _ in + Web3Wallet.instance.sessionProposalPublisher.sink { _ in self.showPairingLoading = false }.store(in: &disposeBag) } From 916b52847b31c4f6104ec91b5ed84c697e93b52c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sun, 7 Jan 2024 13:14:20 +0300 Subject: [PATCH 005/169] add expiry monitor for sequence store --- Sources/WalletConnectUtils/SequenceStore.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/WalletConnectUtils/SequenceStore.swift b/Sources/WalletConnectUtils/SequenceStore.swift index 618ba411e..94591af29 100644 --- a/Sources/WalletConnectUtils/SequenceStore.swift +++ b/Sources/WalletConnectUtils/SequenceStore.swift @@ -12,10 +12,13 @@ public final class SequenceStore where T: SequenceObject { private let store: CodableStore private let dateInitializer: () -> Date + private var expiryMonitorTimer: Timer? + public init(store: CodableStore, dateInitializer: @escaping () -> Date = Date.init) { self.store = store self.dateInitializer = dateInitializer + startExpiryMonitor() } public func hasSequence(forTopic topic: String) -> Bool { @@ -46,6 +49,19 @@ public final class SequenceStore where T: SequenceObject { store.deleteAll() onSequenceUpdate?() } + + // MARK: Expiry Monitor + + private func startExpiryMonitor() { + expiryMonitorTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in + self?.checkAllSequencesForExpiry() + } + } + + private func checkAllSequencesForExpiry() { + let allSequences = getAll() + allSequences.forEach { _ = verifyExpiry(on: $0) } + } } // MARK: Privates From de0a0a0fabd289dd9967102679fa6dd566cbe93c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sun, 7 Jan 2024 14:10:05 +0300 Subject: [PATCH 006/169] add pairing state publisher --- .../ApplicationLayer/SceneDelegate.swift | 4 +-- .../WalletConnectPairing/PairingClient.swift | 9 +++++- .../PairingClientFactory.swift | 4 ++- .../PairingClientProtocol.swift | 1 + .../Services/Common/PairingsProvider.swift | 29 +++++++++++++++++++ Sources/Web3Wallet/Web3WalletClient.swift | 4 +++ 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index c36e6b4f6..c39e67513 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -1,7 +1,7 @@ import Auth import SafariServices import UIKit -import WalletConnectPairing +import Web3Wallet final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? @@ -44,7 +44,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio if let uri { Task { - try await Pair.instance.pair(uri: uri) + try await Web3Wallet.instance.pair(uri: uri) } } } diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 20ee1cfce..5637ba3e6 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -9,6 +9,10 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient pairingDeleteRequestSubscriber.deletePublisherSubject.eraseToAnyPublisher() } + public var pairingStatePublisher: AnyPublisher { + return pairingStateProvider.pairingStatePublisher + } + public let socketConnectionStatusPublisher: AnyPublisher public var pairingExpirationPublisher: AnyPublisher { return expirationService.pairingExpirationPublisher @@ -28,6 +32,7 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient private let resubscribeService: PairingResubscribeService private let expirationService: ExpirationService private let pairingDeleteRequestSubscriber: PairingDeleteRequestSubscriber + private let pairingStateProvider: PairingStateProvider private let cleanupService: PairingCleanupService @@ -50,7 +55,8 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient cleanupService: PairingCleanupService, pingService: PairingPingService, socketConnectionStatusPublisher: AnyPublisher, - pairingsProvider: PairingsProvider + pairingsProvider: PairingsProvider, + pairingStateProvider: PairingStateProvider ) { self.pairingStorage = pairingStorage self.appPairService = appPairService @@ -67,6 +73,7 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient self.pingService = pingService self.pairingRequestsSubscriber = pairingRequestsSubscriber self.pairingsProvider = pairingsProvider + self.pairingStateProvider = pairingStateProvider setUpPublishers() setUpExpiration() } diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index 902ba7d28..22840bed3 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -36,6 +36,7 @@ public struct PairingClientFactory { let expirationService = ExpirationService(pairingStorage: pairingStore, networkInteractor: networkingClient, kms: kms) let resubscribeService = PairingResubscribeService(networkInteractor: networkingClient, pairingStorage: pairingStore) let pairingDeleteRequestSubscriber = PairingDeleteRequestSubscriber(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, logger: logger) + let pairingStateProvider = PairingStateProvider(pairingStorage: pairingStore) return PairingClient( pairingStorage: pairingStore, @@ -52,7 +53,8 @@ public struct PairingClientFactory { cleanupService: cleanupService, pingService: pingService, socketConnectionStatusPublisher: networkingClient.socketConnectionStatusPublisher, - pairingsProvider: pairingsProvider + pairingsProvider: pairingsProvider, + pairingStateProvider: pairingStateProvider ) } } diff --git a/Sources/WalletConnectPairing/PairingClientProtocol.swift b/Sources/WalletConnectPairing/PairingClientProtocol.swift index 5c5e539c2..905c1b4e8 100644 --- a/Sources/WalletConnectPairing/PairingClientProtocol.swift +++ b/Sources/WalletConnectPairing/PairingClientProtocol.swift @@ -3,6 +3,7 @@ import Combine public protocol PairingClientProtocol { var logsPublisher: AnyPublisher {get} var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> {get} + var pairingStatePublisher: AnyPublisher {get} var pairingExpirationPublisher: AnyPublisher {get} func pair(uri: WalletConnectURI) async throws func disconnect(topic: String) async throws diff --git a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift index aa087a3c0..99cd02ca5 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift @@ -22,3 +22,32 @@ class PairingsProvider { return Pairing(pairing) } } + +import Combine +import Foundation + +class PairingStateProvider { + private let pairingStorage: WCPairingStorage + private var pairingStatePublisherSubject = PassthroughSubject() + private var checkTimer: Timer? + + public var pairingStatePublisher: AnyPublisher { + pairingStatePublisherSubject.eraseToAnyPublisher() + } + + public init(pairingStorage: WCPairingStorage) { + self.pairingStorage = pairingStorage + setupPairingStateCheckTimer() + } + + private func setupPairingStateCheckTimer() { + checkTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in + self?.checkPairingState() + } + } + + private func checkPairingState() { + let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active } + pairingStatePublisherSubject.send(pairingStateActive) + } +} diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 72f1ec45a..374d3f549 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -67,6 +67,10 @@ public class Web3WalletClient { pairingClient.pairingDeletePublisher } + public var pairingStatePublisher: AnyPublisher { + pairingClient.pairingStatePublisher + } + public var pairingExpirationPublisher: AnyPublisher { return pairingClient.pairingExpirationPublisher } From 6e07c1e4e12329396c26054beab5b440104cc1fc Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sun, 7 Jan 2024 14:19:38 +0300 Subject: [PATCH 007/169] integrate pairing publisher --- .../Wallet/Wallet/WalletPresenter.swift | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 3e73fa8aa..742e766d3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -40,7 +40,7 @@ final class WalletPresenter: ObservableObject { func onAppear() { showPairingLoading = app.requestSent - showPairingLoading = false + setUpPairingIndicatorRemoval() let pendingRequests = interactor.getPendingRequests() if let request = pendingRequests.first(where: { $0.context != nil }) { @@ -109,11 +109,8 @@ extension WalletPresenter { private func pair(uri: WalletConnectURI) { Task.detached(priority: .high) { @MainActor [unowned self] in do { - self.showPairingLoading = true - self.setUpPairingIndicatorRemoval(topic: uri.topic) try await self.interactor.pair(uri: uri) } catch { - self.showPairingLoading = false self.errorMessage = error.localizedDescription self.showError.toggle() } @@ -127,14 +124,9 @@ extension WalletPresenter { pair(uri: uri) } - private func setUpPairingIndicatorRemoval(topic: String) { - Web3Wallet.instance.pairingExpirationPublisher.sink { pairing in - guard pairing.topic == topic else {return} - self.showPairingLoading = false - }.store(in: &disposeBag) - - Web3Wallet.instance.sessionProposalPublisher.sink { _ in - self.showPairingLoading = false + private func setUpPairingIndicatorRemoval() { + Web3Wallet.instance.pairingStatePublisher.sink { [weak self] isPairing in + self?.showPairingLoading = isPairing }.store(in: &disposeBag) } } From aff3a8a82ae6673a4383559f557e1f5de73a1b00 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sun, 7 Jan 2024 14:42:42 +0300 Subject: [PATCH 008/169] remove inactive pairing on reject --- .../Engine/Common/ApproveEngine.swift | 15 +++++++++++++-- .../Sign/SignClientFactory.swift | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 8c994c094..eca845a97 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -27,6 +27,7 @@ final class ApproveEngine { private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging + private let rpcHistory: RPCHistory private var publishers = Set() @@ -41,7 +42,8 @@ final class ApproveEngine { logger: ConsoleLogging, pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, - verifyClient: VerifyClientProtocol + verifyClient: VerifyClientProtocol, + rpcHistory: RPCHistory ) { self.networkingInteractor = networkingInteractor self.proposalPayloadsStore = proposalPayloadsStore @@ -54,6 +56,7 @@ final class ApproveEngine { self.pairingStore = pairingStore self.sessionStore = sessionStore self.verifyClient = verifyClient + self.rpcHistory = rpcHistory setupRequestSubscriptions() setupResponseSubscriptions() @@ -125,10 +128,18 @@ final class ApproveEngine { guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.proposalPayloadsNotFound } + + if let pairingTopic = rpcHistory.get(recordId: payload.id)?.topic, + let pairing = pairingStore.getPairing(forTopic: pairingTopic), + !pairing.active { + pairingStore.delete(topic: pairingTopic) + } + proposalPayloadsStore.delete(forKey: proposerPubKey) verifyContextStore.delete(forKey: proposerPubKey) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: SessionProposeProtocolMethod(), reason: reason) - // TODO: Delete pairing if inactive + } func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil, pairingTopic: String) async throws { diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 1d9adf073..7970d88d5 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -61,7 +61,8 @@ public struct SignClientFactory { logger: logger, pairingStore: pairingStore, sessionStore: sessionStore, - verifyClient: verifyClient + verifyClient: verifyClient, + rpcHistory: rpcHistory ) let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) From 6f70d105d033e8f4717c49f11878779c820aa566 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 09:07:40 +0300 Subject: [PATCH 009/169] activate pairing on request --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- Sources/Auth/AuthClientFactory.swift | 2 +- Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f11eb5be8..d4a996089 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -168,8 +168,8 @@ "repositoryURL": "https://github.com/WalletConnect/web3modal-swift", "state": { "branch": null, - "revision": "3295d69d1b12df29a5040578d107f56986b1b399", - "version": "1.0.13" + "revision": "e84a07662d71721de4d0ccb2d3bb28fd993dd108", + "version": "1.0.14" } } ] diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index c2748c5bd..069d548ce 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -50,7 +50,7 @@ public struct AuthClientFactory { let signatureVerifier = messageVerifierFactory.create(projectId: projectId) let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: signatureVerifier, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter) let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history) - let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore) + let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore, pairingStore: <#WCPairingStorage#>) let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer) let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history, verifyContextStore: verifyContextStore) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index eca845a97..fff6873a6 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -311,6 +311,12 @@ private extension ApproveEngine { // MARK: SessionProposeRequest func handleSessionProposeRequest(payload: RequestSubscriptionPayload) { + + if var pairing = pairingStore.getPairing(forTopic: payload.topic) { + pairing.activate() + pairingStore.setPairing(pairing) + } + logger.debug("Received Session Proposal") let proposal = payload.request do { try Namespace.validate(proposal.requiredNamespaces) } catch { From 69d5781cfa7ab8ba8279a1d69b58ddfff29c10f3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 09:18:54 +0300 Subject: [PATCH 010/169] fix build --- Sources/Auth/AuthClientFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index 069d548ce..c2748c5bd 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -50,7 +50,7 @@ public struct AuthClientFactory { let signatureVerifier = messageVerifierFactory.create(projectId: projectId) let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: signatureVerifier, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter) let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history) - let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore, pairingStore: <#WCPairingStorage#>) + let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore) let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer) let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history, verifyContextStore: verifyContextStore) From e71c6d7f8cf5aecaeb59a89b4eb5c5f76bba3a0a Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 09:45:11 +0300 Subject: [PATCH 011/169] add ActivityIndicatorManager --- Example/ExampleApp.xcodeproj/project.pbxproj | 4 +++ .../Common/ActivityIndicatorManager.swift | 33 +++++++++++++++++++ .../SessionRequestInteractor.swift | 3 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Example/WalletApp/Common/ActivityIndicatorManager.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index f90232086..1df80a639 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* NotifyTests.swift */; }; 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; }; 84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; }; + 84AEC24F2B4D1EE400E27A5B /* ActivityIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */; }; 84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154D2991099000FAD54E /* BuildConfiguration.swift */; }; 84B8155B2992A18D00FAD54E /* NotifyMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */; }; 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; }; @@ -433,6 +434,7 @@ 849D7A92292E2169006A2BD4 /* NotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyTests.swift; sourceTree = ""; }; 84A6E3C22A386BBC008A0571 /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; }; + 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorManager.swift; sourceTree = ""; }; 84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyMessageViewModel.swift; sourceTree = ""; }; 84CE641C27981DED00142511 /* DApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1666,6 +1668,7 @@ C56EE2A1293F6B9E004840D1 /* Helpers */, C56EE262293F56D6004840D1 /* Extensions */, C56EE263293F56D6004840D1 /* VIPER */, + 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */, ); path = Common; sourceTree = ""; @@ -2512,6 +2515,7 @@ A51811A02A52E83100A52B15 /* SettingsPresenter.swift in Sources */, 847BD1DA2989492500076C90 /* MainRouter.swift in Sources */, C5F32A2E2954814A00A6476E /* ConnectionDetailsRouter.swift in Sources */, + 84AEC24F2B4D1EE400E27A5B /* ActivityIndicatorManager.swift in Sources */, C55D3482295DD7140004314A /* AuthRequestInteractor.swift in Sources */, C55D34B12965FB750004314A /* SessionProposalInteractor.swift in Sources */, C56EE247293F566D004840D1 /* ScanModule.swift in Sources */, diff --git a/Example/WalletApp/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift new file mode 100644 index 000000000..9a8143f85 --- /dev/null +++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift @@ -0,0 +1,33 @@ +import UIKit + +class ActivityIndicatorManager { + static let shared = ActivityIndicatorManager() + private var activityIndicator: UIActivityIndicatorView? + + private init() {} + + func start() { + DispatchQueue.main.async { + self.stop() + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.startAnimating() + window.addSubview(activityIndicator) + + self.activityIndicator = activityIndicator + } + } + + + func stop() { + DispatchQueue.main.async { + self.activityIndicator?.stopAnimating() + self.activityIndicator?.removeFromSuperview() + self.activityIndicator = nil + } + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift index 59886fbfb..2bd386489 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift @@ -7,12 +7,13 @@ final class SessionRequestInteractor { func approve(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool { do { let result = try Signer.sign(request: sessionRequest, importAccount: importAccount) + ActivityIndicatorManager.shared.start() try await Web3Wallet.instance.respond( topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(result) ) - + ActivityIndicatorManager.shared.stop() /* Redirect */ let session = getSession(topic: sessionRequest.topic) if let uri = session?.peer.redirect?.native { From e6553e594b2f6a3822b4dcdf6874d8380227127b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 12:13:38 +0300 Subject: [PATCH 012/169] add alert presenter --- Example/ExampleApp.xcodeproj/project.pbxproj | 21 +++++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 +++++ .../ConfigurationService.swift | 12 +++++++ Example/WalletApp/Common/AlertPresenter.swift | 35 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 Example/WalletApp/Common/AlertPresenter.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 1df80a639..13d1cb82d 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; }; 84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; }; 84AEC24F2B4D1EE400E27A5B /* ActivityIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */; }; + 84AEC2512B4D42C100E27A5B /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */; }; + 84AEC2542B4D43CD00E27A5B /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 84AEC2532B4D43CD00E27A5B /* SwiftMessages */; }; 84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154D2991099000FAD54E /* BuildConfiguration.swift */; }; 84B8155B2992A18D00FAD54E /* NotifyMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */; }; 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; }; @@ -435,6 +437,7 @@ 84A6E3C22A386BBC008A0571 /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; }; 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorManager.swift; sourceTree = ""; }; + 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = ""; }; 84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyMessageViewModel.swift; sourceTree = ""; }; 84CE641C27981DED00142511 /* DApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -791,6 +794,7 @@ C55D349929630D440004314A /* Web3Wallet in Frameworks */, A5F1526F2ACDC46B00D745A6 /* Web3ModalUI in Frameworks */, C56EE255293F569A004840D1 /* Starscream in Frameworks */, + 84AEC2542B4D43CD00E27A5B /* SwiftMessages in Frameworks */, A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */, C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */, C54C24902AEB1B5600DA4BF6 /* WalletConnectRouter in Frameworks */, @@ -1669,6 +1673,7 @@ C56EE262293F56D6004840D1 /* Extensions */, C56EE263293F56D6004840D1 /* VIPER */, 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */, + 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */, ); path = Common; sourceTree = ""; @@ -2145,6 +2150,7 @@ A59D25ED2AB3672700D7EA3A /* AsyncButton */, A5F1526E2ACDC46B00D745A6 /* Web3ModalUI */, C54C248F2AEB1B5600DA4BF6 /* WalletConnectRouter */, + 84AEC2532B4D43CD00E27A5B /* SwiftMessages */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -2223,6 +2229,7 @@ 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */, 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */, A5F1526D2ACDC46B00D745A6 /* XCRemoteSwiftPackageReference "web3modal-swift" */, + 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */, ); productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */; projectDirPath = ""; @@ -2520,6 +2527,7 @@ C55D34B12965FB750004314A /* SessionProposalInteractor.swift in Sources */, C56EE247293F566D004840D1 /* ScanModule.swift in Sources */, C56EE28D293F5757004840D1 /* AppearanceConfigurator.swift in Sources */, + 84AEC2512B4D42C100E27A5B /* AlertPresenter.swift in Sources */, 847BD1D82989492500076C90 /* MainModule.swift in Sources */, 847BD1E7298A806800076C90 /* NotificationsInteractor.swift in Sources */, C56EE241293F566D004840D1 /* WalletModule.swift in Sources */, @@ -3356,6 +3364,14 @@ kind = branch; }; }; + 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftKickMobile/SwiftMessages"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.0.9; + }; + }; A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/flypaper0/solana-swift"; @@ -3444,6 +3460,11 @@ package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */; productName = Mixpanel; }; + 84AEC2532B4D43CD00E27A5B /* SwiftMessages */ = { + isa = XCSwiftPackageProductDependency; + package = 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */; + productName = SwiftMessages; + }; 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d4a996089..c46e295f2 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -127,6 +127,15 @@ "version": "1.1.6" } }, + { + "package": "SwiftMessages", + "repositoryURL": "https://github.com/SwiftKickMobile/SwiftMessages", + "state": { + "branch": null, + "revision": "62e12e138fc3eedf88c7553dd5d98712aa119f40", + "version": "9.0.9" + } + }, { "package": "swiftui-async-button", "repositoryURL": "https://github.com/lorenzofiamingo/swiftui-async-button", diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 1267374f7..a016d8515 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -2,9 +2,12 @@ import UIKit import WalletConnectNetworking import WalletConnectNotify import Web3Wallet +import Combine final class ConfigurationService { + private var publishers = Set() + func configure(importAccount: ImportAccount) { Networking.configure( groupIdentifier: "group.com.walletconnect.sdk", @@ -38,6 +41,15 @@ final class ConfigurationService { } LoggingService.instance.startLogging() + Web3Wallet.instance.socketConnectionStatusPublisher.sink { status in + switch status { + case .connected: + AlertPresenter.present(message: "Your web socket has connected", type: .info) + case .disconnected: + AlertPresenter.present(message: "Your web socket is disconnected", type: .warning) + } + }.store(in: &publishers) + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") diff --git a/Example/WalletApp/Common/AlertPresenter.swift b/Example/WalletApp/Common/AlertPresenter.swift new file mode 100644 index 000000000..2b3b45c38 --- /dev/null +++ b/Example/WalletApp/Common/AlertPresenter.swift @@ -0,0 +1,35 @@ +import Foundation +import SwiftMessages +import UIKit + +struct AlertPresenter { + enum MessageType { + case warning + case error + case info + case success + } + + static func present(message: String, type: AlertPresenter.MessageType) { + DispatchQueue.main.async { + let view = MessageView.viewFromNib(layout: .cardView) + switch type { + case .warning: + view.configureTheme(.warning, iconStyle: .subtle) + case .error: + view.configureTheme(.error, iconStyle: .subtle) + case .info: + view.configureTheme(.info, iconStyle: .subtle) + case .success: + view.configureTheme(.success, iconStyle: .subtle) + } + view.button?.isHidden = true + view.layoutMarginAdditions = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + view.configureContent(title: "", body: message) + var config = SwiftMessages.Config() + config.presentationStyle = .top + config.duration = .seconds(seconds: 1) + SwiftMessages.show(config: config, view: view) + } + } +} From 7d01cc00c7b23d37b314280daca77455521076d0 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 12:31:04 +0300 Subject: [PATCH 013/169] savepoint --- Example/WalletApp/ApplicationLayer/ConfigurationService.swift | 2 +- Example/WalletApp/Common/AlertPresenter.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index a016d8515..1cbab29f5 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -44,7 +44,7 @@ final class ConfigurationService { Web3Wallet.instance.socketConnectionStatusPublisher.sink { status in switch status { case .connected: - AlertPresenter.present(message: "Your web socket has connected", type: .info) + AlertPresenter.present(message: "Your web socket has connected", type: .success) case .disconnected: AlertPresenter.present(message: "Your web socket is disconnected", type: .warning) } diff --git a/Example/WalletApp/Common/AlertPresenter.swift b/Example/WalletApp/Common/AlertPresenter.swift index 2b3b45c38..5da5d4668 100644 --- a/Example/WalletApp/Common/AlertPresenter.swift +++ b/Example/WalletApp/Common/AlertPresenter.swift @@ -28,7 +28,7 @@ struct AlertPresenter { view.configureContent(title: "", body: message) var config = SwiftMessages.Config() config.presentationStyle = .top - config.duration = .seconds(seconds: 1) + config.duration = .seconds(seconds: 1.5) SwiftMessages.show(config: config, view: view) } } From 1aadaebd5a3a293dfa56cd7d5c49b894637f2279 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 19:01:44 +0300 Subject: [PATCH 014/169] present alert for w3w error messages --- .../WalletApp/ApplicationLayer/ConfigurationService.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 1cbab29f5..81335f0d8 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -50,6 +50,14 @@ final class ConfigurationService { } }.store(in: &publishers) + Web3Wallet.instance.logsPublisher.sink { log in + switch log { + case .error(let logMessage): + AlertPresenter.present(message: logMessage.message, type: .error) + default: return + } + }.store(in: &publishers) + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") From b62568d2cda44654d5319895234ad487b83e0673 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 19:33:42 +0300 Subject: [PATCH 015/169] fix activity indicator manager --- .../Common/ActivityIndicatorManager.swift | 20 ++++++++++--------- .../SessionProposalPresenter.swift | 3 +++ .../SessionRequestInteractor.swift | 6 ++---- .../SessionRequestPresenter.swift | 7 +++++-- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Example/WalletApp/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift index 9a8143f85..9382be8a3 100644 --- a/Example/WalletApp/Common/ActivityIndicatorManager.swift +++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift @@ -6,10 +6,10 @@ class ActivityIndicatorManager { private init() {} - func start() { - DispatchQueue.main.async { - self.stop() + func start() async { + await stop() + DispatchQueue.main.async { guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } @@ -22,12 +22,14 @@ class ActivityIndicatorManager { } } - - func stop() { - DispatchQueue.main.async { - self.activityIndicator?.stopAnimating() - self.activityIndicator?.removeFromSuperview() - self.activityIndicator = nil + func stop() async { + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + self.activityIndicator?.stopAnimating() + self.activityIndicator?.removeFromSuperview() + self.activityIndicator = nil + continuation.resume() + } } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 523158eee..7f2a20f68 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -35,11 +35,14 @@ final class SessionProposalPresenter: ObservableObject { @MainActor func onApprove() async throws { do { + await ActivityIndicatorManager.shared.start() let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account) showConnected ? showConnectedSheet.toggle() : router.dismiss() + await ActivityIndicatorManager.shared.stop() } catch { errorMessage = error.localizedDescription showError.toggle() + await ActivityIndicatorManager.shared.stop() } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift index 2bd386489..edc2ed4df 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift @@ -4,16 +4,14 @@ import Web3Wallet import WalletConnectRouter final class SessionRequestInteractor { - func approve(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool { + func respondSessionRequest(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool { do { let result = try Signer.sign(request: sessionRequest, importAccount: importAccount) - ActivityIndicatorManager.shared.start() try await Web3Wallet.instance.respond( topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(result) ) - ActivityIndicatorManager.shared.stop() /* Redirect */ let session = getSession(topic: sessionRequest.topic) if let uri = session?.peer.redirect?.native { @@ -27,7 +25,7 @@ final class SessionRequestInteractor { } } - func reject(sessionRequest: Request) async throws { + func respondError(sessionRequest: Request) async throws { try await Web3Wallet.instance.respond( topic: sessionRequest.topic, requestId: sessionRequest.id, diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index d8c00b710..b8e0819ce 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -43,9 +43,12 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onApprove() async throws { do { - let showConnected = try await interactor.approve(sessionRequest: sessionRequest, importAccount: importAccount) + await ActivityIndicatorManager.shared.start() + let showConnected = try await interactor.respondSessionRequest(sessionRequest: sessionRequest, importAccount: importAccount) showConnected ? showSignedSheet.toggle() : router.dismiss() + await ActivityIndicatorManager.shared.stop() } catch { + await ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } @@ -53,7 +56,7 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onReject() async throws { - try await interactor.reject(sessionRequest: sessionRequest) + try await interactor.respondError(sessionRequest: sessionRequest) router.dismiss() } From abb4bd1924ca32e8fcb9caf7fa1e07cc8c979d7c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 19:58:00 +0300 Subject: [PATCH 016/169] add dismiss button to session request screen --- .../SessionRequestPresenter.swift | 11 +++++++++-- .../SessionRequest/SessionRequestView.swift | 17 +++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index b8e0819ce..e9627d7d1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -56,8 +56,15 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onReject() async throws { - try await interactor.respondError(sessionRequest: sessionRequest) - router.dismiss() + do { + await ActivityIndicatorManager.shared.start() + try await interactor.respondError(sessionRequest: sessionRequest) + await ActivityIndicatorManager.shared.stop() + router.dismiss() + } catch { + await ActivityIndicatorManager.shared.stop() + + } } func onSignedSheetDismiss() { diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index d64b1f82a..595849180 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -11,7 +11,20 @@ struct SessionRequestView: View { VStack { Spacer() - + + VStack { + HStack { + Spacer() + Button(action: { + presenter.dismiss() + }) { + Image(systemName: "xmark") + .foregroundColor(.white) + .padding() + } + } + .padding() + } VStack(spacing: 0) { Image("header") .resizable() @@ -98,7 +111,7 @@ struct SessionRequestView: View { } .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) { - presenter.dismiss() +// presenter.dismiss() } } .sheet( From adc4d0fcac228469864589d92d4ab8fd09d8a5b6 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 9 Jan 2024 20:17:22 +0300 Subject: [PATCH 017/169] savepoint --- .../Wallet/SessionProposal/SessionProposalPresenter.swift | 5 ++++- .../Wallet/SessionRequest/SessionRequestPresenter.swift | 3 ++- .../Wallet/SessionRequest/SessionRequestView.swift | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 7f2a20f68..07a78d931 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -40,18 +40,21 @@ final class SessionProposalPresenter: ObservableObject { showConnected ? showConnectedSheet.toggle() : router.dismiss() await ActivityIndicatorManager.shared.stop() } catch { + await ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() - await ActivityIndicatorManager.shared.stop() } } @MainActor func onReject() async throws { do { + await ActivityIndicatorManager.shared.start() try await interactor.reject(proposal: sessionProposal) + await ActivityIndicatorManager.shared.stop() router.dismiss() } catch { + await ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index e9627d7d1..b2df3650a 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -63,7 +63,8 @@ final class SessionRequestPresenter: ObservableObject { router.dismiss() } catch { await ActivityIndicatorManager.shared.stop() - + errorMessage = error.localizedDescription + showError.toggle() } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index 595849180..ebc9557b2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -18,7 +18,7 @@ struct SessionRequestView: View { Button(action: { presenter.dismiss() }) { - Image(systemName: "xmark") + Image(systemName: "xmark") .foregroundColor(.white) .padding() } From 638bf7693ec2db9131dc330f9a3121a1cb475d2f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 09:38:28 +0100 Subject: [PATCH 018/169] add activity indicator on delete --- .../Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift index 5312123a4..7fdc893c5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift @@ -25,11 +25,14 @@ final class ConnectionDetailsPresenter: ObservableObject { func onDelete() { Task { do { + await ActivityIndicatorManager.shared.start() try await interactor.disconnectSession(session: session) + await ActivityIndicatorManager.shared.stop() DispatchQueue.main.async { self.router.dismiss() } } catch { + await ActivityIndicatorManager.shared.stop() print(error) } } From 9be087e3f1483ff24037c6db137bc8de169d668e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 10:57:47 +0100 Subject: [PATCH 019/169] fix request expiry logic --- .../xcschemes/WalletConnectSignTests.xcscheme | 53 +++++++++++++++++++ .../SessionAccountPresenter.swift | 3 +- Sources/WalletConnectSign/Request.swift | 3 +- .../AppProposalServiceTests.swift | 11 +++- .../ApproveEngineTests.swift | 11 +++- .../SessionRequestTests.swift | 7 --- 6 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectSignTests.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectSignTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectSignTests.xcscheme new file mode 100644 index 000000000..bb6f830b9 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectSignTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 15b0b7e74..3534eda65 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -41,7 +41,8 @@ final class SessionAccountPresenter: ObservableObject { do { let requestParams = try getRequest(for: method) - let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!) + let expiry = UInt64(Date().timeIntervalSince1970 + 300) + let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, expiry: expiry) Task { do { try await Sign.instance.request(params: request) diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 1cae4e0cd..665369a1d 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -31,8 +31,7 @@ public struct Request: Codable, Equatable { let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) guard - abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry, - abs(currentDate.distance(to: expiryDate)) > Constants.minExpiry + abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry else { return true } return expiryDate < currentDate diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift index bdc8c7180..5e4b7da61 100644 --- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift +++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift @@ -5,7 +5,7 @@ import JSONRPC @testable import TestingUtils @testable import WalletConnectKMS @testable import WalletConnectPairing -import WalletConnectUtils +@testable import WalletConnectUtils func deriveTopic(publicKey: String, privateKey: AgreementPrivateKey) -> String { try! KeyManagementService.generateAgreementKey(from: privateKey, peerPublicKey: publicKey).derivedTopic() @@ -59,6 +59,12 @@ final class AppProposalServiceTests: XCTestCase { kms: cryptoMock, logger: logger ) + let history = RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ) approveEngine = ApproveEngine( networkingInteractor: networkingInteractor, proposalPayloadsStore: .init(defaults: RuntimeKeyValueStorage(), identifier: ""), @@ -70,7 +76,8 @@ final class AppProposalServiceTests: XCTestCase { logger: logger, pairingStore: storageMock, sessionStore: WCSessionStorageMock(), - verifyClient: VerifyClientMock() + verifyClient: VerifyClientMock(), + rpcHistory: history ) } diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index de84c86d2..928a4600f 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -1,12 +1,12 @@ import XCTest import Combine import JSONRPC -import WalletConnectUtils import WalletConnectPairing import WalletConnectNetworking @testable import WalletConnectSign @testable import TestingUtils @testable import WalletConnectKMS +@testable import WalletConnectUtils final class ApproveEngineTests: XCTestCase { @@ -33,6 +33,12 @@ final class ApproveEngineTests: XCTestCase { proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "") verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") + let history = RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ) engine = ApproveEngine( networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, @@ -44,7 +50,8 @@ final class ApproveEngineTests: XCTestCase { logger: ConsoleLoggerMock(), pairingStore: pairingStorageMock, sessionStore: sessionStorageMock, - verifyClient: VerifyClientMock() + verifyClient: VerifyClientMock(), + rpcHistory: history ) } diff --git a/Tests/WalletConnectSignTests/SessionRequestTests.swift b/Tests/WalletConnectSignTests/SessionRequestTests.swift index e89ff347d..c4b4a2fbf 100644 --- a/Tests/WalletConnectSignTests/SessionRequestTests.swift +++ b/Tests/WalletConnectSignTests/SessionRequestTests.swift @@ -46,13 +46,6 @@ final class SessionRequestTests: XCTestCase { XCTAssertTrue(request.isExpired(currentDate: currentDate)) } - func testIsExpiredTrueMinValidation() { - let currentDate = Date(timeIntervalSince1970: 500) - let expiry = Date(timeIntervalSince1970: 600) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - XCTAssertTrue(request.isExpired(currentDate: currentDate)) - } - func testIsExpiredTrueMaxValidation() { let currentDate = Date(timeIntervalSince1970: 500) let expiry = Date(timeIntervalSince1970: 700000) From c5dfdeefeafe775a26074775bfde3989dc8dcfe8 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 11:21:25 +0100 Subject: [PATCH 020/169] add ActivityIndicatorManager to a dapp --- .../Common/ActivityIndicatorManager.swift | 35 +++++++++++++++++++ Example/ExampleApp.xcodeproj/project.pbxproj | 4 +++ 2 files changed, 39 insertions(+) create mode 100644 Example/DApp/Common/ActivityIndicatorManager.swift diff --git a/Example/DApp/Common/ActivityIndicatorManager.swift b/Example/DApp/Common/ActivityIndicatorManager.swift new file mode 100644 index 000000000..a21e00f46 --- /dev/null +++ b/Example/DApp/Common/ActivityIndicatorManager.swift @@ -0,0 +1,35 @@ +import UIKit + +class ActivityIndicatorManager { + static let shared = ActivityIndicatorManager() + private var activityIndicator: UIActivityIndicatorView? + + private init() {} + + func start() async { + await stop() + + DispatchQueue.main.async { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.startAnimating() + window.addSubview(activityIndicator) + + self.activityIndicator = activityIndicator + } + } + + func stop() async { + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + self.activityIndicator?.stopAnimating() + self.activityIndicator?.removeFromSuperview() + self.activityIndicator = nil + continuation.resume() + } + } + } +} diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 13d1cb82d..c22452e06 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 84CE642827981DF000142511 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642727981DF000142511 /* Assets.xcassets */; }; 84CE642B27981DF000142511 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642927981DF000142511 /* LaunchScreen.storyboard */; }; 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; }; + 84D093EB2B4EA6CB005B1925 /* ActivityIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */; }; 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; }; 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */; }; 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; }; @@ -448,6 +449,7 @@ 84CE642C27981DF000142511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = ""; }; 84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; }; + 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorManager.swift; sourceTree = ""; }; 84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = ""; }; 84D72FC62B4692770057EAF3 /* DApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DApp.entitlements; sourceTree = ""; }; 84DB38F029828A7C00BFEE37 /* WalletApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletApp.entitlements; sourceTree = ""; }; @@ -1426,6 +1428,7 @@ children = ( A51AC0D828E436A3001BACF9 /* InputConfig.swift */, A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */, + 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */, ); path = Common; sourceTree = ""; @@ -2335,6 +2338,7 @@ C5B4C4C42AF11C8B00B4274A /* SignView.swift in Sources */, C5BE02042AF7764F0064FC88 /* UIViewController.swift in Sources */, C5BE021E2AF79B9A0064FC88 /* SessionAccountView.swift in Sources */, + 84D093EB2B4EA6CB005B1925 /* ActivityIndicatorManager.swift in Sources */, C5BE02032AF774CB0064FC88 /* NewPairingPresenter.swift in Sources */, 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */, C5BE01E32AF696540064FC88 /* SceneViewController.swift in Sources */, From 51d81b2ac41f37369fe7c2684e72bc7b9cc2d4de Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 12:09:46 +0100 Subject: [PATCH 021/169] savepoint --- .../DApp/Common/ActivityIndicatorManager.swift | 1 + Example/DApp/Modules/Main/MainPresenter.swift | 18 ++++++++++++++---- .../SessionAccountPresenter.swift | 17 +---------------- Example/DApp/Modules/Sign/SignPresenter.swift | 3 +++ 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Example/DApp/Common/ActivityIndicatorManager.swift b/Example/DApp/Common/ActivityIndicatorManager.swift index a21e00f46..500731651 100644 --- a/Example/DApp/Common/ActivityIndicatorManager.swift +++ b/Example/DApp/Common/ActivityIndicatorManager.swift @@ -15,6 +15,7 @@ class ActivityIndicatorManager { let activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.center = window.center + activityIndicator.color = .white activityIndicator.startAnimating() window.addSubview(activityIndicator) diff --git a/Example/DApp/Modules/Main/MainPresenter.swift b/Example/DApp/Modules/Main/MainPresenter.swift index c3c7d47ee..4dcda326e 100644 --- a/Example/DApp/Modules/Main/MainPresenter.swift +++ b/Example/DApp/Modules/Main/MainPresenter.swift @@ -1,4 +1,5 @@ import UIKit +import WalletConnectSign import Combine final class MainPresenter { @@ -24,9 +25,18 @@ final class MainPresenter { self.router = router self.interactor = interactor } -} -// MARK: - Private functions -extension MainPresenter { - private func setupInitialState() {} + private func setupInitialState() { + Sign.instance.sessionResponsePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] response in + Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + presentResponse(response: response) + } + .store(in: &disposeBag) + } + + private func presentResponse(response: Response) { + + } } diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 3534eda65..1924a264f 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -8,7 +8,6 @@ final class SessionAccountPresenter: ObservableObject { case notImplemented } - @Published var showResponse = false @Published var showError = false @Published var errorMessage = String.empty @Published var showRequestSent = false @@ -28,7 +27,6 @@ final class SessionAccountPresenter: ObservableObject { sessionAccount: AccountDetails, session: Session ) { - defer { setupInitialState() } self.interactor = interactor self.router = router self.sessionAccount = sessionAccount @@ -45,6 +43,7 @@ final class SessionAccountPresenter: ObservableObject { let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, expiry: expiry) Task { do { + await ActivityIndicatorManager.shared.start() try await Sign.instance.request(params: request) DispatchQueue.main.async { [weak self] in self?.openWallet() @@ -67,15 +66,6 @@ final class SessionAccountPresenter: ObservableObject { // MARK: - Private functions extension SessionAccountPresenter { - private func setupInitialState() { - Sign.instance.sessionResponsePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] response in - presentResponse(response: response) - } - .store(in: &subscriptions) - } - private func getRequest(for method: String) throws -> AnyCodable { let account = session.namespaces.first!.value.accounts.first!.absoluteString if method == "eth_sendTransaction" { @@ -89,11 +79,6 @@ extension SessionAccountPresenter { throw Errors.notImplemented } - private func presentResponse(response: Response) { - self.response = response - showResponse.toggle() - } - private func openWallet() { if let nativeUri = session.peer.redirect?.native { UIApplication.shared.open(URL(string: "\(nativeUri)wc?requestSent")!) diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index b42d2663c..d3a6f1911 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -70,9 +70,12 @@ final class SignPresenter: ObservableObject { if let session { Task { @MainActor in do { + await ActivityIndicatorManager.shared.start() try await Sign.instance.disconnect(topic: session.topic) + await ActivityIndicatorManager.shared.stop() accountsDetails.removeAll() } catch { + await ActivityIndicatorManager.shared.stop() showError.toggle() errorMessage = error.localizedDescription } From 399bdacf86777b86d66dc895333abf1f7bdb66b5 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 20:54:12 +0100 Subject: [PATCH 022/169] savepoint - add response view --- Example/DApp/Modules/Main/MainPresenter.swift | 5 +- Example/DApp/Modules/Main/MainRouter.swift | 6 ++ .../SessionResponseModule.swift | 17 ++++ .../SessionResponsePresenter.swift | 17 ++++ .../SessionResponseRouter.swift | 17 ++++ .../SessionResponse/SessionResponseView.swift | 82 +++++++++++++++++++ .../SessionAccount/SessionAccountView.swift | 24 +++--- Example/ExampleApp.xcodeproj/project.pbxproj | 24 ++++++ 8 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseModule.swift create mode 100644 Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift create mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift create mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseView.swift diff --git a/Example/DApp/Modules/Main/MainPresenter.swift b/Example/DApp/Modules/Main/MainPresenter.swift index 4dcda326e..456b5b2a8 100644 --- a/Example/DApp/Modules/Main/MainPresenter.swift +++ b/Example/DApp/Modules/Main/MainPresenter.swift @@ -31,12 +31,9 @@ final class MainPresenter { .receive(on: DispatchQueue.main) .sink { [unowned self] response in Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } - presentResponse(response: response) + router.presentResponse(response: response) } .store(in: &disposeBag) } - private func presentResponse(response: Response) { - - } } diff --git a/Example/DApp/Modules/Main/MainRouter.swift b/Example/DApp/Modules/Main/MainRouter.swift index 4b3fef3de..2f255eada 100644 --- a/Example/DApp/Modules/Main/MainRouter.swift +++ b/Example/DApp/Modules/Main/MainRouter.swift @@ -1,4 +1,5 @@ import UIKit +import WalletConnectSign final class MainRouter { weak var viewController: UIViewController! @@ -16,4 +17,9 @@ final class MainRouter { func authViewController() -> UIViewController { return AuthModule.create(app: app) } + + func presentResponse(response: Response) { + SessionResponseModule.create(app: app, sessionResponse: response) + .present(from: viewController) + } } diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift b/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift new file mode 100644 index 000000000..1030c26aa --- /dev/null +++ b/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift @@ -0,0 +1,17 @@ +import SwiftUI +import WalletConnectSign + +final class SessionResponseModule { + @discardableResult + static func create(app: Application, sessionResponse: Response) -> UIViewController { + let router = SessionResponseRouter(app: app) + let presenter = SessionResponsePresenter(router: router, sessionResponse: sessionResponse) + + let view = NewPairingView().environmentObject(presenter) + let viewController = UIHostingController(rootView: view) + + router.viewController = viewController + + return viewController + } +} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift b/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift new file mode 100644 index 000000000..b26818599 --- /dev/null +++ b/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift @@ -0,0 +1,17 @@ + +import Foundation +import WalletConnectSign + +final class SessionResponsePresenter: ObservableObject, SceneViewModel { + + private let router: SessionResponseRouter + let response: Response + + init( + router: SessionResponseRouter, + sessionResponse: Response + ) { + self.router = router + self.response = sessionResponse + } +} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift b/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift new file mode 100644 index 000000000..752a95116 --- /dev/null +++ b/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift @@ -0,0 +1,17 @@ +import UIKit + +import WalletConnectSign + +final class SessionResponseRouter { + weak var viewController: UIViewController! + + private let app: Application + + init(app: Application) { + self.app = app + } + + func dismiss() { + viewController.dismiss(animated: true) + } +} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseView.swift b/Example/DApp/Modules/SessionResponse/SessionResponseView.swift new file mode 100644 index 000000000..4160f47be --- /dev/null +++ b/Example/DApp/Modules/SessionResponse/SessionResponseView.swift @@ -0,0 +1,82 @@ +import SwiftUI +import WalletConnectSign + +struct SessionResponseView: View { + + @EnvironmentObject var presenter: SessionResponsePresenter + + var body: some View { + ZStack { + Color(red: 25/255, green: 26/255, blue: 26/255) + .ignoresSafeArea() + + responseView(response: presenter.response) + } + } + + + private func responseView(response: Response) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + VStack(spacing: 5) { + HStack { + RoundedRectangle(cornerRadius: 2) + .fill(.gray.opacity(0.5)) + .frame(width: 30, height: 4) + + } + .padding(20) + + HStack { + Group { + if case RPCResult.error(_) = response.result { + Text("❌ Response") + } else { + Text("✅ Response") + } + } + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(12) + + Spacer() + + let record = Sign.instance.getSessionRequestRecord(id: response.id)! + Text(record.request.method) + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(12) + } + + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + switch response.result { + case .response(let response): + Text(try! response.get(String.self).description) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .padding(.vertical, 12) + .padding(.horizontal, 8) + + case .error(let error): + Text(error.message) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + } + .padding(.bottom, 12) + .padding(.horizontal, 8) + } + } + } +} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index 1b8a3ebb8..a5c825924 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -31,18 +31,18 @@ struct SessionAccountView: View { Color(red: 25/255, green: 26/255, blue: 26/255), for: .navigationBar ) - .sheet(isPresented: $presenter.showResponse) { - ZStack { - Color(red: 25/255, green: 26/255, blue: 26/255) - .ignoresSafeArea() - - ScrollView { - responseView(response: presenter.response!) - .padding(12) - } - } - .presentationDetents([.medium]) - } +// .sheet(isPresented: $presenter.showResponse) { +// ZStack { +// Color(red: 25/255, green: 26/255, blue: 26/255) +// .ignoresSafeArea() +// +// ScrollView { +// responseView(response: presenter.response!) +// .padding(12) +// } +// } +// .presentationDetents([.medium]) +// } .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) {} } diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index c22452e06..3983aabc6 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -32,6 +32,10 @@ 847BD1E8298A806800076C90 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E3298A806800076C90 /* NotificationsView.swift */; }; 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */; }; 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */; }; + 8486EDC52B4F1D04008E53C3 /* SessionResponseModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */; }; + 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */; }; + 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */; }; + 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */; }; 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; }; 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9452A836C3F0003D5AF /* Sentry */; }; 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; }; @@ -430,6 +434,10 @@ 847BD1E3298A806800076C90 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsViewModel.swift; sourceTree = ""; }; 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPlatformW3WTests.swift; sourceTree = ""; }; + 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseModule.swift; sourceTree = ""; }; + 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponsePresenter.swift; sourceTree = ""; }; + 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseRouter.swift; sourceTree = ""; }; + 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseView.swift; sourceTree = ""; }; 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = XPlatformProtocolTests.xctestplan; path = ../XPlatformProtocolTests.xctestplan; sourceTree = ""; }; 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; @@ -939,6 +947,17 @@ path = Web3Wallet; sourceTree = ""; }; + 8486EDC72B4F1D22008E53C3 /* SessionResponse */ = { + isa = PBXGroup; + children = ( + 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */, + 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */, + 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */, + 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */, + ); + path = SessionResponse; + sourceTree = ""; + }; 849D7A91292E2115006A2BD4 /* Push */ = { isa = PBXGroup; children = ( @@ -1890,6 +1909,7 @@ C5BE02062AF777AD0064FC88 /* Main */, C5B4C4C52AF12C2900B4274A /* Sign */, C5B4C4CD2AF12F0B00B4274A /* Auth */, + 8486EDC72B4F1D22008E53C3 /* SessionResponse */, ); path = Modules; sourceTree = ""; @@ -2349,12 +2369,14 @@ A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */, A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */, C5BE02122AF777AD0064FC88 /* MainPresenter.swift in Sources */, + 8486EDC52B4F1D04008E53C3 /* SessionResponseModule.swift in Sources */, C5BE01E22AF693080064FC88 /* Application.swift in Sources */, C5BE021B2AF79B9A0064FC88 /* SessionAccountPresenter.swift in Sources */, A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */, C5BE020E2AF777AD0064FC88 /* MainRouter.swift in Sources */, A5A0843D29D2F624000B9B17 /* DefaultCryptoProvider.swift in Sources */, C5BE021F2AF79B9A0064FC88 /* SessionAccountModule.swift in Sources */, + 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */, C5BE01E42AF697100064FC88 /* UIColor.swift in Sources */, C5BE01DB2AF692060064FC88 /* AuthRouter.swift in Sources */, C5BE01E62AF697FA0064FC88 /* String.swift in Sources */, @@ -2368,9 +2390,11 @@ C5BE02112AF777AD0064FC88 /* MainViewController.swift in Sources */, C5BE021D2AF79B9A0064FC88 /* SessionAccountInteractor.swift in Sources */, A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */, + 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */, C5BE01FB2AF6CB270064FC88 /* SignRouter.swift in Sources */, C5BE01D72AF691CD0064FC88 /* AuthModule.swift in Sources */, C5BE01F72AF6CB250064FC88 /* SignModule.swift in Sources */, + 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */, C5BE01FA2AF6CB270064FC88 /* SignPresenter.swift in Sources */, C5BE02132AF777AD0064FC88 /* MainInteractor.swift in Sources */, ); From 7dcf7dba12a7d034606a6cc9194c65b80294312f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 10 Jan 2024 21:00:13 +0100 Subject: [PATCH 023/169] add socket status to dapp --- Example/DApp/Common/AlertPresenter.swift | 35 ++++++++++++++++++++ Example/DApp/SceneDelegate.swift | 14 +++++++- Example/ExampleApp.xcodeproj/project.pbxproj | 12 +++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Example/DApp/Common/AlertPresenter.swift diff --git a/Example/DApp/Common/AlertPresenter.swift b/Example/DApp/Common/AlertPresenter.swift new file mode 100644 index 000000000..5da5d4668 --- /dev/null +++ b/Example/DApp/Common/AlertPresenter.swift @@ -0,0 +1,35 @@ +import Foundation +import SwiftMessages +import UIKit + +struct AlertPresenter { + enum MessageType { + case warning + case error + case info + case success + } + + static func present(message: String, type: AlertPresenter.MessageType) { + DispatchQueue.main.async { + let view = MessageView.viewFromNib(layout: .cardView) + switch type { + case .warning: + view.configureTheme(.warning, iconStyle: .subtle) + case .error: + view.configureTheme(.error, iconStyle: .subtle) + case .info: + view.configureTheme(.info, iconStyle: .subtle) + case .success: + view.configureTheme(.success, iconStyle: .subtle) + } + view.button?.isHidden = true + view.layoutMarginAdditions = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + view.configureContent(title: "", body: message) + var config = SwiftMessages.Config() + config.presentationStyle = .top + config.duration = .seconds(seconds: 1.5) + SwiftMessages.show(config: config, view: view) + } + } +} diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index b52f47555..aa7e7f3ca 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -4,9 +4,11 @@ import Web3Modal import Auth import WalletConnectRelay import WalletConnectNetworking +import Combine class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + private var publishers = Set() private let app = Application() @@ -30,7 +32,17 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { projectId: InputConfig.projectId, metadata: metadata ) - + + + Sign.instance.socketConnectionStatusPublisher.sink { status in + switch status { + case .connected: + AlertPresenter.present(message: "Your web socket has connected", type: .success) + case .disconnected: + AlertPresenter.present(message: "Your web socket is disconnected", type: .warning) + } + }.store(in: &publishers) + setupWindow(scene: scene) } diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 3983aabc6..99e22abc3 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */; }; 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */; }; 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */; }; + 8486EDD12B4F2DC1008E53C3 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */; }; + 8486EDD32B4F2EA6008E53C3 /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */; }; 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; }; 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9452A836C3F0003D5AF /* Sentry */; }; 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; }; @@ -438,6 +440,7 @@ 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponsePresenter.swift; sourceTree = ""; }; 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseRouter.swift; sourceTree = ""; }; 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseView.swift; sourceTree = ""; }; + 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = ""; }; 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = XPlatformProtocolTests.xctestplan; path = ../XPlatformProtocolTests.xctestplan; sourceTree = ""; }; 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; @@ -737,6 +740,7 @@ A54195A52934E83F0035AD19 /* Web3 in Frameworks */, 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */, A5D85228286333E300DAF5C3 /* Starscream in Frameworks */, + 8486EDD32B4F2EA6008E53C3 /* SwiftMessages in Frameworks */, 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */, A573C53929EC365000E3CBFD /* HDWalletKit in Frameworks */, A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */, @@ -1448,6 +1452,7 @@ A51AC0D828E436A3001BACF9 /* InputConfig.swift */, A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */, 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */, + 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */, ); path = Common; sourceTree = ""; @@ -2044,6 +2049,7 @@ 84943C7A2A9BA206007EBAC2 /* Mixpanel */, C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */, C579FEB52AFA86CD008855EB /* Web3Modal */, + 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; @@ -2383,6 +2389,7 @@ C5BE02012AF774CB0064FC88 /* NewPairingModule.swift in Sources */, C5BE02002AF774CB0064FC88 /* NewPairingRouter.swift in Sources */, C5BE01F82AF6CB270064FC88 /* SignInteractor.swift in Sources */, + 8486EDD12B4F2DC1008E53C3 /* AlertPresenter.swift in Sources */, C5BE01D12AF661D70064FC88 /* NewPairingView.swift in Sources */, 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */, C5BE02022AF774CB0064FC88 /* NewPairingInteractor.swift in Sources */, @@ -3468,6 +3475,11 @@ isa = XCSwiftPackageProductDependency; productName = Web3Wallet; }; + 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */ = { + isa = XCSwiftPackageProductDependency; + package = 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */; + productName = SwiftMessages; + }; 8487A9432A836C2A0003D5AF /* Sentry */ = { isa = XCSwiftPackageProductDependency; package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */; From 1c497f71a69a79cfffd334eef27ea986d2c3872e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 09:15:29 +0100 Subject: [PATCH 024/169] display alert for sign errors --- Example/DApp/SceneDelegate.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index aa7e7f3ca..952f40e68 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -33,6 +33,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { metadata: metadata ) + Sign.instance.logsPublisher.sink { log in + switch log { + case .error(let logMessage): + AlertPresenter.present(message: logMessage.message, type: .error) + default: return + } + }.store(in: &publishers) Sign.instance.socketConnectionStatusPublisher.sink { status in switch status { From 095355dbc1672b6fb3a6735e916dae279ca67b80 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 10:49:12 +0100 Subject: [PATCH 025/169] fix tests, add namespaces test --- .../WalletConnectRouter/Router/Router.swift | 3 +- .../AutoNamespacesValidationTests.swift | 37 +++++++++++++++++++ .../Mocks/PairingClientMock.swift | 11 ++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectRouter/Router/Router.swift b/Sources/WalletConnectRouter/Router/Router.swift index 75a227b31..97db89059 100644 --- a/Sources/WalletConnectRouter/Router/Router.swift +++ b/Sources/WalletConnectRouter/Router/Router.swift @@ -1,5 +1,5 @@ +#if os(iOS) import UIKit - public struct WalletConnectRouter { public static func goBack(uri: String) { if #available(iOS 17, *) { @@ -13,3 +13,4 @@ public struct WalletConnectRouter { } } } +#endif diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index 1942c9d75..23adb074a 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -999,4 +999,41 @@ final class AutoNamespacesValidationTests: XCTestCase { ] XCTAssertEqual(sessionNamespaces, expectedNamespaces) } + + func testSessionNamespacesContainsSupportedChainsAndMethodsWhenOptionalAndRequiredNamespacesAreEmpty() { + let requiredNamespaces = [String: ProposalNamespace]() + let optionalNamespaces = [String: ProposalNamespace]() + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)), + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil))), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + let sessionNamespaces = try! AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], + events: ["chainChanged", "accountsChanged"], + accounts: [] + ) + let expectedNamespaces: [String: SessionNamespace] = [ + "eip155": SessionNamespace( + chains: [Blockchain("eip155:1")!], + accounts: Set([]), + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: ["chainChanged", "accountsChanged"] + ), + "solana": SessionNamespace( + chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + accounts: Set([]), + methods: ["solana_signMessage"], + events: [] + ) + ] + XCTAssertEqual(sessionNamespaces, expectedNamespaces) + } + } diff --git a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift index 0f0012d59..7e2c345c4 100644 --- a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift @@ -4,6 +4,17 @@ import Combine @testable import WalletConnectPairing final class PairingClientMock: PairingClientProtocol { + var pairingStatePublisher: AnyPublisher { + pairingStatePublisherSubject.eraseToAnyPublisher() + } + var pairingStatePublisherSubject = PassthroughSubject() + + var pairingExpirationPublisher: AnyPublisher { + return pairingExpirationPublisherSubject.eraseToAnyPublisher() + } + var pairingExpirationPublisherSubject = PassthroughSubject() + + var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> { pairingDeletePublisherSubject.eraseToAnyPublisher() } From 4f892224130a666c69202d1637648058015b5e58 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 12:42:43 +0100 Subject: [PATCH 026/169] remove url from client authenticator --- .../ClientAuth/ClientIdAuthenticator.swift | 12 ++++-------- Sources/WalletConnectRelay/RelayClientFactory.swift | 3 +-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift b/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift index 87a7daada..150ed00d4 100644 --- a/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift +++ b/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift @@ -1,23 +1,19 @@ import Foundation public protocol ClientIdAuthenticating { - func createAuthToken(url: String?) throws -> String + func createAuthToken(url: String) throws -> String } public final class ClientIdAuthenticator: ClientIdAuthenticating { private let clientIdStorage: ClientIdStoring - private var url: String - public init(clientIdStorage: ClientIdStoring, url: String) { + public init(clientIdStorage: ClientIdStoring) { self.clientIdStorage = clientIdStorage - self.url = url } - public func createAuthToken(url: String? = nil) throws -> String { - url.flatMap { self.url = $0 } - + public func createAuthToken(url: String) throws -> String { let keyPair = try clientIdStorage.getOrCreateKeyPair() - let payload = RelayAuthPayload(subject: getSubject(), audience: self.url) + let payload = RelayAuthPayload(subject: getSubject(), audience: url) return try payload.signAndCreateWrapper(keyPair: keyPair).jwtString } diff --git a/Sources/WalletConnectRelay/RelayClientFactory.swift b/Sources/WalletConnectRelay/RelayClientFactory.swift index 26788c48f..98066e6c8 100644 --- a/Sources/WalletConnectRelay/RelayClientFactory.swift +++ b/Sources/WalletConnectRelay/RelayClientFactory.swift @@ -45,8 +45,7 @@ public struct RelayClientFactory { let clientIdStorage = ClientIdStorage(defaults: keyValueStorage, keychain: keychainStorage, logger: logger) let socketAuthenticator = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://\(relayHost)" + clientIdStorage: clientIdStorage ) let relayUrlFactory = RelayUrlFactory( relayHost: relayHost, From 2218816b733914b1b9dd44fa0f777508fbba46ba Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 12:43:04 +0100 Subject: [PATCH 027/169] savepoint --- .../AutoNamespacesValidationTests.swift | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index 23adb074a..db78b0433 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -1000,40 +1000,40 @@ final class AutoNamespacesValidationTests: XCTestCase { XCTAssertEqual(sessionNamespaces, expectedNamespaces) } - func testSessionNamespacesContainsSupportedChainsAndMethodsWhenOptionalAndRequiredNamespacesAreEmpty() { - let requiredNamespaces = [String: ProposalNamespace]() - let optionalNamespaces = [String: ProposalNamespace]() - let sessionProposal = Session.Proposal( - id: "", - pairingTopic: "", - proposer: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)), - requiredNamespaces: requiredNamespaces, - optionalNamespaces: optionalNamespaces, - sessionProperties: nil, - proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil))), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) - ) - let sessionNamespaces = try! AutoNamespaces.build( - sessionProposal: sessionProposal, - chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], - methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], - events: ["chainChanged", "accountsChanged"], - accounts: [] - ) - let expectedNamespaces: [String: SessionNamespace] = [ - "eip155": SessionNamespace( - chains: [Blockchain("eip155:1")!], - accounts: Set([]), - methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], - events: ["chainChanged", "accountsChanged"] - ), - "solana": SessionNamespace( - chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], - accounts: Set([]), - methods: ["solana_signMessage"], - events: [] - ) - ] - XCTAssertEqual(sessionNamespaces, expectedNamespaces) - } +// func testSessionNamespacesContainsSupportedChainsAndMethodsWhenOptionalAndRequiredNamespacesAreEmpty() { +// let requiredNamespaces = [String: ProposalNamespace]() +// let optionalNamespaces = [String: ProposalNamespace]() +// let sessionProposal = Session.Proposal( +// id: "", +// pairingTopic: "", +// proposer: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)), +// requiredNamespaces: requiredNamespaces, +// optionalNamespaces: optionalNamespaces, +// sessionProperties: nil, +// proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil))), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) +// ) +// let sessionNamespaces = try! AutoNamespaces.build( +// sessionProposal: sessionProposal, +// chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], +// methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], +// events: ["chainChanged", "accountsChanged"], +// accounts: [] +// ) +// let expectedNamespaces: [String: SessionNamespace] = [ +// "eip155": SessionNamespace( +// chains: [Blockchain("eip155:1")!], +// accounts: Set([]), +// methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], +// events: ["chainChanged", "accountsChanged"] +// ), +// "solana": SessionNamespace( +// chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], +// accounts: Set([]), +// methods: ["solana_signMessage"], +// events: [] +// ) +// ] +// XCTAssertEqual(sessionNamespaces, expectedNamespaces) +// } } From c244e8be45088b6fba799041298f234e43c785d3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 13:26:05 +0100 Subject: [PATCH 028/169] savepoint --- Sources/WalletConnectHistory/HistoryNetworkService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectHistory/HistoryNetworkService.swift b/Sources/WalletConnectHistory/HistoryNetworkService.swift index f4d01ddb8..906959577 100644 --- a/Sources/WalletConnectHistory/HistoryNetworkService.swift +++ b/Sources/WalletConnectHistory/HistoryNetworkService.swift @@ -28,8 +28,8 @@ private extension HistoryNetworkService { } func getJwt(historyUrl: String) throws -> String { - let authenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage, url: historyUrl) - return try authenticator.createAuthToken() + let authenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage) + return try authenticator.createAuthToken(url: historyUrl) } func host(from url: String) throws -> String { From 6fee69536db1e76e9cc0ed9dfb52c33191c2b392 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 13:50:27 +0100 Subject: [PATCH 029/169] present pairing expired message in a wallet --- Example/WalletApp/ApplicationLayer/ConfigurationService.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 81335f0d8..31ef0957b 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -58,6 +58,10 @@ final class ConfigurationService { } }.store(in: &publishers) + Web3Wallet.instance.pairingExpirationPublisher.sink { _ in + AlertPresenter.present(message: "Pairing has expired", type: .warning) + }.store(in: &publishers) + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") From f1b7da1e8b67eb539753d58f8bde494d962b1e2e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 22:05:26 +0100 Subject: [PATCH 030/169] Add expiry to session proposal --- .../ConfigurationService.swift | 2 +- .../Types/Session/SessionProposal.swift | 25 +++++++++++++++++++ .../SessionProposalTests.swift | 22 ++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 Tests/WalletConnectSignTests/SessionProposalTests.swift diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 31ef0957b..bebebc71f 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -61,7 +61,7 @@ final class ConfigurationService { Web3Wallet.instance.pairingExpirationPublisher.sink { _ in AlertPresenter.present(message: "Pairing has expired", type: .warning) }.store(in: &publishers) - + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") diff --git a/Sources/WalletConnectSign/Types/Session/SessionProposal.swift b/Sources/WalletConnectSign/Types/Session/SessionProposal.swift index fa1ee979a..8834417a9 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionProposal.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionProposal.swift @@ -1,11 +1,28 @@ import Foundation struct SessionProposal: Codable, Equatable { + let relays: [RelayProtocolOptions] let proposer: Participant let requiredNamespaces: [String: ProposalNamespace] let optionalNamespaces: [String: ProposalNamespace]? let sessionProperties: [String: String]? + let expiry: UInt64? + + static let proposalExpiry: TimeInterval = 300 // 5 minutes + + internal init(relays: [RelayProtocolOptions], + proposer: Participant, + requiredNamespaces: [String : ProposalNamespace], + optionalNamespaces: [String : ProposalNamespace]? = nil, + sessionProperties: [String : String]? = nil) { + self.relays = relays + self.proposer = proposer + self.requiredNamespaces = requiredNamespaces + self.optionalNamespaces = optionalNamespaces + self.sessionProperties = sessionProperties + self.expiry = UInt64(Date().timeIntervalSince1970 + Self.proposalExpiry) + } func publicRepresentation(pairingTopic: String) -> Session.Proposal { return Session.Proposal( @@ -18,4 +35,12 @@ struct SessionProposal: Codable, Equatable { proposal: self ) } + + func isExpired(currentDate: Date = Date()) -> Bool { + guard let expiry = expiry else { return false } + + let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) + + return expiryDate < currentDate + } } diff --git a/Tests/WalletConnectSignTests/SessionProposalTests.swift b/Tests/WalletConnectSignTests/SessionProposalTests.swift new file mode 100644 index 000000000..7fef2737b --- /dev/null +++ b/Tests/WalletConnectSignTests/SessionProposalTests.swift @@ -0,0 +1,22 @@ +import XCTest +@testable import WalletConnectSign + +class SessionProposalTests: XCTestCase { + + func testProposalNotExpiredImmediately() { + let proposal = SessionProposal.stub() + XCTAssertFalse(proposal.isExpired(), "Proposal should not be expired immediately after creation.") + } + + func testProposalExpired() { + let proposal = SessionProposal.stub() + let expiredDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiry! + 1)) + XCTAssertTrue(proposal.isExpired(currentDate: expiredDate), "Proposal should be expired after the expiry time.") + } + + func testProposalNotExpiredJustBeforeExpiry() { + let proposal = SessionProposal.stub() + let justBeforeExpiryDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiry! - 1)) + XCTAssertFalse(proposal.isExpired(currentDate: justBeforeExpiryDate), "Proposal should not be expired just before the expiry time.") + } +} From 9feefe211e107899081180083e752879e1638bb3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 11 Jan 2024 22:13:47 +0100 Subject: [PATCH 031/169] add backward compatibility tests for session proposal --- .../SessionProposalTests.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Tests/WalletConnectSignTests/SessionProposalTests.swift b/Tests/WalletConnectSignTests/SessionProposalTests.swift index 7fef2737b..d78180817 100644 --- a/Tests/WalletConnectSignTests/SessionProposalTests.swift +++ b/Tests/WalletConnectSignTests/SessionProposalTests.swift @@ -19,4 +19,32 @@ class SessionProposalTests: XCTestCase { let justBeforeExpiryDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiry! - 1)) XCTAssertFalse(proposal.isExpired(currentDate: justBeforeExpiryDate), "Proposal should not be expired just before the expiry time.") } + + // for backward compatibility + func testDecodingWithoutExpiry() throws { + let json = """ + { + "relays": [], + "proposer": { + "publicKey": "testKey", + "metadata": { + "name": "Wallet Connect", + "description": "A protocol to connect blockchain wallets to dapps.", + "url": "https://walletconnect.com/", + "icons": [] + } + }, + "requiredNamespaces": {}, + "optionalNamespaces": {}, + "sessionProperties": {} + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let proposal = try decoder.decode(SessionProposal.self, from: json) + + // Assertions + XCTAssertNotNil(proposal, "Proposal should be successfully decoded even without an expiry field.") + XCTAssertNil(proposal.expiry, "Expiry should be nil if not provided in JSON.") + } } From a45b70da4ced0cc959f5e87709ec8d78ac3aa387 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 09:37:53 +0100 Subject: [PATCH 032/169] savepoint --- .../ApplicationLayer/ConfigurationService.swift | 3 ++- .../WalletConnectPairing/Types/Pairing.swift | 10 +++------- .../Engine/Common/ApproveEngine.swift | 17 +++++++++++++---- .../Engine/Common/ProposalExpiryWatcher.swift | 5 +++++ 4 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index bebebc71f..d079296e7 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -58,7 +58,8 @@ final class ConfigurationService { } }.store(in: &publishers) - Web3Wallet.instance.pairingExpirationPublisher.sink { _ in + Web3Wallet.instance.pairingExpirationPublisher.sink { pairing in + guard !pairing.active else { return } AlertPresenter.present(message: "Pairing has expired", type: .warning) }.store(in: &publishers) diff --git a/Sources/WalletConnectPairing/Types/Pairing.swift b/Sources/WalletConnectPairing/Types/Pairing.swift index 03ed01a41..ddd923807 100644 --- a/Sources/WalletConnectPairing/Types/Pairing.swift +++ b/Sources/WalletConnectPairing/Types/Pairing.swift @@ -1,21 +1,17 @@ import Foundation /** - A representation of an active pairing connection. + A representation of a pairing connection. */ public struct Pairing { public let topic: String public let peer: AppMetadata? public let expiryDate: Date - - public init(topic: String, peer: AppMetadata?, expiryDate: Date) { - self.topic = topic - self.peer = peer - self.expiryDate = expiryDate - } + public let active: Bool init(_ pairing: WCPairing) { self.topic = pairing.topic self.peer = pairing.peerMetadata self.expiryDate = pairing.expiryDate + self.active = pairing.active } } diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index fff6873a6..33b148d72 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -3,13 +3,14 @@ import Combine final class ApproveEngine { enum Errors: Error { - case wrongRequestParams + case proposalNotFound case relayNotFound case proposalPayloadsNotFound case pairingNotFound case sessionNotFound case agreementMissingOrInvalid case networkNotConnected + case proposalExpired } var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)? @@ -65,16 +66,24 @@ final class ApproveEngine { func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws { logger.debug("Approving session proposal") + guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { - throw Errors.wrongRequestParams + throw Errors.proposalNotFound } - + + let proposal = payload.request + + guard !proposal.isExpired() else { + logger.debug("Proposal has expired, topic: \(payload.topic)") + proposalPayloadsStore.delete(forKey: proposerPubKey) + throw Errors.proposalExpired + } + let networkConnectionStatus = await resolveNetworkConnectionStatus() guard networkConnectionStatus == .connected else { throw Errors.networkNotConnected } - let proposal = payload.request let pairingTopic = payload.topic proposalPayloadsStore.delete(forKey: proposerPubKey) diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift new file mode 100644 index 000000000..c63bef0a3 --- /dev/null +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -0,0 +1,5 @@ +import Foundation + +class ProposalExpiryWatcher { + +} From 3f05c7911a14c8b02eef46745aba3b0a4a76bd9b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 10:14:54 +0100 Subject: [PATCH 033/169] add ProposalExpiryWatcher --- .../Services/Common/PairingsProvider.swift | 4 +-- .../WalletConnectPairing/Types/Pairing.swift | 8 +++++- .../Engine/Common/ProposalExpiryWatcher.swift | 26 +++++++++++++++++++ Tests/WalletConnectSignTests/Stub/Stubs.swift | 4 +-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift index 99cd02ca5..76d1417f8 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift @@ -41,8 +41,8 @@ class PairingStateProvider { } private func setupPairingStateCheckTimer() { - checkTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in - self?.checkPairingState() + checkTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] _ in + checkPairingState() } } diff --git a/Sources/WalletConnectPairing/Types/Pairing.swift b/Sources/WalletConnectPairing/Types/Pairing.swift index ddd923807..84fab2f9e 100644 --- a/Sources/WalletConnectPairing/Types/Pairing.swift +++ b/Sources/WalletConnectPairing/Types/Pairing.swift @@ -1,6 +1,6 @@ import Foundation /** - A representation of a pairing connection. + A representation of an active pairing connection. */ public struct Pairing { public let topic: String @@ -8,6 +8,12 @@ public struct Pairing { public let expiryDate: Date public let active: Bool +// public init(topic: String, peer: AppMetadata?, expiryDate: Date) { +// self.topic = topic +// self.peer = peer +// self.expiryDate = expiryDate +// } + init(_ pairing: WCPairing) { self.topic = pairing.topic self.peer = pairing.peerMetadata diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift index c63bef0a3..de2a70235 100644 --- a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -1,5 +1,31 @@ import Foundation +import Combine class ProposalExpiryWatcher { + private let sessionProposalExpirationPublisherSubject: PassthroughSubject = .init() + + var sessionProposalExpirationPublisher: AnyPublisher { + return sessionProposalExpirationPublisherSubject.eraseToAnyPublisher() + } + + private let proposalPayloadsStore: CodableStore> + private var checkTimer: Timer? + + internal init(proposalPayloadsStore: CodableStore>) { + self.proposalPayloadsStore = proposalPayloadsStore + } + + func setUpExpiryCheckTimer() { + checkTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [unowned self] _ in + checkForProposalsExpiry() + } + } + + func checkForProposalsExpiry() { + proposalPayloadsStore.getAll().forEach { payload in + sessionProposalExpirationPublisherSubject.send(payload.request) + proposalPayloadsStore.delete(forKey: payload.request.proposer.publicKey) + } + } } diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index 2f7961e22..b1e904e85 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -4,11 +4,11 @@ import JSONRPC import WalletConnectKMS import WalletConnectUtils import TestingUtils -import WalletConnectPairing +@testable import WalletConnectPairing extension Pairing { static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing { - Pairing(topic: topic, peer: nil, expiryDate: expiryDate) + Pairing(WCPairing.stub(expiryDate: expiryDate, topic: topic)) } } From 185de33c576872dfa924e4386dd47b06bb777359 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 10:44:47 +0100 Subject: [PATCH 034/169] savepoint --- .../Engine/Common/ProposalExpiryWatcher.swift | 22 ++++++++++++++----- .../WalletConnectSign/Sign/SignClient.swift | 10 ++++++++- .../Sign/SignClientFactory.swift | 4 +++- .../Sign/SignClientProtocol.swift | 1 + Sources/Web3Wallet/Web3WalletClient.swift | 5 +++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift index de2a70235..6099fd945 100644 --- a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -3,17 +3,23 @@ import Combine class ProposalExpiryWatcher { - private let sessionProposalExpirationPublisherSubject: PassthroughSubject = .init() + private let sessionProposalExpirationPublisherSubject: PassthroughSubject = .init() + private let historyService: HistoryService - var sessionProposalExpirationPublisher: AnyPublisher { + var sessionProposalExpirationPublisher: AnyPublisher { return sessionProposalExpirationPublisherSubject.eraseToAnyPublisher() } private let proposalPayloadsStore: CodableStore> private var checkTimer: Timer? - internal init(proposalPayloadsStore: CodableStore>) { + internal init( + proposalPayloadsStore: CodableStore>, + historyService: HistoryService + ) { self.proposalPayloadsStore = proposalPayloadsStore + self.historyService = historyService + setUpExpiryCheckTimer() } func setUpExpiryCheckTimer() { @@ -23,9 +29,13 @@ class ProposalExpiryWatcher { } func checkForProposalsExpiry() { - proposalPayloadsStore.getAll().forEach { payload in - sessionProposalExpirationPublisherSubject.send(payload.request) - proposalPayloadsStore.delete(forKey: payload.request.proposer.publicKey) + historyService.getPendingProposals().forEach { proposal in + + let proposal = proposal.proposal + + sessionProposalExpirationPublisherSubject.send(proposal) + + proposalPayloadsStore.delete(forKey: proposal.id) } } } diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index b87864de1..8e5e1653c 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -99,6 +99,11 @@ public final class SignClient: SignClientProtocol { return logger.logsPublisher } + /// Publisher that sends session proposal expiration + public var sessionProposalExpirationPublisher: AnyPublisher { + return proposalExpiryWatcher.sessionProposalExpirationPublisher + } + /// An object that loggs SDK's errors and info messages public let logger: ConsoleLogging @@ -119,6 +124,7 @@ public final class SignClient: SignClientProtocol { private let appProposeService: AppProposeService private let historyService: HistoryService private let cleanupService: SignCleanupService + private let proposalExpiryWatcher: ProposalExpiryWatcher private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>() private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() @@ -152,7 +158,8 @@ public final class SignClient: SignClientProtocol { disconnectService: DisconnectService, historyService: HistoryService, cleanupService: SignCleanupService, - pairingClient: PairingClient + pairingClient: PairingClient, + proposalExpiryWatcher: ProposalExpiryWatcher ) { self.logger = logger self.networkingClient = networkingClient @@ -170,6 +177,7 @@ public final class SignClient: SignClientProtocol { self.cleanupService = cleanupService self.disconnectService = disconnectService self.pairingClient = pairingClient + self.proposalExpiryWatcher = proposalExpiryWatcher setUpConnectionObserving() setUpEnginesCallbacks() diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 7970d88d5..9b4ee3526 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -70,6 +70,7 @@ public struct SignClientFactory { let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger) let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger) let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger) + let proposalExpiryWatcher = ProposalExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, historyService: historyService) let client = SignClient( logger: logger, @@ -87,7 +88,8 @@ public struct SignClientFactory { disconnectService: disconnectService, historyService: historyService, cleanupService: cleanupService, - pairingClient: pairingClient + pairingClient: pairingClient, + proposalExpiryWatcher: proposalExpiryWatcher ) return client } diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 2b174ddec..cb1a15e96 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -12,6 +12,7 @@ public protocol SignClientProtocol { var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never> { get } var sessionEventPublisher: AnyPublisher<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never> { get } var logsPublisher: AnyPublisher {get} + var sessionProposalExpirationPublisher: AnyPublisher { get } func connect(requiredNamespaces: [String: ProposalNamespace], optionalNamespaces: [String: ProposalNamespace]?, sessionProperties: [String: String]?, topic: String) async throws func request(params: Request) async throws diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 374d3f549..bc0857418 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -81,6 +81,11 @@ public class Web3WalletClient { .eraseToAnyPublisher() } + /// Publisher that sends session proposal expiration + var sessionProposalExpirationPublisher: AnyPublisher { + return signClient.sessionProposalExpirationPublisher + } + // MARK: - Private Properties private let authClient: AuthClientProtocol private let signClient: SignClientProtocol From 91a7dff025d26a507ef42cab18a4912ef1894f57 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 11:07:11 +0100 Subject: [PATCH 035/169] reorganise stubs --- .../ApplicationLayer/ConfigurationService.swift | 4 ++++ .../WalletConnectPairing/Types/AppMetadata.swift | 14 ++++++++++++++ Sources/WalletConnectPairing/Types/Pairing.swift | 14 ++++++++------ Sources/WalletConnectPairing/Types/WCPairing.swift | 8 ++++++++ .../Engine/Common/ProposalExpiryWatcher.swift | 3 ++- .../WalletConnectUtils/RelayProtocolOptions.swift | 8 ++++++++ Sources/Web3Wallet/Web3WalletClient.swift | 2 +- .../AuthTests/SocketAuthenticatorTests.swift | 5 ++--- Tests/RelayerTests/DispatcherTests.swift | 3 +-- Tests/TestingUtils/Stubs/AppMetadata+Stub.swift | 14 -------------- .../Stubs/RelayProtocolOptions+Stub.swift | 8 -------- .../ApproveEngineTests.swift | 2 +- Tests/WalletConnectSignTests/Stub/Stubs.swift | 12 ------------ .../Web3WalletTests/Mocks/PairingClientMock.swift | 2 +- Tests/Web3WalletTests/Mocks/SignClientMock.swift | 7 ++++++- 15 files changed, 56 insertions(+), 50 deletions(-) delete mode 100644 Tests/TestingUtils/Stubs/AppMetadata+Stub.swift delete mode 100644 Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index d079296e7..85216a471 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -63,6 +63,10 @@ final class ConfigurationService { AlertPresenter.present(message: "Pairing has expired", type: .warning) }.store(in: &publishers) + Web3Wallet.instance.sessionProposalExpirationPublisher.sink { proposal in + AlertPresenter.present(message: "Session Proposal has expired", type: .warning) + }.store(in: &publishers) + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") diff --git a/Sources/WalletConnectPairing/Types/AppMetadata.swift b/Sources/WalletConnectPairing/Types/AppMetadata.swift index 4fcfee409..0ab317f12 100644 --- a/Sources/WalletConnectPairing/Types/AppMetadata.swift +++ b/Sources/WalletConnectPairing/Types/AppMetadata.swift @@ -70,3 +70,17 @@ public struct AppMetadata: Codable, Equatable { self.redirect = redirect } } + +#if DEBUG +public extension AppMetadata { + static func stub() -> AppMetadata { + AppMetadata( + name: "Wallet Connect", + description: "A protocol to connect blockchain wallets to dapps.", + url: "https://walletconnect.com/", + icons: [], + redirect: AppMetadata.Redirect(native: "", universal: nil) + ) + } +} +#endif diff --git a/Sources/WalletConnectPairing/Types/Pairing.swift b/Sources/WalletConnectPairing/Types/Pairing.swift index 84fab2f9e..9edd37941 100644 --- a/Sources/WalletConnectPairing/Types/Pairing.swift +++ b/Sources/WalletConnectPairing/Types/Pairing.swift @@ -8,12 +8,6 @@ public struct Pairing { public let expiryDate: Date public let active: Bool -// public init(topic: String, peer: AppMetadata?, expiryDate: Date) { -// self.topic = topic -// self.peer = peer -// self.expiryDate = expiryDate -// } - init(_ pairing: WCPairing) { self.topic = pairing.topic self.peer = pairing.peerMetadata @@ -21,3 +15,11 @@ public struct Pairing { self.active = pairing.active } } + +#if DEBUG +extension Pairing { + static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing { + Pairing(WCPairing.stub(expiryDate: expiryDate, topic: topic)) + } +} +#endif diff --git a/Sources/WalletConnectPairing/Types/WCPairing.swift b/Sources/WalletConnectPairing/Types/WCPairing.swift index d87bd8946..3fe7cfe8a 100644 --- a/Sources/WalletConnectPairing/Types/WCPairing.swift +++ b/Sources/WalletConnectPairing/Types/WCPairing.swift @@ -75,3 +75,11 @@ public struct WCPairing: SequenceObject { expiryDate = newExpiryDate } } + +#if DEBUG +extension WCPairing { + static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), isActive: Bool = true, topic: String = String.generateTopic()) -> WCPairing { + WCPairing(topic: topic, relay: RelayProtocolOptions.stub(), peerMetadata: AppMetadata.stub(), isActive: isActive, expiryDate: expiryDate) + } +} +#endif diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift index 6099fd945..7d6c3cf0b 100644 --- a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -29,7 +29,8 @@ class ProposalExpiryWatcher { } func checkForProposalsExpiry() { - historyService.getPendingProposals().forEach { proposal in + let proposals = historyService.getPendingProposals() + proposals.forEach { proposal in let proposal = proposal.proposal diff --git a/Sources/WalletConnectUtils/RelayProtocolOptions.swift b/Sources/WalletConnectUtils/RelayProtocolOptions.swift index e437e9342..5a19d9f05 100644 --- a/Sources/WalletConnectUtils/RelayProtocolOptions.swift +++ b/Sources/WalletConnectUtils/RelayProtocolOptions.swift @@ -9,3 +9,11 @@ public struct RelayProtocolOptions: Codable, Equatable { self.data = data } } + +#if DEBUG +public extension RelayProtocolOptions { + static func stub() -> RelayProtocolOptions { + RelayProtocolOptions(protocol: "", data: nil) + } +} +#endif diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index bc0857418..a72f61a1f 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -82,7 +82,7 @@ public class Web3WalletClient { } /// Publisher that sends session proposal expiration - var sessionProposalExpirationPublisher: AnyPublisher { + public var sessionProposalExpirationPublisher: AnyPublisher { return signClient.sessionProposalExpirationPublisher } diff --git a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift index 72dd37bc5..bd9371931 100644 --- a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift +++ b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift @@ -11,8 +11,7 @@ final class SocketAuthenticatorTests: XCTestCase { override func setUp() { clientIdStorage = ClientIdStorageMock() sut = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://relay.walletconnect.com" + clientIdStorage: clientIdStorage ) } @@ -20,7 +19,7 @@ final class SocketAuthenticatorTests: XCTestCase { let keyRaw = Data(hex: "58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295") let signingKey = try SigningPrivateKey(rawRepresentation: keyRaw) clientIdStorage.keyPair = signingKey - let token = try sut.createAuthToken() + let token = try sut.createAuthToken(url: "wss://relay.walletconnect.com") XCTAssertNotNil(token) } } diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift index 4ab1c0a48..87035e364 100644 --- a/Tests/RelayerTests/DispatcherTests.swift +++ b/Tests/RelayerTests/DispatcherTests.swift @@ -65,8 +65,7 @@ final class DispatcherTests: XCTestCase { let keychainStorageMock = DispatcherKeychainStorageMock() let clientIdStorage = ClientIdStorage(defaults: defaults, keychain: keychainStorageMock, logger: logger) let socketAuthenticator = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://relay.walletconnect.com" + clientIdStorage: clientIdStorage ) let relayUrlFactory = RelayUrlFactory( relayHost: "relay.walletconnect.com", diff --git a/Tests/TestingUtils/Stubs/AppMetadata+Stub.swift b/Tests/TestingUtils/Stubs/AppMetadata+Stub.swift deleted file mode 100644 index ffa368454..000000000 --- a/Tests/TestingUtils/Stubs/AppMetadata+Stub.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import WalletConnectPairing - -public extension AppMetadata { - static func stub() -> AppMetadata { - AppMetadata( - name: "Wallet Connect", - description: "A protocol to connect blockchain wallets to dapps.", - url: "https://walletconnect.com/", - icons: [], - redirect: AppMetadata.Redirect(native: "", universal: nil) - ) - } -} diff --git a/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift b/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift deleted file mode 100644 index bd3db0839..000000000 --- a/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift +++ /dev/null @@ -1,8 +0,0 @@ -import WalletConnectUtils - -extension RelayProtocolOptions { - - public static func stub() -> RelayProtocolOptions { - RelayProtocolOptions(protocol: "", data: nil) - } -} diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 928a4600f..d40aee9dc 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -1,8 +1,8 @@ import XCTest import Combine import JSONRPC -import WalletConnectPairing import WalletConnectNetworking +@testable import WalletConnectPairing @testable import WalletConnectSign @testable import TestingUtils @testable import WalletConnectKMS diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index b1e904e85..9666beccf 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -6,18 +6,6 @@ import WalletConnectUtils import TestingUtils @testable import WalletConnectPairing -extension Pairing { - static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing { - Pairing(WCPairing.stub(expiryDate: expiryDate, topic: topic)) - } -} - -extension WCPairing { - static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), isActive: Bool = true, topic: String = String.generateTopic()) -> WCPairing { - WCPairing(topic: topic, relay: RelayProtocolOptions.stub(), peerMetadata: AppMetadata.stub(), isActive: isActive, expiryDate: expiryDate) - } -} - extension ProposalNamespace { static func stubDictionary() -> [String: ProposalNamespace] { return [ diff --git a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift index 7e2c345c4..3e752da08 100644 --- a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift @@ -40,6 +40,6 @@ final class PairingClientMock: PairingClientProtocol { } func getPairings() -> [Pairing] { - return [Pairing(topic: "", peer: nil, expiryDate: Date())] + return [Pairing(WCPairing.stub())] } } diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index de4b3cb22..ba53b83a2 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -4,6 +4,7 @@ import Combine @testable import WalletConnectSign final class SignClientMock: SignClientProtocol { + private var logsSubject = PassthroughSubject() var logsPublisher: AnyPublisher { @@ -74,7 +75,11 @@ final class SignClientMock: SignClientProtocol { ) .eraseToAnyPublisher() } - + + var sessionProposalExpirationPublisher: AnyPublisher { + fatalError() + } + var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never> { let sessionProposal = Session.Proposal( id: "", From 74bf4299246cf33832e2098f555f9c5de8fd2615 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 12:47:10 +0100 Subject: [PATCH 036/169] update proposal expiry watcher --- .../Engine/Common/ProposalExpiryWatcher.swift | 19 +++++++++---------- Sources/WalletConnectSign/Session.swift | 6 +++++- .../Sign/SignClientFactory.swift | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift index 7d6c3cf0b..db6d0121a 100644 --- a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -4,7 +4,7 @@ import Combine class ProposalExpiryWatcher { private let sessionProposalExpirationPublisherSubject: PassthroughSubject = .init() - private let historyService: HistoryService + private let rpcHistory: RPCHistory var sessionProposalExpirationPublisher: AnyPublisher { return sessionProposalExpirationPublisherSubject.eraseToAnyPublisher() @@ -15,10 +15,10 @@ class ProposalExpiryWatcher { internal init( proposalPayloadsStore: CodableStore>, - historyService: HistoryService + rpcHistory: RPCHistory ) { self.proposalPayloadsStore = proposalPayloadsStore - self.historyService = historyService + self.rpcHistory = rpcHistory setUpExpiryCheckTimer() } @@ -29,14 +29,13 @@ class ProposalExpiryWatcher { } func checkForProposalsExpiry() { - let proposals = historyService.getPendingProposals() + let proposals = proposalPayloadsStore.getAll() proposals.forEach { proposal in - - let proposal = proposal.proposal - - sessionProposalExpirationPublisherSubject.send(proposal) - - proposalPayloadsStore.delete(forKey: proposal.id) + let pairingTopic = proposal.topic + guard proposal.request.isExpired() else { return } + sessionProposalExpirationPublisherSubject.send(proposal.request.publicRepresentation(pairingTopic: pairingTopic)) + proposalPayloadsStore.delete(forKey: proposal.request.proposer.publicKey) + rpcHistory.delete(id: proposal.id) } } } diff --git a/Sources/WalletConnectSign/Session.swift b/Sources/WalletConnectSign/Session.swift index 7e0d24fb4..06b2500f7 100644 --- a/Sources/WalletConnectSign/Session.swift +++ b/Sources/WalletConnectSign/Session.swift @@ -28,7 +28,11 @@ extension Session { // TODO: Refactor internal objects to manage only needed data internal let proposal: SessionProposal - + + func isExpired() -> Bool { + return proposal.isExpired() + } + init( id: String, pairingTopic: String, diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 9b4ee3526..2de6eed53 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -70,7 +70,7 @@ public struct SignClientFactory { let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger) let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger) let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger) - let proposalExpiryWatcher = ProposalExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, historyService: historyService) + let proposalExpiryWatcher = ProposalExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, rpcHistory: rpcHistory) let client = SignClient( logger: logger, From a6a77f898fdd5a952d080e8bfe0a7d226ac2fc73 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 13:00:09 +0100 Subject: [PATCH 037/169] add localised description for errors --- .../Engine/Common/ApproveEngine.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 33b148d72..126ec07c9 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -5,7 +5,6 @@ final class ApproveEngine { enum Errors: Error { case proposalNotFound case relayNotFound - case proposalPayloadsNotFound case pairingNotFound case sessionNotFound case agreementMissingOrInvalid @@ -135,7 +134,7 @@ final class ApproveEngine { func reject(proposerPubKey: String, reason: SignReasonCode) async throws { guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { - throw Errors.proposalPayloadsNotFound + throw Errors.proposalNotFound } if let pairingTopic = rpcHistory.get(recordId: payload.id)?.topic, @@ -434,8 +433,20 @@ private extension ApproveEngine { extension ApproveEngine.Errors: LocalizedError { var errorDescription: String? { switch self { - case .networkNotConnected: return "Action failed. You seem to be offline" - default: return "" + case .proposalNotFound: + return "Proposal not found." + case .relayNotFound: + return "Relay not found." + case .pairingNotFound: + return "Pairing not found." + case .sessionNotFound: + return "Session not found." + case .agreementMissingOrInvalid: + return "Agreement missing or invalid." + case .networkNotConnected: + return "Network not connected." + case .proposalExpired: + return "Proposal expired." } } } From e7f90aa1ee7f28711993e4269d79083adf390abe Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 13:02:57 +0100 Subject: [PATCH 038/169] add dismiss button to proposal screen --- .../SessionProposal/SessionProposalPresenter.swift | 4 ++++ .../SessionProposal/SessionProposalView.swift | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 07a78d931..84c3eb7de 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -63,6 +63,10 @@ final class SessionProposalPresenter: ObservableObject { func onConnectedSheetDismiss() { router.dismiss() } + + func dismiss() { + router.dismiss() + } } // MARK: - Private functions diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index 19ee52d1e..02f4b0401 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -13,6 +13,19 @@ struct SessionProposalView: View { VStack { Spacer() + VStack { + HStack { + Spacer() + Button(action: { + presenter.dismiss() + }) { + Image(systemName: "xmark") + .foregroundColor(.white) + .padding() + } + } + .padding() + } VStack(spacing: 0) { Image("header") .resizable() From 777cac9996432a883104b095fbf06cfb5557a3f0 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 15:32:31 +0100 Subject: [PATCH 039/169] add PendingProposalsProvider --- .../Wallet/Wallet/WalletInteractor.swift | 6 +--- .../Engine/Common/File.swift | 34 +++++++++++++++++++ .../Engine/Common/ProposalExpiryWatcher.swift | 12 +++---- .../WalletConnectSign/Sign/SignClient.swift | 19 +++++------ .../Sign/SignClientFactory.swift | 4 ++- .../Sign/SignClientProtocol.swift | 2 +- Sources/Web3Wallet/Web3WalletClient.swift | 12 +++---- 7 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 Sources/WalletConnectSign/Engine/Common/File.swift diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift index 3a32be3f8..2b3c7ff4b 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift @@ -19,11 +19,7 @@ final class WalletInteractor { func disconnectSession(session: Session) async throws { try await Web3Wallet.instance.disconnect(topic: session.topic) } - - func getPendingProposals() -> [(proposal: Session.Proposal, context: VerifyContext?)] { - Web3Wallet.instance.getPendingProposals() - } - + func getPendingRequests() -> [(request: Request, context: VerifyContext?)] { Web3Wallet.instance.getPendingRequests() } diff --git a/Sources/WalletConnectSign/Engine/Common/File.swift b/Sources/WalletConnectSign/Engine/Common/File.swift new file mode 100644 index 000000000..6bebd4e31 --- /dev/null +++ b/Sources/WalletConnectSign/Engine/Common/File.swift @@ -0,0 +1,34 @@ +import Foundation +import Combine + +class PendingProposalsProvider { + + private let proposalPayloadsStore: CodableStore> + private let verifyContextStore: CodableStore + private var publishers = Set() + private let pendingProposalsPublisherSubject = PassthroughSubject<[(proposal: Session.Proposal, context: VerifyContext?)], Never>() + + var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { + return pendingProposalsPublisherSubject.eraseToAnyPublisher() + } + + internal init( + proposalPayloadsStore: CodableStore>, + verifyContextStore: CodableStore) + { + self.proposalPayloadsStore = proposalPayloadsStore + self.verifyContextStore = verifyContextStore + setUpPendingProposalsPublisher() + } + + func setUpPendingProposalsPublisher() { + proposalPayloadsStore.storeUpdatePublisher.sink { [unowned self] _ in + let proposals = proposalPayloadsStore.getAll() + + let proposalsWithVerifyContext = proposals.map { ($0.request.publicRepresentation(pairingTopic: $0.topic), try? verifyContextStore.get(key: $0.request.proposer.publicKey)) + } + pendingProposalsPublisherSubject.send(proposalsWithVerifyContext) + + }.store(in: &publishers) + } +} diff --git a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift index db6d0121a..94bc407ca 100644 --- a/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift +++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift @@ -30,12 +30,12 @@ class ProposalExpiryWatcher { func checkForProposalsExpiry() { let proposals = proposalPayloadsStore.getAll() - proposals.forEach { proposal in - let pairingTopic = proposal.topic - guard proposal.request.isExpired() else { return } - sessionProposalExpirationPublisherSubject.send(proposal.request.publicRepresentation(pairingTopic: pairingTopic)) - proposalPayloadsStore.delete(forKey: proposal.request.proposer.publicKey) - rpcHistory.delete(id: proposal.id) + proposals.forEach { proposalPayload in + let pairingTopic = proposalPayload.topic + guard proposalPayload.request.isExpired() else { return } + sessionProposalExpirationPublisherSubject.send(proposalPayload.request.publicRepresentation(pairingTopic: pairingTopic)) + proposalPayloadsStore.delete(forKey: proposalPayload.request.proposer.publicKey) + rpcHistory.delete(id: proposalPayload.id) } } } diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 8e5e1653c..730083d16 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -104,6 +104,10 @@ public final class SignClient: SignClientProtocol { return proposalExpiryWatcher.sessionProposalExpirationPublisher } + public var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { + return pendingProposalsProvider.pendingProposalsPublisher + } + /// An object that loggs SDK's errors and info messages public let logger: ConsoleLogging @@ -125,6 +129,7 @@ public final class SignClient: SignClientProtocol { private let historyService: HistoryService private let cleanupService: SignCleanupService private let proposalExpiryWatcher: ProposalExpiryWatcher + private let pendingProposalsProvider: PendingProposalsProvider private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>() private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() @@ -159,7 +164,8 @@ public final class SignClient: SignClientProtocol { historyService: HistoryService, cleanupService: SignCleanupService, pairingClient: PairingClient, - proposalExpiryWatcher: ProposalExpiryWatcher + proposalExpiryWatcher: ProposalExpiryWatcher, + pendingProposalsProvider: PendingProposalsProvider ) { self.logger = logger self.networkingClient = networkingClient @@ -178,6 +184,7 @@ public final class SignClient: SignClientProtocol { self.disconnectService = disconnectService self.pairingClient = pairingClient self.proposalExpiryWatcher = proposalExpiryWatcher + self.pendingProposalsProvider = pendingProposalsProvider setUpConnectionObserving() setUpEnginesCallbacks() @@ -311,16 +318,6 @@ public final class SignClient: SignClientProtocol { return historyService.getPendingRequests() } } - - /// Query pending proposals - /// - Returns: Pending proposals received from peer with `wc_sessionPropose` protocol method - public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] { - if let topic = topic { - return historyService.getPendingProposals(topic: topic) - } else { - return historyService.getPendingProposals() - } - } /// - Parameter id: id of a wc_sessionRequest jsonrpc request /// - Returns: json rpc record object for given id or nil if record for give id does not exits diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 2de6eed53..7bb885cc7 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -71,6 +71,7 @@ public struct SignClientFactory { let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger) let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger) let proposalExpiryWatcher = ProposalExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, rpcHistory: rpcHistory) + let pendingProposalsProvider = PendingProposalsProvider(proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore) let client = SignClient( logger: logger, @@ -89,7 +90,8 @@ public struct SignClientFactory { historyService: historyService, cleanupService: cleanupService, pairingClient: pairingClient, - proposalExpiryWatcher: proposalExpiryWatcher + proposalExpiryWatcher: proposalExpiryWatcher, + pendingProposalsProvider: pendingProposalsProvider ) return client } diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index cb1a15e96..55afdccda 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -13,6 +13,7 @@ public protocol SignClientProtocol { var sessionEventPublisher: AnyPublisher<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never> { get } var logsPublisher: AnyPublisher {get} var sessionProposalExpirationPublisher: AnyPublisher { get } + var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { get } func connect(requiredNamespaces: [String: ProposalNamespace], optionalNamespaces: [String: ProposalNamespace]?, sessionProperties: [String: String]?, topic: String) async throws func request(params: Request) async throws @@ -27,6 +28,5 @@ public protocol SignClientProtocol { func cleanup() async throws func getPendingRequests(topic: String?) -> [(request: Request, context: VerifyContext?)] - func getPendingProposals(topic: String?) -> [(proposal: Session.Proposal, context: VerifyContext?)] func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? } diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index a72f61a1f..78a515a14 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -86,6 +86,10 @@ public class Web3WalletClient { return signClient.sessionProposalExpirationPublisher } + public var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { + return signClient.pendingProposalsPublisher + } + // MARK: - Private Properties private let authClient: AuthClientProtocol private let signClient: SignClientProtocol @@ -216,13 +220,7 @@ public class Web3WalletClient { public func getPendingRequests(topic: String? = nil) -> [(request: Request, context: VerifyContext?)] { signClient.getPendingRequests(topic: topic) } - - /// Query pending proposals - /// - Returns: Pending proposals received from peer with `wc_sessionPropose` protocol method - public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] { - signClient.getPendingProposals(topic: topic) - } - + /// - Parameter id: id of a wc_sessionRequest jsonrpc request /// - Returns: json rpc record object for given id or nil if record for give id does not exits public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? { From ee79af8023b04334e9aeb84b1bc6acaa1144e7d6 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 15:44:29 +0100 Subject: [PATCH 040/169] dismiss on session proposal expired --- .../SessionProposal/SessionProposalPresenter.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 84c3eb7de..99fe3bec7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -72,7 +72,14 @@ final class SessionProposalPresenter: ObservableObject { // MARK: - Private functions private extension SessionProposalPresenter { func setupInitialState() { - + Web3Wallet.instance.pendingProposalsPublisher.sink { [weak self] proposals in + guard let self = self else { return } + if !proposals.contains(where: { (proposal: Session.Proposal, context: VerifyContext?) in + proposal.id == self.sessionProposal.id + }) { + dismiss() + } + }.store(in: &disposeBag) } } From 82ef9aacf64da45adb8debd291aa93552ba76869 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 15:46:56 +0100 Subject: [PATCH 041/169] fix crash --- .../Wallet/SessionProposal/SessionProposalRouter.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift index cd9de7971..559009c2e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift @@ -10,6 +10,8 @@ final class SessionProposalRouter { } func dismiss() { - viewController.dismiss() + DispatchQueue.main.async { [weak self] in + self?.viewController.dismiss() + } } } From dc3ae63f5466ec9df5bb974fb244c18cc537d7d2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 12 Jan 2024 15:56:53 +0100 Subject: [PATCH 042/169] remove pending proposals from history service --- .../Services/HistoryService.swift | 50 ------------------- .../Mocks/SignClientMock.swift | 4 -- 2 files changed, 54 deletions(-) diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index 2a5974471..a64144a71 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -3,16 +3,13 @@ import Foundation final class HistoryService { private let history: RPCHistory - private let proposalPayloadsStore: CodableStore> private let verifyContextStore: CodableStore init( history: RPCHistory, - proposalPayloadsStore: CodableStore>, verifyContextStore: CodableStore ) { self.history = history - self.proposalPayloadsStore = proposalPayloadsStore self.verifyContextStore = verifyContextStore } @@ -34,32 +31,6 @@ final class HistoryService { func getPendingRequests(topic: String) -> [(request: Request, context: VerifyContext?)] { return getPendingRequests().filter { $0.request.topic == topic } } - - func getPendingProposals() -> [(proposal: Session.Proposal, context: VerifyContext?)] { - let pendingHistory = history.getPending() - - let requestSubscriptionPayloads = pendingHistory - .compactMap { record -> RequestSubscriptionPayload? in - guard let proposalParams = mapProposeParams(record) else { - return nil - } - return RequestSubscriptionPayload(id: record.id, topic: record.topic, request: proposalParams, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil) - } - - requestSubscriptionPayloads.forEach { - let proposal = $0.request - proposalPayloadsStore.set($0, forKey: proposal.proposer.publicKey) - } - - let proposals = pendingHistory - .compactMap { mapProposalRecord($0) } - - return proposals.map { ($0, try? verifyContextStore.get(key: $0.proposal.proposer.publicKey)) } - } - - func getPendingProposals(topic: String) -> [(proposal: Session.Proposal, context: VerifyContext?)] { - return getPendingProposals().filter { $0.proposal.pairingTopic == topic } - } } private extension HistoryService { @@ -76,25 +47,4 @@ private extension HistoryService { expiry: request.request.expiry ) } - - func mapProposeParams(_ record: RPCHistory.Record) -> SessionType.ProposeParams? { - guard let proposal = try? record.request.params?.get(SessionType.ProposeParams.self) - else { return nil } - return proposal - } - - func mapProposalRecord(_ record: RPCHistory.Record) -> Session.Proposal? { - guard let proposal = try? record.request.params?.get(SessionType.ProposeParams.self) - else { return nil } - - return Session.Proposal( - id: proposal.proposer.publicKey, - pairingTopic: record.topic, - proposer: proposal.proposer.metadata, - requiredNamespaces: proposal.requiredNamespaces, - optionalNamespaces: proposal.optionalNamespaces ?? [:], - sessionProperties: proposal.sessionProperties, - proposal: proposal - ) - } } diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index ba53b83a2..75a788a55 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -136,10 +136,6 @@ final class SignClientMock: SignClientProtocol { return [WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())] } - func getPendingProposals(topic: String?) -> [(proposal: WalletConnectSign.Session.Proposal, context: VerifyContext?)] { - return [] - } - func getPendingRequests(topic: String?) -> [(request: WalletConnectSign.Request, context: WalletConnectSign.VerifyContext?)] { return [(request, nil)] } From 2a6e7d0fc39d91e99c5a5c49ca5d66a80a454a73 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 08:38:04 +0100 Subject: [PATCH 043/169] remove tabbar from dapp --- .../DApp/Modules/Auth/AuthInteractor.swift | 3 - Example/DApp/Modules/Auth/AuthModule.swift | 16 -- Example/DApp/Modules/Auth/AuthPresenter.swift | 116 --------------- Example/DApp/Modules/Auth/AuthRouter.swift | 16 -- Example/DApp/Modules/Auth/AuthView.swift | 140 ------------------ .../DApp/Modules/Main/MainInteractor.swift | 3 - Example/DApp/Modules/Main/MainModule.swift | 15 -- Example/DApp/Modules/Main/MainPresenter.swift | 39 ----- Example/DApp/Modules/Main/MainRouter.swift | 25 ---- .../Modules/Main/MainViewController.swift | 43 ------ Example/DApp/Modules/Main/Model/TabPage.swift | 32 ---- Example/DApp/SceneDelegate.swift | 2 +- Example/ExampleApp.xcodeproj/project.pbxproj | 68 --------- .../Sign/SignClientFactory.swift | 2 +- 14 files changed, 2 insertions(+), 518 deletions(-) delete mode 100644 Example/DApp/Modules/Auth/AuthInteractor.swift delete mode 100644 Example/DApp/Modules/Auth/AuthModule.swift delete mode 100644 Example/DApp/Modules/Auth/AuthPresenter.swift delete mode 100644 Example/DApp/Modules/Auth/AuthRouter.swift delete mode 100644 Example/DApp/Modules/Auth/AuthView.swift delete mode 100644 Example/DApp/Modules/Main/MainInteractor.swift delete mode 100644 Example/DApp/Modules/Main/MainModule.swift delete mode 100644 Example/DApp/Modules/Main/MainPresenter.swift delete mode 100644 Example/DApp/Modules/Main/MainRouter.swift delete mode 100644 Example/DApp/Modules/Main/MainViewController.swift delete mode 100644 Example/DApp/Modules/Main/Model/TabPage.swift diff --git a/Example/DApp/Modules/Auth/AuthInteractor.swift b/Example/DApp/Modules/Auth/AuthInteractor.swift deleted file mode 100644 index ffddc61dd..000000000 --- a/Example/DApp/Modules/Auth/AuthInteractor.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -final class AuthInteractor {} diff --git a/Example/DApp/Modules/Auth/AuthModule.swift b/Example/DApp/Modules/Auth/AuthModule.swift deleted file mode 100644 index 9252f89e3..000000000 --- a/Example/DApp/Modules/Auth/AuthModule.swift +++ /dev/null @@ -1,16 +0,0 @@ -import SwiftUI - -final class AuthModule { - @discardableResult - static func create(app: Application) -> UIViewController { - let router = AuthRouter(app: app) - let interactor = AuthInteractor() - let presenter = AuthPresenter(interactor: interactor, router: router) - let view = AuthView().environmentObject(presenter) - let viewController = SceneViewController(viewModel: presenter, content: view) - - router.viewController = viewController - - return viewController - } -} diff --git a/Example/DApp/Modules/Auth/AuthPresenter.swift b/Example/DApp/Modules/Auth/AuthPresenter.swift deleted file mode 100644 index 8e01e32e6..000000000 --- a/Example/DApp/Modules/Auth/AuthPresenter.swift +++ /dev/null @@ -1,116 +0,0 @@ -import UIKit -import Combine - -import Auth - -final class AuthPresenter: ObservableObject { - enum SigningState { - case none - case signed(Cacao) - case error(Error) - } - - private let interactor: AuthInteractor - private let router: AuthRouter - - @Published var qrCodeImageData: Data? - @Published var signingState = SigningState.none - @Published var showSigningState = false - - private var walletConnectUri: WalletConnectURI? - - private var subscriptions = Set() - - init( - interactor: AuthInteractor, - router: AuthRouter - ) { - defer { - Task { - await setupInitialState() - } - } - self.interactor = interactor - self.router = router - } - - func onAppear() { - generateQR() - } - - func copyUri() { - UIPasteboard.general.string = walletConnectUri?.absoluteString - } - - func connectWallet() { - if let walletConnectUri { - let walletUri = URL(string: "walletapp://wc?uri=\(walletConnectUri.deeplinkUri.removingPercentEncoding!)")! - DispatchQueue.main.async { - UIApplication.shared.open(walletUri) - } - } - } -} - -// MARK: - Private functions -extension AuthPresenter { - @MainActor - private func setupInitialState() { - Auth.instance.authResponsePublisher.sink { [weak self] (_, result) in - switch result { - case .success(let cacao): - self?.signingState = .signed(cacao) - self?.generateQR() - self?.showSigningState.toggle() - - case .failure(let error): - self?.signingState = .error(error) - self?.showSigningState.toggle() - } - } - .store(in: &subscriptions) - } - - private func generateQR() { - Task { @MainActor in - let uri = try! await Pair.instance.create() - walletConnectUri = uri - try await Auth.instance.request(.stub(), topic: uri.topic) - let qrCodeImage = QRCodeGenerator.generateQRCode(from: uri.absoluteString) - DispatchQueue.main.async { - self.qrCodeImageData = qrCodeImage.pngData() - } - } - } -} - -// MARK: - SceneViewModel -extension AuthPresenter: SceneViewModel {} - -// MARK: - Auth request stub -private extension RequestParams { - static func stub( - domain: String = "service.invalid", - chainId: String = "eip155:1", - nonce: String = "32891756", - aud: String = "https://service.invalid/login", - nbf: String? = nil, - exp: String? = nil, - statement: String? = "I accept the ServiceOrg Terms of Service: https://service.invalid/tos", - requestId: String? = nil, - resources: [String]? = ["ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/", "https://example.com/my-web2-claim.json"] - ) -> RequestParams { - return RequestParams( - domain: domain, - chainId: chainId, - nonce: nonce, - aud: aud, - nbf: nbf, - exp: exp, - statement: statement, - requestId: requestId, - resources: resources - ) - } -} - diff --git a/Example/DApp/Modules/Auth/AuthRouter.swift b/Example/DApp/Modules/Auth/AuthRouter.swift deleted file mode 100644 index 3caacfd38..000000000 --- a/Example/DApp/Modules/Auth/AuthRouter.swift +++ /dev/null @@ -1,16 +0,0 @@ -import UIKit - -final class AuthRouter { - weak var viewController: UIViewController! - - private let app: Application - - init(app: Application) { - self.app = app - } - - func dismiss() { - viewController.dismiss(animated: true) - UIApplication.shared.open(URL(string: "showcase://")!) - } -} diff --git a/Example/DApp/Modules/Auth/AuthView.swift b/Example/DApp/Modules/Auth/AuthView.swift deleted file mode 100644 index 8e15bacc0..000000000 --- a/Example/DApp/Modules/Auth/AuthView.swift +++ /dev/null @@ -1,140 +0,0 @@ -import SwiftUI - -struct AuthView: View { - @EnvironmentObject var presenter: AuthPresenter - - var body: some View { - NavigationStack { - ZStack { - Color(red: 25/255, green: 26/255, blue: 26/255) - .ignoresSafeArea() - - VStack { - ZStack { - RoundedRectangle(cornerRadius: 25) - .fill(.white) - .aspectRatio(1, contentMode: .fit) - .padding(20) - - if let data = presenter.qrCodeImageData { - let qrCodeImage = UIImage(data: data) ?? UIImage() - Image(uiImage: qrCodeImage) - .resizable() - .aspectRatio(1, contentMode: .fit) - .padding(40) - } - } - - Button { - presenter.connectWallet() - } label: { - Text("Connect Sample Wallet") - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(Color(red: 95/255, green: 159/255, blue: 248/255)) - .cornerRadius(16) - } - - Button { - presenter.copyUri() - } label: { - HStack { - Image("copy") - Text("Copy link") - .font(.system(size: 14, weight: .semibold)) - .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) - } - } - .padding(.top, 16) - - Spacer() - } - } - .navigationTitle("Auth") - .navigationBarTitleDisplayMode(.inline) - .toolbarColorScheme(.dark, for: .navigationBar) - .toolbarBackground(.visible, for: .navigationBar) - .toolbarBackground( - Color(red: 25/255, green: 26/255, blue: 26/255), - for: .navigationBar - ) - .onAppear { - presenter.onAppear() - } - .sheet(isPresented: $presenter.showSigningState) { - ZStack { - Color(red: 25/255, green: 26/255, blue: 26/255) - .ignoresSafeArea() - - VStack { - HStack { - RoundedRectangle(cornerRadius: 2) - .fill(.gray.opacity(0.5)) - .frame(width: 30, height: 4) - - } - .padding(20) - - Image("profile") - .resizable() - .frame(width: 64, height: 64) - - switch presenter.signingState { - case .error(let error): - Text(error.localizedDescription) - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(.green) - .cornerRadius(16) - .padding(.top, 16) - - case .signed(let cacao): - HStack { - Text(cacao.p.iss.split(separator: ":").last ?? "") - .lineLimit(1) - .truncationMode(.middle) - .frame(width: 135) - .font(.system(size: 24, weight: .semibold)) - .foregroundColor(Color(red: 0.89, green: 0.91, blue: 0.91)) - - Button { - UIPasteboard.general.string = String(cacao.p.iss.split(separator: ":").last ?? "") - } label: { - Image("copy") - .resizable() - .frame(width: 14, height: 14) - } - } - - Text("Authenticated") - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(.green) - .cornerRadius(16) - .padding(.top, 16) - - case .none: - EmptyView() - } - - Spacer() - } - } - .presentationDetents([.medium]) - } - } - } -} - -struct AuthView_Previews: PreviewProvider { - static var previews: some View { - AuthView() - } -} - diff --git a/Example/DApp/Modules/Main/MainInteractor.swift b/Example/DApp/Modules/Main/MainInteractor.swift deleted file mode 100644 index a3954796d..000000000 --- a/Example/DApp/Modules/Main/MainInteractor.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -final class MainInteractor {} diff --git a/Example/DApp/Modules/Main/MainModule.swift b/Example/DApp/Modules/Main/MainModule.swift deleted file mode 100644 index 67a9d6060..000000000 --- a/Example/DApp/Modules/Main/MainModule.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftUI - -final class MainModule { - @discardableResult - static func create(app: Application) -> UIViewController { - let router = MainRouter(app: app) - let interactor = MainInteractor() - let presenter = MainPresenter(router: router, interactor: interactor) - let viewController = MainViewController(presenter: presenter) - - router.viewController = viewController - - return viewController - } -} diff --git a/Example/DApp/Modules/Main/MainPresenter.swift b/Example/DApp/Modules/Main/MainPresenter.swift deleted file mode 100644 index 456b5b2a8..000000000 --- a/Example/DApp/Modules/Main/MainPresenter.swift +++ /dev/null @@ -1,39 +0,0 @@ -import UIKit -import WalletConnectSign -import Combine - -final class MainPresenter { - private let interactor: MainInteractor - private let router: MainRouter - private var disposeBag = Set() - - var tabs: [TabPage] { - return TabPage.allCases - } - - var viewControllers: [UIViewController] { - return [ - router.signViewController(), - router.authViewController() - ] - } - - init(router: MainRouter, interactor: MainInteractor) { - defer { - setupInitialState() - } - self.router = router - self.interactor = interactor - } - - private func setupInitialState() { - Sign.instance.sessionResponsePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] response in - Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } - router.presentResponse(response: response) - } - .store(in: &disposeBag) - } - -} diff --git a/Example/DApp/Modules/Main/MainRouter.swift b/Example/DApp/Modules/Main/MainRouter.swift deleted file mode 100644 index 2f255eada..000000000 --- a/Example/DApp/Modules/Main/MainRouter.swift +++ /dev/null @@ -1,25 +0,0 @@ -import UIKit -import WalletConnectSign - -final class MainRouter { - weak var viewController: UIViewController! - - private let app: Application - - init(app: Application) { - self.app = app - } - - func signViewController() -> UIViewController { - return SignModule.create(app: app) - } - - func authViewController() -> UIViewController { - return AuthModule.create(app: app) - } - - func presentResponse(response: Response) { - SessionResponseModule.create(app: app, sessionResponse: response) - .present(from: viewController) - } -} diff --git a/Example/DApp/Modules/Main/MainViewController.swift b/Example/DApp/Modules/Main/MainViewController.swift deleted file mode 100644 index 539b2a789..000000000 --- a/Example/DApp/Modules/Main/MainViewController.swift +++ /dev/null @@ -1,43 +0,0 @@ -import UIKit -import Sentry - -enum LoginError: Error { - case wrongUser(id: String) - case wrongPassword -} - -final class MainViewController: UITabBarController { - - private let presenter: MainPresenter - - init(presenter: MainPresenter) { - self.presenter = presenter - super.init(nibName: nil, bundle: nil) - } - - override func viewDidLoad() { - super.viewDidLoad() - setupTabs() - } - - private func setupTabs() { - let viewControllers = presenter.viewControllers - - for (index, viewController) in viewControllers.enumerated() { - let model = presenter.tabs[index] - let item = UITabBarItem() - item.title = model.title - item.image = model.icon - item.isEnabled = TabPage.enabledTabs.contains(model) - viewController.tabBarItem = item - viewController.view.backgroundColor = .w_background - } - - self.viewControllers = viewControllers - self.selectedIndex = TabPage.selectedIndex - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Modules/Main/Model/TabPage.swift b/Example/DApp/Modules/Main/Model/TabPage.swift deleted file mode 100644 index faec2ba34..000000000 --- a/Example/DApp/Modules/Main/Model/TabPage.swift +++ /dev/null @@ -1,32 +0,0 @@ -import UIKit - -enum TabPage: CaseIterable { - case sign - case auth - - var title: String { - switch self { - case .sign: - return "Sign" - case .auth: - return "Auth" - } - } - - var icon: UIImage { - switch self { - case .sign: - return UIImage(named: "pen")! - case .auth: - return UIImage(named: "auth")! - } - } - - static var selectedIndex: Int { - return 0 - } - - static var enabledTabs: [TabPage] { - return [.sign, .auth] - } -} diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index 952f40e68..c1e84694f 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -57,7 +57,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let viewController = MainModule.create(app: app) + let viewController = SignModule.create(app: app) window?.rootViewController = viewController window?.makeKeyAndVisible() diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 99e22abc3..2f12b415c 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -299,12 +299,7 @@ C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */ = {isa = PBXBuildFile; productRef = C5B2F7042970573D000DBA0E /* SolanaSwift */; }; C5B2F71029705827000DBA0E /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; }; C5B4C4C42AF11C8B00B4274A /* SignView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B4C4C32AF11C8B00B4274A /* SignView.swift */; }; - C5B4C4CF2AF12F1600B4274A /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B4C4CE2AF12F1600B4274A /* AuthView.swift */; }; C5BE01D12AF661D70064FC88 /* NewPairingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01D02AF661D70064FC88 /* NewPairingView.swift */; }; - C5BE01D72AF691CD0064FC88 /* AuthModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01D62AF691CD0064FC88 /* AuthModule.swift */; }; - C5BE01D92AF691FE0064FC88 /* AuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01D82AF691FE0064FC88 /* AuthPresenter.swift */; }; - C5BE01DB2AF692060064FC88 /* AuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01DA2AF692060064FC88 /* AuthRouter.swift */; }; - C5BE01DD2AF692100064FC88 /* AuthInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01DC2AF692100064FC88 /* AuthInteractor.swift */; }; C5BE01DF2AF692D80064FC88 /* WalletConnectRouter in Frameworks */ = {isa = PBXBuildFile; productRef = C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */; }; C5BE01E22AF693080064FC88 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01E12AF693080064FC88 /* Application.swift */; }; C5BE01E32AF696540064FC88 /* SceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE264293F56D6004840D1 /* SceneViewController.swift */; }; @@ -320,12 +315,6 @@ C5BE02022AF774CB0064FC88 /* NewPairingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01F62AF6CA2B0064FC88 /* NewPairingInteractor.swift */; }; C5BE02032AF774CB0064FC88 /* NewPairingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01F42AF6CA2B0064FC88 /* NewPairingPresenter.swift */; }; C5BE02042AF7764F0064FC88 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE26C293F56D6004840D1 /* UIViewController.swift */; }; - C5BE020E2AF777AD0064FC88 /* MainRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02082AF777AD0064FC88 /* MainRouter.swift */; }; - C5BE020F2AF777AD0064FC88 /* TabPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE020D2AF777AD0064FC88 /* TabPage.swift */; }; - C5BE02102AF777AD0064FC88 /* MainModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02072AF777AD0064FC88 /* MainModule.swift */; }; - C5BE02112AF777AD0064FC88 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE020A2AF777AD0064FC88 /* MainViewController.swift */; }; - C5BE02122AF777AD0064FC88 /* MainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE020B2AF777AD0064FC88 /* MainPresenter.swift */; }; - C5BE02132AF777AD0064FC88 /* MainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02092AF777AD0064FC88 /* MainInteractor.swift */; }; C5BE02142AF77A940064FC88 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5F32A352954FE3C00A6476E /* Colors.xcassets */; }; C5BE021B2AF79B9A0064FC88 /* SessionAccountPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02172AF79B950064FC88 /* SessionAccountPresenter.swift */; }; C5BE021C2AF79B9A0064FC88 /* SessionAccountRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02162AF79B950064FC88 /* SessionAccountRouter.swift */; }; @@ -666,12 +655,7 @@ C5B2F6F42970511B000DBA0E /* SessionRequestInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRequestInteractor.swift; sourceTree = ""; }; C5B2F6F52970511B000DBA0E /* SessionRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRequestView.swift; sourceTree = ""; }; C5B4C4C32AF11C8B00B4274A /* SignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignView.swift; sourceTree = ""; }; - C5B4C4CE2AF12F1600B4274A /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; C5BE01D02AF661D70064FC88 /* NewPairingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPairingView.swift; sourceTree = ""; }; - C5BE01D62AF691CD0064FC88 /* AuthModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthModule.swift; sourceTree = ""; }; - C5BE01D82AF691FE0064FC88 /* AuthPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthPresenter.swift; sourceTree = ""; }; - C5BE01DA2AF692060064FC88 /* AuthRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRouter.swift; sourceTree = ""; }; - C5BE01DC2AF692100064FC88 /* AuthInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInteractor.swift; sourceTree = ""; }; C5BE01E12AF693080064FC88 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; C5BE01ED2AF6C9DF0064FC88 /* SignPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignPresenter.swift; sourceTree = ""; }; C5BE01EE2AF6C9DF0064FC88 /* SignModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignModule.swift; sourceTree = ""; }; @@ -681,12 +665,6 @@ C5BE01F42AF6CA2B0064FC88 /* NewPairingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPairingPresenter.swift; sourceTree = ""; }; C5BE01F52AF6CA2B0064FC88 /* NewPairingModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPairingModule.swift; sourceTree = ""; }; C5BE01F62AF6CA2B0064FC88 /* NewPairingInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPairingInteractor.swift; sourceTree = ""; }; - C5BE02072AF777AD0064FC88 /* MainModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainModule.swift; sourceTree = ""; }; - C5BE02082AF777AD0064FC88 /* MainRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRouter.swift; sourceTree = ""; }; - C5BE02092AF777AD0064FC88 /* MainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainInteractor.swift; sourceTree = ""; }; - C5BE020A2AF777AD0064FC88 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - C5BE020B2AF777AD0064FC88 /* MainPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainPresenter.swift; sourceTree = ""; }; - C5BE020D2AF777AD0064FC88 /* TabPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPage.swift; sourceTree = ""; }; C5BE02162AF79B950064FC88 /* SessionAccountRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAccountRouter.swift; sourceTree = ""; }; C5BE02172AF79B950064FC88 /* SessionAccountPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAccountPresenter.swift; sourceTree = ""; }; C5BE02182AF79B950064FC88 /* SessionAccountInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAccountInteractor.swift; sourceTree = ""; }; @@ -1843,18 +1821,6 @@ path = Sign; sourceTree = ""; }; - C5B4C4CD2AF12F0B00B4274A /* Auth */ = { - isa = PBXGroup; - children = ( - C5BE01D62AF691CD0064FC88 /* AuthModule.swift */, - C5BE01D82AF691FE0064FC88 /* AuthPresenter.swift */, - C5BE01DA2AF692060064FC88 /* AuthRouter.swift */, - C5BE01DC2AF692100064FC88 /* AuthInteractor.swift */, - C5B4C4CE2AF12F1600B4274A /* AuthView.swift */, - ); - path = Auth; - sourceTree = ""; - }; C5BE01E02AF692F80064FC88 /* ApplicationLayer */ = { isa = PBXGroup; children = ( @@ -1875,27 +1841,6 @@ path = NewPairing; sourceTree = ""; }; - C5BE02062AF777AD0064FC88 /* Main */ = { - isa = PBXGroup; - children = ( - C5BE02072AF777AD0064FC88 /* MainModule.swift */, - C5BE020B2AF777AD0064FC88 /* MainPresenter.swift */, - C5BE02092AF777AD0064FC88 /* MainInteractor.swift */, - C5BE02082AF777AD0064FC88 /* MainRouter.swift */, - C5BE020A2AF777AD0064FC88 /* MainViewController.swift */, - C5BE020C2AF777AD0064FC88 /* Model */, - ); - path = Main; - sourceTree = ""; - }; - C5BE020C2AF777AD0064FC88 /* Model */ = { - isa = PBXGroup; - children = ( - C5BE020D2AF777AD0064FC88 /* TabPage.swift */, - ); - path = Model; - sourceTree = ""; - }; C5BE02152AF79B860064FC88 /* SessionAccount */ = { isa = PBXGroup; children = ( @@ -1911,9 +1856,7 @@ C5BE02202AF7DDE70064FC88 /* Modules */ = { isa = PBXGroup; children = ( - C5BE02062AF777AD0064FC88 /* Main */, C5B4C4C52AF12C2900B4274A /* Sign */, - C5B4C4CD2AF12F0B00B4274A /* Auth */, 8486EDC72B4F1D22008E53C3 /* SessionResponse */, ); path = Modules; @@ -2359,8 +2302,6 @@ buildActionMask = 2147483647; files = ( C5BE021C2AF79B9A0064FC88 /* SessionAccountRouter.swift in Sources */, - C5BE02102AF777AD0064FC88 /* MainModule.swift in Sources */, - C5BE01DD2AF692100064FC88 /* AuthInteractor.swift in Sources */, C5B4C4C42AF11C8B00B4274A /* SignView.swift in Sources */, C5BE02042AF7764F0064FC88 /* UIViewController.swift in Sources */, C5BE021E2AF79B9A0064FC88 /* SessionAccountView.swift in Sources */, @@ -2368,23 +2309,18 @@ C5BE02032AF774CB0064FC88 /* NewPairingPresenter.swift in Sources */, 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */, C5BE01E32AF696540064FC88 /* SceneViewController.swift in Sources */, - C5BE020F2AF777AD0064FC88 /* TabPage.swift in Sources */, A5A8E47D293A1CFE00FEB97D /* DefaultSocketFactory.swift in Sources */, C5BE01E52AF697470064FC88 /* Color.swift in Sources */, - C5B4C4CF2AF12F1600B4274A /* AuthView.swift in Sources */, A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */, A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */, - C5BE02122AF777AD0064FC88 /* MainPresenter.swift in Sources */, 8486EDC52B4F1D04008E53C3 /* SessionResponseModule.swift in Sources */, C5BE01E22AF693080064FC88 /* Application.swift in Sources */, C5BE021B2AF79B9A0064FC88 /* SessionAccountPresenter.swift in Sources */, A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */, - C5BE020E2AF777AD0064FC88 /* MainRouter.swift in Sources */, A5A0843D29D2F624000B9B17 /* DefaultCryptoProvider.swift in Sources */, C5BE021F2AF79B9A0064FC88 /* SessionAccountModule.swift in Sources */, 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */, C5BE01E42AF697100064FC88 /* UIColor.swift in Sources */, - C5BE01DB2AF692060064FC88 /* AuthRouter.swift in Sources */, C5BE01E62AF697FA0064FC88 /* String.swift in Sources */, C5BE02012AF774CB0064FC88 /* NewPairingModule.swift in Sources */, C5BE02002AF774CB0064FC88 /* NewPairingRouter.swift in Sources */, @@ -2393,17 +2329,13 @@ C5BE01D12AF661D70064FC88 /* NewPairingView.swift in Sources */, 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */, C5BE02022AF774CB0064FC88 /* NewPairingInteractor.swift in Sources */, - C5BE01D92AF691FE0064FC88 /* AuthPresenter.swift in Sources */, - C5BE02112AF777AD0064FC88 /* MainViewController.swift in Sources */, C5BE021D2AF79B9A0064FC88 /* SessionAccountInteractor.swift in Sources */, A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */, 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */, C5BE01FB2AF6CB270064FC88 /* SignRouter.swift in Sources */, - C5BE01D72AF691CD0064FC88 /* AuthModule.swift in Sources */, C5BE01F72AF6CB250064FC88 /* SignModule.swift in Sources */, 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */, C5BE01FA2AF6CB270064FC88 /* SignPresenter.swift in Sources */, - C5BE02132AF777AD0064FC88 /* MainInteractor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 7bb885cc7..62cacfc48 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -41,7 +41,7 @@ public struct SignClientFactory { let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.sessions.rawValue))) let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.proposals.rawValue) let verifyContextStore = CodableStore(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue) - let historyService = HistoryService(history: rpcHistory, proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore) + let historyService = HistoryService(history: rpcHistory, verifyContextStore: verifyContextStore) let verifyClient = VerifyClientFactory.create() let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) From cf10686021d00b15cef22dcaa9409a595dceb1e2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 09:06:43 +0100 Subject: [PATCH 044/169] walletapp - fix session proposal dismiss on expiry --- .../Wallet/SessionProposal/SessionProposalPresenter.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 99fe3bec7..c853b381d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -72,11 +72,9 @@ final class SessionProposalPresenter: ObservableObject { // MARK: - Private functions private extension SessionProposalPresenter { func setupInitialState() { - Web3Wallet.instance.pendingProposalsPublisher.sink { [weak self] proposals in + Web3Wallet.instance.sessionProposalExpirationPublisher.sink { [weak self] proposal in guard let self = self else { return } - if !proposals.contains(where: { (proposal: Session.Proposal, context: VerifyContext?) in - proposal.id == self.sessionProposal.id - }) { + if proposal.id == self.sessionProposal.id { dismiss() } }.store(in: &disposeBag) From 94cea98e24025b2b159c1297a7e8cbdd27a8086b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 10:10:00 +0100 Subject: [PATCH 045/169] fix, present request screen when proposal still active --- .../PresentationLayer/Wallet/Main/MainPresenter.swift | 1 + .../WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift | 4 ++++ .../Wallet/SessionProposal/SessionProposalRouter.swift | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 762bdf97c..3d5c8a0b6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -49,6 +49,7 @@ extension MainPresenter { interactor.sessionRequestPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] request, context in + router.dismiss() router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) }.store(in: &disposeBag) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index 211a1fc62..2f087957f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -41,4 +41,8 @@ final class MainRouter { AuthRequestModule.create(app: app, request: request, importAccount: importAccount, context: context) .presentFullScreen(from: viewController, transparentBackground: true) } + + func dismiss() { + viewController.dismiss() + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift index 559009c2e..f03cce6db 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift @@ -11,7 +11,7 @@ final class SessionProposalRouter { func dismiss() { DispatchQueue.main.async { [weak self] in - self?.viewController.dismiss() + self?.viewController?.dismiss() } } } From f3cc0740e4c6135c9bea1644fa653c7d9bd964da Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 12:25:11 +0100 Subject: [PATCH 046/169] revert session response small modal --- .../SessionResponseModule.swift | 17 ---- .../SessionResponsePresenter.swift | 17 ---- .../SessionResponseRouter.swift | 17 ---- .../SessionResponse/SessionResponseView.swift | 82 ------------------- .../SessionAccountPresenter.swift | 16 ++++ .../SessionAccount/SessionAccountView.swift | 24 +++--- Example/DApp/Modules/Sign/SignPresenter.swift | 8 ++ Example/DApp/Modules/Sign/SignRouter.swift | 8 +- Example/DApp/SceneDelegate.swift | 1 + Example/ExampleApp.xcodeproj/project.pbxproj | 24 ------ .../ConfigurationService.swift | 12 ++- .../SessionProposalPresenter.swift | 4 +- 12 files changed, 51 insertions(+), 179 deletions(-) delete mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseModule.swift delete mode 100644 Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift delete mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift delete mode 100644 Example/DApp/Modules/SessionResponse/SessionResponseView.swift diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift b/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift deleted file mode 100644 index 1030c26aa..000000000 --- a/Example/DApp/Modules/SessionResponse/SessionResponseModule.swift +++ /dev/null @@ -1,17 +0,0 @@ -import SwiftUI -import WalletConnectSign - -final class SessionResponseModule { - @discardableResult - static func create(app: Application, sessionResponse: Response) -> UIViewController { - let router = SessionResponseRouter(app: app) - let presenter = SessionResponsePresenter(router: router, sessionResponse: sessionResponse) - - let view = NewPairingView().environmentObject(presenter) - let viewController = UIHostingController(rootView: view) - - router.viewController = viewController - - return viewController - } -} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift b/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift deleted file mode 100644 index b26818599..000000000 --- a/Example/DApp/Modules/SessionResponse/SessionResponsePresenter.swift +++ /dev/null @@ -1,17 +0,0 @@ - -import Foundation -import WalletConnectSign - -final class SessionResponsePresenter: ObservableObject, SceneViewModel { - - private let router: SessionResponseRouter - let response: Response - - init( - router: SessionResponseRouter, - sessionResponse: Response - ) { - self.router = router - self.response = sessionResponse - } -} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift b/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift deleted file mode 100644 index 752a95116..000000000 --- a/Example/DApp/Modules/SessionResponse/SessionResponseRouter.swift +++ /dev/null @@ -1,17 +0,0 @@ -import UIKit - -import WalletConnectSign - -final class SessionResponseRouter { - weak var viewController: UIViewController! - - private let app: Application - - init(app: Application) { - self.app = app - } - - func dismiss() { - viewController.dismiss(animated: true) - } -} diff --git a/Example/DApp/Modules/SessionResponse/SessionResponseView.swift b/Example/DApp/Modules/SessionResponse/SessionResponseView.swift deleted file mode 100644 index 4160f47be..000000000 --- a/Example/DApp/Modules/SessionResponse/SessionResponseView.swift +++ /dev/null @@ -1,82 +0,0 @@ -import SwiftUI -import WalletConnectSign - -struct SessionResponseView: View { - - @EnvironmentObject var presenter: SessionResponsePresenter - - var body: some View { - ZStack { - Color(red: 25/255, green: 26/255, blue: 26/255) - .ignoresSafeArea() - - responseView(response: presenter.response) - } - } - - - private func responseView(response: Response) -> some View { - ZStack { - RoundedRectangle(cornerRadius: 16) - .fill(.white.opacity(0.02)) - - VStack(spacing: 5) { - HStack { - RoundedRectangle(cornerRadius: 2) - .fill(.gray.opacity(0.5)) - .frame(width: 30, height: 4) - - } - .padding(20) - - HStack { - Group { - if case RPCResult.error(_) = response.result { - Text("❌ Response") - } else { - Text("✅ Response") - } - } - .font( - Font.system(size: 14, weight: .medium) - ) - .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) - .padding(12) - - Spacer() - - let record = Sign.instance.getSessionRequestRecord(id: response.id)! - Text(record.request.method) - .font( - Font.system(size: 14, weight: .medium) - ) - .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) - .padding(12) - } - - ZStack { - RoundedRectangle(cornerRadius: 16) - .fill(.white.opacity(0.02)) - - switch response.result { - case .response(let response): - Text(try! response.get(String.self).description) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - .padding(.vertical, 12) - .padding(.horizontal, 8) - - case .error(let error): - Text(error.message) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - .padding(.vertical, 12) - .padding(.horizontal, 8) - } - } - .padding(.bottom, 12) - .padding(.horizontal, 8) - } - } - } -} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 1924a264f..3df1a674f 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -8,6 +8,7 @@ final class SessionAccountPresenter: ObservableObject { case notImplemented } + @Published var showResponse = false @Published var showError = false @Published var errorMessage = String.empty @Published var showRequestSent = false @@ -27,6 +28,7 @@ final class SessionAccountPresenter: ObservableObject { sessionAccount: AccountDetails, session: Session ) { + defer { setupInitialState() } self.interactor = interactor self.router = router self.sessionAccount = sessionAccount @@ -66,6 +68,15 @@ final class SessionAccountPresenter: ObservableObject { // MARK: - Private functions extension SessionAccountPresenter { + private func setupInitialState() { + Sign.instance.sessionResponsePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] response in + presentResponse(response: response) + } + .store(in: &subscriptions) + } + private func getRequest(for method: String) throws -> AnyCodable { let account = session.namespaces.first!.value.accounts.first!.absoluteString if method == "eth_sendTransaction" { @@ -79,6 +90,11 @@ extension SessionAccountPresenter { throw Errors.notImplemented } + private func presentResponse(response: Response) { + self.response = response + showResponse.toggle() + } + private func openWallet() { if let nativeUri = session.peer.redirect?.native { UIApplication.shared.open(URL(string: "\(nativeUri)wc?requestSent")!) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index a5c825924..1b8a3ebb8 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -31,18 +31,18 @@ struct SessionAccountView: View { Color(red: 25/255, green: 26/255, blue: 26/255), for: .navigationBar ) -// .sheet(isPresented: $presenter.showResponse) { -// ZStack { -// Color(red: 25/255, green: 26/255, blue: 26/255) -// .ignoresSafeArea() -// -// ScrollView { -// responseView(response: presenter.response!) -// .padding(12) -// } -// } -// .presentationDetents([.medium]) -// } + .sheet(isPresented: $presenter.showResponse) { + ZStack { + Color(red: 25/255, green: 26/255, blue: 26/255) + .ignoresSafeArea() + + ScrollView { + responseView(response: presenter.response!) + .padding(12) + } + } + .presentationDetents([.medium]) + } .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) {} } diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index d3a6f1911..a4ab56cce 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -109,6 +109,14 @@ extension SignPresenter { self.accountsDetails.removeAll() } .store(in: &subscriptions) + + Sign.instance.sessionResponsePublisher + .receive(on: DispatchQueue.main) + .sink { response in + Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + } + .store(in: &subscriptions) + } private func getSession() { diff --git a/Example/DApp/Modules/Sign/SignRouter.swift b/Example/DApp/Modules/Sign/SignRouter.swift index 60da1928c..5e0155007 100644 --- a/Example/DApp/Modules/Sign/SignRouter.swift +++ b/Example/DApp/Modules/Sign/SignRouter.swift @@ -20,13 +20,9 @@ final class SignRouter { func presentSessionAccount(sessionAccount: AccountDetails, session: Session) { SessionAccountModule.create(app: app, sessionAccount: sessionAccount, session: session) - .present(from: viewController) + .push(from: viewController) } - - func dismissNewPairing() { - newPairingViewController?.dismiss() - } - + func dismiss() { viewController.dismiss(animated: true) } diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index c1e84694f..489a1c2d0 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -58,6 +58,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) let viewController = SignModule.create(app: app) + .wrapToNavigationController() window?.rootViewController = viewController window?.makeKeyAndVisible() diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 2f12b415c..53b510607 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -32,10 +32,6 @@ 847BD1E8298A806800076C90 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E3298A806800076C90 /* NotificationsView.swift */; }; 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */; }; 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */; }; - 8486EDC52B4F1D04008E53C3 /* SessionResponseModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */; }; - 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */; }; - 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */; }; - 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */; }; 8486EDD12B4F2DC1008E53C3 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */; }; 8486EDD32B4F2EA6008E53C3 /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */; }; 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; }; @@ -425,10 +421,6 @@ 847BD1E3298A806800076C90 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsViewModel.swift; sourceTree = ""; }; 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPlatformW3WTests.swift; sourceTree = ""; }; - 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseModule.swift; sourceTree = ""; }; - 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponsePresenter.swift; sourceTree = ""; }; - 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseRouter.swift; sourceTree = ""; }; - 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionResponseView.swift; sourceTree = ""; }; 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = ""; }; 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = XPlatformProtocolTests.xctestplan; path = ../XPlatformProtocolTests.xctestplan; sourceTree = ""; }; 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; @@ -929,17 +921,6 @@ path = Web3Wallet; sourceTree = ""; }; - 8486EDC72B4F1D22008E53C3 /* SessionResponse */ = { - isa = PBXGroup; - children = ( - 8486EDC42B4F1D04008E53C3 /* SessionResponseModule.swift */, - 8486EDC82B4F1D73008E53C3 /* SessionResponsePresenter.swift */, - 8486EDCA2B4F1D7B008E53C3 /* SessionResponseRouter.swift */, - 8486EDCE2B4F1DA6008E53C3 /* SessionResponseView.swift */, - ); - path = SessionResponse; - sourceTree = ""; - }; 849D7A91292E2115006A2BD4 /* Push */ = { isa = PBXGroup; children = ( @@ -1857,7 +1838,6 @@ isa = PBXGroup; children = ( C5B4C4C52AF12C2900B4274A /* Sign */, - 8486EDC72B4F1D22008E53C3 /* SessionResponse */, ); path = Modules; sourceTree = ""; @@ -2313,13 +2293,11 @@ C5BE01E52AF697470064FC88 /* Color.swift in Sources */, A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */, A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */, - 8486EDC52B4F1D04008E53C3 /* SessionResponseModule.swift in Sources */, C5BE01E22AF693080064FC88 /* Application.swift in Sources */, C5BE021B2AF79B9A0064FC88 /* SessionAccountPresenter.swift in Sources */, A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */, A5A0843D29D2F624000B9B17 /* DefaultCryptoProvider.swift in Sources */, C5BE021F2AF79B9A0064FC88 /* SessionAccountModule.swift in Sources */, - 8486EDC92B4F1D73008E53C3 /* SessionResponsePresenter.swift in Sources */, C5BE01E42AF697100064FC88 /* UIColor.swift in Sources */, C5BE01E62AF697FA0064FC88 /* String.swift in Sources */, C5BE02012AF774CB0064FC88 /* NewPairingModule.swift in Sources */, @@ -2331,10 +2309,8 @@ C5BE02022AF774CB0064FC88 /* NewPairingInteractor.swift in Sources */, C5BE021D2AF79B9A0064FC88 /* SessionAccountInteractor.swift in Sources */, A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */, - 8486EDCB2B4F1D7B008E53C3 /* SessionResponseRouter.swift in Sources */, C5BE01FB2AF6CB270064FC88 /* SignRouter.swift in Sources */, C5BE01F72AF6CB250064FC88 /* SignModule.swift in Sources */, - 8486EDCF2B4F1DA6008E53C3 /* SessionResponseView.swift in Sources */, C5BE01FA2AF6CB270064FC88 /* SignPresenter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 85216a471..deded4c38 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -41,7 +41,9 @@ final class ConfigurationService { } LoggingService.instance.startLogging() - Web3Wallet.instance.socketConnectionStatusPublisher.sink { status in + Web3Wallet.instance.socketConnectionStatusPublisher + .receive(on: DispatchQueue.main) + .sink { status in switch status { case .connected: AlertPresenter.present(message: "Your web socket has connected", type: .success) @@ -50,7 +52,9 @@ final class ConfigurationService { } }.store(in: &publishers) - Web3Wallet.instance.logsPublisher.sink { log in + Web3Wallet.instance.logsPublisher + .receive(on: DispatchQueue.main) + .sink { log in switch log { case .error(let logMessage): AlertPresenter.present(message: logMessage.message, type: .error) @@ -58,7 +62,9 @@ final class ConfigurationService { } }.store(in: &publishers) - Web3Wallet.instance.pairingExpirationPublisher.sink { pairing in + Web3Wallet.instance.pairingExpirationPublisher + .receive(on: DispatchQueue.main) + .sink { pairing in guard !pairing.active else { return } AlertPresenter.present(message: "Pairing has expired", type: .warning) }.store(in: &publishers) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index c853b381d..5e40120f1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -72,7 +72,9 @@ final class SessionProposalPresenter: ObservableObject { // MARK: - Private functions private extension SessionProposalPresenter { func setupInitialState() { - Web3Wallet.instance.sessionProposalExpirationPublisher.sink { [weak self] proposal in + Web3Wallet.instance.sessionProposalExpirationPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] proposal in guard let self = self else { return } if proposal.id == self.sessionProposal.id { dismiss() From 0e6b23c59d429def47c7ee2376794925642ba6b3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 12:31:22 +0100 Subject: [PATCH 047/169] revert activate pairing logic --- Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 126ec07c9..2bb7b63f5 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -319,12 +319,6 @@ private extension ApproveEngine { // MARK: SessionProposeRequest func handleSessionProposeRequest(payload: RequestSubscriptionPayload) { - - if var pairing = pairingStore.getPairing(forTopic: payload.topic) { - pairing.activate() - pairingStore.setPairing(pairing) - } - logger.debug("Received Session Proposal") let proposal = payload.request do { try Namespace.validate(proposal.requiredNamespaces) } catch { From befdfab3d17a54505c654d705317d9f8fe971c61 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 13:12:03 +0100 Subject: [PATCH 048/169] change pairing state publisher logic --- .../WalletConnectPairing/Services/Common/PairingsProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift index 76d1417f8..0b5daa5bc 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift @@ -47,7 +47,7 @@ class PairingStateProvider { } private func checkPairingState() { - let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active } + let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active || $0.requestReceived} pairingStatePublisherSubject.send(pairingStateActive) } } From ae36fa61fa9180d5996217d405bfd12f2353a9bf Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 15:48:25 +0100 Subject: [PATCH 049/169] Add RequestsExpiryWatcher --- .../Common/PairingStateProvider.swift | 29 +++++++++++++ .../Services/Common/PairingsProvider.swift | 29 ------------- ...e.swift => PendingProposalsProvider.swift} | 0 .../Engine/Common/RequestsExpiryWatcher.swift | 41 +++++++++++++++++++ .../Services/HistoryService.swift | 22 ++++++---- 5 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift rename Sources/WalletConnectSign/Engine/Common/{File.swift => PendingProposalsProvider.swift} (100%) create mode 100644 Sources/WalletConnectSign/Engine/Common/RequestsExpiryWatcher.swift diff --git a/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift new file mode 100644 index 000000000..fa2291f80 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift @@ -0,0 +1,29 @@ + +import Combine +import Foundation + +class PairingStateProvider { + private let pairingStorage: WCPairingStorage + private var pairingStatePublisherSubject = PassthroughSubject() + private var checkTimer: Timer? + + public var pairingStatePublisher: AnyPublisher { + pairingStatePublisherSubject.eraseToAnyPublisher() + } + + public init(pairingStorage: WCPairingStorage) { + self.pairingStorage = pairingStorage + setupPairingStateCheckTimer() + } + + private func setupPairingStateCheckTimer() { + checkTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] _ in + checkPairingState() + } + } + + private func checkPairingState() { + let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active || $0.requestReceived} + pairingStatePublisherSubject.send(pairingStateActive) + } +} diff --git a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift index 0b5daa5bc..aa087a3c0 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift @@ -22,32 +22,3 @@ class PairingsProvider { return Pairing(pairing) } } - -import Combine -import Foundation - -class PairingStateProvider { - private let pairingStorage: WCPairingStorage - private var pairingStatePublisherSubject = PassthroughSubject() - private var checkTimer: Timer? - - public var pairingStatePublisher: AnyPublisher { - pairingStatePublisherSubject.eraseToAnyPublisher() - } - - public init(pairingStorage: WCPairingStorage) { - self.pairingStorage = pairingStorage - setupPairingStateCheckTimer() - } - - private func setupPairingStateCheckTimer() { - checkTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [unowned self] _ in - checkPairingState() - } - } - - private func checkPairingState() { - let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active || $0.requestReceived} - pairingStatePublisherSubject.send(pairingStateActive) - } -} diff --git a/Sources/WalletConnectSign/Engine/Common/File.swift b/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift similarity index 100% rename from Sources/WalletConnectSign/Engine/Common/File.swift rename to Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift diff --git a/Sources/WalletConnectSign/Engine/Common/RequestsExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/RequestsExpiryWatcher.swift new file mode 100644 index 000000000..a1a2726df --- /dev/null +++ b/Sources/WalletConnectSign/Engine/Common/RequestsExpiryWatcher.swift @@ -0,0 +1,41 @@ + +import Foundation +import Combine + +class RequestsExpiryWatcher { + + private let requestExpirationPublisherSubject: PassthroughSubject = .init() + private let rpcHistory: RPCHistory + private let historyService: HistoryService + + var requestExpirationPublisher: AnyPublisher { + return requestExpirationPublisherSubject.eraseToAnyPublisher() + } + + private var checkTimer: Timer? + + internal init( + proposalPayloadsStore: CodableStore>, + rpcHistory: RPCHistory, + historyService: HistoryService + ) { + self.rpcHistory = rpcHistory + self.historyService = historyService + setUpExpiryCheckTimer() + } + + func setUpExpiryCheckTimer() { + checkTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [unowned self] _ in + checkForRequestExpiry() + } + } + + func checkForRequestExpiry() { + let requests = historyService.getPendingRequestsWithRecordId() + requests.forEach { (request: Request, recordId: RPCID) in + guard request.isExpired() else { return } + requestExpirationPublisherSubject.send(request) + rpcHistory.delete(id: recordId) + } + } +} diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index a64144a71..95129d427 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -15,17 +15,23 @@ final class HistoryService { public func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? { guard let record = history.get(recordId: id) else { return nil } - guard let request = mapRequestRecord(record) else { + guard let (request, recordId) = mapRequestRecord(record) else { return nil } - return (request, try? verifyContextStore.get(key: request.id.string)) + return (request, try? verifyContextStore.get(key: recordId.string)) } - + func getPendingRequests() -> [(request: Request, context: VerifyContext?)] { let requests = history.getPending() .compactMap { mapRequestRecord($0) } - .filter { !$0.isExpired() } - return requests.map { ($0, try? verifyContextStore.get(key: $0.id.string)) } + .filter { !$0.0.isExpired() } // Note the change here to access the Request part of the tuple + return requests.map { (request: $0.0, context: try? verifyContextStore.get(key: $0.1.string)) } + } + + + func getPendingRequestsWithRecordId() -> [(request: Request, recordId: RPCID)] { + history.getPending() + .compactMap { mapRequestRecord($0) } } func getPendingRequests(topic: String) -> [(request: Request, context: VerifyContext?)] { @@ -34,11 +40,11 @@ final class HistoryService { } private extension HistoryService { - func mapRequestRecord(_ record: RPCHistory.Record) -> Request? { + func mapRequestRecord(_ record: RPCHistory.Record) -> (Request, RPCID)? { guard let request = try? record.request.params?.get(SessionType.RequestParams.self) else { return nil } - return Request( + let mappedRequest = Request( id: record.id, topic: record.topic, method: request.request.method, @@ -46,5 +52,7 @@ private extension HistoryService { chainId: request.chainId, expiry: request.request.expiry ) + + return (mappedRequest, record.id) } } From 497cff633f6a3f8af346db92ade7c5f73317826c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 15:55:21 +0100 Subject: [PATCH 050/169] expose request expiry publisher --- Sources/WalletConnectSign/Sign/SignClient.swift | 10 +++++++++- Sources/WalletConnectSign/Sign/SignClientFactory.swift | 4 +++- .../WalletConnectSign/Sign/SignClientProtocol.swift | 1 + Sources/Web3Wallet/Web3WalletClient.swift | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 730083d16..b1c16fcdf 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -108,6 +108,11 @@ public final class SignClient: SignClientProtocol { return pendingProposalsProvider.pendingProposalsPublisher } + public var requestExpirationPublisher: AnyPublisher { + return requestsExpiryWatcher.requestExpirationPublisher + } + + /// An object that loggs SDK's errors and info messages public let logger: ConsoleLogging @@ -130,6 +135,7 @@ public final class SignClient: SignClientProtocol { private let cleanupService: SignCleanupService private let proposalExpiryWatcher: ProposalExpiryWatcher private let pendingProposalsProvider: PendingProposalsProvider + private let requestsExpiryWatcher: RequestsExpiryWatcher private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>() private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() @@ -165,7 +171,8 @@ public final class SignClient: SignClientProtocol { cleanupService: SignCleanupService, pairingClient: PairingClient, proposalExpiryWatcher: ProposalExpiryWatcher, - pendingProposalsProvider: PendingProposalsProvider + pendingProposalsProvider: PendingProposalsProvider, + requestsExpiryWatcher: RequestsExpiryWatcher ) { self.logger = logger self.networkingClient = networkingClient @@ -185,6 +192,7 @@ public final class SignClient: SignClientProtocol { self.pairingClient = pairingClient self.proposalExpiryWatcher = proposalExpiryWatcher self.pendingProposalsProvider = pendingProposalsProvider + self.requestsExpiryWatcher = requestsExpiryWatcher setUpConnectionObserving() setUpEnginesCallbacks() diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 62cacfc48..3d63bcf39 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -72,6 +72,7 @@ public struct SignClientFactory { let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger) let proposalExpiryWatcher = ProposalExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, rpcHistory: rpcHistory) let pendingProposalsProvider = PendingProposalsProvider(proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore) + let requestsExpiryWatcher = RequestsExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, rpcHistory: rpcHistory, historyService: historyService) let client = SignClient( logger: logger, @@ -91,7 +92,8 @@ public struct SignClientFactory { cleanupService: cleanupService, pairingClient: pairingClient, proposalExpiryWatcher: proposalExpiryWatcher, - pendingProposalsProvider: pendingProposalsProvider + pendingProposalsProvider: pendingProposalsProvider, + requestsExpiryWatcher: requestsExpiryWatcher ) return client } diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 55afdccda..a58e35eb1 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -14,6 +14,7 @@ public protocol SignClientProtocol { var logsPublisher: AnyPublisher {get} var sessionProposalExpirationPublisher: AnyPublisher { get } var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { get } + var requestExpirationPublisher: AnyPublisher { get } func connect(requiredNamespaces: [String: ProposalNamespace], optionalNamespaces: [String: ProposalNamespace]?, sessionProperties: [String: String]?, topic: String) async throws func request(params: Request) async throws diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 78a515a14..4f26b76f0 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -90,6 +90,10 @@ public class Web3WalletClient { return signClient.pendingProposalsPublisher } + public var requestExpirationPublisher: AnyPublisher { + return signClient.requestExpirationPublisher + } + // MARK: - Private Properties private let authClient: AuthClientProtocol private let signClient: SignClientProtocol From bc881ea5f62ae6ae1914467836d982ed8ed91694 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 18:40:34 +0100 Subject: [PATCH 051/169] publish pairing state on change only --- .../Services/Common/PairingStateProvider.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift index fa2291f80..ff917e33d 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift @@ -1,4 +1,3 @@ - import Combine import Foundation @@ -6,6 +5,7 @@ class PairingStateProvider { private let pairingStorage: WCPairingStorage private var pairingStatePublisherSubject = PassthroughSubject() private var checkTimer: Timer? + private var lastPairingState: Bool? public var pairingStatePublisher: AnyPublisher { pairingStatePublisherSubject.eraseToAnyPublisher() @@ -23,7 +23,11 @@ class PairingStateProvider { } private func checkPairingState() { - let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active || $0.requestReceived} - pairingStatePublisherSubject.send(pairingStateActive) + let pairingStateActive = !pairingStorage.getAll().allSatisfy { $0.active || $0.requestReceived } + + if lastPairingState != pairingStateActive { + pairingStatePublisherSubject.send(pairingStateActive) + lastPairingState = pairingStateActive + } } } From dc3c972d348a56329605d8201e1a1a8a437402be Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 18:56:07 +0100 Subject: [PATCH 052/169] dismiss request screen on expiry --- .../SessionRequest/SessionRequestPresenter.swift | 11 ++++++++++- .../Wallet/SessionRequest/SessionRequestRouter.swift | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index b2df3650a..bb72f3be7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -79,7 +79,16 @@ final class SessionRequestPresenter: ObservableObject { // MARK: - Private functions private extension SessionRequestPresenter { - func setupInitialState() {} + func setupInitialState() { + Web3Wallet.instance.requestExpirationPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] request in + guard let self = self else { return } + if request.id == sessionRequest.id { + dismiss() + } + }.store(in: &disposeBag) + } } // MARK: - SceneViewModel diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestRouter.swift index cb7dff530..b4cbce9b5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestRouter.swift @@ -10,6 +10,8 @@ final class SessionRequestRouter { } func dismiss() { - viewController.dismiss() + DispatchQueue.main.async { [weak self] in + self?.viewController?.dismiss() + } } } From 3606015d919abdbb12b7bc95fa59b2d1af32c34e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 15 Jan 2024 19:00:46 +0100 Subject: [PATCH 053/169] add alert on request expiry --- .../WalletApp/ApplicationLayer/ConfigurationService.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index deded4c38..51d1f0c28 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -69,10 +69,14 @@ final class ConfigurationService { AlertPresenter.present(message: "Pairing has expired", type: .warning) }.store(in: &publishers) - Web3Wallet.instance.sessionProposalExpirationPublisher.sink { proposal in + Web3Wallet.instance.sessionProposalExpirationPublisher.sink { _ in AlertPresenter.present(message: "Session Proposal has expired", type: .warning) }.store(in: &publishers) + Web3Wallet.instance.requestExpirationPublisher.sink { _ in + AlertPresenter.present(message: "Session Request has expired", type: .warning) + }.store(in: &publishers) + Task { do { let params = try await Notify.instance.prepareRegistration(account: importAccount.account, domain: "com.walletconnect") From f325146f60c88dcb6a2d86c6b52bd54b15da9745 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 07:38:02 +0100 Subject: [PATCH 054/169] update Request and its tests --- .../Engine/Common/SessionEngine.swift | 3 +- Sources/WalletConnectSign/Request.swift | 68 ++++++++------- .../SessionEngineTests.swift | 3 - .../SessionRequestTests.swift | 85 +++++++++---------- 4 files changed, 78 insertions(+), 81 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index f2598b074..9053ff292 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -65,7 +65,8 @@ final class SessionEngine { } let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiry) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) - let protocolMethod = SessionRequestProtocolMethod(ttl: request.calculateTtl()) + let ttl = try request.calculateTtl() + let protocolMethod = SessionRequestProtocolMethod(ttl: ttl) let rpcRequest = RPCRequest(method: protocolMethod.method, params: sessionRequestParams, rpcid: request.id) try await networkingInteractor.request(rpcRequest, topic: request.topic, protocolMethod: SessionRequestProtocolMethod()) } diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 665369a1d..b971d8716 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -1,12 +1,39 @@ import Foundation public struct Request: Codable, Equatable { + public enum Errors: Error { + case invalidTtl + case requestExpired + } + public let id: RPCID public let topic: String public let method: String public let params: AnyCodable public let chainId: Blockchain - public let expiry: UInt64? + public var expiry: UInt64? + + // TTL bounds + static let minTtl: TimeInterval = 300 // 5 minutes + static let maxTtl: TimeInterval = 604800 // 7 days + + public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 300) throws { + guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { + throw Errors.invalidTtl + } + + let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl) + self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: calculatedExpiry) + } + + init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval) throws where C: Codable { + guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { + throw Errors.invalidTtl + } + + let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl) + self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiry: calculatedExpiry) + } internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64?) { self.id = id @@ -17,45 +44,22 @@ public struct Request: Codable, Equatable { self.expiry = expiry } - public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64? = nil) { - self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: expiry) - } - - init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, expiry: UInt64?) where C: Codable { - self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiry: expiry) - } - func isExpired(currentDate: Date = Date()) -> Bool { guard let expiry = expiry else { return false } - let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) - - guard - abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry - else { return true } - return expiryDate < currentDate } - func calculateTtl(currentDate: Date = Date()) -> Int { - guard let expiry = expiry else { return SessionRequestProtocolMethod.defaultTtl } - + func calculateTtl(currentDate: Date = Date()) throws -> Int { + guard let expiry = expiry else { return Int(Self.minTtl) } + let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) - let diff = expiryDate - currentDate.timeIntervalSince1970 - - guard - diff.timeIntervalSince1970 < Constants.maxExpiry, - diff.timeIntervalSince1970 > Constants.minExpiry - else { return SessionRequestProtocolMethod.defaultTtl } - - return Int(diff.timeIntervalSince1970) - } -} + let diff = expiryDate.timeIntervalSince(currentDate) -private extension Request { + guard diff > 0 else { + throw Errors.requestExpired + } - struct Constants { - static let minExpiry: TimeInterval = 300 // 5 minutes - static let maxExpiry: TimeInterval = 604800 // 7 days + return Int(diff) } } diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index 23f1e420b..c4c7a1112 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -7,14 +7,12 @@ final class SessionEngineTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! var sessionStorage: WCSessionStorageMock! - var proposalPayloadsStore: CodableStore>! var verifyContextStore: CodableStore! var engine: SessionEngine! override func setUp() { networkingInteractor = NetworkingInteractorMock() sessionStorage = WCSessionStorageMock() - proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "") verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") engine = SessionEngine( networkingInteractor: networkingInteractor, @@ -25,7 +23,6 @@ final class SessionEngineTests: XCTestCase { identifier: "" ) ), - proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore ), verifyContextStore: verifyContextStore, diff --git a/Tests/WalletConnectSignTests/SessionRequestTests.swift b/Tests/WalletConnectSignTests/SessionRequestTests.swift index c4b4a2fbf..10c70ea3a 100644 --- a/Tests/WalletConnectSignTests/SessionRequestTests.swift +++ b/Tests/WalletConnectSignTests/SessionRequestTests.swift @@ -1,71 +1,66 @@ import XCTest @testable import WalletConnectSign -final class SessionRequestTests: XCTestCase { +class RequestTests: XCTestCase { - func testRequestTtlDefault() { - let request = Request.stub() - - XCTAssertEqual(request.calculateTtl(), SessionRequestProtocolMethod.defaultTtl) + func testInitWithValidTtl() { + XCTAssertNoThrow(try Request.stub(ttl: 3600)) // 1 hour } - func testRequestTtlExtended() { - let currentDate = Date(timeIntervalSince1970: 0) - let expiry = currentDate.advanced(by: 500) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - - XCTAssertEqual(request.calculateTtl(currentDate: currentDate), 500) + func testInitWithInvalidTtlTooShort() { + XCTAssertThrowsError(try Request.stub(ttl: 100)) { error in // Less than minTtl + XCTAssertEqual(error as? Request.Errors, Request.Errors.invalidTtl) + } } - func testRequestTtlNotExtendedMinValidation() { - let currentDate = Date(timeIntervalSince1970: 0) - let expiry = currentDate.advanced(by: 200) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - - XCTAssertEqual(request.calculateTtl(currentDate: currentDate), SessionRequestProtocolMethod.defaultTtl) + func testInitWithInvalidTtlTooLong() { + XCTAssertThrowsError(try Request.stub(ttl: 700000)) { error in // More than maxTtl + XCTAssertEqual(error as? Request.Errors, Request.Errors.invalidTtl) + } } - func testRequestTtlNotExtendedMaxValidation() { - let currentDate = Date(timeIntervalSince1970: 0) - let expiry = currentDate.advanced(by: 700000) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - - XCTAssertEqual(request.calculateTtl(currentDate: currentDate), SessionRequestProtocolMethod.defaultTtl) - } - - func testIsExpiredDefault() { - let request = Request.stub() - + func testIsExpiredForNonExpiredRequest() { + let request = try! Request.stub(ttl: 3600) // 1 hour XCTAssertFalse(request.isExpired()) } - func testIsExpiredTrue() { - let currentDate = Date(timeIntervalSince1970: 500) - let expiry = Date(timeIntervalSince1970: 0) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - XCTAssertTrue(request.isExpired(currentDate: currentDate)) + func testIsExpiredForExpiredRequest() { + let pastTimestamp = UInt64(Date().timeIntervalSince1970) - 3600 // 1 hour ago + let request = Request.stubWithExpiry(expiry: pastTimestamp) + XCTAssertTrue(request.isExpired()) } - func testIsExpiredTrueMaxValidation() { - let currentDate = Date(timeIntervalSince1970: 500) - let expiry = Date(timeIntervalSince1970: 700000) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - XCTAssertTrue(request.isExpired(currentDate: currentDate)) + func testCalculateTtlForNonExpiredRequest() { + let request = try! Request.stub(ttl: 3600) // 1 hour + XCTAssertNoThrow(try request.calculateTtl()) } - func testIsExpiredFalse() { - let currentDate = Date(timeIntervalSince1970: 0) - let expiry = Date(timeIntervalSince1970: 500) - let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) - - XCTAssertFalse(request.isExpired(currentDate: currentDate)) + func testCalculateTtlForExpiredRequest() { + let pastTimestamp = UInt64(Date().timeIntervalSince1970) - 3600 // 1 hour ago + let request = Request.stubWithExpiry(expiry: pastTimestamp) + XCTAssertThrowsError(try request.calculateTtl()) { error in + XCTAssertEqual(error as? Request.Errors, Request.Errors.requestExpired) + } } } + + private extension Request { - static func stub(expiry: UInt64? = nil) -> Request { + static func stub(ttl: TimeInterval = 300) throws -> Request { + return try Request( + topic: "topic", + method: "method", + params: AnyCodable("params"), + chainId: Blockchain("eip155:1")!, + ttl: ttl + ) + } + + static func stubWithExpiry(expiry: UInt64) -> Request { return Request( + id: RPCID(JsonRpcID.generate()), topic: "topic", method: "method", params: AnyCodable("params"), From 9cbab482954571bb52ba740cece3574d265a3e93 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 10:29:01 +0100 Subject: [PATCH 055/169] remove the commented namespace test --- Sources/WalletConnectSign/Namespace.swift | 3 -- .../AutoNamespacesValidationTests.swift | 37 ------------------- 2 files changed, 40 deletions(-) diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index 6de73e1cc..eb3230523 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -148,9 +148,6 @@ enum SessionProperties { public enum AutoNamespaces { /// For a wallet to build session proposal structure by provided supported chains, methods, events & accounts. - /// - Parameters: - /// - proposalId: Session Proposal id - /// - namespaces: namespaces for given session, needs to contain at least required namespaces proposed by dApp. public static func build( sessionProposal: Session.Proposal, chains: [Blockchain], diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index db78b0433..1942c9d75 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -999,41 +999,4 @@ final class AutoNamespacesValidationTests: XCTestCase { ] XCTAssertEqual(sessionNamespaces, expectedNamespaces) } - -// func testSessionNamespacesContainsSupportedChainsAndMethodsWhenOptionalAndRequiredNamespacesAreEmpty() { -// let requiredNamespaces = [String: ProposalNamespace]() -// let optionalNamespaces = [String: ProposalNamespace]() -// let sessionProposal = Session.Proposal( -// id: "", -// pairingTopic: "", -// proposer: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)), -// requiredNamespaces: requiredNamespaces, -// optionalNamespaces: optionalNamespaces, -// sessionProperties: nil, -// proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil))), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) -// ) -// let sessionNamespaces = try! AutoNamespaces.build( -// sessionProposal: sessionProposal, -// chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], -// methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], -// events: ["chainChanged", "accountsChanged"], -// accounts: [] -// ) -// let expectedNamespaces: [String: SessionNamespace] = [ -// "eip155": SessionNamespace( -// chains: [Blockchain("eip155:1")!], -// accounts: Set([]), -// methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], -// events: ["chainChanged", "accountsChanged"] -// ), -// "solana": SessionNamespace( -// chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], -// accounts: Set([]), -// methods: ["solana_signMessage"], -// events: [] -// ) -// ] -// XCTAssertEqual(sessionNamespaces, expectedNamespaces) -// } - } From fd25d8acda61659653db5591ff9e6fa723c97764 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 10:45:47 +0100 Subject: [PATCH 056/169] Add check for empty session namespaces --- .../WalletConnectSign/Engine/Common/ApproveEngine.swift | 7 ++++++- Sources/WalletConnectSign/Namespace.swift | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 2bb7b63f5..d2448c800 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -10,6 +10,7 @@ final class ApproveEngine { case agreementMissingOrInvalid case networkNotConnected case proposalExpired + case emtySessionNamespacesForbidden } var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)? @@ -65,7 +66,9 @@ final class ApproveEngine { func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws { logger.debug("Approving session proposal") - + + guard !sessionNamespaces.isEmpty else { throw Errors.emtySessionNamespacesForbidden } + guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.proposalNotFound } @@ -441,6 +444,8 @@ extension ApproveEngine.Errors: LocalizedError { return "Network not connected." case .proposalExpired: return "Proposal expired." + case .emtySessionNamespacesForbidden: + return "Session Namespaces Cannot Be Empty" } } } diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index eb3230523..97f4a70a5 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -3,6 +3,7 @@ public enum AutoNamespacesError: Error { case requiredAccountsNotSatisfied case requiredMethodsNotSatisfied case requiredEventsNotSatisfied + case emtySessionNamespacesForbidden } public struct ProposalNamespace: Equatable, Codable { @@ -322,7 +323,8 @@ public enum AutoNamespaces { } } } - + guard !sessionNamespaces.isEmpty else { throw AutoNamespacesError.emtySessionNamespacesForbidden } + return sessionNamespaces } } From 00d1080e8aec54bcd9936697d731fcf956832773 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 10:52:53 +0100 Subject: [PATCH 057/169] add check for empty session namespaces --- .../AutoNamespacesValidationTests.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index 1942c9d75..8e2d5260c 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -999,4 +999,28 @@ final class AutoNamespacesValidationTests: XCTestCase { ] XCTAssertEqual(sessionNamespaces, expectedNamespaces) } + + func testBuildThrowsWhenSessionNamespacesAreEmpty() { + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)), + requiredNamespaces: [:], + optionalNamespaces: [:], + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil))), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + + XCTAssertThrowsError(try AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [], + methods: [], + events: [], + accounts: [] + ), "Expected to throw AutoNamespacesError.emtySessionNamespacesForbidden, but it did not") { error in + guard case AutoNamespacesError.emtySessionNamespacesForbidden = error else { + return XCTFail("Unexpected error type: \(error)") + } + } + } } From 3f50f729c43800ff6b9492c4aa28030d7385a4d7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 11:12:42 +0100 Subject: [PATCH 058/169] add localised description for namespaces error --- Sources/WalletConnectSign/Namespace.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index 97f4a70a5..08ded8326 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -1,9 +1,26 @@ -public enum AutoNamespacesError: Error { +import Foundation + +public enum AutoNamespacesError: Error, LocalizedError { case requiredChainsNotSatisfied case requiredAccountsNotSatisfied case requiredMethodsNotSatisfied case requiredEventsNotSatisfied case emtySessionNamespacesForbidden + + public var errorDescription: String? { + switch self { + case .requiredChainsNotSatisfied: + return "The required chains are not satisfied." + case .requiredAccountsNotSatisfied: + return "The required accounts are not satisfied." + case .requiredMethodsNotSatisfied: + return "The required methods are not satisfied." + case .requiredEventsNotSatisfied: + return "The required events are not satisfied." + case .emtySessionNamespacesForbidden: + return "Empty session namespaces are not allowed." + } + } } public struct ProposalNamespace: Equatable, Codable { From 94c2a029b55e3d7cb2fc6e1dd6585fbd8df6a012 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 14:58:01 +0100 Subject: [PATCH 059/169] savepoint --- .../Sign/SessionAccount/SessionAccountPresenter.swift | 4 ++-- Sources/WalletConnectSign/Request.swift | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 3df1a674f..a5d8db3f0 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -41,8 +41,8 @@ final class SessionAccountPresenter: ObservableObject { do { let requestParams = try getRequest(for: method) - let expiry = UInt64(Date().timeIntervalSince1970 + 300) - let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, expiry: expiry) + let ttl: TimeInterval = 300 + let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) Task { do { await ActivityIndicatorManager.shared.start() diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index b971d8716..796ae02f2 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -17,6 +17,13 @@ public struct Request: Codable, Equatable { static let minTtl: TimeInterval = 300 // 5 minutes static let maxTtl: TimeInterval = 604800 // 7 days + + /// - Parameters: + /// - topic: topic of a session + /// - method: request method + /// - params: request params + /// - chainId: chain id + /// - ttl: ttl of a request, will be used to calculate expiry, 5 minutes by default public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 300) throws { guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { throw Errors.invalidTtl From 69d4b4c8b352d8037a5b81da02e25c0c997c50ce Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 16:12:18 +0100 Subject: [PATCH 060/169] integrate loaders in a dapp --- .../Sign/SessionAccount/SessionAccountPresenter.swift | 2 +- Example/DApp/Modules/Sign/SignPresenter.swift | 9 +++++++++ Example/DApp/Modules/Sign/SignRouter.swift | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index a5d8db3f0..9614555f4 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -42,7 +42,7 @@ final class SessionAccountPresenter: ObservableObject { let requestParams = try getRequest(for: method) let ttl: TimeInterval = 300 - let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) + let request = try Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) Task { do { await ActivityIndicatorManager.shared.start() diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index a4ab56cce..978ee5694 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -107,6 +107,8 @@ extension SignPresenter { .receive(on: DispatchQueue.main) .sink { [unowned self] _ in self.accountsDetails.removeAll() + router.popToRoot() + Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } } .store(in: &subscriptions) @@ -117,6 +119,13 @@ extension SignPresenter { } .store(in: &subscriptions) + Sign.instance.requestExpirationPublisher + .receive(on: DispatchQueue.main) + .sink { _ in + Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + } + .store(in: &subscriptions) + } private func getSession() { diff --git a/Example/DApp/Modules/Sign/SignRouter.swift b/Example/DApp/Modules/Sign/SignRouter.swift index 5e0155007..a68ad5f9a 100644 --- a/Example/DApp/Modules/Sign/SignRouter.swift +++ b/Example/DApp/Modules/Sign/SignRouter.swift @@ -26,4 +26,8 @@ final class SignRouter { func dismiss() { viewController.dismiss(animated: true) } + + func popToRoot() { + viewController.popToRoot() + } } From 53b760cefb3fe25ff85fa8291957b3e3769910b6 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 16 Jan 2024 16:14:02 +0100 Subject: [PATCH 061/169] dapp - add alert on expired request --- Example/DApp/Modules/Sign/SignPresenter.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 978ee5694..eebd94013 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -123,6 +123,7 @@ extension SignPresenter { .receive(on: DispatchQueue.main) .sink { _ in Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + AlertPresenter.present(message: "Session Request has expired", type: .warning) } .store(in: &subscriptions) From 2d77e486eebd889c318c4d9786395979f40a074a Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 11:02:09 +0100 Subject: [PATCH 062/169] update min expiry to 10 minutes --- .../Sign/SessionAccount/SessionAccountPresenter.swift | 2 +- Sources/WalletConnectSign/Request.swift | 6 +++--- .../ProtocolMethods/SessionRequestProtocolMethod.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 9614555f4..62e9ae725 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -41,7 +41,7 @@ final class SessionAccountPresenter: ObservableObject { do { let requestParams = try getRequest(for: method) - let ttl: TimeInterval = 300 + let ttl: TimeInterval = 600 let request = try Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) Task { do { diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 796ae02f2..9a185141a 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -14,7 +14,7 @@ public struct Request: Codable, Equatable { public var expiry: UInt64? // TTL bounds - static let minTtl: TimeInterval = 300 // 5 minutes + static let minTtl: TimeInterval = 600 // 10 minutes static let maxTtl: TimeInterval = 604800 // 7 days @@ -23,8 +23,8 @@ public struct Request: Codable, Equatable { /// - method: request method /// - params: request params /// - chainId: chain id - /// - ttl: ttl of a request, will be used to calculate expiry, 5 minutes by default - public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 300) throws { + /// - ttl: ttl of a request, will be used to calculate expiry, 10 minutes by default + public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 600) throws { guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { throw Errors.invalidTtl } diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift index 28ee6d446..c6decfd9f 100644 --- a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift +++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift @@ -2,7 +2,7 @@ import Foundation struct SessionRequestProtocolMethod: ProtocolMethod { - static let defaultTtl: Int = 300 + static let defaultTtl: Int = 600 let method: String = "wc_sessionRequest" From 8f76ed75a05426df1a1f2a95dcb91acf94175cd9 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 11:46:35 +0100 Subject: [PATCH 063/169] add padding to sing view list --- Example/DApp/Modules/Sign/SignView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift index fc60a1754..54c9d62e7 100644 --- a/Example/DApp/Modules/Sign/SignView.swift +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -59,6 +59,7 @@ struct SignView: View { .padding(12) } } + .padding(.bottom, presenter.accountsDetails.isEmpty ? 0 : 76) .onAppear { presenter.onAppear() } From 5a6f237aa1ebb720bf6b0ed5be2a716b389ddc22 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 12:38:57 +0100 Subject: [PATCH 064/169] fix encoded uri parsing --- .../WalletConnectUtilsTests.xcscheme | 53 +++++++++++++++++++ .../WalletConnectUtils/WalletConnectURI.swift | 15 +++--- .../WalletConnectURITests.swift | 12 +++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme new file mode 100644 index 000000000..c78f495d4 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift index 2f068a67f..02d1af1aa 100644 --- a/Sources/WalletConnectUtils/WalletConnectURI.swift +++ b/Sources/WalletConnectUtils/WalletConnectURI.swift @@ -23,7 +23,8 @@ public struct WalletConnectURI: Equatable { } public init?(string: String) { - guard let components = Self.parseURIComponents(from: string) else { + let decodedString = string.removingPercentEncoding ?? string + guard let components = Self.parseURIComponents(from: decodedString) else { return nil } let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value } @@ -45,11 +46,8 @@ public struct WalletConnectURI: Equatable { } public init?(deeplinkUri: URL) { - if let deeplinkUri = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") { - self.init(string: deeplinkUri) - } else { - return nil - } + let uriString = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") ?? "" + self.init(string: uriString) } private var relayQuery: String { @@ -61,10 +59,11 @@ public struct WalletConnectURI: Equatable { } private static func parseURIComponents(from string: String) -> URLComponents? { - guard string.hasPrefix("wc:") else { + let decodedString = string.removingPercentEncoding ?? string + guard decodedString.hasPrefix("wc:") else { return nil } - let urlString = !string.hasPrefix("wc://") ? string.replacingOccurrences(of: "wc:", with: "wc://") : string + let urlString = !decodedString.hasPrefix("wc://") ? decodedString.replacingOccurrences(of: "wc:", with: "wc://") : decodedString return URLComponents(string: urlString) } } diff --git a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift index 5f65b5c28..6404e7b41 100644 --- a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift +++ b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift @@ -67,4 +67,16 @@ final class WalletConnectURITests: XCTestCase { let uri = WalletConnectURI(string: inputURIString) XCTAssertNil(uri) } + + func testInitHandlesURLEncodedString() { + let input = stubURI() + let encodedURIString = input.string + .addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "" + let uri = WalletConnectURI(string: encodedURIString) + + // Assert that the initializer can handle encoded URI and it matches the expected URI + XCTAssertEqual(input.uri, uri) + XCTAssertEqual(input.string, uri?.absoluteString) + } + } From a38716ca4c4147cfae92c543e9ca20ddd85322c3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 12:39:09 +0100 Subject: [PATCH 065/169] fix tests build --- Tests/Web3WalletTests/Mocks/SignClientMock.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index 75a788a55..4d74e4b47 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -4,7 +4,6 @@ import Combine @testable import WalletConnectSign final class SignClientMock: SignClientProtocol { - private var logsSubject = PassthroughSubject() var logsPublisher: AnyPublisher { @@ -23,7 +22,7 @@ final class SignClientMock: SignClientProtocol { var requestCalled = false private let metadata = AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)) - private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: "", chainId: Blockchain("eip155:1")!, expiry: nil) + private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: AnyCodable(""), chainId: Blockchain("eip155:1")!, expiry: nil) private let response = WalletConnectSign.Response(id: RPCID(1234567890123456789), topic: "", chainId: "", result: .response(AnyCodable(any: ""))) var sessionProposalPublisher: AnyPublisher<(proposal: WalletConnectSign.Session.Proposal, context: VerifyContext?), Never> { @@ -64,7 +63,17 @@ final class SignClientMock: SignClientProtocol { return Result.Publisher(("topic", ReasonMock())) .eraseToAnyPublisher() } - + + var pendingProposalsPublisher: AnyPublisher<[(proposal: WalletConnectSign.Session.Proposal, context: WalletConnectSign.VerifyContext?)], Never> { + return Result.Publisher([]) + .eraseToAnyPublisher() + } + + var requestExpirationPublisher: AnyPublisher { + Result.Publisher(request) + .eraseToAnyPublisher() + } + var sessionEventPublisher: AnyPublisher<(event: WalletConnectSign.Session.Event, sessionTopic: String, chainId: WalletConnectUtils.Blockchain?), Never> { return Result.Publisher( ( From 6482f1a49e100cfec26dd2b92b4fff492f0bb583 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 12:48:17 +0100 Subject: [PATCH 066/169] remove showcase garbage --- Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift | 1 - .../PresentationLayer/Wallet/AuthRequest/AuthRequestRouter.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift index 51fc5b2f2..89c0ea8a7 100644 --- a/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift @@ -11,6 +11,5 @@ final class NewPairingRouter { func dismiss() { viewController.dismiss(animated: true) - UIApplication.shared.open(URL(string: "showcase://")!) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestRouter.swift index 76e35da11..d21c62d49 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestRouter.swift @@ -11,6 +11,5 @@ final class AuthRequestRouter { func dismiss() { viewController.dismiss() - UIApplication.shared.open(URL(string: "showcase://")!) } } From 1a94a20d78ad0c9e2a53c2b8922cb0bc148093fe Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 17 Jan 2024 13:06:41 +0100 Subject: [PATCH 067/169] add loading view --- .../SessionAccountPresenter.swift | 9 ++++++- .../SessionAccount/SessionAccountView.swift | 25 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 62e9ae725..026ce969f 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -12,7 +12,9 @@ final class SessionAccountPresenter: ObservableObject { @Published var showError = false @Published var errorMessage = String.empty @Published var showRequestSent = false - + @Published var requesting = false + + private let interactor: SessionAccountInteractor private let router: SessionAccountRouter private let session: Session @@ -47,10 +49,14 @@ final class SessionAccountPresenter: ObservableObject { do { await ActivityIndicatorManager.shared.start() try await Sign.instance.request(params: request) + await ActivityIndicatorManager.shared.stop() + requesting = true DispatchQueue.main.async { [weak self] in self?.openWallet() } } catch { + await ActivityIndicatorManager.shared.stop() + requesting = false showError.toggle() errorMessage = error.localizedDescription } @@ -72,6 +78,7 @@ extension SessionAccountPresenter { Sign.instance.sessionResponsePublisher .receive(on: DispatchQueue.main) .sink { [unowned self] response in + requesting = false presentResponse(response: response) } .store(in: &subscriptions) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index 1b8a3ebb8..b4ee2a608 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -8,9 +8,11 @@ struct SessionAccountView: View { var body: some View { NavigationStack { ZStack { + Color(red: 25/255, green: 26/255, blue: 26/255) .ignoresSafeArea() - + + ScrollView { VStack(spacing: 12) { networkView(title: String(presenter.sessionAccount.chain.split(separator: ":").first ?? "")) @@ -21,6 +23,14 @@ struct SessionAccountView: View { } .padding(12) } + + if presenter.requesting { + loadingView + .frame(width: 200, height: 200) + .background(Color.gray.opacity(0.95)) + .cornerRadius(20) + .shadow(radius: 10) + } } .navigationTitle(presenter.sessionAccount.chain) .navigationBarTitleDisplayMode(.inline) @@ -179,7 +189,18 @@ struct SessionAccountView: View { } } } - + + private var loadingView: some View { + VStack { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .black)) + .scaleEffect(1.5) + Text("Request sent, waiting for response") + .foregroundColor(.white) + .padding(.top, 20) + } + } + private func responseView(response: Response) -> some View { ZStack { RoundedRectangle(cornerRadius: 16) From 3e5add4d37df3cd97fc4fe6e5d611304b99ce4e1 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 17 Jan 2024 19:06:16 +0300 Subject: [PATCH 068/169] publish with ack --- Sources/WalletConnectRelay/RelayClient.swift | 39 +++++++++++++++++--- Sources/WalletConnectRelay/RelayError.swift | 16 ++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 Sources/WalletConnectRelay/RelayError.swift diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 441f40314..24cae47c0 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -34,6 +34,11 @@ public final class RelayClient { subscriptionResponsePublisherSubject.eraseToAnyPublisher() } + private let requestAcknowledgePublisherSubject = PassthroughSubject() + private var requestAcknowledgePublisher: AnyPublisher { + requestAcknowledgePublisherSubject.eraseToAnyPublisher() + } + private let clientIdStorage: ClientIdStoring private var dispatcher: Dispatching @@ -86,13 +91,35 @@ public final class RelayClient { try dispatcher.disconnect(closeCode: closeCode) } - /// Completes when networking client sends a request, error if it fails on client side + /// Completes with an acknowledgement from the relay network public func publish(topic: String, payload: String, tag: Int, prompt: Bool, ttl: Int) async throws { - let request = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag)) - .asRPCRequest() + let request = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag)).asRPCRequest() let message = try request.asJSONEncodedString() - logger.debug("Publishing payload on topic: \(topic)") + + logger.debug("[Publish] Sending payload on topic: \(topic)") + try await dispatcher.protectedSend(message) + + return try await withUnsafeThrowingContinuation { continuation in + var cancellable: AnyCancellable? + cancellable = requestAcknowledgePublisher + .filter { $0 == request.id } + .setFailureType(to: RelayError.self) + .timeout(.seconds(10), scheduler: concurrentQueue, customError: { .requestTimeout }) + .sink(receiveCompletion: { [unowned self] result in + switch result { + case .failure(let error): + cancellable?.cancel() + logger.debug("[Publish] Relay request timeout for topic: \(topic)") + continuation.resume(throwing: error) + case .finished: break + } + }, receiveValue: { [unowned self] _ in + cancellable?.cancel() + logger.debug("[Publish] Published payload on topic: \(topic)") + continuation.resume(returning: ()) + }) + } } public func subscribe(topic: String) async throws { @@ -204,7 +231,9 @@ public final class RelayClient { } else if let response = tryDecode(RPCResponse.self, from: payload) { switch response.outcome { case .response(let anyCodable): - if let subscriptionId = try? anyCodable.get(String.self) { + if let _ = try? anyCodable.get(Bool.self) { + requestAcknowledgePublisherSubject.send(response.id) + } else if let subscriptionId = try? anyCodable.get(String.self) { subscriptionResponsePublisherSubject.send((response.id, [subscriptionId])) } else if let subscriptionIds = try? anyCodable.get([String].self) { subscriptionResponsePublisherSubject.send((response.id, subscriptionIds)) diff --git a/Sources/WalletConnectRelay/RelayError.swift b/Sources/WalletConnectRelay/RelayError.swift new file mode 100644 index 000000000..39d725d7c --- /dev/null +++ b/Sources/WalletConnectRelay/RelayError.swift @@ -0,0 +1,16 @@ +import Foundation + +enum RelayError: Error, LocalizedError { + case requestTimeout + + var errorDescription: String? { + return localizedDescription + } + + var localizedDescription: String { + switch self { + case .requestTimeout: + return "Relay request timeout" + } + } +} From feeca0cece77d23520c6d8935ec7294473dc63c7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 11:00:44 +0100 Subject: [PATCH 069/169] approve idempotency --- Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index d2448c800..6c5238dbb 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -88,9 +88,6 @@ final class ApproveEngine { let pairingTopic = payload.topic - proposalPayloadsStore.delete(forKey: proposerPubKey) - verifyContextStore.delete(forKey: proposerPubKey) - try Namespace.validate(sessionNamespaces) try Namespace.validateApproved(sessionNamespaces, against: proposal.requiredNamespaces) @@ -129,6 +126,9 @@ final class ApproveEngine { logger.debug("Session proposal response and settle request have been sent") + proposalPayloadsStore.delete(forKey: proposerPubKey) + verifyContextStore.delete(forKey: proposerPubKey) + pairingRegisterer.activate( pairingTopic: payload.topic, peerMetadata: payload.request.proposer.metadata From 6530fdcb0bba9399d7efb89b05414910465f3fd0 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 13:11:32 +0100 Subject: [PATCH 070/169] add more loaders --- Example/DApp/Modules/Sign/SignPresenter.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index eebd94013..9c3c9ecca 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -57,12 +57,18 @@ final class SignPresenter: ObservableObject { Task { let uri = try await Pair.instance.create() walletConnectUri = uri - try await Sign.instance.connect( - requiredNamespaces: Proposal.requiredNamespaces, - optionalNamespaces: Proposal.optionalNamespaces, - topic: uri.topic - ) - router.presentNewPairing(walletConnectUri: uri) + do { + await ActivityIndicatorManager.shared.start() + try await Sign.instance.connect( + requiredNamespaces: Proposal.requiredNamespaces, + optionalNamespaces: Proposal.optionalNamespaces, + topic: uri.topic + ) + await ActivityIndicatorManager.shared.stop() + router.presentNewPairing(walletConnectUri: uri) + } catch { + await ActivityIndicatorManager.shared.stop() + } } } From 330429ba6cbd9c32b3f7e3bdd01f8406ed2734ef Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 14:07:43 +0100 Subject: [PATCH 071/169] change expiry to expiryTimestamp --- Example/IntegrationTests/Sign/SignClientTests.swift | 4 ++-- .../Engine/Common/SessionEngine.swift | 2 +- Sources/WalletConnectSign/Request.swift | 10 +++++----- .../Types/Session/SessionProposal.swift | 8 ++++---- .../WalletConnectSign/Types/Session/WCSession.swift | 4 ++-- .../WalletConnectSignTests/SessionProposalTests.swift | 6 +++--- Tests/WalletConnectSignTests/Stub/Session+Stub.swift | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 4cecd7bb0..900289d42 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -197,7 +197,7 @@ final class SignClientTests: XCTestCase { }.store(in: &publishers) dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil) + let request = try! Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) try await dapp.request(params: request) } }.store(in: &publishers) @@ -244,7 +244,7 @@ final class SignClientTests: XCTestCase { }.store(in: &publishers) dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil) + let request = try! Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) try await dapp.request(params: request) } }.store(in: &publishers) diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 9053ff292..9d252423f 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -63,7 +63,7 @@ final class SessionEngine { guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else { throw WalletConnectError.invalidPermissions } - let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiry) + let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiryTimestamp) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) let ttl = try request.calculateTtl() let protocolMethod = SessionRequestProtocolMethod(ttl: ttl) diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 9a185141a..22d03eb81 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -11,7 +11,7 @@ public struct Request: Codable, Equatable { public let method: String public let params: AnyCodable public let chainId: Blockchain - public var expiry: UInt64? + public var expiryTimestamp: UInt64? // TTL bounds static let minTtl: TimeInterval = 600 // 10 minutes @@ -33,7 +33,7 @@ public struct Request: Codable, Equatable { self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: calculatedExpiry) } - init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval) throws where C: Codable { + init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval = 600) throws where C: Codable { guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { throw Errors.invalidTtl } @@ -48,17 +48,17 @@ public struct Request: Codable, Equatable { self.method = method self.params = params self.chainId = chainId - self.expiry = expiry + self.expiryTimestamp = expiry } func isExpired(currentDate: Date = Date()) -> Bool { - guard let expiry = expiry else { return false } + guard let expiry = expiryTimestamp else { return false } let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) return expiryDate < currentDate } func calculateTtl(currentDate: Date = Date()) throws -> Int { - guard let expiry = expiry else { return Int(Self.minTtl) } + guard let expiry = expiryTimestamp else { return Int(Self.minTtl) } let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) let diff = expiryDate.timeIntervalSince(currentDate) diff --git a/Sources/WalletConnectSign/Types/Session/SessionProposal.swift b/Sources/WalletConnectSign/Types/Session/SessionProposal.swift index 8834417a9..2723ef46c 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionProposal.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionProposal.swift @@ -7,9 +7,9 @@ struct SessionProposal: Codable, Equatable { let requiredNamespaces: [String: ProposalNamespace] let optionalNamespaces: [String: ProposalNamespace]? let sessionProperties: [String: String]? - let expiry: UInt64? + let expiryTimestamp: UInt64? - static let proposalExpiry: TimeInterval = 300 // 5 minutes + static let proposalTtl: TimeInterval = 300 // 5 minutes internal init(relays: [RelayProtocolOptions], proposer: Participant, @@ -21,7 +21,7 @@ struct SessionProposal: Codable, Equatable { self.requiredNamespaces = requiredNamespaces self.optionalNamespaces = optionalNamespaces self.sessionProperties = sessionProperties - self.expiry = UInt64(Date().timeIntervalSince1970 + Self.proposalExpiry) + self.expiryTimestamp = UInt64(Date().timeIntervalSince1970 + Self.proposalTtl) } func publicRepresentation(pairingTopic: String) -> Session.Proposal { @@ -37,7 +37,7 @@ struct SessionProposal: Codable, Equatable { } func isExpired(currentDate: Date = Date()) -> Bool { - guard let expiry = expiry else { return false } + guard let expiry = expiryTimestamp else { return false } let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift index 30d237a85..a24b13691 100644 --- a/Sources/WalletConnectSign/Types/Session/WCSession.swift +++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift @@ -65,7 +65,7 @@ struct WCSession: SequenceObject, Equatable { events: Set, accounts: Set, acknowledged: Bool, - expiry: Int64 + expiryTimestamp: Int64 ) { self.topic = topic self.pairingTopic = pairingTopic @@ -78,7 +78,7 @@ struct WCSession: SequenceObject, Equatable { self.sessionProperties = sessionProperties self.requiredNamespaces = requiredNamespaces self.acknowledged = acknowledged - self.expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) + self.expiryDate = Date(timeIntervalSince1970: TimeInterval(expiryTimestamp)) } #endif diff --git a/Tests/WalletConnectSignTests/SessionProposalTests.swift b/Tests/WalletConnectSignTests/SessionProposalTests.swift index d78180817..e490ba5be 100644 --- a/Tests/WalletConnectSignTests/SessionProposalTests.swift +++ b/Tests/WalletConnectSignTests/SessionProposalTests.swift @@ -10,13 +10,13 @@ class SessionProposalTests: XCTestCase { func testProposalExpired() { let proposal = SessionProposal.stub() - let expiredDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiry! + 1)) + let expiredDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiryTimestamp! + 1)) XCTAssertTrue(proposal.isExpired(currentDate: expiredDate), "Proposal should be expired after the expiry time.") } func testProposalNotExpiredJustBeforeExpiry() { let proposal = SessionProposal.stub() - let justBeforeExpiryDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiry! - 1)) + let justBeforeExpiryDate = Date(timeIntervalSince1970: TimeInterval(proposal.expiryTimestamp! - 1)) XCTAssertFalse(proposal.isExpired(currentDate: justBeforeExpiryDate), "Proposal should not be expired just before the expiry time.") } @@ -45,6 +45,6 @@ class SessionProposalTests: XCTestCase { // Assertions XCTAssertNotNil(proposal, "Proposal should be successfully decoded even without an expiry field.") - XCTAssertNil(proposal.expiry, "Expiry should be nil if not provided in JSON.") + XCTAssertNil(proposal.expiryTimestamp, "Expiry should be nil if not provided in JSON.") } } diff --git a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift index 37fcfdef9..67bc7045d 100644 --- a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift +++ b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift @@ -31,7 +31,7 @@ extension WCSession { events: [], accounts: Account.stubSet(), acknowledged: acknowledged, - expiry: Int64(expiryDate.timeIntervalSince1970)) + expiryTimestamp: Int64(expiryDate.timeIntervalSince1970)) } } From 808b114d54ab7cf8c1deb6ab528232b9ca07a75c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 14:20:03 +0100 Subject: [PATCH 072/169] fix testSessionReject --- Example/IntegrationTests/Sign/SignClientTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 900289d42..fd4100025 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -100,6 +100,7 @@ final class SignClientTests: XCTestCase { class Store { var rejectedProposal: Session.Proposal? } let store = Store() + let semaphore = DispatchSemaphore(value: 0) let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) @@ -110,10 +111,12 @@ final class SignClientTests: XCTestCase { do { try await wallet.reject(proposalId: proposal.id, reason: .userRejectedChains) // TODO: Review reason store.rejectedProposal = proposal + semaphore.signal() } catch { XCTFail("\(error)") } } }.store(in: &publishers) dapp.sessionRejectionPublisher.sink { proposal, _ in + semaphore.wait() XCTAssertEqual(store.rejectedProposal, proposal) sessionRejectExpectation.fulfill() // TODO: Assert reason code }.store(in: &publishers) From 37f80dedb0941e48364e6230cba9360564a76465 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 14:20:03 +0100 Subject: [PATCH 073/169] fix testSessionReject --- Example/IntegrationTests/Sign/SignClientTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 4cecd7bb0..28202a224 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -100,6 +100,7 @@ final class SignClientTests: XCTestCase { class Store { var rejectedProposal: Session.Proposal? } let store = Store() + let semaphore = DispatchSemaphore(value: 0) let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) @@ -110,10 +111,12 @@ final class SignClientTests: XCTestCase { do { try await wallet.reject(proposalId: proposal.id, reason: .userRejectedChains) // TODO: Review reason store.rejectedProposal = proposal + semaphore.signal() } catch { XCTFail("\(error)") } } }.store(in: &publishers) dapp.sessionRejectionPublisher.sink { proposal, _ in + semaphore.wait() XCTAssertEqual(store.rejectedProposal, proposal) sessionRejectExpectation.fulfill() // TODO: Assert reason code }.store(in: &publishers) From a93ba0b150e61249a1d13d171f4be8ffb436c599 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 14:49:02 +0100 Subject: [PATCH 074/169] change min ttl for request --- .../Sign/SessionAccount/SessionAccountPresenter.swift | 2 +- Sources/WalletConnectSign/Request.swift | 6 +++--- .../ProtocolMethods/SessionRequestProtocolMethod.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 026ce969f..9d3fecad8 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -43,7 +43,7 @@ final class SessionAccountPresenter: ObservableObject { do { let requestParams = try getRequest(for: method) - let ttl: TimeInterval = 600 + let ttl: TimeInterval = 300 let request = try Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) Task { do { diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 22d03eb81..f5404ec0b 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -14,7 +14,7 @@ public struct Request: Codable, Equatable { public var expiryTimestamp: UInt64? // TTL bounds - static let minTtl: TimeInterval = 600 // 10 minutes + static let minTtl: TimeInterval = 300 // 5 minutes static let maxTtl: TimeInterval = 604800 // 7 days @@ -24,7 +24,7 @@ public struct Request: Codable, Equatable { /// - params: request params /// - chainId: chain id /// - ttl: ttl of a request, will be used to calculate expiry, 10 minutes by default - public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 600) throws { + public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, ttl: TimeInterval = 300) throws { guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { throw Errors.invalidTtl } @@ -33,7 +33,7 @@ public struct Request: Codable, Equatable { self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: calculatedExpiry) } - init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval = 600) throws where C: Codable { + init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval = 300) throws where C: Codable { guard ttl >= Request.minTtl && ttl <= Request.maxTtl else { throw Errors.invalidTtl } diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift index c6decfd9f..28ee6d446 100644 --- a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift +++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift @@ -2,7 +2,7 @@ import Foundation struct SessionRequestProtocolMethod: ProtocolMethod { - static let defaultTtl: Int = 600 + static let defaultTtl: Int = 300 let method: String = "wc_sessionRequest" From 7680f68facc3ce37a0c508484f3a402849b2f9a1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 15:03:28 +0100 Subject: [PATCH 075/169] add message when pairing takes too long --- .../Wallet/Wallet/WalletPresenter.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index d88ced542..41bcbe2c5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -13,10 +13,15 @@ final class WalletPresenter: ObservableObject { private let importAccount: ImportAccount private let app: Application - + private var isPairingTimer: Timer? + @Published var sessions = [Session]() - @Published var showPairingLoading = false + @Published var showPairingLoading = false { + didSet { + handlePairingLoadingChanged() + } + } @Published var showError = false @Published var errorMessage = "Error" @Published var showConnectedSheet = false @@ -97,6 +102,16 @@ final class WalletPresenter: ObservableObject { } } } + + private func handlePairingLoadingChanged() { + isPairingTimer?.invalidate() + + if showPairingLoading { + isPairingTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { [weak self] _ in + AlertPresenter.present(message: "Pairing takes longer then expected, check your internet connection or try again", type: .warning) + } + } + } } // MARK: - Private functions From ff1d34a8a32a9535829c982b9be6d81615e52479 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 15:04:50 +0100 Subject: [PATCH 076/169] savepoint --- .../PresentationLayer/Wallet/Wallet/WalletPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 41bcbe2c5..1532b17b1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -107,7 +107,7 @@ final class WalletPresenter: ObservableObject { isPairingTimer?.invalidate() if showPairingLoading { - isPairingTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { [weak self] _ in + isPairingTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { _ in AlertPresenter.present(message: "Pairing takes longer then expected, check your internet connection or try again", type: .warning) } } From fc1a8a6ac6f5ef09bd2b72bfbacba163878d06e0 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 18 Jan 2024 19:41:45 +0300 Subject: [PATCH 077/169] Delete on resolve --- .../NetworkingInteractor.swift | 6 +++--- .../WalletConnectUtils/RPCHistory/RPCHistory.swift | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index f311cdc5b..b428a77f5 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -171,9 +171,10 @@ public class NetworkingInteractor: NetworkInteracting { } public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { - try rpcHistory.resolve(response) + try rpcHistory.validate(response) let message = try serializer.serialize(topic: topic, encodable: response, envelopeType: envelopeType) try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.responseConfig.tag, prompt: protocolMethod.responseConfig.prompt, ttl: protocolMethod.responseConfig.ttl) + try rpcHistory.resolve(response) } public func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { @@ -216,8 +217,7 @@ public class NetworkingInteractor: NetworkInteracting { private func handleResponse(topic: String, response: RPCResponse, publishedAt: Date, derivedTopic: String?) { do { - try rpcHistory.resolve(response) - let record = rpcHistory.get(recordId: response.id!)! + let record = try rpcHistory.resolve(response) responsePublisherSubject.send((topic, record.request, response, publishedAt, derivedTopic)) } catch { logger.debug("Handle json rpc response error: \(error)") diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 4fc00aebe..4cf815884 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -43,17 +43,22 @@ public final class RPCHistory { @discardableResult public func resolve(_ response: RPCResponse) throws -> Record { + let record = try validate(response) + storage.delete(forKey: "\(record.id)") + return record + } + + @discardableResult + public func validate(_ response: RPCResponse) throws -> Record { guard let id = response.id else { throw HistoryError.unidentifiedResponse } - guard var record = get(recordId: id) else { + guard let record = get(recordId: id) else { throw HistoryError.requestMatchingResponseNotFound } guard record.response == nil else { throw HistoryError.responseDuplicateNotAllowed } - record.response = response - storage.set(record, forKey: "\(record.id)") return record } From aaa925b0ce7c7fb4fbfb4c14e579747941f16856 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 18 Jan 2024 20:21:16 +0300 Subject: [PATCH 078/169] removeOutdated --- .../RPCHistory/RPCHistory.swift | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 4cf815884..5f02a10c8 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -1,3 +1,5 @@ +import Foundation + public final class RPCHistory { public struct Record: Codable { @@ -9,7 +11,8 @@ public final class RPCHistory { public let topic: String let origin: Origin public let request: RPCRequest - public var response: RPCResponse? + public let response: RPCResponse? + public var timestamp: Date? } enum HistoryError: Error { @@ -24,10 +27,13 @@ public final class RPCHistory { init(keyValueStore: CodableStore) { self.storage = keyValueStore + + removeOutdated() } public func get(recordId: RPCID) -> Record? { - try? storage.get(key: recordId.string) + // FOR TESTING!!! + try! storage.get(key: recordId.string) } public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws { @@ -37,7 +43,7 @@ public final class RPCHistory { guard get(recordId: id) == nil else { throw HistoryError.requestDuplicateNotAllowed } - let record = Record(id: id, topic: topic, origin: origin, request: request) + let record = Record(id: id, topic: topic, origin: origin, request: request, response: nil, timestamp: Date()) storage.set(record, forKey: "\(record.id)") } @@ -100,3 +106,23 @@ public final class RPCHistory { storage.getAll().filter { $0.response == nil } } } + +private extension RPCHistory { + + func removeOutdated() { + let records = storage.getAll() + + let thirtyDays: TimeInterval = 30*86400 + + for var record in records { + if let timestamp = record.timestamp { + if timestamp.distance(to: Date()) > thirtyDays { + storage.delete(forKey: record.id.string) + } + } else { + record.timestamp = Date() + storage.set(record, forKey: "\(record.id)") + } + } + } +} From a41e93c630691d5b65cc163012c95fc04b3db1f6 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 18 Jan 2024 20:21:49 +0300 Subject: [PATCH 079/169] Force unwrap removed --- Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 5f02a10c8..44dfdad68 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -32,8 +32,7 @@ public final class RPCHistory { } public func get(recordId: RPCID) -> Record? { - // FOR TESTING!!! - try! storage.get(key: recordId.string) + try? storage.get(key: recordId.string) } public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws { From 7d709770c034d19a160f4d828155bae5bc698910 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 11:52:01 +0300 Subject: [PATCH 080/169] RPCHistory tests resplved --- Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift | 2 +- Tests/WalletConnectUtilsTests/RPCHistoryTests.swift | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 44dfdad68..84f93768f 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -106,7 +106,7 @@ public final class RPCHistory { } } -private extension RPCHistory { +extension RPCHistory { func removeOutdated() { let records = storage.getAll() diff --git a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift index 37b05ccdd..82becd7b8 100644 --- a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift +++ b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift @@ -35,10 +35,8 @@ final class RPCHistoryTests: XCTestCase { try sut.set(requestB, forTopic: String.randomTopic(), emmitedBy: .local) try sut.resolve(responseA) try sut.resolve(responseB) - let recordA = sut.get(recordId: requestA.id!) - let recordB = sut.get(recordId: requestB.id!) - XCTAssertEqual(recordA?.response, responseA) - XCTAssertEqual(recordB?.response, responseB) + XCTAssertNil(sut.get(recordId: requestA.id!)) + XCTAssertNil(sut.get(recordId: requestB.id!)) } func testDelete() throws { @@ -95,7 +93,7 @@ final class RPCHistoryTests: XCTestCase { } func testResolveDuplicateResponse() throws { - let expectedError = RPCHistory.HistoryError.responseDuplicateNotAllowed + let expectedError = RPCHistory.HistoryError.requestMatchingResponseNotFound let request = RPCRequest.stub() let responseA = RPCResponse(matchingRequest: request, result: true) From 7195db91b827fbfc4548a121f9b3828dfae98e31 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 12:38:49 +0300 Subject: [PATCH 081/169] Remove outdated tests --- .../RPCHistory/RPCHistory.swift | 4 ++-- Sources/WalletConnectUtils/TimeProvider.swift | 12 ++++++++++ .../RPCHistoryTests.swift | 23 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 Sources/WalletConnectUtils/TimeProvider.swift diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 84f93768f..b310586d0 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -35,14 +35,14 @@ public final class RPCHistory { try? storage.get(key: recordId.string) } - public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws { + public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin, time: TimeProvider = DefaultTimeProvider()) throws { guard let id = request.id else { throw HistoryError.unidentifiedRequest } guard get(recordId: id) == nil else { throw HistoryError.requestDuplicateNotAllowed } - let record = Record(id: id, topic: topic, origin: origin, request: request, response: nil, timestamp: Date()) + let record = Record(id: id, topic: topic, origin: origin, request: request, response: nil, timestamp: time.currentDate) storage.set(record, forKey: "\(record.id)") } diff --git a/Sources/WalletConnectUtils/TimeProvider.swift b/Sources/WalletConnectUtils/TimeProvider.swift new file mode 100644 index 000000000..86732b6f0 --- /dev/null +++ b/Sources/WalletConnectUtils/TimeProvider.swift @@ -0,0 +1,12 @@ +import Foundation + +public protocol TimeProvider { + var currentDate: Date { get } +} + +public struct DefaultTimeProvider: TimeProvider { + public init() {} + public var currentDate: Date { + return Date() + } +} diff --git a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift index 82becd7b8..b4023eaff 100644 --- a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift +++ b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift @@ -105,4 +105,27 @@ final class RPCHistoryTests: XCTestCase { XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError) } } + + func testRemoveOutdated() throws { + let request1 = RPCRequest.stub() + let request2 = RPCRequest.stub() + + let time1 = TestTimeProvider(currentDate: .distantPast) + let time2 = TestTimeProvider(currentDate: Date()) + + try sut.set(request1, forTopic: .randomTopic(), emmitedBy: .local, time: time1) + try sut.set(request2, forTopic: .randomTopic(), emmitedBy: .local, time: time2) + + XCTAssertEqual(sut.get(recordId: request1.id!)?.request, request1) + XCTAssertEqual(sut.get(recordId: request2.id!)?.request, request2) + + sut.removeOutdated() + + XCTAssertEqual(sut.get(recordId: request1.id!)?.request, nil) + XCTAssertEqual(sut.get(recordId: request2.id!)?.request, request2) + } + + struct TestTimeProvider: TimeProvider { + var currentDate: Date + } } From d08497513af4a702cb577f3c2f235a54754b5ce1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 Jan 2024 12:16:38 +0100 Subject: [PATCH 082/169] refactor sign tests --- .../Sign/SignClientTests.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index fd4100025..5789ed95c 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -91,7 +91,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) } func testSessionReject() async throws { @@ -120,7 +120,7 @@ final class SignClientTests: XCTestCase { XCTAssertEqual(store.rejectedProposal, proposal) sessionRejectExpectation.fulfill() // TODO: Assert reason code }.store(in: &publishers) - wait(for: [sessionRejectExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [sessionRejectExpectation], timeout: InputConfig.defaultTimeout) } func testSessionDelete() async throws { @@ -145,7 +145,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [sessionDeleteExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [sessionDeleteExpectation], timeout: InputConfig.defaultTimeout) } func testSessionPing() async throws { @@ -176,7 +176,7 @@ final class SignClientTests: XCTestCase { try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testSessionRequest() async throws { @@ -226,7 +226,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [requestExpectation, responseExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [requestExpectation, responseExpectation], timeout: InputConfig.defaultTimeout) } func testSessionRequestFailureResponse() async throws { @@ -269,7 +269,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testNewSessionOnExistingPairing() async throws { @@ -307,7 +307,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) } func testSuccessfulSessionUpdateNamespaces() async throws { @@ -331,7 +331,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testSuccessfulSessionExtend() async throws { @@ -360,7 +360,7 @@ final class SignClientTests: XCTestCase { try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testSessionEventSucceeds() async throws { @@ -391,7 +391,7 @@ final class SignClientTests: XCTestCase { try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testSessionEventFails() async throws { @@ -419,7 +419,7 @@ final class SignClientTests: XCTestCase { try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) } func testCaip25SatisfyAllRequiredAllOptionalNamespacesSuccessful() async throws { @@ -497,7 +497,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) } func testCaip25SatisfyAllRequiredNamespacesSuccessful() async throws { @@ -566,7 +566,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) } func testCaip25SatisfyEmptyRequiredNamespacesExtraOptionalNamespacesSuccessful() async throws { @@ -625,7 +625,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout) } func testCaip25SatisfyPartiallyRequiredNamespacesFails() async throws { @@ -688,7 +688,7 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [settlementFailedExpectation], timeout: 1) + await fulfillment(of: [settlementFailedExpectation], timeout: InputConfig.defaultTimeout) } func testCaip25SatisfyPartiallyRequiredNamespacesMethodsFails() async throws { @@ -754,6 +754,6 @@ final class SignClientTests: XCTestCase { let uri = try! await dappPairingClient.create() try await dapp.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: uri.topic) try await walletPairingClient.pair(uri: uri) - wait(for: [settlementFailedExpectation], timeout: 1) + await fulfillment(of: [settlementFailedExpectation], timeout: 1) } } From 07ada933fbbb6589a5181d802c453e2c66f347a3 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 9 Jan 2024 16:40:20 +0300 Subject: [PATCH 083/169] History package cleanup --- Example/IntegrationTests/Chat/ChatTests.swift | 10 +-- .../History/HistoryTests.swift | 73 ------------------- Package.swift | 4 +- Sources/Chat/Chat.swift | 3 +- Sources/Chat/ChatClientFactory.swift | 11 +-- .../History/HistoryService.swift | 27 +------ Sources/WalletConnectHistory/HistoryAPI.swift | 56 -------------- .../WalletConnectHistory/HistoryClient.swift | 42 +---------- .../HistoryClientFactory.swift | 24 +----- .../WalletConnectHistory/HistoryImports.swift | 3 +- .../HistoryNetworkService.swift | 41 ----------- .../WalletConnectHistory/HistoryRecord.swift | 11 --- .../Types/GetMessagesPayload.swift | 19 ----- .../Types/GetMessagesResponse.swift | 28 ------- .../Types/RegisterPayload.swift | 11 --- 15 files changed, 14 insertions(+), 349 deletions(-) delete mode 100644 Sources/WalletConnectHistory/HistoryAPI.swift delete mode 100644 Sources/WalletConnectHistory/HistoryNetworkService.swift delete mode 100644 Sources/WalletConnectHistory/HistoryRecord.swift delete mode 100644 Sources/WalletConnectHistory/Types/GetMessagesPayload.swift delete mode 100644 Sources/WalletConnectHistory/Types/GetMessagesResponse.swift delete mode 100644 Sources/WalletConnectHistory/Types/RegisterPayload.swift diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 04cdc7e68..0b4528fe4 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -71,18 +71,10 @@ final class ChatTests: XCTestCase { keychain: keychain ) - let historyClient = HistoryClientFactory.create( - historyUrl: "https://history.walletconnect.com", - relayUrl: "wss://relay.walletconnect.com", - keyValueStorage: keyValueStorage, - keychain: keychain, - logger: logger - ) - let clientId = try! networkingInteractor.getClientId() logger.debug("My client id is: \(clientId)") - return ChatClientFactory.create(keyserverURL: keyserverURL, relayClient: relayClient, networkingInteractor: networkingInteractor, keychain: keychain, logger: logger, storage: keyValueStorage, syncClient: syncClient, historyClient: historyClient) + return ChatClientFactory.create(keyserverURL: keyserverURL, relayClient: relayClient, networkingInteractor: networkingInteractor, keychain: keychain, logger: logger, storage: keyValueStorage, syncClient: syncClient) } func testInvite() async throws { diff --git a/Example/IntegrationTests/History/HistoryTests.swift b/Example/IntegrationTests/History/HistoryTests.swift index d96e0f811..4ebbdd80d 100644 --- a/Example/IntegrationTests/History/HistoryTests.swift +++ b/Example/IntegrationTests/History/HistoryTests.swift @@ -5,77 +5,4 @@ import XCTest final class HistoryTests: XCTestCase { - var publishers = Set() - - let relayUrl = "wss://relay.walletconnect.com" - let historyUrl = "https://history.walletconnect.com" - - var relayClient1: RelayClient! - var relayClient2: RelayClient! - - var historyClient: HistoryNetworkService! - - override func setUp() { - let keychain1 = KeychainStorageMock() - let keychain2 = KeychainStorageMock() - let logger1 = ConsoleLoggerMock() - let defaults1 = RuntimeKeyValueStorage() - relayClient1 = makeRelayClient(prefix: "🐄", keychain: keychain1) - relayClient2 = makeRelayClient(prefix: "🐫", keychain: keychain2) - historyClient = makeHistoryClient(defaults: defaults1, keychain: keychain1, logger: logger1) - } - - private func makeRelayClient(prefix: String, keychain: KeychainStorageProtocol) -> RelayClient { - return RelayClientFactory.create( - relayHost: InputConfig.relayHost, - projectId: InputConfig.projectId, - keyValueStorage: RuntimeKeyValueStorage(), - keychainStorage: keychain, - socketFactory: DefaultSocketFactory(), - logger: ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug)) - } - - private func makeHistoryClient(defaults: KeyValueStorage, keychain: KeychainStorageProtocol, logger: ConsoleLogging) -> HistoryNetworkService { - let clientIdStorage = ClientIdStorage(defaults: defaults, keychain: keychain, logger: logger) - return HistoryNetworkService(clientIdStorage: clientIdStorage) - } - - func testRegister() async throws { - let payload = RegisterPayload(tags: ["7000"], relayUrl: relayUrl) - - try await historyClient.registerTags(payload: payload, historyUrl: historyUrl) - } - - func testGetMessages() async throws { - let exp = expectation(description: "Test Get Messages") - let tag = 7000 - let payload = "{}" - let agreement = AgreementPrivateKey() - let topic = agreement.publicKey.rawRepresentation.sha256().hex - - relayClient2.messagePublisher.sink { (topic, message, publishedAt) in - exp.fulfill() - }.store(in: &publishers) - - try await historyClient.registerTags( - payload: RegisterPayload(tags: [String(tag)], relayUrl: relayUrl), - historyUrl: historyUrl) - - try await relayClient2.subscribe(topic: topic) - try await relayClient1.publish(topic: topic, payload: payload, tag: tag, prompt: false, ttl: 3000) - - wait(for: [exp], timeout: InputConfig.defaultTimeout) - - sleep(5) // History server has a queue - - let messages = try await historyClient.getMessages( - payload: GetMessagesPayload( - topic: topic, - originId: nil, - messageCount: 200, - direction: .forward), - historyUrl: historyUrl) - - XCTAssertEqual(messages.messages, [payload]) - } } diff --git a/Package.swift b/Package.swift index dcfa42b87..c53f5ee38 100644 --- a/Package.swift +++ b/Package.swift @@ -62,7 +62,7 @@ let package = Package( path: "Sources/WalletConnectSign"), .target( name: "WalletConnectChat", - dependencies: ["WalletConnectIdentity", "WalletConnectSync", "WalletConnectHistory"], + dependencies: ["WalletConnectIdentity", "WalletConnectSync"], path: "Sources/Chat"), .target( name: "Auth", @@ -94,7 +94,7 @@ let package = Package( dependencies: ["WalletConnectNetworking"]), .target( name: "WalletConnectHistory", - dependencies: ["HTTPClient", "WalletConnectRelay"]), + dependencies: []), .target( name: "WalletConnectSigner", dependencies: ["WalletConnectNetworking"]), diff --git a/Sources/Chat/Chat.swift b/Sources/Chat/Chat.swift index 2bb3ac56b..003b64335 100644 --- a/Sources/Chat/Chat.swift +++ b/Sources/Chat/Chat.swift @@ -12,8 +12,7 @@ public class Chat { keyserverUrl: keyserverUrl, relayClient: Relay.instance, networkingInteractor: Networking.interactor, - syncClient: Sync.instance, - historyClient: History.instance + syncClient: Sync.instance ) }() diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift index ebce9f3c3..52bd97024 100644 --- a/Sources/Chat/ChatClientFactory.swift +++ b/Sources/Chat/ChatClientFactory.swift @@ -2,7 +2,7 @@ import Foundation public struct ChatClientFactory { - static func create(keyserverUrl: String, relayClient: RelayClient, networkingInteractor: NetworkingInteractor, syncClient: SyncClient, historyClient: HistoryClient) -> ChatClient { + static func create(keyserverUrl: String, relayClient: RelayClient, networkingInteractor: NetworkingInteractor, syncClient: SyncClient) -> ChatClient { fatalError("fix access group") let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: "") let keyserverURL = URL(string: keyserverUrl)! @@ -13,8 +13,7 @@ public struct ChatClientFactory { keychain: keychain, logger: ConsoleLogger(loggingLevel: .debug), storage: UserDefaults.standard, - syncClient: syncClient, - historyClient: historyClient + syncClient: syncClient ) } @@ -25,12 +24,10 @@ public struct ChatClientFactory { keychain: KeychainStorageProtocol, logger: ConsoleLogging, storage: KeyValueStorage, - syncClient: SyncClient, - historyClient: HistoryClient + syncClient: SyncClient ) -> ChatClient { let kms = KeyManagementService(keychain: keychain) - let serializer = Serializer(kms: kms, logger: logger) - let historyService = HistoryService(historyClient: historyClient, seiralizer: serializer) + let historyService = HistoryService() let messageStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.messages.rawValue) let receivedInviteStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.receivedInvites.rawValue) let threadStore: SyncStore = SyncStoreFactory.create(name: ChatStorageIdentifiers.thread.rawValue, syncClient: syncClient, storage: storage) diff --git a/Sources/Chat/ProtocolServices/History/HistoryService.swift b/Sources/Chat/ProtocolServices/History/HistoryService.swift index ebad3a50d..13f014333 100644 --- a/Sources/Chat/ProtocolServices/History/HistoryService.swift +++ b/Sources/Chat/ProtocolServices/History/HistoryService.swift @@ -2,36 +2,15 @@ import Foundation final class HistoryService { - private let historyClient: HistoryClient - private let seiralizer: Serializing + init() { - init(historyClient: HistoryClient, seiralizer: Serializing) { - self.historyClient = historyClient - self.seiralizer = seiralizer } func register() async throws { - try await historyClient.register(tags: ["2002"]) + fatalError() } func fetchMessageHistory(thread: Thread) async throws -> [Message] { - let wrappers: [MessagePayload.Wrapper] = try await historyClient.getMessages( - topic: thread.topic, - count: 200, direction: .backward - ) - - return wrappers.map { wrapper in - let (messagePayload, messageClaims) = try! MessagePayload.decodeAndVerify(from: wrapper) - - let authorAccount = messagePayload.recipientAccount == thread.selfAccount - ? thread.peerAccount - : thread.selfAccount - - return Message( - topic: thread.topic, - message: messagePayload.message, - authorAccount: authorAccount, - timestamp: messageClaims.iat) - } + fatalError() } } diff --git a/Sources/WalletConnectHistory/HistoryAPI.swift b/Sources/WalletConnectHistory/HistoryAPI.swift deleted file mode 100644 index 58f6b0cbd..000000000 --- a/Sources/WalletConnectHistory/HistoryAPI.swift +++ /dev/null @@ -1,56 +0,0 @@ -import Foundation - -enum HistoryAPI: HTTPService { - case register(payload: RegisterPayload, jwt: String) - case messages(payload: GetMessagesPayload) - - var path: String { - switch self { - case .register: - return "/register" - case .messages: - return "/messages" - } - } - - var method: HTTPMethod { - switch self { - case .register: - return .post - case .messages: - return .get - } - } - - var body: Data? { - switch self { - case .register(let payload, _): - return try? JSONEncoder().encode(payload) - case .messages: - return nil - } - } - - var additionalHeaderFields: [String : String]? { - switch self { - case .register(_, let jwt): - return ["Authorization": "Bearer \(jwt)"] - case .messages: - return nil - } - } - - var queryParameters: [String: String]? { - switch self { - case .messages(let payload): - return [ - "topic": payload.topic, - "originId": payload.originId.map { String($0) }, - "messageCount": payload.messageCount.map { String($0) }, - "direction": payload.direction.rawValue - ].compactMapValues { $0 } - case .register: - return nil - } - } -} diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 3022a05aa..65f50acab 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -2,47 +2,7 @@ import Foundation public final class HistoryClient { - private let historyUrl: String - private let relayUrl: String - private let serializer: Serializer - private let historyNetworkService: HistoryNetworkService + init() { - init(historyUrl: String, relayUrl: String, serializer: Serializer, historyNetworkService: HistoryNetworkService) { - self.historyUrl = historyUrl - self.relayUrl = relayUrl - self.serializer = serializer - self.historyNetworkService = historyNetworkService - } - - public func register(tags: [String]) async throws { - let payload = RegisterPayload(tags: tags, relayUrl: relayUrl) - try await historyNetworkService.registerTags(payload: payload, historyUrl: historyUrl) - } - - public func getMessages(topic: String, count: Int, direction: GetMessagesPayload.Direction) async throws -> [T] { - return try await getRecords(topic: topic, count: count, direction: direction).map { $0.object } - } - - public func getRecords(topic: String, count: Int, direction: GetMessagesPayload.Direction) async throws -> [HistoryRecord] { - let payload = GetMessagesPayload(topic: topic, originId: nil, messageCount: count, direction: direction) - let response = try await historyNetworkService.getMessages(payload: payload, historyUrl: historyUrl) - - return response.messages.compactMap { payload in - do { - let (request, _, _): (RPCRequest, _, _) = try serializer.deserialize( - topic: topic, - encodedEnvelope: payload - ) - - guard - let id = request.id, - let object = try request.params?.get(T.self) - else { return nil } - - return HistoryRecord(id: id, object: object) - } catch { - fatalError(error.localizedDescription) - } - } } } diff --git a/Sources/WalletConnectHistory/HistoryClientFactory.swift b/Sources/WalletConnectHistory/HistoryClientFactory.swift index 26d0d2044..06784b9db 100644 --- a/Sources/WalletConnectHistory/HistoryClientFactory.swift +++ b/Sources/WalletConnectHistory/HistoryClientFactory.swift @@ -3,28 +3,6 @@ import Foundation class HistoryClientFactory { static func create() -> HistoryClient { - let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: "") - let keyValueStorage = UserDefaults.standard - let logger = ConsoleLogger() - return HistoryClientFactory.create( - historyUrl: "https://history.walletconnect.com", - relayUrl: "wss://relay.walletconnect.com", - keyValueStorage: keyValueStorage, - keychain: keychain, - logger: logger - ) - } - - static func create(historyUrl: String, relayUrl: String, keyValueStorage: KeyValueStorage, keychain: KeychainStorageProtocol, logger: ConsoleLogging) -> HistoryClient { - let clientIdStorage = ClientIdStorage(defaults: keyValueStorage, keychain: keychain, logger: logger) - let kms = KeyManagementService(keychain: keychain) - let serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) - let historyNetworkService = HistoryNetworkService(clientIdStorage: clientIdStorage) - return HistoryClient( - historyUrl: historyUrl, - relayUrl: relayUrl, - serializer: serializer, - historyNetworkService: historyNetworkService - ) + return HistoryClient() } } diff --git a/Sources/WalletConnectHistory/HistoryImports.swift b/Sources/WalletConnectHistory/HistoryImports.swift index e6d4b859c..2db10894a 100644 --- a/Sources/WalletConnectHistory/HistoryImports.swift +++ b/Sources/WalletConnectHistory/HistoryImports.swift @@ -1,4 +1,3 @@ #if !CocoaPods -@_exported import HTTPClient -@_exported import WalletConnectRelay + #endif diff --git a/Sources/WalletConnectHistory/HistoryNetworkService.swift b/Sources/WalletConnectHistory/HistoryNetworkService.swift deleted file mode 100644 index 906959577..000000000 --- a/Sources/WalletConnectHistory/HistoryNetworkService.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -final class HistoryNetworkService { - - private let clientIdStorage: ClientIdStorage - - init(clientIdStorage: ClientIdStorage) { - self.clientIdStorage = clientIdStorage - } - - func registerTags(payload: RegisterPayload, historyUrl: String) async throws { - let service = HTTPNetworkClient(host: try host(from: historyUrl)) - let api = HistoryAPI.register(payload: payload, jwt: try getJwt(historyUrl: historyUrl)) - try await service.request(service: api) - } - - func getMessages(payload: GetMessagesPayload, historyUrl: String) async throws -> GetMessagesResponse { - let service = HTTPNetworkClient(host: try host(from: historyUrl)) - let api = HistoryAPI.messages(payload: payload) - return try await service.request(GetMessagesResponse.self, at: api) - } -} - -private extension HistoryNetworkService { - - enum Errors: Error { - case couldNotResolveHost - } - - func getJwt(historyUrl: String) throws -> String { - let authenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage) - return try authenticator.createAuthToken(url: historyUrl) - } - - func host(from url: String) throws -> String { - guard let host = URL(string: url)?.host else { - throw Errors.couldNotResolveHost - } - return host - } -} diff --git a/Sources/WalletConnectHistory/HistoryRecord.swift b/Sources/WalletConnectHistory/HistoryRecord.swift deleted file mode 100644 index cd2793081..000000000 --- a/Sources/WalletConnectHistory/HistoryRecord.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public struct HistoryRecord { - public let id: RPCID - public let object: Object - - public init(id: RPCID, object: Object) { - self.id = id - self.object = object - } -} diff --git a/Sources/WalletConnectHistory/Types/GetMessagesPayload.swift b/Sources/WalletConnectHistory/Types/GetMessagesPayload.swift deleted file mode 100644 index 7dcc9a08d..000000000 --- a/Sources/WalletConnectHistory/Types/GetMessagesPayload.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -public struct GetMessagesPayload: Codable { - public enum Direction: String, Codable { - case forward - case backward - } - public let topic: String - public let originId: Int64? - public let messageCount: Int? - public let direction: Direction - - public init(topic: String, originId: Int64?, messageCount: Int?, direction: GetMessagesPayload.Direction) { - self.topic = topic - self.originId = originId - self.messageCount = messageCount - self.direction = direction - } -} diff --git a/Sources/WalletConnectHistory/Types/GetMessagesResponse.swift b/Sources/WalletConnectHistory/Types/GetMessagesResponse.swift deleted file mode 100644 index 032bad07e..000000000 --- a/Sources/WalletConnectHistory/Types/GetMessagesResponse.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -public struct GetMessagesResponse: Decodable { - public struct Message: Codable { - public let message: String - } - public let topic: String - public let direction: GetMessagesPayload.Direction - public let nextId: Int64? - public let messages: [String] - - enum CodingKeys: String, CodingKey { - case topic - case direction - case nextId - case messages - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.topic = try container.decode(String.self, forKey: .topic) - self.direction = try container.decode(GetMessagesPayload.Direction.self, forKey: .direction) - self.nextId = try container.decodeIfPresent(Int64.self, forKey: .nextId) - - let messages = try container.decode([Message].self, forKey: .messages) - self.messages = messages.map { $0.message } - } -} diff --git a/Sources/WalletConnectHistory/Types/RegisterPayload.swift b/Sources/WalletConnectHistory/Types/RegisterPayload.swift deleted file mode 100644 index b759c5ce5..000000000 --- a/Sources/WalletConnectHistory/Types/RegisterPayload.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public struct RegisterPayload: Codable { - public let tags: [String] - public let relayUrl: String - - public init(tags: [String], relayUrl: String) { - self.tags = tags - self.relayUrl = relayUrl - } -} From b29a0eab3f32d979139208e8407cd859522fe3be Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 15:03:44 +0300 Subject: [PATCH 084/169] awaitResponse --- .../NetworkInteracting.swift | 10 ++++++ .../NetworkingInteractor.swift | 31 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 6bad90646..75b7283b6 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -42,6 +42,16 @@ public protocol NetworkInteracting { subscription: @escaping (ResponseSubscriptionPayload) async throws -> Void ) + func awaitResponse( + request: RPCRequest, + topic: String, + requestOfType: Request, + requestMethod: ProtocolMethod, + responseOfType: Response, + responseMethod: ProtocolMethod, + envelopeType: Envelope.EnvelopeType + ) async throws -> Response + func getClientId() throws -> String } diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index b428a77f5..a4d36c0b5 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -29,7 +29,7 @@ public class NetworkingInteractor: NetworkInteracting { public var networkConnectionStatusPublisher: AnyPublisher public var socketConnectionStatusPublisher: AnyPublisher - + private let networkMonitor: NetworkMonitoring public init( @@ -139,6 +139,35 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } + public func awaitResponse( + request: RPCRequest, + topic: String, + requestOfType: Request, + requestMethod: ProtocolMethod, + responseOfType: Response, + responseMethod: ProtocolMethod, + envelopeType: Envelope.EnvelopeType + ) async throws -> Response { + + try await self.request(request, topic: topic, protocolMethod: requestMethod, envelopeType: envelopeType) + + return try await withCheckedThrowingContinuation { [unowned self] continuation in + var response, error: AnyCancellable? + + response = responseSubscription(on: responseMethod) + .sink { (payload: ResponseSubscriptionPayload) in + response?.cancel() + continuation.resume(with: .success(payload.response)) + } + + error = responseErrorSubscription(on: responseMethod) + .sink { (payload: ResponseSubscriptionErrorPayload) in + error?.cancel() + continuation.resume(throwing: payload.error) + } + } + } + public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { rpcRequest in From c067781cda5aed5b04af3c5c5009bbe0dde465fd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 15:08:53 +0300 Subject: [PATCH 085/169] requestMethod == responseMethod --- Sources/WalletConnectHistory/HistoryClient.swift | 2 +- .../WalletConnectNetworking/NetworkingInteractor.swift | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 65f50acab..6ddc09ee1 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -3,6 +3,6 @@ import Foundation public final class HistoryClient { init() { - + } } diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index a4d36c0b5..f4af51a6c 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -142,25 +142,24 @@ public class NetworkingInteractor: NetworkInteracting { public func awaitResponse( request: RPCRequest, topic: String, + method: ProtocolMethod, requestOfType: Request, - requestMethod: ProtocolMethod, responseOfType: Response, - responseMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType ) async throws -> Response { - try await self.request(request, topic: topic, protocolMethod: requestMethod, envelopeType: envelopeType) + try await self.request(request, topic: topic, protocolMethod: method, envelopeType: envelopeType) return try await withCheckedThrowingContinuation { [unowned self] continuation in var response, error: AnyCancellable? - response = responseSubscription(on: responseMethod) + response = responseSubscription(on: method) .sink { (payload: ResponseSubscriptionPayload) in response?.cancel() continuation.resume(with: .success(payload.response)) } - error = responseErrorSubscription(on: responseMethod) + error = responseErrorSubscription(on: method) .sink { (payload: ResponseSubscriptionErrorPayload) in error?.cancel() continuation.resume(throwing: payload.error) From 57fdaf2cfe61820d26f63dcf3c3d6518b8699b4e Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 15:25:21 +0300 Subject: [PATCH 086/169] Imports + NotifyGetNotificationsRequestPayload mask --- Package.swift | 2 +- .../WalletConnectHistory/HistoryImports.swift | 2 +- ...NotifyGetNotificationsRequestPayload.swift | 38 +++++++++++++++++++ .../NetworkInteracting.swift | 3 +- .../NetworkingInteractor.swift | 2 + 5 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift diff --git a/Package.swift b/Package.swift index c53f5ee38..01bd9ebcc 100644 --- a/Package.swift +++ b/Package.swift @@ -94,7 +94,7 @@ let package = Package( dependencies: ["WalletConnectNetworking"]), .target( name: "WalletConnectHistory", - dependencies: []), + dependencies: ["WalletConnectIdentity"]), .target( name: "WalletConnectSigner", dependencies: ["WalletConnectNetworking"]), diff --git a/Sources/WalletConnectHistory/HistoryImports.swift b/Sources/WalletConnectHistory/HistoryImports.swift index 2db10894a..51e07cac3 100644 --- a/Sources/WalletConnectHistory/HistoryImports.swift +++ b/Sources/WalletConnectHistory/HistoryImports.swift @@ -1,3 +1,3 @@ #if !CocoaPods - +@_exported import WalletConnectIdentity #endif diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift new file mode 100644 index 000000000..2e339da97 --- /dev/null +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift @@ -0,0 +1,38 @@ +import Foundation + +struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + var iss: String + + var iat: UInt64 + + var exp: UInt64 + + var act: String? + + static var action: String? { + return "notify_get_notifications" + } + } + + struct Wrapper: JWTWrapper { + let auth: String + + init(jwtString: String) { + self.auth = jwtString + } + + var jwtString: String { + return auth + } + } + + init(claims: Claims) throws { + fatalError() + } + + func encode(iss: String) throws -> Claims { + fatalError() + } +} diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 75b7283b6..068ccb31b 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -45,10 +45,9 @@ public protocol NetworkInteracting { func awaitResponse( request: RPCRequest, topic: String, + method: ProtocolMethod, requestOfType: Request, - requestMethod: ProtocolMethod, responseOfType: Response, - responseMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType ) async throws -> Response diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index f4af51a6c..a3c360beb 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -156,11 +156,13 @@ public class NetworkingInteractor: NetworkInteracting { response = responseSubscription(on: method) .sink { (payload: ResponseSubscriptionPayload) in response?.cancel() + error?.cancel() continuation.resume(with: .success(payload.response)) } error = responseErrorSubscription(on: method) .sink { (payload: ResponseSubscriptionErrorPayload) in + response?.cancel() error?.cancel() continuation.resume(throwing: payload.error) } From 859a85ad591a71f0e5edac8793d1c3cc14614c24 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 16:23:34 +0300 Subject: [PATCH 087/169] NotifyGetNotificationsRequestPayload --- ...NotifyGetNotificationsRequestPayload.swift | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift index 2e339da97..a6444de52 100644 --- a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift @@ -3,13 +3,15 @@ import Foundation struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { struct Claims: JWTClaims { - var iss: String - - var iat: UInt64 - - var exp: UInt64 - - var act: String? + let iat: UInt64 + let exp: UInt64 + let act: String? // - `notify_get_notifications` + let iss: String // - did:key of client identity key + let ksu: String // - key server for identity key verification + let aud: String // - did:key of dapp authentication key + let app: String // - did:web of app domain that this request is associated with - Example: `did:web:app.example.com` + let lmt: UInt64 // - the max number of notifications to return. Maximum value is 50. + let aft: String? // - the notification ID to start returning messages after. Null to start with the most recent notification static var action: String? { return "notify_get_notifications" @@ -28,11 +30,28 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { } } + let identityKey: DIDKey + let keyserver: String + let dappAuthKey: DIDKey + let app: DIDWeb + let limit: UInt64 + let after: String? + init(claims: Claims) throws { fatalError() } func encode(iss: String) throws -> Claims { - fatalError() + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + act: Claims.action, + iss: identityKey.did(variant: .ED25519), + ksu: keyserver, + aud: dappAuthKey.did(variant: .ED25519), + app: app.did, + lmt: limit, + aft: after + ) } } From eb632db6f4d88c38cbb03cb8c3aac04d38ad20ad Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 16:52:35 +0300 Subject: [PATCH 088/169] NotifyGetNotificationsResponsePayload --- Package.swift | 2 +- .../WalletConnectHistory/HistoryClient.swift | 1 + ...otifyGetNotificationsResponsePayload.swift | 39 +++++++++++++++++++ .../Types}/NotifyMessage.swift | 0 .../WalletConnectNotify/NotifyImports.swift | 2 +- 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift rename Sources/{WalletConnectNotify/Types/DataStructures => WalletConnectHistory/Types}/NotifyMessage.swift (100%) diff --git a/Package.swift b/Package.swift index 01bd9ebcc..617226143 100644 --- a/Package.swift +++ b/Package.swift @@ -74,7 +74,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSigner", "Database"], + dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectHistory", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 6ddc09ee1..3456f7fc0 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -5,4 +5,5 @@ public final class HistoryClient { init() { } + } diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift new file mode 100644 index 000000000..cf8cb2243 --- /dev/null +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift @@ -0,0 +1,39 @@ +import Foundation + +struct NotifyGetNotificationsResponsePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + let iat: UInt64 + let exp: UInt64 + let act: String? // - `notify_get_notifications_response` + let iss: String // - did:key of client identity key + let aud: String // - did:key of Notify Server authentication key + let nfs: [NotifyMessage] // array of [Notify Notifications](./data-structures.md#notify-notification) + + static var action: String? { + return "notify_get_notifications_response" + } + } + + struct Wrapper: JWTWrapper { + let auth: String + + init(jwtString: String) { + self.auth = jwtString + } + + var jwtString: String { + return auth + } + } + + let messages: [NotifyMessage] + + init(claims: Claims) throws { + self.messages = claims.nfs + } + + func encode(iss: String) throws -> Claims { + fatalError() + } +} diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift b/Sources/WalletConnectHistory/Types/NotifyMessage.swift similarity index 100% rename from Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift rename to Sources/WalletConnectHistory/Types/NotifyMessage.swift diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift index 43b543236..e2f267c31 100644 --- a/Sources/WalletConnectNotify/NotifyImports.swift +++ b/Sources/WalletConnectNotify/NotifyImports.swift @@ -1,7 +1,7 @@ #if !CocoaPods @_exported import WalletConnectPairing @_exported import WalletConnectPush -@_exported import WalletConnectIdentity +@_exported import WalletConnectHistory @_exported import WalletConnectSigner @_exported import Database #endif From e4b37995ee74d5c7ef53c424fbc0d8fea3e01655 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 17:24:50 +0300 Subject: [PATCH 089/169] HistoryClient package --- Sources/WalletConnectHistory/History.swift | 12 ------ .../WalletConnectHistory/HistoryClient.swift | 42 ++++++++++++++++++- .../HistoryClientFactory.swift | 6 +-- ...NotifyGetNotificationsRequestPayload.swift | 9 ++++ ...NotifyGetNotificationsProtocolMethod.swift | 9 ++++ .../NetworkInteracting.swift | 4 +- .../NetworkingInteractor.swift | 4 +- 7 files changed, 65 insertions(+), 21 deletions(-) delete mode 100644 Sources/WalletConnectHistory/History.swift create mode 100644 Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift diff --git a/Sources/WalletConnectHistory/History.swift b/Sources/WalletConnectHistory/History.swift deleted file mode 100644 index d0954f756..000000000 --- a/Sources/WalletConnectHistory/History.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -/// History instatnce wrapper -public class History { - - /// Sync client instance - public static var instance: HistoryClient = { - return HistoryClientFactory.create() - }() - - private init() { } -} diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 3456f7fc0..4aef22b0c 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -2,8 +2,46 @@ import Foundation public final class HistoryClient { - init() { - + private let keyserver: URL + private let networkingClient: NetworkInteracting + private let identityClient: IdentityClient + + init(keyserver: URL, networkingClient: NetworkInteracting, identityClient: IdentityClient) { + self.keyserver = keyserver + self.networkingClient = networkingClient + self.identityClient = identityClient } + public func fetchHistory(account: Account, topic: String, appAuthenticationKey: String, host: String) async throws -> [NotifyMessage] { + let identityKey = try identityClient.getInviteKey(for: account) + let dappAuthKey = try DIDKey(did: appAuthenticationKey) + let app = DIDWeb(host: host) + + let requestPayload = NotifyGetNotificationsRequestPayload( + identityKey: DIDKey(rawData: identityKey.rawRepresentation), + keyserver: keyserver.absoluteString, + dappAuthKey: dappAuthKey, + app: app, + limit: 50, + after: nil + ) + + let wrapper = try identityClient.signAndCreateWrapper(payload: requestPayload, account: account) + + let protocolMethod = NotifyGetNotificationsProtocolMethod() + let request = RPCRequest(method: protocolMethod.method, params: wrapper) + + let response = try await networkingClient.awaitResponse( + request: request, + topic: topic, + method: protocolMethod, + requestOfType: NotifyGetNotificationsRequestPayload.Wrapper.self, + responseOfType: NotifyGetNotificationsResponsePayload.Wrapper.self, + envelopeType: .type0 + ) + + let (responsePayload, _) = try NotifyGetNotificationsResponsePayload.decodeAndVerify(from: response) + + return responsePayload.messages + } } diff --git a/Sources/WalletConnectHistory/HistoryClientFactory.swift b/Sources/WalletConnectHistory/HistoryClientFactory.swift index 06784b9db..9db17a320 100644 --- a/Sources/WalletConnectHistory/HistoryClientFactory.swift +++ b/Sources/WalletConnectHistory/HistoryClientFactory.swift @@ -1,8 +1,8 @@ import Foundation -class HistoryClientFactory { +public class HistoryClientFactory { - static func create() -> HistoryClient { - return HistoryClient() + public static func create(keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient) -> HistoryClient { + return HistoryClient(keyserver: keyserver, networkingClient: networkingInteractor, identityClient: identityClient) } } diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift index a6444de52..a479f3af0 100644 --- a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift @@ -37,6 +37,15 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { let limit: UInt64 let after: String? + init(identityKey: DIDKey, keyserver: String, dappAuthKey: DIDKey, app: DIDWeb, limit: UInt64, after: String? = nil) { + self.identityKey = identityKey + self.keyserver = keyserver + self.dappAuthKey = dappAuthKey + self.app = app + self.limit = limit + self.after = after + } + init(claims: Claims) throws { fatalError() } diff --git a/Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift b/Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift new file mode 100644 index 000000000..b86c57a1a --- /dev/null +++ b/Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift @@ -0,0 +1,9 @@ +import Foundation + +struct NotifyGetNotificationsProtocolMethod: ProtocolMethod { + let method: String = "wc_notifyGetNotifications" + + let requestConfig: RelayConfig = RelayConfig(tag: 4014, prompt: false, ttl: 300) + + let responseConfig: RelayConfig = RelayConfig(tag: 4015, prompt: false, ttl: 300) +} diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 068ccb31b..a79814399 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -46,8 +46,8 @@ public protocol NetworkInteracting { request: RPCRequest, topic: String, method: ProtocolMethod, - requestOfType: Request, - responseOfType: Response, + requestOfType: Request.Type, + responseOfType: Response.Type, envelopeType: Envelope.EnvelopeType ) async throws -> Response diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index a3c360beb..f0bc0a2bb 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -143,8 +143,8 @@ public class NetworkingInteractor: NetworkInteracting { request: RPCRequest, topic: String, method: ProtocolMethod, - requestOfType: Request, - responseOfType: Response, + requestOfType: Request.Type, + responseOfType: Response.Type, envelopeType: Envelope.EnvelopeType ) async throws -> Response { From edbbabcd2f9ac3748e4be47f2d14b85bfb6bd3bc Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 17:40:29 +0300 Subject: [PATCH 090/169] fetchHistory method --- .../Client/Wallet/NotifyClient.swift | 14 ++++++++++++++ .../Client/Wallet/NotifyClientFactory.swift | 4 +++- .../Client/Wallet/NotifyStorage.swift | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index ba30e6956..d64f13879 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -25,6 +25,7 @@ public class NotifyClient { private let keyserverURL: URL private let pushClient: PushClient private let identityClient: IdentityClient + private let historyClient: HistoryClient private let notifyStorage: NotifyStorage private let notifyAccountProvider: NotifyAccountProvider private let notifyMessageSubscriber: NotifyMessageSubscriber @@ -42,6 +43,7 @@ public class NotifyClient { keyserverURL: URL, kms: KeyManagementServiceProtocol, identityClient: IdentityClient, + historyClient: HistoryClient, pushClient: PushClient, notifyMessageSubscriber: NotifyMessageSubscriber, notifyStorage: NotifyStorage, @@ -62,6 +64,7 @@ public class NotifyClient { self.keyserverURL = keyserverURL self.pushClient = pushClient self.identityClient = identityClient + self.historyClient = historyClient self.notifyMessageSubscriber = notifyMessageSubscriber self.notifyStorage = notifyStorage self.deleteNotifySubscriptionRequester = deleteNotifySubscriptionRequester @@ -144,6 +147,17 @@ public class NotifyClient { public func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> { return notifyStorage.messagesPublisher(topic: topic) } + + public func fetchHistory(subscription: NotifySubscription) async throws { + let messages = try await historyClient.fetchHistory( + account: subscription.account, + topic: subscription.topic, + appAuthenticationKey: subscription.appAuthenticationKey, + host: subscription.metadata.url + ) + + // TBD + } } private extension NotifyClient { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 3a08a5727..3bbcbe4af 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -68,12 +68,14 @@ public struct NotifyClientFactory { let notifyWatchSubscriptionsResponseSubscriber = NotifyWatchSubscriptionsResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage, groupKeychainStorage: groupKeychainStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, kms: kms, identityClient: identityClient, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let subscriptionWatcher = SubscriptionWatcher(notifyWatchSubscriptionsRequester: notifyWatchSubscriptionsRequester, logger: logger) + let historyClient = HistoryClientFactory.create(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient) return NotifyClient( logger: logger, keyserverURL: keyserverURL, kms: kms, - identityClient: identityClient, + identityClient: identityClient, + historyClient: historyClient, pushClient: pushClient, notifyMessageSubscriber: notifyMessageSubscriber, notifyStorage: notifyStorage, diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index cca2c3e6a..e26667ec6 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -122,6 +122,10 @@ final class NotifyStorage: NotifyStoring { func setMessage(_ message: NotifyMessageRecord) throws { try database.save(message: message) } + + func setMessages(_ messages: [NotifyMessageRecord]) throws { + try database.save(messages: messages) + } } private extension NotifyStorage { From bdd99e7142156586d788e3ba7add39f3d8676096 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 17:45:22 +0300 Subject: [PATCH 091/169] sent_at for NotifyMessage --- Sources/WalletConnectHistory/Types/NotifyMessage.swift | 8 +++++++- .../Client/Wallet/NotifyMessageRecord.swift | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectHistory/Types/NotifyMessage.swift b/Sources/WalletConnectHistory/Types/NotifyMessage.swift index e61d46223..f7fc00a72 100644 --- a/Sources/WalletConnectHistory/Types/NotifyMessage.swift +++ b/Sources/WalletConnectHistory/Types/NotifyMessage.swift @@ -7,13 +7,19 @@ public struct NotifyMessage: Codable, Equatable { public let icon: String public let url: String public let type: String + public let sent_at: UInt64 - public init(id: String, title: String, body: String, icon: String, url: String, type: String) { + public var sentAt: Date { + return Date(timeIntervalSince1970: TimeInterval(sent_at)) + } + + public init(id: String, title: String, body: String, icon: String, url: String, type: String, sentAt: Date) { self.id = id self.title = title self.body = body self.icon = icon self.url = url self.type = type + self.sent_at = UInt64(sentAt.timeIntervalSince1970) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift index 9fc7b1c2b..97b9a3567 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift @@ -18,16 +18,19 @@ public struct NotifyMessageRecord: Codable, Equatable, SqliteRow { public init(decoder: SqliteRowDecoder) throws { self.topic = try decoder.decodeString(at: 1) + let sentAt = try decoder.decodeDate(at: 7) + self.message = NotifyMessage( id: try decoder.decodeString(at: 0), title: try decoder.decodeString(at: 2), body: try decoder.decodeString(at: 3), icon: try decoder.decodeString(at: 4), url: try decoder.decodeString(at: 5), - type: try decoder.decodeString(at: 6) + type: try decoder.decodeString(at: 6), + sentAt: sentAt ) - self.publishedAt = try decoder.decodeDate(at: 7) + self.publishedAt = sentAt } public func encode() -> SqliteRowEncoder { From 6f5c86ce498421aa4aedfabf47d77050b7063a43 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 17:51:41 +0300 Subject: [PATCH 092/169] fetchHistory finalized --- .../WalletConnectNotify/Client/Wallet/NotifyClient.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index d64f13879..89f5342a3 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -156,7 +156,11 @@ public class NotifyClient { host: subscription.metadata.url ) - // TBD + let records = messages.map { message in + return NotifyMessageRecord(topic: subscription.topic, message: message, publishedAt: message.sentAt) + } + + try notifyStorage.setMessages(records) } } From abb66fc6ba281641f6f41663e9ccd7c8458513ab Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 12 Jan 2024 18:16:12 +0300 Subject: [PATCH 093/169] fetch history on sample app --- .../Wallet/PushMessages/SubscriptionInteractor.swift | 6 ++++++ .../Wallet/PushMessages/SubscriptionPresenter.swift | 2 ++ Sources/WalletConnectHistory/HistoryClient.swift | 2 -- .../NotifyGetNotificationsRequestPayload.swift | 10 +++++----- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift index 9b0c84ff7..e3d1a14e4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift @@ -30,4 +30,10 @@ final class SubscriptionInteractor { try await Notify.instance.deleteSubscription(topic: subscription.topic) } } + + func fetchHistory() { + Task(priority: .high) { + try await Notify.instance.fetchHistory(subscription: subscription) + } + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index 23f9729f9..c0527399e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -77,6 +77,8 @@ extension SubscriptionPresenter: SceneViewModel { private extension SubscriptionPresenter { func setupInitialState() { + interactor.fetchHistory() + pushMessages = interactor.getPushMessages() interactor.messagesPublisher diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 4aef22b0c..6125c2b4f 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -13,12 +13,10 @@ public final class HistoryClient { } public func fetchHistory(account: Account, topic: String, appAuthenticationKey: String, host: String) async throws -> [NotifyMessage] { - let identityKey = try identityClient.getInviteKey(for: account) let dappAuthKey = try DIDKey(did: appAuthenticationKey) let app = DIDWeb(host: host) let requestPayload = NotifyGetNotificationsRequestPayload( - identityKey: DIDKey(rawData: identityKey.rawRepresentation), keyserver: keyserver.absoluteString, dappAuthKey: dappAuthKey, app: app, diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift index a479f3af0..cce3a69b3 100644 --- a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift @@ -12,6 +12,7 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { let app: String // - did:web of app domain that this request is associated with - Example: `did:web:app.example.com` let lmt: UInt64 // - the max number of notifications to return. Maximum value is 50. let aft: String? // - the notification ID to start returning messages after. Null to start with the most recent notification + let urf: Bool static var action: String? { return "notify_get_notifications" @@ -30,15 +31,13 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { } } - let identityKey: DIDKey let keyserver: String let dappAuthKey: DIDKey let app: DIDWeb let limit: UInt64 let after: String? - init(identityKey: DIDKey, keyserver: String, dappAuthKey: DIDKey, app: DIDWeb, limit: UInt64, after: String? = nil) { - self.identityKey = identityKey + init(keyserver: String, dappAuthKey: DIDKey, app: DIDWeb, limit: UInt64, after: String? = nil) { self.keyserver = keyserver self.dappAuthKey = dappAuthKey self.app = app @@ -55,12 +54,13 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { iat: defaultIat(), exp: expiry(days: 1), act: Claims.action, - iss: identityKey.did(variant: .ED25519), + iss: iss, ksu: keyserver, aud: dappAuthKey.did(variant: .ED25519), app: app.did, lmt: limit, - aft: after + aft: after, + urf: false ) } } From 5653ddccc6eaf2f1518f04dc222988ede691b70d Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 15 Jan 2024 17:08:06 +0300 Subject: [PATCH 094/169] sub missed --- Sources/WalletConnectHistory/HistoryClient.swift | 1 + .../Payloads/NotifyGetNotificationsRequestPayload.swift | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectHistory/HistoryClient.swift index 6125c2b4f..fc2fa3e0c 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectHistory/HistoryClient.swift @@ -17,6 +17,7 @@ public final class HistoryClient { let app = DIDWeb(host: host) let requestPayload = NotifyGetNotificationsRequestPayload( + account: account, keyserver: keyserver.absoluteString, dappAuthKey: dappAuthKey, app: app, diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift index cce3a69b3..d54825d85 100644 --- a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift +++ b/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift @@ -5,6 +5,7 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { struct Claims: JWTClaims { let iat: UInt64 let exp: UInt64 + let sub: String let act: String? // - `notify_get_notifications` let iss: String // - did:key of client identity key let ksu: String // - key server for identity key verification @@ -31,13 +32,15 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { } } + let account: Account let keyserver: String let dappAuthKey: DIDKey let app: DIDWeb let limit: UInt64 let after: String? - init(keyserver: String, dappAuthKey: DIDKey, app: DIDWeb, limit: UInt64, after: String? = nil) { + init(account: Account, keyserver: String, dappAuthKey: DIDKey, app: DIDWeb, limit: UInt64, after: String? = nil) { + self.account = account self.keyserver = keyserver self.dappAuthKey = dappAuthKey self.app = app @@ -52,7 +55,8 @@ struct NotifyGetNotificationsRequestPayload: JWTClaimsCodable { func encode(iss: String) throws -> Claims { return Claims( iat: defaultIat(), - exp: expiry(days: 1), + exp: expiry(days: 1), + sub: account.did, act: Claims.action, iss: iss, ksu: keyserver, From 551a10ee529fc0d73b22b8da82d94c875a79a1fb Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 15 Jan 2024 17:17:12 +0300 Subject: [PATCH 095/169] Message sent_at fix --- Sources/WalletConnectHistory/Types/NotifyMessage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectHistory/Types/NotifyMessage.swift b/Sources/WalletConnectHistory/Types/NotifyMessage.swift index f7fc00a72..6c88dfa7a 100644 --- a/Sources/WalletConnectHistory/Types/NotifyMessage.swift +++ b/Sources/WalletConnectHistory/Types/NotifyMessage.swift @@ -10,7 +10,7 @@ public struct NotifyMessage: Codable, Equatable { public let sent_at: UInt64 public var sentAt: Date { - return Date(timeIntervalSince1970: TimeInterval(sent_at)) + return Date(milliseconds: sent_at) } public init(id: String, title: String, body: String, icon: String, url: String, type: String, sentAt: Date) { @@ -20,6 +20,6 @@ public struct NotifyMessage: Codable, Equatable { self.icon = icon self.url = url self.type = type - self.sent_at = UInt64(sentAt.timeIntervalSince1970) + self.sent_at = UInt64(sentAt.millisecondsSince1970) } } From 04458460064cd2f8f5717ba0707971ae58082eee Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 17 Jan 2024 17:23:35 +0300 Subject: [PATCH 096/169] Unit tests fixed --- .../NetworkingInteractorMock.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 27fa35a5c..a1fa196ad 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -134,6 +134,36 @@ public class NetworkingInteractorMock: NetworkInteracting { }.store(in: &publishers) } + public func awaitResponse( + request: RPCRequest, + topic: String, + method: ProtocolMethod, + requestOfType: Request.Type, + responseOfType: Response.Type, + envelopeType: Envelope.EnvelopeType + ) async throws -> Response { + + try await self.request(request, topic: topic, protocolMethod: method, envelopeType: envelopeType) + + return try await withCheckedThrowingContinuation { [unowned self] continuation in + var response, error: AnyCancellable? + + response = responseSubscription(on: method) + .sink { (payload: ResponseSubscriptionPayload) in + response?.cancel() + error?.cancel() + continuation.resume(with: .success(payload.response)) + } + + error = responseErrorSubscription(on: method) + .sink { (payload: ResponseSubscriptionErrorPayload) in + response?.cancel() + error?.cancel() + continuation.resume(throwing: payload.error) + } + } + } + public func subscribe(topic: String) async throws { defer { onSubscribeCalled?() } subscriptions.append(topic) From ed7f3fb5baa26633765a04019fe05fcab8af4dae Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 17 Jan 2024 17:24:18 +0300 Subject: [PATCH 097/169] Integration tests fixed --- Example/IntegrationTests/Stubs/PushMessage.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/IntegrationTests/Stubs/PushMessage.swift b/Example/IntegrationTests/Stubs/PushMessage.swift index 634d78ea1..68d08b36e 100644 --- a/Example/IntegrationTests/Stubs/PushMessage.swift +++ b/Example/IntegrationTests/Stubs/PushMessage.swift @@ -9,6 +9,7 @@ extension NotifyMessage { body: "body", icon: "https://images.unsplash.com/photo-1581224463294-908316338239?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80", url: "https://web3inbox.com", - type: type) + type: type, + sentAt: Date()) } } From 2f309312f72b014ce7a65ac4e874fe644d19a8a2 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 15:26:24 +0300 Subject: [PATCH 098/169] HistoryPackage removed --- Package.swift | 8 +------- Sources/WalletConnectHistory/HistoryClientFactory.swift | 8 -------- Sources/WalletConnectHistory/HistoryImports.swift | 3 --- .../Client/Wallet/HistoryService.swift} | 2 +- .../WalletConnectNotify/Client/Wallet/NotifyClient.swift | 8 ++++---- .../Client/Wallet/NotifyClientFactory.swift | 4 ++-- Sources/WalletConnectNotify/NotifyImports.swift | 2 +- .../NotifyGetNotificationsProtocolMethod.swift | 0 .../Types/DataStructures}/NotifyMessage.swift | 0 .../NotifyGetNotificationsRequestPayload.swift | 0 .../NotifyGetNotificationsResponsePayload.swift | 0 11 files changed, 9 insertions(+), 26 deletions(-) delete mode 100644 Sources/WalletConnectHistory/HistoryClientFactory.swift delete mode 100644 Sources/WalletConnectHistory/HistoryImports.swift rename Sources/{WalletConnectHistory/HistoryClient.swift => WalletConnectNotify/Client/Wallet/HistoryService.swift} (97%) rename Sources/{WalletConnectHistory => WalletConnectNotify}/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift (100%) rename Sources/{WalletConnectHistory/Types => WalletConnectNotify/Types/DataStructures}/NotifyMessage.swift (100%) rename Sources/{WalletConnectHistory/Payloads => WalletConnectNotify/Types/JWTPayloads/notify_get_notifications}/NotifyGetNotificationsRequestPayload.swift (100%) rename Sources/{WalletConnectHistory/Payloads => WalletConnectNotify/Types/JWTPayloads/notify_get_notifications}/NotifyGetNotificationsResponsePayload.swift (100%) diff --git a/Package.swift b/Package.swift index 617226143..23593b272 100644 --- a/Package.swift +++ b/Package.swift @@ -43,9 +43,6 @@ let package = Package( .library( name: "WalletConnectVerify", targets: ["WalletConnectVerify"]), - .library( - name: "WalletConnectHistory", - targets: ["WalletConnectHistory"]), .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), @@ -74,7 +71,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectHistory", "WalletConnectSigner", "Database"], + dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", @@ -92,9 +89,6 @@ let package = Package( .target( name: "WalletConnectPairing", dependencies: ["WalletConnectNetworking"]), - .target( - name: "WalletConnectHistory", - dependencies: ["WalletConnectIdentity"]), .target( name: "WalletConnectSigner", dependencies: ["WalletConnectNetworking"]), diff --git a/Sources/WalletConnectHistory/HistoryClientFactory.swift b/Sources/WalletConnectHistory/HistoryClientFactory.swift deleted file mode 100644 index 9db17a320..000000000 --- a/Sources/WalletConnectHistory/HistoryClientFactory.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -public class HistoryClientFactory { - - public static func create(keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient) -> HistoryClient { - return HistoryClient(keyserver: keyserver, networkingClient: networkingInteractor, identityClient: identityClient) - } -} diff --git a/Sources/WalletConnectHistory/HistoryImports.swift b/Sources/WalletConnectHistory/HistoryImports.swift deleted file mode 100644 index 51e07cac3..000000000 --- a/Sources/WalletConnectHistory/HistoryImports.swift +++ /dev/null @@ -1,3 +0,0 @@ -#if !CocoaPods -@_exported import WalletConnectIdentity -#endif diff --git a/Sources/WalletConnectHistory/HistoryClient.swift b/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift similarity index 97% rename from Sources/WalletConnectHistory/HistoryClient.swift rename to Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift index fc2fa3e0c..21574e569 100644 --- a/Sources/WalletConnectHistory/HistoryClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift @@ -1,6 +1,6 @@ import Foundation -public final class HistoryClient { +public final class HistoryService { private let keyserver: URL private let networkingClient: NetworkInteracting diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 89f5342a3..e4eab7e08 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -25,7 +25,7 @@ public class NotifyClient { private let keyserverURL: URL private let pushClient: PushClient private let identityClient: IdentityClient - private let historyClient: HistoryClient + private let historyService: HistoryService private let notifyStorage: NotifyStorage private let notifyAccountProvider: NotifyAccountProvider private let notifyMessageSubscriber: NotifyMessageSubscriber @@ -43,7 +43,7 @@ public class NotifyClient { keyserverURL: URL, kms: KeyManagementServiceProtocol, identityClient: IdentityClient, - historyClient: HistoryClient, + historyService: HistoryService, pushClient: PushClient, notifyMessageSubscriber: NotifyMessageSubscriber, notifyStorage: NotifyStorage, @@ -64,7 +64,7 @@ public class NotifyClient { self.keyserverURL = keyserverURL self.pushClient = pushClient self.identityClient = identityClient - self.historyClient = historyClient + self.historyService = historyService self.notifyMessageSubscriber = notifyMessageSubscriber self.notifyStorage = notifyStorage self.deleteNotifySubscriptionRequester = deleteNotifySubscriptionRequester @@ -149,7 +149,7 @@ public class NotifyClient { } public func fetchHistory(subscription: NotifySubscription) async throws { - let messages = try await historyClient.fetchHistory( + let messages = try await historyService.fetchHistory( account: subscription.account, topic: subscription.topic, appAuthenticationKey: subscription.appAuthenticationKey, diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 3bbcbe4af..ec417a765 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -68,14 +68,14 @@ public struct NotifyClientFactory { let notifyWatchSubscriptionsResponseSubscriber = NotifyWatchSubscriptionsResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage, groupKeychainStorage: groupKeychainStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, kms: kms, identityClient: identityClient, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let subscriptionWatcher = SubscriptionWatcher(notifyWatchSubscriptionsRequester: notifyWatchSubscriptionsRequester, logger: logger) - let historyClient = HistoryClientFactory.create(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient) + let historyService = HistoryService(keyserver: keyserverURL, networkingClient: networkInteractor, identityClient: identityClient) return NotifyClient( logger: logger, keyserverURL: keyserverURL, kms: kms, identityClient: identityClient, - historyClient: historyClient, + historyService: historyService, pushClient: pushClient, notifyMessageSubscriber: notifyMessageSubscriber, notifyStorage: notifyStorage, diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift index e2f267c31..43b543236 100644 --- a/Sources/WalletConnectNotify/NotifyImports.swift +++ b/Sources/WalletConnectNotify/NotifyImports.swift @@ -1,7 +1,7 @@ #if !CocoaPods @_exported import WalletConnectPairing @_exported import WalletConnectPush -@_exported import WalletConnectHistory +@_exported import WalletConnectIdentity @_exported import WalletConnectSigner @_exported import Database #endif diff --git a/Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift similarity index 100% rename from Sources/WalletConnectHistory/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyGetNotificationsProtocolMethod.swift diff --git a/Sources/WalletConnectHistory/Types/NotifyMessage.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift similarity index 100% rename from Sources/WalletConnectHistory/Types/NotifyMessage.swift rename to Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/notify_get_notifications/NotifyGetNotificationsRequestPayload.swift similarity index 100% rename from Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsRequestPayload.swift rename to Sources/WalletConnectNotify/Types/JWTPayloads/notify_get_notifications/NotifyGetNotificationsRequestPayload.swift diff --git a/Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/notify_get_notifications/NotifyGetNotificationsResponsePayload.swift similarity index 100% rename from Sources/WalletConnectHistory/Payloads/NotifyGetNotificationsResponsePayload.swift rename to Sources/WalletConnectNotify/Types/JWTPayloads/notify_get_notifications/NotifyGetNotificationsResponsePayload.swift From af7f2707defda0b219c670a3cc34cdcaece3da51 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 15:51:15 +0300 Subject: [PATCH 099/169] testWalletCreatesAndUpdatesSubscription reenabled --- Example/ExampleApp.xcodeproj/project.pbxproj | 7 ------- Example/IntegrationTests/Push/NotifyTests.swift | 2 -- Package.swift | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index f90232086..1524f8161 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -61,7 +61,6 @@ A50D53C32ABA055700A4FD8B /* NotifyPreferencesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */; }; A50D53C42ABA055700A4FD8B /* NotifyPreferencesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BF2ABA055700A4FD8B /* NotifyPreferencesInteractor.swift */; }; A50D53C52ABA055700A4FD8B /* NotifyPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53C02ABA055700A4FD8B /* NotifyPreferencesView.swift */; }; - A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */ = {isa = PBXBuildFile; productRef = A50DF19C2A25084A0036EA6C /* WalletConnectHistory */; }; A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; }; A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */; }; A51606F92A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */; }; @@ -767,7 +766,6 @@ A5E03DFF2864662500888481 /* WalletConnect in Frameworks */, A561C80529DFCD4500DF540D /* WalletConnectSync in Frameworks */, A5E03DF52864651200888481 /* Starscream in Frameworks */, - A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */, A5C8BE85292FE20B006CC85C /* Web3 in Frameworks */, 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */, C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */, @@ -2107,7 +2105,6 @@ C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */, A561C80429DFCD4500DF540D /* WalletConnectSync */, A573C53A29EC365800E3CBFD /* HDWalletKit */, - A50DF19C2A25084A0036EA6C /* WalletConnectHistory */, A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */, ); productName = IntegrationTests; @@ -3444,10 +3441,6 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; }; - A50DF19C2A25084A0036EA6C /* WalletConnectHistory */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectHistory; - }; A54195A42934E83F0035AD19 /* Web3 */ = { isa = XCSwiftPackageProductDependency; package = A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */; diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 8d26941ae..5e7d06038 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -165,7 +165,6 @@ final class NotifyTests: XCTestCase { } } - /* func testWalletCreatesAndUpdatesSubscription() async throws { let created = expectation(description: "Subscription created") @@ -202,7 +201,6 @@ final class NotifyTests: XCTestCase { try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) } - */ func testNotifyServerSubscribeAndNotifies() async throws { let subscribeExpectation = expectation(description: "creates notify subscription") diff --git a/Package.swift b/Package.swift index 23593b272..c40c3b05c 100644 --- a/Package.swift +++ b/Package.swift @@ -71,7 +71,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectSigner", "Database"], + dependencies: ["WalletConnectIdentity", "WalletConnectPairing", "WalletConnectPush", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", From f3d2b68a4392472557ab4c1efcb993cabc976899 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 16:01:57 +0300 Subject: [PATCH 100/169] Missing WalletConnectIdentity added --- Example/ExampleApp.xcodeproj/project.pbxproj | 7 +++++++ Example/IntegrationTests/Push/NotifyTests.swift | 1 - Package.swift | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 1524f8161..9a7ee789d 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -176,6 +176,7 @@ A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; }; A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; }; A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */; }; + A5B814FA2B5AAA2F00AECCFD /* WalletConnectIdentity in Frameworks */ = {isa = PBXBuildFile; productRef = A5B814F92B5AAA2F00AECCFD /* WalletConnectIdentity */; }; A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */; }; A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */; }; A5C2020B287D9DEE007E3188 /* WelcomeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C20206287D9DEE007E3188 /* WelcomeModule.swift */; }; @@ -765,6 +766,7 @@ files = ( A5E03DFF2864662500888481 /* WalletConnect in Frameworks */, A561C80529DFCD4500DF540D /* WalletConnectSync in Frameworks */, + A5B814FA2B5AAA2F00AECCFD /* WalletConnectIdentity in Frameworks */, A5E03DF52864651200888481 /* Starscream in Frameworks */, A5C8BE85292FE20B006CC85C /* Web3 in Frameworks */, 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */, @@ -2106,6 +2108,7 @@ A561C80429DFCD4500DF540D /* WalletConnectSync */, A573C53A29EC365800E3CBFD /* HDWalletKit */, A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */, + A5B814F92B5AAA2F00AECCFD /* WalletConnectIdentity */, ); productName = IntegrationTests; productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */; @@ -3523,6 +3526,10 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectNotify; }; + A5B814F92B5AAA2F00AECCFD /* WalletConnectIdentity */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectIdentity; + }; A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 5e7d06038..3fd829afd 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -252,7 +252,6 @@ final class NotifyTests: XCTestCase { try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic) } } - } diff --git a/Package.swift b/Package.swift index c40c3b05c..3321d7eb7 100644 --- a/Package.swift +++ b/Package.swift @@ -46,7 +46,9 @@ let package = Package( .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), - + .library( + name: "WalletConnectIdentity", + targets: ["WalletConnectIdentity"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"), From 3b3ebcc9717d96a9da985e6a83824ab290949a41 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 Jan 2024 14:24:35 +0100 Subject: [PATCH 101/169] savepoint --- .../PresentationLayer/Wallet/Wallet/WalletPresenter.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 1532b17b1..753ad8ad6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -148,7 +148,9 @@ extension WalletPresenter { } private func setUpPairingIndicatorRemoval() { - Web3Wallet.instance.pairingStatePublisher.sink { [weak self] isPairing in + Web3Wallet.instance.pairingStatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isPairing in self?.showPairingLoading = isPairing }.store(in: &disposeBag) } @@ -173,3 +175,4 @@ extension WalletPresenter.Errors: LocalizedError { } } } + From 79fe9950ae7a68e5cba904f6ad36a3b3828ff5bd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 17:11:49 +0300 Subject: [PATCH 102/169] Messages integration tests --- .../IntegrationTests/Push/NotifyTests.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 3fd829afd..fe1125fa9 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -252,6 +252,29 @@ final class NotifyTests: XCTestCase { try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic) } } + + func testFetchHistory() async throws { + let subscribeExpectation = expectation(description: "fetch notify subscription") + let account = Account("eip155:1:0x622b17376F76d72C43527a917f59273247A917b4")! + + var subscription: NotifySubscription! + walletNotifyClientA.subscriptionsPublisher + .sink { subscriptions in + subscription = subscriptions.first + subscribeExpectation.fulfill() + }.store(in: &publishers) + + try await walletNotifyClientA.register(account: account, domain: gmDappDomain) { message in + let privateKey = Data(hex: "c3ff8a0ae33ac5d58e515055c5870fa2f220d070997bd6fd77a5f2c148528ff0") + let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) + return try! signer.sign(message: message, privateKey: privateKey, type: .eip191) + } + + await fulfillment(of: [subscribeExpectation], timeout: InputConfig.defaultTimeout) + + try await walletNotifyClientA.fetchHistory(subscription: subscription) + XCTAssertEqual(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count, 41) + } } From ba16939601fa3f5d8f86069d666fd6c60cfb7c36 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 17:12:06 +0300 Subject: [PATCH 103/169] awaitResponse updated with ack --- .../NetworkingInteractor.swift | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index f0bc0a2bb..078c8adf7 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -147,25 +147,34 @@ public class NetworkingInteractor: NetworkInteracting { responseOfType: Response.Type, envelopeType: Envelope.EnvelopeType ) async throws -> Response { - - try await self.request(request, topic: topic, protocolMethod: method, envelopeType: envelopeType) - return try await withCheckedThrowingContinuation { [unowned self] continuation in var response, error: AnyCancellable? + let cancel: () -> Void = { + response?.cancel() + error?.cancel() + } + response = responseSubscription(on: method) .sink { (payload: ResponseSubscriptionPayload) in - response?.cancel() - error?.cancel() + cancel() continuation.resume(with: .success(payload.response)) } error = responseErrorSubscription(on: method) .sink { (payload: ResponseSubscriptionErrorPayload) in - response?.cancel() - error?.cancel() + cancel() continuation.resume(throwing: payload.error) } + + Task(priority: .high) { + do { + try await self.request(request, topic: topic, protocolMethod: method, envelopeType: envelopeType) + } catch { + cancel() + continuation.resume(throwing: error) + } + } } } From c5319859e6f054e72d75e0ec02b79ce678f75a2d Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 19 Jan 2024 17:23:43 +0300 Subject: [PATCH 104/169] History imports removed --- Example/ExampleApp.xcodeproj/project.pbxproj | 12 ------------ Example/IntegrationTests/Chat/ChatTests.swift | 1 - Example/IntegrationTests/History/HistoryTests.swift | 8 -------- Example/IntegrationTests/Pairing/PairingTests.swift | 1 - Sources/Chat/ChatImports.swift | 1 - WalletConnectSwiftV2.podspec | 7 ------- 6 files changed, 30 deletions(-) delete mode 100644 Example/IntegrationTests/History/HistoryTests.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 9a7ee789d..a5ee276b5 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -78,7 +78,6 @@ A518B31428E33A6500A2CE93 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518B31328E33A6500A2CE93 /* InputConfig.swift */; }; A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0D828E436A3001BACF9 /* InputConfig.swift */; }; A51AC0DF28E4379F001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0DE28E4379F001BACF9 /* InputConfig.swift */; }; - A5321C2B2A250367006CADC3 /* HistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5321C2A2A250367006CADC3 /* HistoryTests.swift */; }; A5417BBE299BFC3E00B469F3 /* ImportAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5417BBD299BFC3E00B469F3 /* ImportAccount.swift */; }; A541959E2934BFEF0035AD19 /* CacaoSignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A541959A2934BFEF0035AD19 /* CacaoSignerTests.swift */; }; A541959F2934BFEF0035AD19 /* SignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A541959B2934BFEF0035AD19 /* SignerTests.swift */; }; @@ -475,7 +474,6 @@ A518B31328E33A6500A2CE93 /* InputConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; A51AC0D828E436A3001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; A51AC0DE28E4379F001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; - A5321C2A2A250367006CADC3 /* HistoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryTests.swift; sourceTree = ""; }; A5417BBD299BFC3E00B469F3 /* ImportAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportAccount.swift; sourceTree = ""; }; A541959A2934BFEF0035AD19 /* CacaoSignerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacaoSignerTests.swift; sourceTree = ""; }; A541959B2934BFEF0035AD19 /* SignerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignerTests.swift; sourceTree = ""; }; @@ -1047,14 +1045,6 @@ path = Settings; sourceTree = ""; }; - A5321C292A25035A006CADC3 /* History */ = { - isa = PBXGroup; - children = ( - A5321C2A2A250367006CADC3 /* HistoryTests.swift */, - ); - path = History; - sourceTree = ""; - }; A54195992934BFDD0035AD19 /* Signer */ = { isa = PBXGroup; children = ( @@ -1481,7 +1471,6 @@ isa = PBXGroup; children = ( 847F07FE2A25DBC700B2A5A4 /* XPlatform */, - A5321C292A25035A006CADC3 /* History */, A561C80129DFCCD300DF540D /* Sync */, 849D7A91292E2115006A2BD4 /* Push */, 84CEC64728D8A98900D081A8 /* Pairing */, @@ -2488,7 +2477,6 @@ 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, 7694A5262874296A0001257E /* RegistryTests.swift in Sources */, A541959F2934BFEF0035AD19 /* SignerTests.swift in Sources */, - A5321C2B2A250367006CADC3 /* HistoryTests.swift in Sources */, A58A1ECC29BF458600A82A20 /* ENSResolverTests.swift in Sources */, A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */, 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */, diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 0b4528fe4..ff09c65f1 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -4,7 +4,6 @@ import XCTest import WalletConnectUtils @testable import WalletConnectKMS @testable import WalletConnectSync -@testable import WalletConnectHistory import WalletConnectRelay import Combine import Web3 diff --git a/Example/IntegrationTests/History/HistoryTests.swift b/Example/IntegrationTests/History/HistoryTests.swift deleted file mode 100644 index 4ebbdd80d..000000000 --- a/Example/IntegrationTests/History/HistoryTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation -import Combine -import XCTest -@testable import WalletConnectHistory - -final class HistoryTests: XCTestCase { - -} diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 657530902..6bc02014c 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -9,7 +9,6 @@ import WalletConnectPush @testable import Auth @testable import WalletConnectPairing @testable import WalletConnectSync -@testable import WalletConnectHistory final class PairingTests: XCTestCase { diff --git a/Sources/Chat/ChatImports.swift b/Sources/Chat/ChatImports.swift index 447ee4a25..24dfe29a5 100644 --- a/Sources/Chat/ChatImports.swift +++ b/Sources/Chat/ChatImports.swift @@ -2,5 +2,4 @@ @_exported import WalletConnectSigner @_exported import WalletConnectIdentity @_exported import WalletConnectSync -@_exported import WalletConnectHistory #endif diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index c3cf47387..5a294b701 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -101,17 +101,10 @@ Pod::Spec.new do |spec| ss.dependency 'WalletConnectSwiftV2/WalletConnectNetworking' end - spec.subspec 'WalletConnectHistory' do |ss| - ss.source_files = 'Sources/WalletConnectHistory/**/*.{h,m,swift}' - ss.dependency 'WalletConnectSwiftV2/WalletConnectRelay' - ss.dependency 'WalletConnectSwiftV2/HTTPClient' - end - spec.subspec 'WalletConnectChat' do |ss| ss.source_files = 'Sources/Chat/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectSync' ss.dependency 'WalletConnectSwiftV2/WalletConnectIdentity' - ss.dependency 'WalletConnectSwiftV2/WalletConnectHistory' end spec.subspec 'WalletConnectSync' do |ss| From a25e2cf6ba108e61439e74a2c6c24a98d7f4fc78 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 08:51:07 +0100 Subject: [PATCH 105/169] Add expiry to pairing uri --- .../DApp/ApplicationLayer/Application.swift | 1 - .../ApplicationLayer/SceneDelegate.swift | 17 +++-- .../Wallet/Wallet/WalletPresenter.swift | 34 +++++----- .../WalletConnectUtils/WalletConnectURI.swift | 64 ++++++++++++++---- .../WalletConnectURITests.swift | 66 +++++++++++++------ 5 files changed, 127 insertions(+), 55 deletions(-) diff --git a/Example/DApp/ApplicationLayer/Application.swift b/Example/DApp/ApplicationLayer/Application.swift index feba5e373..e4c9d7c63 100644 --- a/Example/DApp/ApplicationLayer/Application.swift +++ b/Example/DApp/ApplicationLayer/Application.swift @@ -4,5 +4,4 @@ import WalletConnectUtils final class Application { var uri: WalletConnectURI? - var requestSent = false } diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index c39e67513..6b32b28ef 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -29,7 +29,13 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio window = UIWindow(windowScene: windowScene) window?.makeKeyAndVisible() - app.uri = WalletConnectURI(connectionOptions: connectionOptions) + do { + let uri = try WalletConnectURI(connectionOptions: connectionOptions) + app.uri = uri + } catch { + print("Error initializing WalletConnectURI: \(error.localizedDescription)") + } + app.requestSent = (connectionOptions.urlContexts.first?.url.absoluteString.replacingOccurrences(of: "walletapp://wc?", with: "") == "requestSent") configurators.configure() @@ -37,18 +43,21 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio UNUserNotificationCenter.current().delegate = self } + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { guard let context = URLContexts.first else { return } - let uri = WalletConnectURI(urlContext: context) - - if let uri { + do { + let uri = try WalletConnectURI(urlContext: context) Task { try await Web3Wallet.instance.pair(uri: uri) } + } catch { + print("Error initializing WalletConnectURI: \(error.localizedDescription)") } } + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { open(notification: notification) return [.sound, .banner, .badge] diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 753ad8ad6..ef158d674 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -58,15 +58,15 @@ final class WalletPresenter: ObservableObject { } func onPasteUri() { - router.presentPaste { [weak self] uri in - guard let uri = WalletConnectURI(string: uri) else { - self?.errorMessage = Errors.invalidUri(uri: uri).localizedDescription + router.presentPaste { [weak self] uriString in + do { + let uri = try WalletConnectURI(string: uriString) + print("URI: \(uri)") + self?.pair(uri: uri) + } catch { + self?.errorMessage = error.localizedDescription self?.showError.toggle() - return } - print("URI: \(uri)") - self?.pair(uri: uri) - } onError: { [weak self] error in print(error.localizedDescription) self?.router.dismiss() @@ -74,20 +74,22 @@ final class WalletPresenter: ObservableObject { } func onScanUri() { - router.presentScan { [weak self] uri in - guard let uri = WalletConnectURI(string: uri) else { - self?.errorMessage = Errors.invalidUri(uri: uri).localizedDescription + router.presentScan { [weak self] uriString in + do { + let uri = try WalletConnectURI(string: uriString) + print("URI: \(uri)") + self?.pair(uri: uri) + self?.router.dismiss() + } catch { + self?.errorMessage = error.localizedDescription self?.showError.toggle() - return } - print("URI: \(uri)") - self?.pair(uri: uri) - self?.router.dismiss() - } onError: { error in + } onError: { [weak self] error in print(error.localizedDescription) - self.router.dismiss() + self?.router.dismiss() } } + func removeSession(at indexSet: IndexSet) async { if let index = indexSet.first { diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift index 02d1af1aa..08945a8ea 100644 --- a/Sources/WalletConnectUtils/WalletConnectURI.swift +++ b/Sources/WalletConnectUtils/WalletConnectURI.swift @@ -1,13 +1,18 @@ import Foundation public struct WalletConnectURI: Equatable { + public enum Errors: Error { + case expired + case invalidFormat + } public let topic: String public let version: String public let symKey: String public let relay: RelayProtocolOptions + public let expiryTimestamp: UInt64 public var absoluteString: String { - return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)" + return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)&expiryTimestamp=\(fiveMinutesFromNow)" } public var deeplinkUri: String { @@ -20,12 +25,16 @@ public struct WalletConnectURI: Equatable { self.topic = topic self.symKey = symKey self.relay = relay + + // Only after all properties are initialized, you can use self or its methods + self.expiryTimestamp = fiveMinutesFromNow } - public init?(string: String) { + + public init(string: String) throws { let decodedString = string.removingPercentEncoding ?? string guard let components = Self.parseURIComponents(from: decodedString) else { - return nil + throw Errors.invalidFormat } let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value } @@ -35,19 +44,31 @@ public struct WalletConnectURI: Equatable { let symKey = query?["symKey"], let relayProtocol = query?["relay-protocol"] else { - return nil + throw Errors.invalidFormat } + let relayData = query?["relay-data"] + // Check if expiryTimestamp is provided and valid + if let expiryTimestampString = query?["expiryTimestamp"], + let expiryTimestamp = UInt64(expiryTimestampString), + expiryTimestamp <= UInt64(Date().timeIntervalSince1970) { + throw Errors.expired + } + self.version = version self.topic = topic self.symKey = symKey self.relay = RelayProtocolOptions(protocol: relayProtocol, data: relayData) + // Set expiryTimestamp to 5 minutes in the future if not included in the uri + self.expiryTimestamp = UInt64(query?["expiryTimestamp"] ?? "") ?? fiveMinutesFromNow + } - - public init?(deeplinkUri: URL) { + + + public init(deeplinkUri: URL) throws { let uriString = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") ?? "" - self.init(string: uriString) + try self.init(string: uriString) } private var relayQuery: String { @@ -68,24 +89,41 @@ public struct WalletConnectURI: Equatable { } } +extension WalletConnectURI.Errors: LocalizedError { + public var errorDescription: String? { + switch self { + case .expired: + return NSLocalizedString("The URI has expired.", comment: "Expired URI Error") + case .invalidFormat: + return NSLocalizedString("The format of the URI is invalid.", comment: "Invalid Format URI Error") + } + } +} + + +fileprivate var fiveMinutesFromNow: UInt64 { + return UInt64(Date().timeIntervalSince1970) + 5 * 60 +} + + #if canImport(UIKit) import UIKit extension WalletConnectURI { - public init?(connectionOptions: UIScene.ConnectionOptions) { + public init(connectionOptions: UIScene.ConnectionOptions) throws { if let uri = connectionOptions.urlContexts.first?.url.query?.replacingOccurrences(of: "uri=", with: "") { - self.init(string: uri) + try self.init(string: uri) } else { - return nil + throw Errors.invalidFormat } } - public init?(urlContext: UIOpenURLContext) { + public init(urlContext: UIOpenURLContext) throws { if let uri = urlContext.url.query?.replacingOccurrences(of: "uri=", with: "") { - self.init(string: uri) + try self.init(string: uri) } else { - return nil + throw Errors.invalidFormat } } } diff --git a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift index 6404e7b41..67f48e78a 100644 --- a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift +++ b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift @@ -5,11 +5,11 @@ private func stubURI() -> (uri: WalletConnectURI, string: String) { let topic = Data.randomBytes(count: 32).toHexString() let symKey = Data.randomBytes(count: 32).toHexString() let protocolName = "irn" - let uriString = "wc:\(topic)@2?symKey=\(symKey)&relay-protocol=\(protocolName)" let uri = WalletConnectURI( topic: topic, symKey: symKey, relay: RelayProtocolOptions(protocol: protocolName, data: nil)) + let uriString = uri.absoluteString return (uri, uriString) } @@ -17,26 +17,26 @@ final class WalletConnectURITests: XCTestCase { // MARK: - Init URI with string - func testInitURIToString() { + func testInitURIToString() throws { let input = stubURI() let uriString = input.uri.absoluteString - let outputURI = WalletConnectURI(string: uriString) + let outputURI = try WalletConnectURI(string: uriString) XCTAssertEqual(input.uri, outputURI) - XCTAssertEqual(input.string, outputURI?.absoluteString) + XCTAssertEqual(input.string, outputURI.absoluteString) } - func testInitStringToURI() { + func testInitStringToURI() throws { let inputURIString = stubURI().string - let uri = WalletConnectURI(string: inputURIString) - let outputURIString = uri?.absoluteString + let uri = try WalletConnectURI(string: inputURIString) + let outputURIString = uri.absoluteString XCTAssertEqual(inputURIString, outputURIString) } - func testInitStringToURIAlternate() { + func testInitStringToURIAlternate() throws { let expectedString = stubURI().string let inputURIString = expectedString.replacingOccurrences(of: "wc:", with: "wc://") - let uri = WalletConnectURI(string: inputURIString) - let outputURIString = uri?.absoluteString + let uri = try WalletConnectURI(string: inputURIString) + let outputURIString = uri.absoluteString XCTAssertEqual(expectedString, outputURIString) } @@ -44,39 +44,63 @@ final class WalletConnectURITests: XCTestCase { func testInitFailsBadScheme() { let inputURIString = stubURI().string.replacingOccurrences(of: "wc:", with: "") - let uri = WalletConnectURI(string: inputURIString) - XCTAssertNil(uri) + XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) } func testInitFailsMalformedURL() { let inputURIString = "wc://<" - let uri = WalletConnectURI(string: inputURIString) - XCTAssertNil(uri) + XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) } func testInitFailsNoSymKeyParam() { let input = stubURI() let inputURIString = input.string.replacingOccurrences(of: "symKey=\(input.uri.symKey)", with: "") - let uri = WalletConnectURI(string: inputURIString) - XCTAssertNil(uri) + XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) } func testInitFailsNoRelayParam() { let input = stubURI() let inputURIString = input.string.replacingOccurrences(of: "&relay-protocol=\(input.uri.relay.protocol)", with: "") - let uri = WalletConnectURI(string: inputURIString) - XCTAssertNil(uri) + XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) } - func testInitHandlesURLEncodedString() { + func testInitHandlesURLEncodedString() throws { let input = stubURI() let encodedURIString = input.string .addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "" - let uri = WalletConnectURI(string: encodedURIString) + let uri = try WalletConnectURI(string: encodedURIString) // Assert that the initializer can handle encoded URI and it matches the expected URI XCTAssertEqual(input.uri, uri) - XCTAssertEqual(input.string, uri?.absoluteString) + XCTAssertEqual(input.string, uri.absoluteString) + } + + // MARK: - Expiry Logic Tests + + func testExpiryTimestampIsSet() { + let uri = stubURI().uri + XCTAssertNotNil(uri.expiryTimestamp) + XCTAssertTrue(uri.expiryTimestamp > UInt64(Date().timeIntervalSince1970)) + } + + func testInitFailsIfURIExpired() { + let input = stubURI() + // Create a URI string with an expired timestamp + let expiredTimestamp = UInt64(Date().timeIntervalSince1970) - 300 // 5 minutes in the past + let expiredURIString = "wc:\(input.uri.topic)@\(input.uri.version)?symKey=\(input.uri.symKey)&relay-protocol=\(input.uri.relay.protocol)&expiryTimestamp=\(expiredTimestamp)" + XCTAssertThrowsError(try WalletConnectURI(string: expiredURIString)) + } + + // Test compatibility with old clients that don't include expiryTimestamp in their uri + func testDefaultExpiryTimestampIfNotIncluded() throws { + let input = stubURI().string + // Remove expiryTimestamp from the URI string + let uriStringWithoutExpiry = input.replacingOccurrences(of: "&expiryTimestamp=\(stubURI().uri.expiryTimestamp)", with: "") + let uri = try WalletConnectURI(string: uriStringWithoutExpiry) + + // Check if the expiryTimestamp is set to 5 minutes in the future + let expectedExpiryTimestamp = UInt64(Date().timeIntervalSince1970) + 5 * 60 + XCTAssertTrue(uri.expiryTimestamp >= expectedExpiryTimestamp) } } From 10cbba321c3fe3edfb9c8025462e96e4621c143a Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 09:36:41 +0100 Subject: [PATCH 106/169] add deprecated uri init --- .../Wallet/Wallet/WalletPresenter.swift | 4 ++-- .../WalletConnectUtils/WalletConnectURI.swift | 19 +++++++++++++----- .../WalletConnectURITests.swift | 20 +++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index ef158d674..4fbd34253 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -60,7 +60,7 @@ final class WalletPresenter: ObservableObject { func onPasteUri() { router.presentPaste { [weak self] uriString in do { - let uri = try WalletConnectURI(string: uriString) + let uri = try WalletConnectURI(uriString: uriString) print("URI: \(uri)") self?.pair(uri: uri) } catch { @@ -76,7 +76,7 @@ final class WalletPresenter: ObservableObject { func onScanUri() { router.presentScan { [weak self] uriString in do { - let uri = try WalletConnectURI(string: uriString) + let uri = try WalletConnectURI(uriString: uriString) print("URI: \(uri)") self?.pair(uri: uri) self?.router.dismiss() diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift index 08945a8ea..7195bb19f 100644 --- a/Sources/WalletConnectUtils/WalletConnectURI.swift +++ b/Sources/WalletConnectUtils/WalletConnectURI.swift @@ -30,9 +30,18 @@ public struct WalletConnectURI: Equatable { self.expiryTimestamp = fiveMinutesFromNow } + @available(*, deprecated, message: "Use the throwing initializer instead") + public init?(string: String) { + do { + try self.init(uriString: string) + } catch { + print("Initialization failed: \(error.localizedDescription)") + return nil + } + } - public init(string: String) throws { - let decodedString = string.removingPercentEncoding ?? string + public init(uriString: String) throws { + let decodedString = uriString.removingPercentEncoding ?? uriString guard let components = Self.parseURIComponents(from: decodedString) else { throw Errors.invalidFormat } @@ -68,7 +77,7 @@ public struct WalletConnectURI: Equatable { public init(deeplinkUri: URL) throws { let uriString = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") ?? "" - try self.init(string: uriString) + try self.init(uriString: uriString) } private var relayQuery: String { @@ -113,7 +122,7 @@ import UIKit extension WalletConnectURI { public init(connectionOptions: UIScene.ConnectionOptions) throws { if let uri = connectionOptions.urlContexts.first?.url.query?.replacingOccurrences(of: "uri=", with: "") { - try self.init(string: uri) + try self.init(uriString: uri) } else { throw Errors.invalidFormat } @@ -121,7 +130,7 @@ extension WalletConnectURI { public init(urlContext: UIOpenURLContext) throws { if let uri = urlContext.url.query?.replacingOccurrences(of: "uri=", with: "") { - try self.init(string: uri) + try self.init(uriString: uri) } else { throw Errors.invalidFormat } diff --git a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift index 67f48e78a..be9913382 100644 --- a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift +++ b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift @@ -20,14 +20,14 @@ final class WalletConnectURITests: XCTestCase { func testInitURIToString() throws { let input = stubURI() let uriString = input.uri.absoluteString - let outputURI = try WalletConnectURI(string: uriString) + let outputURI = try WalletConnectURI(uriString: uriString) XCTAssertEqual(input.uri, outputURI) XCTAssertEqual(input.string, outputURI.absoluteString) } func testInitStringToURI() throws { let inputURIString = stubURI().string - let uri = try WalletConnectURI(string: inputURIString) + let uri = try WalletConnectURI(uriString: inputURIString) let outputURIString = uri.absoluteString XCTAssertEqual(inputURIString, outputURIString) } @@ -35,7 +35,7 @@ final class WalletConnectURITests: XCTestCase { func testInitStringToURIAlternate() throws { let expectedString = stubURI().string let inputURIString = expectedString.replacingOccurrences(of: "wc:", with: "wc://") - let uri = try WalletConnectURI(string: inputURIString) + let uri = try WalletConnectURI(uriString: inputURIString) let outputURIString = uri.absoluteString XCTAssertEqual(expectedString, outputURIString) } @@ -44,31 +44,31 @@ final class WalletConnectURITests: XCTestCase { func testInitFailsBadScheme() { let inputURIString = stubURI().string.replacingOccurrences(of: "wc:", with: "") - XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) + XCTAssertThrowsError(try WalletConnectURI(uriString: inputURIString)) } func testInitFailsMalformedURL() { let inputURIString = "wc://<" - XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) + XCTAssertThrowsError(try WalletConnectURI(uriString: inputURIString)) } func testInitFailsNoSymKeyParam() { let input = stubURI() let inputURIString = input.string.replacingOccurrences(of: "symKey=\(input.uri.symKey)", with: "") - XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) + XCTAssertThrowsError(try WalletConnectURI(uriString: inputURIString)) } func testInitFailsNoRelayParam() { let input = stubURI() let inputURIString = input.string.replacingOccurrences(of: "&relay-protocol=\(input.uri.relay.protocol)", with: "") - XCTAssertThrowsError(try WalletConnectURI(string: inputURIString)) + XCTAssertThrowsError(try WalletConnectURI(uriString: inputURIString)) } func testInitHandlesURLEncodedString() throws { let input = stubURI() let encodedURIString = input.string .addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "" - let uri = try WalletConnectURI(string: encodedURIString) + let uri = try WalletConnectURI(uriString: encodedURIString) // Assert that the initializer can handle encoded URI and it matches the expected URI XCTAssertEqual(input.uri, uri) @@ -88,7 +88,7 @@ final class WalletConnectURITests: XCTestCase { // Create a URI string with an expired timestamp let expiredTimestamp = UInt64(Date().timeIntervalSince1970) - 300 // 5 minutes in the past let expiredURIString = "wc:\(input.uri.topic)@\(input.uri.version)?symKey=\(input.uri.symKey)&relay-protocol=\(input.uri.relay.protocol)&expiryTimestamp=\(expiredTimestamp)" - XCTAssertThrowsError(try WalletConnectURI(string: expiredURIString)) + XCTAssertThrowsError(try WalletConnectURI(uriString: expiredURIString)) } // Test compatibility with old clients that don't include expiryTimestamp in their uri @@ -96,7 +96,7 @@ final class WalletConnectURITests: XCTestCase { let input = stubURI().string // Remove expiryTimestamp from the URI string let uriStringWithoutExpiry = input.replacingOccurrences(of: "&expiryTimestamp=\(stubURI().uri.expiryTimestamp)", with: "") - let uri = try WalletConnectURI(string: uriStringWithoutExpiry) + let uri = try WalletConnectURI(uriString: uriStringWithoutExpiry) // Check if the expiryTimestamp is set to 5 minutes in the future let expectedExpiryTimestamp = UInt64(Date().timeIntervalSince1970) + 5 * 60 From e20094fd7b42529215eef8cbf4dc2ebe30b43386 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 11:02:43 +0100 Subject: [PATCH 107/169] fix crash --- .../Modules/Sign/SessionAccount/SessionAccountPresenter.swift | 2 ++ .../DApp/Modules/Sign/SessionAccount/SessionAccountView.swift | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 9d3fecad8..4320f535e 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -13,6 +13,7 @@ final class SessionAccountPresenter: ObservableObject { @Published var errorMessage = String.empty @Published var showRequestSent = false @Published var requesting = false + @Published var lastRequest: Request? private let interactor: SessionAccountInteractor @@ -49,6 +50,7 @@ final class SessionAccountPresenter: ObservableObject { do { await ActivityIndicatorManager.shared.start() try await Sign.instance.request(params: request) + lastRequest = request await ActivityIndicatorManager.shared.stop() requesting = true DispatchQueue.main.async { [weak self] in diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index b4ee2a608..54824e886 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -231,8 +231,7 @@ struct SessionAccountView: View { Spacer() - let record = Sign.instance.getSessionRequestRecord(id: response.id)! - Text(record.request.method) + Text(presenter.lastRequest!.method) .font( Font.system(size: 14, weight: .medium) ) From 97eb2d933d2d4b1d52937c5e3c97c33ce6e5c685 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 11:39:55 +0100 Subject: [PATCH 108/169] remove getSessionRequestRecord --- Sources/WalletConnectSign/Sign/SignClient.swift | 6 ------ Sources/WalletConnectSign/Sign/SignClientProtocol.swift | 1 - Sources/Web3Wallet/Web3WalletClient.swift | 6 ------ 3 files changed, 13 deletions(-) diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index b1c16fcdf..06a69661e 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -327,12 +327,6 @@ public final class SignClient: SignClientProtocol { } } - /// - Parameter id: id of a wc_sessionRequest jsonrpc request - /// - Returns: json rpc record object for given id or nil if record for give id does not exits - public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? { - return historyService.getSessionRequest(id: id) - } - /// Delete all stored data such as: pairings, sessions, keys /// /// - Note: Will unsubscribe from all topics diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index a58e35eb1..452ccbf3b 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -29,5 +29,4 @@ public protocol SignClientProtocol { func cleanup() async throws func getPendingRequests(topic: String?) -> [(request: Request, context: VerifyContext?)] - func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? } diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 4f26b76f0..9947245d3 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -225,12 +225,6 @@ public class Web3WalletClient { signClient.getPendingRequests(topic: topic) } - /// - Parameter id: id of a wc_sessionRequest jsonrpc request - /// - Returns: json rpc record object for given id or nil if record for give id does not exits - public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? { - signClient.getSessionRequestRecord(id: id) - } - /// Query pending authentication requests /// - Returns: Pending authentication requests public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] { From 9eb7a3d165e664d8136284af5bf0e4c366fe333c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 12:21:15 +0100 Subject: [PATCH 109/169] use expiry from uri in wcpairing --- .../xcschemes/WalletConnectPairing.xcscheme | 10 +++++ .../Services/App/AppPairService.swift | 5 ++- .../Types/WCPairing.swift | 39 ++++++++++--------- .../Stubs/WalletConnectURI+Stub.swift | 13 ------- .../AppPairActivationServiceTests.swift | 4 +- .../WCPairingTests.swift | 10 ++--- Tests/Web3WalletTests/Web3WalletTests.swift | 5 --- 7 files changed, 41 insertions(+), 45 deletions(-) delete mode 100644 Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme index dc1c7488b..f4bd38aa8 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + WCPairing { WCPairing(topic: topic, relay: RelayProtocolOptions.stub(), peerMetadata: AppMetadata.stub(), isActive: isActive, expiryDate: expiryDate) } + + init(topic: String, relay: RelayProtocolOptions, peerMetadata: AppMetadata, isActive: Bool = false, requestReceived: Bool = false, expiryDate: Date) { + self.topic = topic + self.relay = relay + self.peerMetadata = peerMetadata + self.active = isActive + self.requestReceived = requestReceived + self.expiryDate = expiryDate + } +} + +extension WalletConnectURI { + public static func stub() -> WalletConnectURI { + WalletConnectURI( + topic: String.generateTopic(), + symKey: SymmetricKey().hexRepresentation, + relay: RelayProtocolOptions(protocol: "", data: nil) + ) + } } + #endif diff --git a/Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift b/Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift deleted file mode 100644 index 67ee5dfe9..000000000 --- a/Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift +++ /dev/null @@ -1,13 +0,0 @@ -import WalletConnectKMS -import WalletConnectUtils - -extension WalletConnectURI { - - public static func stub(isController: Bool = false) -> WalletConnectURI { - WalletConnectURI( - topic: String.generateTopic(), - symKey: SymmetricKey().hexRepresentation, - relay: RelayProtocolOptions(protocol: "", data: nil) - ) - } -} diff --git a/Tests/WalletConnectPairingTests/AppPairActivationServiceTests.swift b/Tests/WalletConnectPairingTests/AppPairActivationServiceTests.swift index 9cfd39b60..601862ad6 100644 --- a/Tests/WalletConnectPairingTests/AppPairActivationServiceTests.swift +++ b/Tests/WalletConnectPairingTests/AppPairActivationServiceTests.swift @@ -22,8 +22,8 @@ final class AppPairActivationServiceTests: XCTestCase { } func testActivate() { - let topic = "topic" - let pairing = WCPairing(topic: topic) + let pairing = WCPairing(uri: WalletConnectURI.stub()) + let topic = pairing.topic let date = pairing.expiryDate storageMock.setPairing(pairing) diff --git a/Tests/WalletConnectPairingTests/WCPairingTests.swift b/Tests/WalletConnectPairingTests/WCPairingTests.swift index f180efee2..8565b5bfa 100644 --- a/Tests/WalletConnectPairingTests/WCPairingTests.swift +++ b/Tests/WalletConnectPairingTests/WCPairingTests.swift @@ -23,21 +23,21 @@ final class WCPairingTests: XCTestCase { } func testInitInactiveFromTopic() { - let pairing = WCPairing(topic: "") + let pairing = WCPairing(uri: WalletConnectURI.stub()) let inactiveExpiry = referenceDate.advanced(by: WCPairing.timeToLiveInactive) XCTAssertFalse(pairing.active) - XCTAssertEqual(pairing.expiryDate, inactiveExpiry) + XCTAssertEqual(pairing.expiryDate.timeIntervalSince1970, inactiveExpiry.timeIntervalSince1970, accuracy: 1) } func testInitInactiveFromURI() { let pairing = WCPairing(uri: WalletConnectURI.stub()) let inactiveExpiry = referenceDate.advanced(by: WCPairing.timeToLiveInactive) XCTAssertFalse(pairing.active) - XCTAssertEqual(pairing.expiryDate, inactiveExpiry) + XCTAssertEqual(pairing.expiryDate.timeIntervalSince1970, inactiveExpiry.timeIntervalSince1970, accuracy: 1) } func testUpdateExpiryForTopic() { - var pairing = WCPairing(topic: "") + var pairing = WCPairing(uri: WalletConnectURI.stub()) let activeExpiry = referenceDate.advanced(by: WCPairing.timeToLiveActive) try? pairing.updateExpiry() XCTAssertEqual(pairing.expiryDate, activeExpiry) @@ -51,7 +51,7 @@ final class WCPairingTests: XCTestCase { } func testActivateTopic() { - var pairing = WCPairing(topic: "") + var pairing = WCPairing(uri: WalletConnectURI.stub()) let activeExpiry = referenceDate.advanced(by: WCPairing.timeToLiveActive) XCTAssertFalse(pairing.active) pairing.activate() diff --git a/Tests/Web3WalletTests/Web3WalletTests.swift b/Tests/Web3WalletTests/Web3WalletTests.swift index 39165751d..520639d83 100644 --- a/Tests/Web3WalletTests/Web3WalletTests.swift +++ b/Tests/Web3WalletTests/Web3WalletTests.swift @@ -249,11 +249,6 @@ final class Web3WalletTests: XCTestCase { XCTAssertEqual(1, pendingRequests.count) } - func testSessionRequestRecordCalledAndNotNil() async { - let sessionRequestRecord = web3WalletClient.getSessionRequestRecord(id: .left("")) - XCTAssertNotNil(sessionRequestRecord) - } - func testAuthPendingRequestsCalledAndNotEmpty() async { let pendingRequests = try! web3WalletClient.getPendingRequests() XCTAssertEqual(1, pendingRequests.count) From f5dac534799b28981c1196e405042ce9d187800e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 16:00:58 +0100 Subject: [PATCH 110/169] not create a session when propose response fails --- .../Engine/Common/ApproveEngine.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 6c5238dbb..899b384cc 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -108,13 +108,13 @@ final class ApproveEngine { let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) let response = RPCResponse(id: payload.id, result: result) - async let proposeResponse: () = networkingInteractor.respond( + async let proposeResponseTask: () = networkingInteractor.respond( topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod() ) - async let settleRequest: () = settle( + async let settleRequestTask: WCSession = settle( topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces, @@ -122,8 +122,11 @@ final class ApproveEngine { pairingTopic: pairingTopic ) - _ = try await [proposeResponse, settleRequest] + _ = try await proposeResponseTask + let session: WCSession = try await settleRequestTask + sessionStore.setSession(session) + onSessionSettle?(session.publicRepresentation()) logger.debug("Session proposal response and settle request have been sent") proposalPayloadsStore.delete(forKey: proposerPubKey) @@ -140,6 +143,8 @@ final class ApproveEngine { throw Errors.proposalNotFound } + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: SessionProposeProtocolMethod(), reason: reason) + if let pairingTopic = rpcHistory.get(recordId: payload.id)?.topic, let pairing = pairingStore.getPairing(forTopic: pairingTopic), !pairing.active { @@ -149,11 +154,9 @@ final class ApproveEngine { proposalPayloadsStore.delete(forKey: proposerPubKey) verifyContextStore.delete(forKey: proposerPubKey) - try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: SessionProposeProtocolMethod(), reason: reason) - } - func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil, pairingTopic: String) async throws { + func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil, pairingTopic: String) async throws -> WCSession { guard let agreementKeys = kms.getAgreementSecret(for: topic) else { throw Errors.agreementMissingOrInvalid } @@ -191,7 +194,6 @@ final class ApproveEngine { logger.debug("Sending session settle request") - sessionStore.setSession(session) let protocolMethod = SessionSettleProtocolMethod() let request = RPCRequest(method: protocolMethod.method, params: settleParams) @@ -200,7 +202,7 @@ final class ApproveEngine { async let settleRequest: () = networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) _ = try await [settleRequest, subscription] - onSessionSettle?(session.publicRepresentation()) + return session } } From f0d60efae6d4e39dc4b62bd46111b01def8c215f Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 22 Jan 2024 19:52:33 +0300 Subject: [PATCH 111/169] Nullable icon and url parsing --- .../Types/DataStructures/NotifyMessage.swift | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift index 6c88dfa7a..f34549757 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift @@ -13,13 +13,24 @@ public struct NotifyMessage: Codable, Equatable { return Date(milliseconds: sent_at) } - public init(id: String, title: String, body: String, icon: String, url: String, type: String, sentAt: Date) { + public init(id: String, title: String, body: String, icon: String?, url: String?, type: String, sentAt: Date) { self.id = id self.title = title self.body = body - self.icon = icon - self.url = url + self.icon = icon ?? "" + self.url = url ?? "" self.type = type self.sent_at = UInt64(sentAt.millisecondsSince1970) } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.title = try container.decode(String.self, forKey: .title) + self.body = try container.decode(String.self, forKey: .body) + self.icon = try container.decodeIfPresent(String.self, forKey: .icon) ?? "" + self.url = try container.decodeIfPresent(String.self, forKey: .url) ?? "" + self.type = try container.decode(String.self, forKey: .type) + self.sent_at = try container.decode(UInt64.self, forKey: .sent_at) + } } From 5f8ab6263d395cab492735b7008233599aa7f77b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 18:46:42 +0100 Subject: [PATCH 112/169] add localised description --- .../RPCHistory/RPCHistory.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index b310586d0..274122c3d 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -15,14 +15,29 @@ public final class RPCHistory { public var timestamp: Date? } - enum HistoryError: Error { + enum HistoryError: Error, LocalizedError { case unidentifiedRequest case unidentifiedResponse case requestDuplicateNotAllowed case responseDuplicateNotAllowed case requestMatchingResponseNotFound + var errorDescription: String? { + switch self { + case .unidentifiedRequest: + return "Unidentified request." + case .unidentifiedResponse: + return "Unidentified response." + case .requestDuplicateNotAllowed: + return "Request duplicates are not allowed." + case .responseDuplicateNotAllowed: + return "Response duplicates are not allowed." + case .requestMatchingResponseNotFound: + return "Matching requesr for the response not found." + } + } } + private let storage: CodableStore init(keyValueStore: CodableStore) { From 93d132c813ca8d9e38a1c3c33cd8036e6da3f00f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 18:52:06 +0100 Subject: [PATCH 113/169] dismiss proposal on pairing expiry --- .../Wallet/SessionProposal/SessionProposalPresenter.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 5e40120f1..3730951c1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -80,6 +80,14 @@ private extension SessionProposalPresenter { dismiss() } }.store(in: &disposeBag) + + Web3Wallet.instance.pairingExpirationPublisher + .receive(on: DispatchQueue.main) + .sink {[weak self] pairing in + if self?.sessionProposal.pairingTopic == pairing.topic { + self?.dismiss() + } + }.store(in: &disposeBag) } } From bff243f00fcb66172c6120d7938c6fa7f7adcd9c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 19:25:10 +0100 Subject: [PATCH 114/169] show alerts on pairing expiry --- Example/WalletApp/ApplicationLayer/SceneDelegate.swift | 1 + Sources/WalletConnectUtils/WalletConnectURI.swift | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index 6b32b28ef..e9a133d89 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -53,6 +53,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio try await Web3Wallet.instance.pair(uri: uri) } } catch { + AlertPresenter.present(message: error.localizedDescription, type: .error) print("Error initializing WalletConnectURI: \(error.localizedDescription)") } } diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift index 7195bb19f..5cc42eaf0 100644 --- a/Sources/WalletConnectUtils/WalletConnectURI.swift +++ b/Sources/WalletConnectUtils/WalletConnectURI.swift @@ -12,7 +12,7 @@ public struct WalletConnectURI: Equatable { public let expiryTimestamp: UInt64 public var absoluteString: String { - return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)&expiryTimestamp=\(fiveMinutesFromNow)" + return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)&expiryTimestamp=\(expiryTimestamp)" } public var deeplinkUri: String { @@ -102,9 +102,9 @@ extension WalletConnectURI.Errors: LocalizedError { public var errorDescription: String? { switch self { case .expired: - return NSLocalizedString("The URI has expired.", comment: "Expired URI Error") + return NSLocalizedString("The WalletConnect Pairing URI has expired.", comment: "Expired URI Error") case .invalidFormat: - return NSLocalizedString("The format of the URI is invalid.", comment: "Invalid Format URI Error") + return NSLocalizedString("The format of the WalletConnect Pairing URI is invalid.", comment: "Invalid Format URI Error") } } } From abe3f153534823a448526149bc793d64722e39a0 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 08:13:57 +0100 Subject: [PATCH 115/169] savepoint --- .../Sign/SessionAccount/SessionAccountView.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index 54824e886..939a9edb6 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -230,13 +230,14 @@ struct SessionAccountView: View { .padding(12) Spacer() - - Text(presenter.lastRequest!.method) - .font( - Font.system(size: 14, weight: .medium) - ) - .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) - .padding(12) + if let lastRequest = presenter.lastRequest { + Text(lastRequest.method) + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(12) + } } ZStack { From bce0e68fc6edcf058a583578c21e9ad5d2484b11 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 08:39:22 +0100 Subject: [PATCH 116/169] refactor activityindicator --- .../Common/ActivityIndicatorManager.swift | 45 +++++++++++-------- .../Common/ActivityIndicatorManager.swift | 45 +++++++++++-------- .../ConnectionDetailsPresenter.swift | 6 +-- .../SessionProposalPresenter.swift | 12 ++--- .../SessionRequestPresenter.swift | 12 ++--- .../Wallet/Wallet/WalletPresenter.swift | 6 +-- 6 files changed, 70 insertions(+), 56 deletions(-) diff --git a/Example/DApp/Common/ActivityIndicatorManager.swift b/Example/DApp/Common/ActivityIndicatorManager.swift index 500731651..f17183296 100644 --- a/Example/DApp/Common/ActivityIndicatorManager.swift +++ b/Example/DApp/Common/ActivityIndicatorManager.swift @@ -3,34 +3,41 @@ import UIKit class ActivityIndicatorManager { static let shared = ActivityIndicatorManager() private var activityIndicator: UIActivityIndicatorView? + private let serialQueue = DispatchQueue(label: "com.yourapp.activityIndicatorManager") private init() {} - func start() async { - await stop() + func start() { + serialQueue.async { + self.stopInternal { + DispatchQueue.main.async { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } - DispatchQueue.main.async { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.color = .white + activityIndicator.startAnimating() + window.addSubview(activityIndicator) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.center = window.center - activityIndicator.color = .white - activityIndicator.startAnimating() - window.addSubview(activityIndicator) + self.activityIndicator = activityIndicator + } + } + } + } - self.activityIndicator = activityIndicator + func stop() { + serialQueue.async { + self.stopInternal(completion: nil) } } - func stop() async { - await withCheckedContinuation { continuation in - DispatchQueue.main.async { - self.activityIndicator?.stopAnimating() - self.activityIndicator?.removeFromSuperview() - self.activityIndicator = nil - continuation.resume() - } + private func stopInternal(completion: (() -> Void)?) { + DispatchQueue.main.async { + self.activityIndicator?.stopAnimating() + self.activityIndicator?.removeFromSuperview() + self.activityIndicator = nil + completion?() } } } diff --git a/Example/WalletApp/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift index 9382be8a3..79b8785e0 100644 --- a/Example/WalletApp/Common/ActivityIndicatorManager.swift +++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift @@ -1,35 +1,42 @@ import UIKit - class ActivityIndicatorManager { static let shared = ActivityIndicatorManager() private var activityIndicator: UIActivityIndicatorView? + private let serialQueue = DispatchQueue(label: "com.yourapp.activityIndicatorManager") private init() {} - func start() async { - await stop() + func start() { + serialQueue.async { + self.stopInternal { + DispatchQueue.main.async { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } - DispatchQueue.main.async { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.color = .white + activityIndicator.startAnimating() + window.addSubview(activityIndicator) - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.center = window.center - activityIndicator.startAnimating() - window.addSubview(activityIndicator) + self.activityIndicator = activityIndicator + } + } + } + } - self.activityIndicator = activityIndicator + func stop() { + serialQueue.async { + self.stopInternal(completion: nil) } } - func stop() async { - await withCheckedContinuation { continuation in - DispatchQueue.main.async { - self.activityIndicator?.stopAnimating() - self.activityIndicator?.removeFromSuperview() - self.activityIndicator = nil - continuation.resume() - } + private func stopInternal(completion: (() -> Void)?) { + DispatchQueue.main.async { + self.activityIndicator?.stopAnimating() + self.activityIndicator?.removeFromSuperview() + self.activityIndicator = nil + completion?() } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift index 7fdc893c5..8ba46dc19 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift @@ -25,14 +25,14 @@ final class ConnectionDetailsPresenter: ObservableObject { func onDelete() { Task { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await interactor.disconnectSession(session: session) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() DispatchQueue.main.async { self.router.dismiss() } } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() print(error) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 3730951c1..e494039a2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -35,12 +35,12 @@ final class SessionProposalPresenter: ObservableObject { @MainActor func onApprove() async throws { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account) showConnected ? showConnectedSheet.toggle() : router.dismiss() - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } @@ -49,12 +49,12 @@ final class SessionProposalPresenter: ObservableObject { @MainActor func onReject() async throws { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await interactor.reject(proposal: sessionProposal) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() router.dismiss() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index bb72f3be7..17d2f9b49 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -43,12 +43,12 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onApprove() async throws { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() let showConnected = try await interactor.respondSessionRequest(sessionRequest: sessionRequest, importAccount: importAccount) showConnected ? showSignedSheet.toggle() : router.dismiss() - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } @@ -57,12 +57,12 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onReject() async throws { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await interactor.respondError(sessionRequest: sessionRequest) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() router.dismiss() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() errorMessage = error.localizedDescription showError.toggle() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 4fbd34253..0affd430f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -94,11 +94,11 @@ final class WalletPresenter: ObservableObject { func removeSession(at indexSet: IndexSet) async { if let index = indexSet.first { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await interactor.disconnectSession(session: sessions[index]) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() sessions = sessions AlertPresenter.present(message: error.localizedDescription, type: .error) } From 9c2a9dac33ec0a812457143e2f7e37f712ccdf72 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 08:48:10 +0100 Subject: [PATCH 117/169] savepoint --- .../SessionAccountPresenter.swift | 6 +++--- Example/DApp/Modules/Sign/SignPresenter.swift | 18 +++++++++--------- .../ApplicationLayer/SceneDelegate.swift | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 4320f535e..4bfb41139 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -48,16 +48,16 @@ final class SessionAccountPresenter: ObservableObject { let request = try Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl) Task { do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await Sign.instance.request(params: request) lastRequest = request - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() requesting = true DispatchQueue.main.async { [weak self] in self?.openWallet() } } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() requesting = false showError.toggle() errorMessage = error.localizedDescription diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 9c3c9ecca..1c5194e2c 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -58,16 +58,16 @@ final class SignPresenter: ObservableObject { let uri = try await Pair.instance.create() walletConnectUri = uri do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await Sign.instance.connect( requiredNamespaces: Proposal.requiredNamespaces, optionalNamespaces: Proposal.optionalNamespaces, topic: uri.topic ) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() router.presentNewPairing(walletConnectUri: uri) } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() } } } @@ -76,12 +76,12 @@ final class SignPresenter: ObservableObject { if let session { Task { @MainActor in do { - await ActivityIndicatorManager.shared.start() + ActivityIndicatorManager.shared.start() try await Sign.instance.disconnect(topic: session.topic) - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() accountsDetails.removeAll() } catch { - await ActivityIndicatorManager.shared.stop() + ActivityIndicatorManager.shared.stop() showError.toggle() errorMessage = error.localizedDescription } @@ -114,21 +114,21 @@ extension SignPresenter { .sink { [unowned self] _ in self.accountsDetails.removeAll() router.popToRoot() - Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + Task(priority: .high) { ActivityIndicatorManager.shared.stop() } } .store(in: &subscriptions) Sign.instance.sessionResponsePublisher .receive(on: DispatchQueue.main) .sink { response in - Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + Task(priority: .high) { ActivityIndicatorManager.shared.stop() } } .store(in: &subscriptions) Sign.instance.requestExpirationPublisher .receive(on: DispatchQueue.main) .sink { _ in - Task(priority: .high) { await ActivityIndicatorManager.shared.stop() } + Task(priority: .high) { ActivityIndicatorManager.shared.stop() } AlertPresenter.present(message: "Session Request has expired", type: .warning) } .store(in: &subscriptions) diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index e9a133d89..6b32b28ef 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -53,7 +53,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio try await Web3Wallet.instance.pair(uri: uri) } } catch { - AlertPresenter.present(message: error.localizedDescription, type: .error) print("Error initializing WalletConnectURI: \(error.localizedDescription)") } } From cd0f67fe633ac1937d42c1d1fcbd5e5fb28e86e5 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 09:36:18 +0100 Subject: [PATCH 118/169] add review suggestions --- .../Common/ActivityIndicatorManager.swift | 33 +++++++++--------- .../SessionAccountPresenter.swift | 2 +- .../Common/ActivityIndicatorManager.swift | 34 +++++++++---------- .../Common/PendingProposalsProvider.swift | 29 ++++++++++++---- .../WalletConnectSign/Sign/SignClient.swift | 4 +++ .../Sign/SignClientProtocol.swift | 2 ++ Sources/Web3Wallet/Web3WalletClient.swift | 4 +++ .../Mocks/SignClientMock.swift | 4 +++ 8 files changed, 70 insertions(+), 42 deletions(-) diff --git a/Example/DApp/Common/ActivityIndicatorManager.swift b/Example/DApp/Common/ActivityIndicatorManager.swift index f17183296..2405ea052 100644 --- a/Example/DApp/Common/ActivityIndicatorManager.swift +++ b/Example/DApp/Common/ActivityIndicatorManager.swift @@ -9,35 +9,34 @@ class ActivityIndicatorManager { func start() { serialQueue.async { - self.stopInternal { - DispatchQueue.main.async { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } - - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.center = window.center - activityIndicator.color = .white - activityIndicator.startAnimating() - window.addSubview(activityIndicator) - - self.activityIndicator = activityIndicator - } + self.stopInternal() + + DispatchQueue.main.async { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.color = .white + activityIndicator.startAnimating() + window.addSubview(activityIndicator) + + self.activityIndicator = activityIndicator } } } func stop() { serialQueue.async { - self.stopInternal(completion: nil) + self.stopInternal() } } - private func stopInternal(completion: (() -> Void)?) { - DispatchQueue.main.async { + private func stopInternal() { + DispatchQueue.main.sync { self.activityIndicator?.stopAnimating() self.activityIndicator?.removeFromSuperview() self.activityIndicator = nil - completion?() } } } diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index 4bfb41139..31920ce46 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -13,7 +13,7 @@ final class SessionAccountPresenter: ObservableObject { @Published var errorMessage = String.empty @Published var showRequestSent = false @Published var requesting = false - @Published var lastRequest: Request? + var lastRequest: Request? private let interactor: SessionAccountInteractor diff --git a/Example/WalletApp/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift index 79b8785e0..2405ea052 100644 --- a/Example/WalletApp/Common/ActivityIndicatorManager.swift +++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift @@ -1,4 +1,5 @@ import UIKit + class ActivityIndicatorManager { static let shared = ActivityIndicatorManager() private var activityIndicator: UIActivityIndicatorView? @@ -8,35 +9,34 @@ class ActivityIndicatorManager { func start() { serialQueue.async { - self.stopInternal { - DispatchQueue.main.async { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } - - let activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.center = window.center - activityIndicator.color = .white - activityIndicator.startAnimating() - window.addSubview(activityIndicator) - - self.activityIndicator = activityIndicator - } + self.stopInternal() + + DispatchQueue.main.async { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { return } + + let activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.center = window.center + activityIndicator.color = .white + activityIndicator.startAnimating() + window.addSubview(activityIndicator) + + self.activityIndicator = activityIndicator } } } func stop() { serialQueue.async { - self.stopInternal(completion: nil) + self.stopInternal() } } - private func stopInternal(completion: (() -> Void)?) { - DispatchQueue.main.async { + private func stopInternal() { + DispatchQueue.main.sync { self.activityIndicator?.stopAnimating() self.activityIndicator?.removeFromSuperview() self.activityIndicator = nil - completion?() } } } diff --git a/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift b/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift index 6bebd4e31..4af7e3808 100644 --- a/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift +++ b/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift @@ -2,11 +2,11 @@ import Foundation import Combine class PendingProposalsProvider { - + private let proposalPayloadsStore: CodableStore> private let verifyContextStore: CodableStore private var publishers = Set() - private let pendingProposalsPublisherSubject = PassthroughSubject<[(proposal: Session.Proposal, context: VerifyContext?)], Never>() + private let pendingProposalsPublisherSubject = CurrentValueSubject<[(proposal: Session.Proposal, context: VerifyContext?)], Never>([]) var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> { return pendingProposalsPublisherSubject.eraseToAnyPublisher() @@ -18,17 +18,32 @@ class PendingProposalsProvider { { self.proposalPayloadsStore = proposalPayloadsStore self.verifyContextStore = verifyContextStore + updatePendingProposals() setUpPendingProposalsPublisher() } + private func updatePendingProposals() { + let proposalsWithVerifyContext = getPendingProposals() + pendingProposalsPublisherSubject.send(proposalsWithVerifyContext) + } + func setUpPendingProposalsPublisher() { proposalPayloadsStore.storeUpdatePublisher.sink { [unowned self] _ in - let proposals = proposalPayloadsStore.getAll() + updatePendingProposals() + }.store(in: &publishers) + } - let proposalsWithVerifyContext = proposals.map { ($0.request.publicRepresentation(pairingTopic: $0.topic), try? verifyContextStore.get(key: $0.request.proposer.publicKey)) - } - pendingProposalsPublisherSubject.send(proposalsWithVerifyContext) + private func getPendingProposals() -> [(proposal: Session.Proposal, context: VerifyContext?)] { + let proposals = proposalPayloadsStore.getAll() + return proposals.map { ($0.request.publicRepresentation(pairingTopic: $0.topic), try? verifyContextStore.get(key: $0.request.proposer.publicKey)) } + } - }.store(in: &publishers) + public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] { + if let topic = topic { + return getPendingProposals().filter { $0.proposal.pairingTopic == topic } + } else { + return getPendingProposals() + } } + } diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 06a69661e..9c4b5e14d 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -327,6 +327,10 @@ public final class SignClient: SignClientProtocol { } } + public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] { + pendingProposalsProvider.getPendingProposals() + } + /// Delete all stored data such as: pairings, sessions, keys /// /// - Note: Will unsubscribe from all topics diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 452ccbf3b..4aecfac04 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -29,4 +29,6 @@ public protocol SignClientProtocol { func cleanup() async throws func getPendingRequests(topic: String?) -> [(request: Request, context: VerifyContext?)] + func getPendingProposals(topic: String?) -> [(proposal: Session.Proposal, context: VerifyContext?)] } + diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 9947245d3..0930b8443 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -225,6 +225,10 @@ public class Web3WalletClient { signClient.getPendingRequests(topic: topic) } + public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] { + signClient.getPendingProposals(topic: topic) + } + /// Query pending authentication requests /// - Returns: Pending authentication requests public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] { diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index 4d74e4b47..65b63b9c3 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -145,6 +145,10 @@ final class SignClientMock: SignClientProtocol { return [WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())] } + func getPendingProposals(topic: String?) -> [(proposal: WalletConnectSign.Session.Proposal, context: VerifyContext?)] { + return [] + } + func getPendingRequests(topic: String?) -> [(request: WalletConnectSign.Request, context: WalletConnectSign.VerifyContext?)] { return [(request, nil)] } From 5fdb29aa7223bca24912e66a12ad57908dce7944 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 10:27:27 +0100 Subject: [PATCH 119/169] fix wc modal tests --- Sources/WalletConnectUtils/WalletConnectURI.swift | 12 ++++++++++++ .../Mocks/ModalSheetInteractorMock.swift | 4 ++-- .../ModalViewModelTests.swift | 10 +++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift index 5cc42eaf0..4e8701b50 100644 --- a/Sources/WalletConnectUtils/WalletConnectURI.swift +++ b/Sources/WalletConnectUtils/WalletConnectURI.swift @@ -136,5 +136,17 @@ extension WalletConnectURI { } } } +#endif +#if DEBUG +extension WalletConnectURI { + init(topic: String, symKey: String, relay: RelayProtocolOptions, expiryTimestamp: UInt64) { + self.version = "2" + self.topic = topic + self.symKey = symKey + self.relay = relay + self.expiryTimestamp = expiryTimestamp + } + +} #endif diff --git a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift index 182e50d0f..bfc9a34b6 100644 --- a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift +++ b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift @@ -1,7 +1,7 @@ import Combine import Foundation import WalletConnectSign -import WalletConnectUtils +@testable import WalletConnectUtils @testable import WalletConnectModal @testable import WalletConnectSign @@ -18,7 +18,7 @@ final class ModalSheetInteractorMock: ModalSheetInteractor { } func createPairingAndConnect() async throws -> WalletConnectURI? { - .init(topic: "foo", symKey: "bar", relay: .init(protocol: "irn", data: nil)) + .init(topic: "foo", symKey: "bar", relay: .init(protocol: "irn", data: nil), expiryTimestamp: 1706001526) } var sessionSettlePublisher: AnyPublisher { diff --git a/Tests/WalletConnectModalTests/ModalViewModelTests.swift b/Tests/WalletConnectModalTests/ModalViewModelTests.swift index 55de25cc9..2b9fd7c89 100644 --- a/Tests/WalletConnectModalTests/ModalViewModelTests.swift +++ b/Tests/WalletConnectModalTests/ModalViewModelTests.swift @@ -82,7 +82,7 @@ final class ModalViewModelTests: XCTestCase { await sut.fetchWallets() await sut.createURI() - XCTAssertEqual(sut.uri, "wc:foo@2?symKey=bar&relay-protocol=irn") + XCTAssertEqual(sut.uri, "wc:foo@2?symKey=bar&relay-protocol=irn&expiryTimestamp=1706001526") XCTAssertEqual(sut.wallets.count, 2) XCTAssertEqual(sut.wallets.map(\.id), ["1", "2"]) XCTAssertEqual(sut.wallets.map(\.name), ["Sample App", "Awesome App"]) @@ -94,7 +94,7 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "https://example.com/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + URL(string: "https://example.com/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) expectation = XCTestExpectation(description: "Wait for openUrl to be called using universal link") @@ -104,7 +104,7 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link") @@ -114,7 +114,7 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "https://awesome.com/awesome/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + URL(string: "https://awesome.com/awesome/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link") @@ -124,7 +124,7 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "https://awesome.com/awesome/desktop/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + URL(string: "https://awesome.com/awesome/desktop/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) } } From 3e9b7fc6b3e37d3c139ce6c8eabf3054b4e9d896 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 10:34:26 +0100 Subject: [PATCH 120/169] fix sign tests --- Tests/WalletConnectSignTests/ApproveEngineTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index d40aee9dc..c840d0ad0 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -80,6 +80,7 @@ final class ApproveEngineTests: XCTestCase { XCTAssertTrue(networkingInteractor.didCallSubscribe) XCTAssert(cryptoMock.hasAgreementSecret(for: topicB), "Responder must store agreement key for topic B") XCTAssertEqual(networkingInteractor.didRespondOnTopic!, topicA, "Responder must respond on topic A") + XCTAssertTrue(sessionStorageMock.hasSession(forTopic: topicB), "Responder must persist session on topic B") XCTAssertTrue(pairingRegisterer.isActivateCalled) } @@ -105,8 +106,7 @@ final class ApproveEngineTests: XCTestCase { let topicB = String.generateTopic() cryptoMock.setAgreementSecret(agreementKeys, topic: topicB) let proposal = SessionProposal.stub(proposerPubKey: AgreementPrivateKey().publicKey.hexRepresentation) - try await engine.settle(topic: topicB, proposal: proposal, namespaces: SessionNamespace.stubDictionary(), pairingTopic: "") - XCTAssertTrue(sessionStorageMock.hasSession(forTopic: topicB), "Responder must persist session on topic B") + _ = try await engine.settle(topic: topicB, proposal: proposal, namespaces: SessionNamespace.stubDictionary(), pairingTopic: "") XCTAssert(networkingInteractor.didSubscribe(to: topicB), "Responder must subscribe for topic B") XCTAssertTrue(networkingInteractor.didCallRequest, "Responder must send session settle payload on topic B") } From 3ac1823941ecb7d2fcac7287db42b969c33d69bf Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 13:22:31 +0300 Subject: [PATCH 121/169] testFetchHistory stability --- Example/IntegrationTests/Push/NotifyTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index fe1125fa9..5644fcd89 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -273,7 +273,7 @@ final class NotifyTests: XCTestCase { await fulfillment(of: [subscribeExpectation], timeout: InputConfig.defaultTimeout) try await walletNotifyClientA.fetchHistory(subscription: subscription) - XCTAssertEqual(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count, 41) + XCTAssertTrue(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count > 40) } } From bb2f36db277a5fcd1ff9949395749bab33635af6 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 11:33:16 +0100 Subject: [PATCH 122/169] fix compatibility issue --- .../WalletConnectSign/Engine/Common/SessionEngine.swift | 4 ++-- Sources/WalletConnectSign/Request.swift | 8 ++++---- Sources/WalletConnectSign/Services/HistoryService.swift | 2 +- Sources/WalletConnectSign/SignDecryptionService.swift | 2 +- Sources/WalletConnectSign/Types/Session/SessionType.swift | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 9d252423f..9d0c034f6 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -63,7 +63,7 @@ final class SessionEngine { guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else { throw WalletConnectError.invalidPermissions } - let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiryTimestamp) + let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiryTimestamp: request.expiryTimestamp) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) let ttl = try request.calculateTtl() let protocolMethod = SessionRequestProtocolMethod(ttl: ttl) @@ -229,7 +229,7 @@ private extension SessionEngine { method: payload.request.request.method, params: payload.request.request.params, chainId: payload.request.chainId, - expiry: payload.request.request.expiry + expiryTimestamp: payload.request.request.expiryTimestamp ) guard let session = sessionStore.getSession(forTopic: topic) else { return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod) diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index f5404ec0b..3898d45df 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -30,7 +30,7 @@ public struct Request: Codable, Equatable { } let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl) - self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: calculatedExpiry) + self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiryTimestamp: calculatedExpiry) } init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, ttl: TimeInterval = 300) throws where C: Codable { @@ -39,16 +39,16 @@ public struct Request: Codable, Equatable { } let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl) - self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiry: calculatedExpiry) + self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiryTimestamp: calculatedExpiry) } - internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64?) { + internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiryTimestamp: UInt64?) { self.id = id self.topic = topic self.method = method self.params = params self.chainId = chainId - self.expiryTimestamp = expiry + self.expiryTimestamp = expiryTimestamp } func isExpired(currentDate: Date = Date()) -> Bool { diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index 95129d427..a34f52bb5 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -50,7 +50,7 @@ private extension HistoryService { method: request.request.method, params: request.request.params, chainId: request.chainId, - expiry: request.request.expiry + expiryTimestamp: request.request.expiryTimestamp ) return (mappedRequest, record.id) diff --git a/Sources/WalletConnectSign/SignDecryptionService.swift b/Sources/WalletConnectSign/SignDecryptionService.swift index 8b0e82125..588d07337 100644 --- a/Sources/WalletConnectSign/SignDecryptionService.swift +++ b/Sources/WalletConnectSign/SignDecryptionService.swift @@ -37,7 +37,7 @@ public class SignDecryptionService { method: request.request.method, params: request.request.params, chainId: request.chainId, - expiry: request.request.expiry + expiryTimestamp: request.request.expiryTimestamp ) return request diff --git a/Sources/WalletConnectSign/Types/Session/SessionType.swift b/Sources/WalletConnectSign/Types/Session/SessionType.swift index cc838f084..d4411aa9a 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionType.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionType.swift @@ -43,7 +43,7 @@ internal enum SessionType { struct Request: Codable, Equatable { let method: String let params: AnyCodable - let expiry: UInt64? + let expiryTimestamp: UInt64? } } From a05be877ef7a744bcb8dabc96cb573fb0620de76 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 14:06:09 +0300 Subject: [PATCH 123/169] NetworkMonitor in Dispatcher --- .../NetworkingInteractor.swift | 5 +---- Sources/WalletConnectRelay/Dispatching.swift | 16 ++++++++++++---- .../WalletConnectRelay/NetworkMonitoring.swift | 7 ++++++- Sources/WalletConnectRelay/RelayClient.swift | 4 ++++ .../WalletConnectRelay/RelayClientFactory.swift | 8 +++++++- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 078c8adf7..08ace2bdb 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -30,8 +30,6 @@ public class NetworkingInteractor: NetworkInteracting { public var networkConnectionStatusPublisher: AnyPublisher public var socketConnectionStatusPublisher: AnyPublisher - private let networkMonitor: NetworkMonitoring - public init( relayClient: RelayClient, serializer: Serializing, @@ -43,8 +41,7 @@ public class NetworkingInteractor: NetworkInteracting { self.rpcHistory = rpcHistory self.logger = logger self.socketConnectionStatusPublisher = relayClient.socketConnectionStatusPublisher - self.networkMonitor = NetworkMonitor() - self.networkConnectionStatusPublisher = networkMonitor.networkConnectionStatusPublisher + self.networkConnectionStatusPublisher = relayClient.networkConnectionStatusPublisher setupRelaySubscribtion() } diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index bf3ef2c92..9327608e6 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -3,6 +3,7 @@ import Combine protocol Dispatching { var onMessage: ((String) -> Void)? { get set } + var networkConnectionStatusPublisher: AnyPublisher { get } var socketConnectionStatusPublisher: AnyPublisher { get } func send(_ string: String, completion: @escaping (Error?) -> Void) func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) @@ -17,8 +18,9 @@ final class Dispatcher: NSObject, Dispatching { var socketConnectionHandler: SocketConnectionHandler private let relayUrlFactory: RelayUrlFactory + private let networkMonitor: NetworkMonitoring private let logger: ConsoleLogging - + private let defaultTimeout: Int = 5 /// The property is used to determine whether relay.walletconnect.org will be used /// in case relay.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location). @@ -30,15 +32,21 @@ final class Dispatcher: NSObject, Dispatching { socketConnectionStatusPublisherSubject.eraseToAnyPublisher() } + var networkConnectionStatusPublisher: AnyPublisher { + networkMonitor.networkConnectionStatusPublisher + } + private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.dispatcher", attributes: .concurrent) init( socketFactory: WebSocketFactory, relayUrlFactory: RelayUrlFactory, + networkMonitor: NetworkMonitoring, socketConnectionType: SocketConnectionType, logger: ConsoleLogging ) { self.relayUrlFactory = relayUrlFactory + self.networkMonitor = networkMonitor self.logger = logger let socket = socketFactory.create(with: relayUrlFactory.create(fallback: fallback)) @@ -69,13 +77,13 @@ final class Dispatcher: NSObject, Dispatching { } func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) { - guard !socket.isConnected else { + guard !socket.isConnected, !networkMonitor.isConnected else { return send(string, completion: completion) } var cancellable: AnyCancellable? - cancellable = socketConnectionStatusPublisher - .filter { $0 == .connected } + cancellable = Publishers.CombineLatest(socketConnectionStatusPublisher, networkConnectionStatusPublisher) + .filter { $0.0 == .connected && $0.1 == .connected } .setFailureType(to: NetworkError.self) .timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .webSocketNotConnected }) .sink(receiveCompletion: { [unowned self] result in diff --git a/Sources/WalletConnectRelay/NetworkMonitoring.swift b/Sources/WalletConnectRelay/NetworkMonitoring.swift index 1d3932db5..e6c6b4477 100644 --- a/Sources/WalletConnectRelay/NetworkMonitoring.swift +++ b/Sources/WalletConnectRelay/NetworkMonitoring.swift @@ -8,6 +8,7 @@ public enum NetworkConnectionStatus { } public protocol NetworkMonitoring: AnyObject { + var isConnected: Bool { get } var networkConnectionStatusPublisher: AnyPublisher { get } } @@ -16,7 +17,11 @@ public final class NetworkMonitor: NetworkMonitoring { private let workerQueue = DispatchQueue(label: "com.walletconnect.sdk.network.monitor") private let networkConnectionStatusPublisherSubject = CurrentValueSubject(.connected) - + + public var isConnected: Bool { + return networkConnectionStatusPublisherSubject.value == .connected + } + public var networkConnectionStatusPublisher: AnyPublisher { networkConnectionStatusPublisherSubject .share() diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 24cae47c0..3febc4c60 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -27,6 +27,10 @@ public final class RelayClient { dispatcher.socketConnectionStatusPublisher } + public var networkConnectionStatusPublisher: AnyPublisher { + dispatcher.networkConnectionStatusPublisher + } + private let messagePublisherSubject = PassthroughSubject<(topic: String, message: String, publishedAt: Date), Never>() private let subscriptionResponsePublisherSubject = PassthroughSubject<(RPCID?, [String]), Never>() diff --git a/Sources/WalletConnectRelay/RelayClientFactory.swift b/Sources/WalletConnectRelay/RelayClientFactory.swift index 98066e6c8..b59a50d29 100644 --- a/Sources/WalletConnectRelay/RelayClientFactory.swift +++ b/Sources/WalletConnectRelay/RelayClientFactory.swift @@ -20,6 +20,8 @@ public struct RelayClientFactory { let logger = ConsoleLogger(prefix: "🚄" ,loggingLevel: .off) + let networkMonitor = NetworkMonitor() + return RelayClientFactory.create( relayHost: relayHost, projectId: projectId, @@ -27,6 +29,7 @@ public struct RelayClientFactory { keychainStorage: keychainStorage, socketFactory: socketFactory, socketConnectionType: socketConnectionType, + networkMonitor: networkMonitor, logger: logger ) } @@ -39,6 +42,7 @@ public struct RelayClientFactory { keychainStorage: KeychainStorageProtocol, socketFactory: WebSocketFactory, socketConnectionType: SocketConnectionType = .automatic, + networkMonitor: NetworkMonitoring, logger: ConsoleLogging ) -> RelayClient { @@ -52,9 +56,11 @@ public struct RelayClientFactory { projectId: projectId, socketAuthenticator: socketAuthenticator ) + let dispatcher = Dispatcher( socketFactory: socketFactory, - relayUrlFactory: relayUrlFactory, + relayUrlFactory: relayUrlFactory, + networkMonitor: networkMonitor, socketConnectionType: socketConnectionType, logger: logger ) From 6489245568a8a36f527b1aa627f61f199277ba5a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 14:36:58 +0300 Subject: [PATCH 124/169] protectedSend logic fixed --- Sources/WalletConnectRelay/Dispatching.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index 9327608e6..f32d7a5e8 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -77,7 +77,7 @@ final class Dispatcher: NSObject, Dispatching { } func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) { - guard !socket.isConnected, !networkMonitor.isConnected else { + guard !socket.isConnected || !networkMonitor.isConnected else { return send(string, completion: completion) } From 8f54f0b5d02fd6740aeda118483ff1a6d7f9dc66 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 14:37:06 +0300 Subject: [PATCH 125/169] Integration tests repaired --- Example/IntegrationTests/Auth/AuthTests.swift | 1 + Example/IntegrationTests/Chat/ChatTests.swift | 1 + Example/IntegrationTests/Pairing/PairingTests.swift | 1 + Example/IntegrationTests/Push/NotifyTests.swift | 1 + Example/IntegrationTests/Sign/SignClientTests.swift | 1 + Example/IntegrationTests/Sync/SyncTests.swift | 1 + .../XPlatform/Web3Wallet/XPlatformW3WTests.swift | 1 + 7 files changed, 7 insertions(+) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 276e14e84..ab831899f 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -40,6 +40,7 @@ final class AuthTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: logger) let networkingClient = NetworkingClientFactory.create( diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index ff09c65f1..747bd29a3 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -56,6 +56,7 @@ final class ChatTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: logger) let networkingInteractor = NetworkingClientFactory.create( diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 6bc02014c..d9e83de6e 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -34,6 +34,7 @@ final class PairingTests: XCTestCase { keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: logger) let networkingClient = NetworkingClientFactory.create( diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 5644fcd89..26400548c 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -45,6 +45,7 @@ final class NotifyTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: relayLogger) let networkingClient = NetworkingClientFactory.create( diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 28202a224..9e0fc6867 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -26,6 +26,7 @@ final class SignClientTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: logger ) diff --git a/Example/IntegrationTests/Sync/SyncTests.swift b/Example/IntegrationTests/Sync/SyncTests.swift index adcfdc532..5e19c1345 100644 --- a/Example/IntegrationTests/Sync/SyncTests.swift +++ b/Example/IntegrationTests/Sync/SyncTests.swift @@ -63,6 +63,7 @@ final class SyncTests: XCTestCase { keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: logger) let networkingInteractor = NetworkingClientFactory.create( relayClient: relayClient, diff --git a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift index 3d794d18a..c2f18b4c1 100644 --- a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift +++ b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift @@ -33,6 +33,7 @@ final class XPlatformW3WTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), + networkMonitor: NetworkMonitor(), logger: relayLogger ) From ff5a895561cb529d2fc13edfd1ff52e1bcbf959b Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 14:50:32 +0300 Subject: [PATCH 126/169] Relayer tests updated --- Tests/RelayerTests/DispatcherTests.swift | 4 +++- Tests/RelayerTests/Mocks/DispatcherMock.swift | 4 ++++ Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift index 8d86455df..331bd640d 100644 --- a/Tests/RelayerTests/DispatcherTests.swift +++ b/Tests/RelayerTests/DispatcherTests.swift @@ -62,6 +62,7 @@ final class DispatcherTests: XCTestCase { networkMonitor = NetworkMonitoringMock() let defaults = RuntimeKeyValueStorage() let logger = ConsoleLoggerMock() + let networkMonitor = NetworkMonitoringMock() let keychainStorageMock = DispatcherKeychainStorageMock() let clientIdStorage = ClientIdStorage(defaults: defaults, keychain: keychainStorageMock, logger: logger) let socketAuthenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage) @@ -72,7 +73,8 @@ final class DispatcherTests: XCTestCase { ) sut = Dispatcher( socketFactory: webSocketFactory, - relayUrlFactory: relayUrlFactory, + relayUrlFactory: relayUrlFactory, + networkMonitor: networkMonitor, socketConnectionType: .manual, logger: ConsoleLoggerMock() ) diff --git a/Tests/RelayerTests/Mocks/DispatcherMock.swift b/Tests/RelayerTests/Mocks/DispatcherMock.swift index d5088bf61..869e3a0f9 100644 --- a/Tests/RelayerTests/Mocks/DispatcherMock.swift +++ b/Tests/RelayerTests/Mocks/DispatcherMock.swift @@ -4,11 +4,15 @@ import Combine @testable import WalletConnectRelay class DispatcherMock: Dispatching { + private var publishers = Set() private let socketConnectionStatusPublisherSubject = CurrentValueSubject(.disconnected) var socketConnectionStatusPublisher: AnyPublisher { return socketConnectionStatusPublisherSubject.eraseToAnyPublisher() } + var networkConnectionStatusPublisher: AnyPublisher { + return Just(.connected).eraseToAnyPublisher() + } var sent = false var lastMessage: String = "" diff --git a/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift b/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift index 1095d1677..bfbad58cf 100644 --- a/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift +++ b/Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift @@ -4,6 +4,10 @@ import Combine @testable import WalletConnectRelay class NetworkMonitoringMock: NetworkMonitoring { + var isConnected: Bool { + return true + } + var networkConnectionStatusPublisher: AnyPublisher { networkConnectionStatusPublisherSubject.eraseToAnyPublisher() } From c8fd1670ffed79e807121d0192f48d9204b11769 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 15:05:06 +0300 Subject: [PATCH 127/169] RelayClientEndToEndTests repaired --- Example/RelayIntegrationTests/RelayClientEndToEndTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index 119a62901..4fee45c97 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -47,6 +47,7 @@ final class RelayClientEndToEndTests: XCTestCase { let dispatcher = Dispatcher( socketFactory: webSocketFactory, relayUrlFactory: urlFactory, + networkMonitor: NetworkMonitor(), socketConnectionType: .manual, logger: logger ) From e05bce532d97e0bf01e9a188f103976505d3fb37 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 15:42:01 +0300 Subject: [PATCH 128/169] Update RelayClientEndToEndTests.swift --- .../RelayIntegrationTests/RelayClientEndToEndTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index 4fee45c97..3ac8d75b7 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -44,10 +44,11 @@ final class RelayClientEndToEndTests: XCTestCase { ) let socket = WebSocket(url: urlFactory.create(fallback: false)) let webSocketFactory = WebSocketFactoryMock(webSocket: socket) + let networkMonitor = NetworkMonitor() let dispatcher = Dispatcher( socketFactory: webSocketFactory, relayUrlFactory: urlFactory, - networkMonitor: NetworkMonitor(), + networkMonitor: networkMonitor, socketConnectionType: .manual, logger: logger ) @@ -58,7 +59,8 @@ final class RelayClientEndToEndTests: XCTestCase { keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), - socketConnectionType: .manual, + socketConnectionType: .manual, + networkMonitor: networkMonitor, logger: logger ) let clientId = try! relayClient.getClientId() From 14cbe5214e97139da89ca8469b9220921ce076fb Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 16:15:12 +0300 Subject: [PATCH 129/169] Unsubscribe optional completion --- Sources/WalletConnectRelay/RelayClient.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 3febc4c60..5ff6135fd 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -169,9 +169,9 @@ public final class RelayClient { } } - public func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) { + public func unsubscribe(topic: String, completion: ((Error?) -> Void)?) { guard let subscriptionId = subscriptions[topic] else { - completion(Errors.subscriptionIdNotFound) + completion?(Errors.subscriptionIdNotFound) return } logger.debug("Unsubscribing from topic: \(topic)") @@ -183,12 +183,12 @@ public final class RelayClient { dispatcher.protectedSend(message) { [weak self] error in if let error = error { self?.logger.debug("Failed to unsubscribe from topic") - completion(error) + completion?(error) } else { self?.concurrentQueue.async(flags: .barrier) { self?.subscriptions[topic] = nil } - completion(nil) + completion?(nil) } } } From 5774ada2e4673b7242b968537eaca91f0702cbe5 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 17:06:00 +0300 Subject: [PATCH 130/169] RPCHistory cleanup on error --- .../NetworkingInteractor.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 08ace2bdb..d086b8db8 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -202,8 +202,21 @@ public class NetworkingInteractor: NetworkInteracting { public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { try rpcHistory.set(request, forTopic: topic, emmitedBy: .local) - let message = try serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) - try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.requestConfig.tag, prompt: protocolMethod.requestConfig.prompt, ttl: protocolMethod.requestConfig.ttl) + + do { + let message = try serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) + + try await relayClient.publish(topic: topic, + payload: message, + tag: protocolMethod.requestConfig.tag, + prompt: protocolMethod.requestConfig.prompt, + ttl: protocolMethod.requestConfig.ttl) + } catch { + if let id = request.id { + rpcHistory.delete(id: id) + } + throw error + } } public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { From 39085d52d16de98c027de3077ca30978bb15a638 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 08:31:50 +0100 Subject: [PATCH 131/169] saveopint --- Example/WalletApp/Common/ActivityIndicatorManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift index 2405ea052..9022a6f41 100644 --- a/Example/WalletApp/Common/ActivityIndicatorManager.swift +++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift @@ -17,7 +17,7 @@ class ActivityIndicatorManager { let activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.center = window.center - activityIndicator.color = .white + activityIndicator.color = .blue activityIndicator.startAnimating() window.addSubview(activityIndicator) From ce749eb0f4b4b0b97f46babe28c2daa2840cdac9 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 08:41:57 +0100 Subject: [PATCH 132/169] add alert on uri expired --- Example/WalletApp/ApplicationLayer/SceneDelegate.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index 6b32b28ef..ae9208eff 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -53,7 +53,11 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificatio try await Web3Wallet.instance.pair(uri: uri) } } catch { - print("Error initializing WalletConnectURI: \(error.localizedDescription)") + if case WalletConnectURI.Errors.expired = error { + AlertPresenter.present(message: error.localizedDescription, type: .error) + } else { + print("Error initializing WalletConnectURI: \(error.localizedDescription)") + } } } From 65aaf219404ff582f3134309b975be19bfbb6051 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 09:25:50 +0100 Subject: [PATCH 133/169] fix build --- Tests/WalletConnectSignTests/SessionRequestTests.swift | 2 +- Tests/WalletConnectSignTests/Stub/Stubs.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/WalletConnectSignTests/SessionRequestTests.swift b/Tests/WalletConnectSignTests/SessionRequestTests.swift index 10c70ea3a..3321f2fe1 100644 --- a/Tests/WalletConnectSignTests/SessionRequestTests.swift +++ b/Tests/WalletConnectSignTests/SessionRequestTests.swift @@ -65,7 +65,7 @@ private extension Request { method: "method", params: AnyCodable("params"), chainId: Blockchain("eip155:1")!, - expiry: expiry + expiryTimestamp: expiry ) } } diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index 9666beccf..9c5de2ccb 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -56,7 +56,7 @@ extension RPCRequest { static func stubRequest(method: String, chainId: Blockchain, expiry: UInt64? = nil) -> RPCRequest { let params = SessionType.RequestParams( - request: SessionType.RequestParams.Request(method: method, params: AnyCodable(EmptyCodable()), expiry: expiry), + request: SessionType.RequestParams.Request(method: method, params: AnyCodable(EmptyCodable()), expiryTimestamp: expiry), chainId: chainId) return RPCRequest(method: SessionRequestProtocolMethod().method, params: params) } From ea96e8d1d17097f4b9ed1c6686152b5af6fc2913 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 09:32:39 +0100 Subject: [PATCH 134/169] fix tests build --- Tests/Web3WalletTests/Mocks/SignClientMock.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index 65b63b9c3..f144ef727 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -22,7 +22,7 @@ final class SignClientMock: SignClientProtocol { var requestCalled = false private let metadata = AppMetadata(name: "", description: "", url: "", icons: [], redirect: AppMetadata.Redirect(native: "", universal: nil)) - private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: AnyCodable(""), chainId: Blockchain("eip155:1")!, expiry: nil) + private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: AnyCodable(""), chainId: Blockchain("eip155:1")!, expiryTimestamp: nil) private let response = WalletConnectSign.Response(id: RPCID(1234567890123456789), topic: "", chainId: "", result: .response(AnyCodable(any: ""))) var sessionProposalPublisher: AnyPublisher<(proposal: WalletConnectSign.Session.Proposal, context: VerifyContext?), Never> { @@ -152,11 +152,7 @@ final class SignClientMock: SignClientProtocol { func getPendingRequests(topic: String?) -> [(request: WalletConnectSign.Request, context: WalletConnectSign.VerifyContext?)] { return [(request, nil)] } - - func getSessionRequestRecord(id: JSONRPC.RPCID) -> (request: WalletConnectSign.Request, context: WalletConnectSign.VerifyContext?)? { - return (request, nil) - } - + func cleanup() async throws { cleanupCalled = true } From cb77784c0380f2f5026f8a05e2f3ca0dc24fc08a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 24 Jan 2024 15:19:10 +0300 Subject: [PATCH 135/169] Common network error --- Sources/WalletConnectRelay/Dispatching.swift | 6 +++--- Sources/WalletConnectRelay/Misc/NetworkError.swift | 8 ++++---- Tests/RelayerTests/Helpers/Error+Extension.swift | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index f32d7a5e8..efd932f70 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -68,7 +68,7 @@ final class Dispatcher: NSObject, Dispatching { func send(_ string: String, completion: @escaping (Error?) -> Void) { guard socket.isConnected else { - completion(NetworkError.webSocketNotConnected) + completion(NetworkError.connectionFailed) return } socket.write(string: string) { @@ -85,7 +85,7 @@ final class Dispatcher: NSObject, Dispatching { cancellable = Publishers.CombineLatest(socketConnectionStatusPublisher, networkConnectionStatusPublisher) .filter { $0.0 == .connected && $0.1 == .connected } .setFailureType(to: NetworkError.self) - .timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .webSocketNotConnected }) + .timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .connectionFailed }) .sink(receiveCompletion: { [unowned self] result in switch result { case .failure(let error): @@ -145,7 +145,7 @@ extension Dispatcher { } private func handleFallbackIfNeeded(error: NetworkError) { - if error == .webSocketNotConnected && socket.request.url?.host == NetworkConstants.defaultUrl { + if error == .connectionFailed && socket.request.url?.host == NetworkConstants.defaultUrl { logger.debug("[WebSocket] - Fallback to \(NetworkConstants.fallbackUrl)") fallback = true socket.request.url = relayUrlFactory.create(fallback: fallback) diff --git a/Sources/WalletConnectRelay/Misc/NetworkError.swift b/Sources/WalletConnectRelay/Misc/NetworkError.swift index f31340bbd..e0a66c2c1 100644 --- a/Sources/WalletConnectRelay/Misc/NetworkError.swift +++ b/Sources/WalletConnectRelay/Misc/NetworkError.swift @@ -1,13 +1,13 @@ import Foundation enum NetworkError: Error, Equatable { - case webSocketNotConnected + case connectionFailed case sendMessageFailed(Error) case receiveMessageFailure(Error) static func == (lhs: NetworkError, rhs: NetworkError) -> Bool { switch (lhs, rhs) { - case (.webSocketNotConnected, .webSocketNotConnected): return true + case (.connectionFailed, .connectionFailed): return true case (.sendMessageFailed, .sendMessageFailed): return true case (.receiveMessageFailure, .receiveMessageFailure): return true default: return false @@ -22,8 +22,8 @@ extension NetworkError: LocalizedError { var localizedDescription: String { switch self { - case .webSocketNotConnected: - return "Web socket is not connected to any URL." + case .connectionFailed: + return "Web socket is not connected to any URL or networking connection error" case .sendMessageFailed(let error): return "Failed to send a message through the web socket: \(error)" case .receiveMessageFailure(let error): diff --git a/Tests/RelayerTests/Helpers/Error+Extension.swift b/Tests/RelayerTests/Helpers/Error+Extension.swift index 901d2d829..76dd92672 100644 --- a/Tests/RelayerTests/Helpers/Error+Extension.swift +++ b/Tests/RelayerTests/Helpers/Error+Extension.swift @@ -24,7 +24,7 @@ extension Error { extension NetworkError { var isWebSocketError: Bool { - guard case .webSocketNotConnected = self else { return false } + guard case .connectionFailed = self else { return false } return true } From 2dce12f1c1ee1fa1b32cf6da5ae201b29ba436f2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 15:15:23 +0100 Subject: [PATCH 136/169] remove dependency pairing dependency in notify --- Example/IntegrationTests/Push/NotifyTests.swift | 15 +++------------ Package.swift | 2 +- .../Client/Wallet/NotifyClientFactory.swift | 4 +--- Sources/WalletConnectNotify/Notify.swift | 1 - 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 26400548c..4673fe55d 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -8,7 +8,6 @@ import Combine import WalletConnectNetworking import WalletConnectPush @testable import WalletConnectNotify -@testable import WalletConnectPairing import WalletConnectIdentity import WalletConnectSigner @@ -30,12 +29,11 @@ final class NotifyTests: XCTestCase { private var publishers = Set() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) { + func makeClientDependencies(prefix: String) -> (NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug) let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug) let kmsLogger = ConsoleLogger(prefix: prefix + " [KMS]", loggingLevel: .debug) @@ -55,19 +53,13 @@ final class NotifyTests: XCTestCase { keyValueStorage: keyValueStorage, kmsLogger: kmsLogger) - let pairingClient = PairingClientFactory.create( - logger: pairingLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - networkingClient: networkingClient) - let clientId = try! networkingClient.getClientId() networkingLogger.debug("My client id is: \(clientId)") - return (pairingClient, networkingClient, keychain, keyValueStorage) + return (networkingClient, keychain, keyValueStorage) } func makeWalletClient(prefix: String = "🦋 Wallet: ") -> NotifyClient { - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) let pushClient = PushClientFactory.create(projectId: "", pushHost: "echo.walletconnect.com", @@ -84,7 +76,6 @@ final class NotifyTests: XCTestCase { keychainStorage: keychain, groupKeychainStorage: KeychainStorageMock(), networkInteractor: networkingInteractor, - pairingRegisterer: pairingClient, pushClient: pushClient, crypto: DefaultCryptoProvider(), notifyHost: InputConfig.notifyHost, diff --git a/Package.swift b/Package.swift index 3321d7eb7..d6d573c09 100644 --- a/Package.swift +++ b/Package.swift @@ -73,7 +73,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectIdentity", "WalletConnectPairing", "WalletConnectPush", "WalletConnectSigner", "Database"], + dependencies: ["WalletConnectIdentity", "WalletConnectPush", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index ec417a765..676f37b5a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -2,7 +2,7 @@ import Foundation public struct NotifyClientFactory { - public static func create(projectId: String, groupIdentifier: String, networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, crypto: CryptoProvider, notifyHost: String, explorerHost: String) -> NotifyClient { + public static func create(projectId: String, groupIdentifier: String, networkInteractor: NetworkInteracting, pushClient: PushClient, crypto: CryptoProvider, notifyHost: String, explorerHost: String) -> NotifyClient { let logger = ConsoleLogger(prefix: "🔔",loggingLevel: .debug) let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier) @@ -18,7 +18,6 @@ public struct NotifyClientFactory { keychainStorage: keychainStorage, groupKeychainStorage: groupKeychainService, networkInteractor: networkInteractor, - pairingRegisterer: pairingRegisterer, pushClient: pushClient, crypto: crypto, notifyHost: notifyHost, @@ -34,7 +33,6 @@ public struct NotifyClientFactory { keychainStorage: KeychainStorageProtocol, groupKeychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, - pairingRegisterer: PairingRegisterer, pushClient: PushClient, crypto: CryptoProvider, notifyHost: String, diff --git a/Sources/WalletConnectNotify/Notify.swift b/Sources/WalletConnectNotify/Notify.swift index 9ed189307..3bb7b41f1 100644 --- a/Sources/WalletConnectNotify/Notify.swift +++ b/Sources/WalletConnectNotify/Notify.swift @@ -10,7 +10,6 @@ public class Notify { projectId: Networking.projectId, groupIdentifier: Networking.groupIdentifier, networkInteractor: Networking.interactor, - pairingRegisterer: Pair.registerer, pushClient: Push.instance, crypto: config.crypto, notifyHost: config.notifyHost, From e816d31421c5fbe4e0b5e1c83cf4d1497de173a2 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 24 Jan 2024 17:29:44 +0300 Subject: [PATCH 137/169] Update Dispatching.swift --- Sources/WalletConnectRelay/Dispatching.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index efd932f70..7d13bc39f 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -90,7 +90,9 @@ final class Dispatcher: NSObject, Dispatching { switch result { case .failure(let error): cancellable?.cancel() - self.handleFallbackIfNeeded(error: error) + if !socket.isConnected { + handleFallbackIfNeeded(error: error) + } completion(error) case .finished: break } From 326cde375d44ea33190a73911cabcaaaee47129a Mon Sep 17 00:00:00 2001 From: Talha Ali <> Date: Wed, 24 Jan 2024 12:01:31 -0600 Subject: [PATCH 138/169] removed the cron job that releases wallet and showcase sample every Monday --- .github/workflows/release.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df62cc8a9..06c696525 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,6 @@ name: release on: - schedule: - # Runs "Every Monday 10am CET" - - cron: '0 10 * * 1' - workflow_dispatch: jobs: From 657f92c23b8f35f24f4e19b61f8c79a962c3e73b Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 25 Jan 2024 16:05:46 +0300 Subject: [PATCH 139/169] Pagination implemented --- .../PushMessages/SubscriptionInteractor.swift | 6 ++--- .../PushMessages/SubscriptionPresenter.swift | 23 +++++++++++++++-- .../PushMessages/SubscriptionView.swift | 25 +++++++++++++++++++ .../Client/Wallet/HistoryService.swift | 6 ++--- .../Client/Wallet/NotifyClient.swift | 8 ++++-- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift index e3d1a14e4..37ea5fcd0 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift @@ -31,9 +31,7 @@ final class SubscriptionInteractor { } } - func fetchHistory() { - Task(priority: .high) { - try await Notify.instance.fetchHistory(subscription: subscription) - } + func fetchHistory(after: String?, limit: Int) async throws -> Bool { + return try await Notify.instance.fetchHistory(subscription: subscription, after: after, limit: limit) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index c0527399e..e5b69d57c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -4,6 +4,11 @@ import WalletConnectNotify final class SubscriptionPresenter: ObservableObject { + enum LoadingState { + case loading + case idle + } + private var subscription: NotifySubscription private let interactor: SubscriptionInteractor private let router: SubscriptionRouter @@ -11,6 +16,9 @@ final class SubscriptionPresenter: ObservableObject { @Published private var pushMessages: [NotifyMessageRecord] = [] + @Published var loadingState: LoadingState = .idle + @Published var isMoreDataAvailable: Bool = true + var subscriptionViewModel: SubscriptionsViewModel { return SubscriptionsViewModel(subscription: subscription) } @@ -49,6 +57,19 @@ final class SubscriptionPresenter: ObservableObject { router.dismiss() } + func loadMoreMessages() { + switch loadingState { + case .loading: + break + case .idle: + Task(priority: .high) { @MainActor in + loadingState = .loading + isMoreDataAvailable = try await interactor.fetchHistory(after: messages.last?.id, limit: 50) + loadingState = .idle + } + } + } + @objc func preferencesDidPress() { router.presentPreferences(subscription: subscription) } @@ -77,8 +98,6 @@ extension SubscriptionPresenter: SceneViewModel { private extension SubscriptionPresenter { func setupInitialState() { - interactor.fetchHistory() - pushMessages = interactor.getPushMessages() interactor.messagesPublisher diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index 358affe2b..890629513 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -34,6 +34,11 @@ struct SubscriptionView: View { .listRowSeparator(.hidden) .listRowBackground(Color.clear) } + + if presenter.isMoreDataAvailable { + lastRowView() + .listRowSeparator(.hidden) + } } .listStyle(PlainListStyle()) } @@ -161,6 +166,26 @@ struct SubscriptionView: View { .frame(maxWidth: .infinity) .frame(height: 410) } + + func lastRowView() -> some View { + VStack { + switch presenter.loadingState { + case .loading: + HStack { + Spacer() + ProgressView() + Spacer() + } + .padding(.bottom, 24) + case .idle: + EmptyView() + } + } + .frame(height: 50) + .onAppear { + presenter.loadMoreMessages() + } + } } #if DEBUG diff --git a/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift b/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift index 21574e569..7cece784e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/HistoryService.swift @@ -12,7 +12,7 @@ public final class HistoryService { self.identityClient = identityClient } - public func fetchHistory(account: Account, topic: String, appAuthenticationKey: String, host: String) async throws -> [NotifyMessage] { + public func fetchHistory(account: Account, topic: String, appAuthenticationKey: String, host: String, after: String?, limit: Int) async throws -> [NotifyMessage] { let dappAuthKey = try DIDKey(did: appAuthenticationKey) let app = DIDWeb(host: host) @@ -21,8 +21,8 @@ public final class HistoryService { keyserver: keyserver.absoluteString, dappAuthKey: dappAuthKey, app: app, - limit: 50, - after: nil + limit: UInt64(limit), + after: after ) let wrapper = try identityClient.signAndCreateWrapper(payload: requestPayload, account: account) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index e4eab7e08..d806dba04 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -148,12 +148,14 @@ public class NotifyClient { return notifyStorage.messagesPublisher(topic: topic) } - public func fetchHistory(subscription: NotifySubscription) async throws { + public func fetchHistory(subscription: NotifySubscription, after: String?, limit: Int) async throws -> Bool { let messages = try await historyService.fetchHistory( account: subscription.account, topic: subscription.topic, appAuthenticationKey: subscription.appAuthenticationKey, - host: subscription.metadata.url + host: subscription.metadata.url, + after: after, + limit: limit ) let records = messages.map { message in @@ -161,6 +163,8 @@ public class NotifyClient { } try notifyStorage.setMessages(records) + + return messages.count == limit } } From b9c819a8cf27195d333d03be6701cdfcbde9e8e4 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 25 Jan 2024 16:07:43 +0300 Subject: [PATCH 140/169] Integration tests hasMore --- Example/IntegrationTests/Push/NotifyTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 4673fe55d..742fd8d9e 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -264,7 +264,8 @@ final class NotifyTests: XCTestCase { await fulfillment(of: [subscribeExpectation], timeout: InputConfig.defaultTimeout) - try await walletNotifyClientA.fetchHistory(subscription: subscription) + let hasMore = try await walletNotifyClientA.fetchHistory(subscription: subscription, after: nil, limit: 50) + XCTAssertTrue(hasMore) XCTAssertTrue(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count > 40) } } From aa31a50679b392e566f053394232bd3703e7640a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 25 Jan 2024 16:09:31 +0300 Subject: [PATCH 141/169] testFetchHistory updated --- Example/IntegrationTests/Push/NotifyTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 742fd8d9e..1618881e6 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -264,9 +264,9 @@ final class NotifyTests: XCTestCase { await fulfillment(of: [subscribeExpectation], timeout: InputConfig.defaultTimeout) - let hasMore = try await walletNotifyClientA.fetchHistory(subscription: subscription, after: nil, limit: 50) + let hasMore = try await walletNotifyClientA.fetchHistory(subscription: subscription, after: nil, limit: 20) XCTAssertTrue(hasMore) - XCTAssertTrue(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count > 40) + XCTAssertTrue(walletNotifyClientA.getMessageHistory(topic: subscription.topic).count == 20) } } From 556efc27ea858dc95e5d2f2bfa8e3be451eb6f7a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 14 Dec 2023 20:51:41 +0300 Subject: [PATCH 142/169] Subscriptions Updater --- .../Client/Wallet/NotifyClient.swift | 5 +- .../Client/Wallet/NotifyClientFactory.swift | 16 +++-- .../Wallet/NotifySubsctiptionsUpdater.swift | 56 ++++++++++++++++++ ...ubscriptionsChangedRequestSubscriber.swift | 59 +++---------------- .../NotifyUpdateResponseSubscriber.swift | 19 ++---- ...WatchSubscriptionsResponseSubscriber.swift | 53 +++-------------- .../NotifySubscribeResponseSubscriber.swift | 30 ++++------ .../NotifySubscriptionResponsePayload.swift | 14 ++--- 8 files changed, 108 insertions(+), 144 deletions(-) create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index d806dba04..c0c22af51 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -37,6 +37,7 @@ public class NotifyClient { private let notifyWatchSubscriptionsResponseSubscriber: NotifyWatchSubscriptionsResponseSubscriber private let notifyWatcherAgreementKeysProvider: NotifyWatcherAgreementKeysProvider private let notifySubscriptionsChangedRequestSubscriber: NotifySubscriptionsChangedRequestSubscriber + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater private let subscriptionWatcher: SubscriptionWatcher init(logger: ConsoleLogging, @@ -58,6 +59,7 @@ public class NotifyClient { notifyWatchSubscriptionsResponseSubscriber: NotifyWatchSubscriptionsResponseSubscriber, notifyWatcherAgreementKeysProvider: NotifyWatcherAgreementKeysProvider, notifySubscriptionsChangedRequestSubscriber: NotifySubscriptionsChangedRequestSubscriber, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater, subscriptionWatcher: SubscriptionWatcher ) { self.logger = logger @@ -78,6 +80,7 @@ public class NotifyClient { self.notifyWatchSubscriptionsResponseSubscriber = notifyWatchSubscriptionsResponseSubscriber self.notifyWatcherAgreementKeysProvider = notifyWatcherAgreementKeysProvider self.notifySubscriptionsChangedRequestSubscriber = notifySubscriptionsChangedRequestSubscriber + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater self.subscriptionWatcher = subscriptionWatcher } @@ -184,7 +187,7 @@ private extension NotifyClient { extension NotifyClient { public var subscriptionChangedPublisher: AnyPublisher<[NotifySubscription], Never> { - return notifySubscriptionsChangedRequestSubscriber.subscriptionChangedPublisher + return notifySubscriptionsUpdater.subscriptionChangedPublisher } public func register(deviceToken: String) async throws { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 676f37b5a..8e7de5dae 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -52,19 +52,22 @@ public struct NotifyClientFactory { let notifySubscribeRequester = NotifySubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, webDidResolver: webDidResolver, notifyConfigProvider: notifyConfigProvider) - let notifySubscribeResponseSubscriber = NotifySubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifyConfigProvider: notifyConfigProvider) + let notifySubscriptionsUpdater = NotifySubsctiptionsUpdater(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage, groupKeychainStorage: groupKeychainStorage) + + let notifySubscriptionsBuilder = NotifySubscriptionsBuilder(notifyConfigProvider: notifyConfigProvider) + + let notifySubscribeResponseSubscriber = NotifySubscribeResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, notifySubscriptionsBuilder: notifySubscriptionsBuilder, notifySubscriptionsUpdater: notifySubscriptionsUpdater) let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, notifyConfigProvider: notifyConfigProvider, logger: logger, notifyStorage: notifyStorage) - let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, notifyConfigProvider: notifyConfigProvider, notifyStorage: notifyStorage) + let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger) let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, notifyStorage: notifyStorage) let notifyWatcherAgreementKeysProvider = NotifyWatcherAgreementKeysProvider(kms: kms) let notifyWatchSubscriptionsRequester = NotifyWatchSubscriptionsRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, webDidResolver: webDidResolver, notifyAccountProvider: notifyAccountProvider, notifyWatcherAgreementKeysProvider: notifyWatcherAgreementKeysProvider, notifyHost: notifyHost) - let notifySubscriptionsBuilder = NotifySubscriptionsBuilder(notifyConfigProvider: notifyConfigProvider) - let notifyWatchSubscriptionsResponseSubscriber = NotifyWatchSubscriptionsResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage, groupKeychainStorage: groupKeychainStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) - let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, kms: kms, identityClient: identityClient, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifySubscriptionsBuilder: notifySubscriptionsBuilder) + let notifyWatchSubscriptionsResponseSubscriber = NotifyWatchSubscriptionsResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, notifySubscriptionsBuilder: notifySubscriptionsBuilder, notifySubscriptionsUpdater: notifySubscriptionsUpdater) + let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, notifySubscriptionsUpdater: notifySubscriptionsUpdater, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let subscriptionWatcher = SubscriptionWatcher(notifyWatchSubscriptionsRequester: notifyWatchSubscriptionsRequester, logger: logger) let historyService = HistoryService(keyserver: keyserverURL, networkingClient: networkInteractor, identityClient: identityClient) @@ -87,7 +90,8 @@ public struct NotifyClientFactory { subscriptionsAutoUpdater: subscriptionsAutoUpdater, notifyWatchSubscriptionsResponseSubscriber: notifyWatchSubscriptionsResponseSubscriber, notifyWatcherAgreementKeysProvider: notifyWatcherAgreementKeysProvider, - notifySubscriptionsChangedRequestSubscriber: notifySubscriptionsChangedRequestSubscriber, + notifySubscriptionsChangedRequestSubscriber: notifySubscriptionsChangedRequestSubscriber, + notifySubscriptionsUpdater: notifySubscriptionsUpdater, subscriptionWatcher: subscriptionWatcher ) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift new file mode 100644 index 000000000..367e3b23a --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift @@ -0,0 +1,56 @@ +import Foundation +import Combine + +final class NotifySubsctiptionsUpdater { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + private let notifyStorage: NotifyStorage + private let groupKeychainStorage: KeychainStorageProtocol + + private let subscriptionChangedSubject = PassthroughSubject<[NotifySubscription], Never>() + + var subscriptionChangedPublisher: AnyPublisher<[NotifySubscription], Never> { + return subscriptionChangedSubject.eraseToAnyPublisher() + } + + init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, notifyStorage: NotifyStorage, groupKeychainStorage: KeychainStorageProtocol) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.notifyStorage = notifyStorage + self.groupKeychainStorage = groupKeychainStorage + } + + func update(subscriptions newSubscriptions: [NotifySubscription], for account: Account) async throws { + let oldSubscriptions = notifyStorage.getSubscriptions(account: account) + + subscriptionChangedSubject.send(newSubscriptions) + + try Task.checkCancellation() + + let subscriptions = oldSubscriptions.difference(from: newSubscriptions) + + logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") + + if subscriptions.count > 0 { + try notifyStorage.replaceAllSubscriptions(newSubscriptions) + + for subscription in newSubscriptions { + let symKey = try SymmetricKey(hex: subscription.symKey) + try groupKeychainStorage.add(symKey, forKey: subscription.topic) + try kms.setSymmetricKey(symKey, for: subscription.topic) + } + + let topics = newSubscriptions.map { $0.topic } + + try await networkingInteractor.batchSubscribe(topics: topics) + + try Task.checkCancellation() + + logger.debug("Updated Subscriptions by Subscriptions Changed Request", properties: [ + "topics": newSubscriptions.map { $0.topic }.joined(separator: ",") + ]) + } + } +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift index a7c9cdd95..5125fc9f4 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift @@ -5,36 +5,25 @@ class NotifySubscriptionsChangedRequestSubscriber { private let keyserver: URL private let networkingInteractor: NetworkInteracting private let identityClient: IdentityClient - private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - private let groupKeychainStorage: KeychainStorageProtocol - private let notifyStorage: NotifyStorage + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder - - private let subscriptionChangedSubject = PassthroughSubject<[NotifySubscription], Never>() - - var subscriptionChangedPublisher: AnyPublisher<[NotifySubscription], Never> { - return subscriptionChangedSubject.eraseToAnyPublisher() - } init( keyserver: URL, networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, identityClient: IdentityClient, logger: ConsoleLogging, - groupKeychainStorage: KeychainStorageProtocol, - notifyStorage: NotifyStorage, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater, notifySubscriptionsBuilder: NotifySubscriptionsBuilder ) { self.keyserver = keyserver self.networkingInteractor = networkingInteractor - self.kms = kms self.logger = logger self.identityClient = identityClient - self.groupKeychainStorage = groupKeychainStorage - self.notifyStorage = notifyStorage + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater self.notifySubscriptionsBuilder = notifySubscriptionsBuilder + subscribeForNofifyChangedRequests() } @@ -44,54 +33,20 @@ class NotifySubscriptionsChangedRequestSubscriber { protocolMethod: NotifySubscriptionsChangedProtocolMethod(), requestOfType: NotifySubscriptionsChangedRequestPayload.Wrapper.self, errorHandler: logger) { [unowned self] payload in + logger.debug("Received Subscriptions Changed Request") let (jwtPayload, _) = try NotifySubscriptionsChangedRequestPayload.decodeAndVerify(from: payload.request) - let account = jwtPayload.account - - // TODO: varify signature with notify server diddoc authentication key - - let oldSubscriptions = notifyStorage.getSubscriptions(account: account) - let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(jwtPayload.subscriptions) - - subscriptionChangedSubject.send(newSubscriptions) - - try Task.checkCancellation() - let subscriptions = oldSubscriptions.difference(from: newSubscriptions) + let subscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(jwtPayload.subscriptions) - logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") - - if subscriptions.count > 0 { - try notifyStorage.replaceAllSubscriptions(newSubscriptions) - - for subscription in newSubscriptions { - let symKey = try SymmetricKey(hex: subscription.symKey) - try groupKeychainStorage.add(symKey, forKey: subscription.topic) - try kms.setSymmetricKey(symKey, for: subscription.topic) - } - - let topics = newSubscriptions.map { $0.topic } - - try await networkingInteractor.batchSubscribe(topics: topics) - - try Task.checkCancellation() - - var logProperties = ["rpcId": payload.id.string] - for (index, subscription) in newSubscriptions.enumerated() { - let key = "subscription_\(index + 1)" - logProperties[key] = subscription.topic - } - - logger.debug("Updated Subscriptions by Subscriptions Changed Request", properties: logProperties) - } + try await notifySubscriptionsUpdater.update(subscriptions: subscriptions, for: jwtPayload.account) try await respond(topic: payload.topic, account: jwtPayload.account, rpcId: payload.id, notifyServerAuthenticationKey: jwtPayload.notifyServerAuthenticationKey) } } private func respond(topic: String, account: Account, rpcId: RPCID, notifyServerAuthenticationKey: DIDKey) async throws { - let receiptPayload = NotifySubscriptionsChangedResponsePayload(account: account, keyserver: keyserver, notifyServerAuthenticationKey: notifyServerAuthenticationKey) let wrapper = try identityClient.signAndCreateWrapper( diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift index d9b2bafbd..4843f5dbf 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -2,21 +2,14 @@ import Foundation import Combine class NotifyUpdateResponseSubscriber { + private let networkingInteractor: NetworkInteracting - private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let notifyStorage: NotifyStorage - private let nofityConfigProvider: NotifyConfigProvider - - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - notifyConfigProvider: NotifyConfigProvider, - notifyStorage: NotifyStorage - ) { + + init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor self.logger = logger - self.notifyStorage = notifyStorage - self.nofityConfigProvider = notifyConfigProvider + subscribeForUpdateResponse() } @@ -24,10 +17,6 @@ class NotifyUpdateResponseSubscriber { } private extension NotifyUpdateResponseSubscriber { - enum Errors: Error { - case subscriptionDoesNotExist - case selectedScopeNotFound - } func subscribeForUpdateResponse() { networkingInteractor.subscribeOnResponse( diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift index dccdcbdd7..34239ec13 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift @@ -3,25 +3,20 @@ import Combine class NotifyWatchSubscriptionsResponseSubscriber { private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - private let notifyStorage: NotifyStorage - private let groupKeychainStorage: KeychainStorageProtocol private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, logger: ConsoleLogging, - notifyStorage: NotifyStorage, - groupKeychainStorage: KeychainStorageProtocol, - notifySubscriptionsBuilder: NotifySubscriptionsBuilder + notifySubscriptionsBuilder: NotifySubscriptionsBuilder, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater ) { self.networkingInteractor = networkingInteractor - self.kms = kms self.logger = logger - self.notifyStorage = notifyStorage - self.groupKeychainStorage = groupKeychainStorage self.notifySubscriptionsBuilder = notifySubscriptionsBuilder + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater + subscribeForWatchSubscriptionsResponse() } @@ -32,45 +27,15 @@ class NotifyWatchSubscriptionsResponseSubscriber { requestOfType: NotifyWatchSubscriptionsPayload.Wrapper.self, responseOfType: NotifyWatchSubscriptionsResponsePayload.Wrapper.self, errorHandler: logger) { [unowned self] payload in + logger.debug("Received Notify Watch Subscriptions response") + let (requestPayload, _) = try NotifyWatchSubscriptionsPayload.decodeAndVerify(from: payload.request) let (responsePayload, _) = try NotifyWatchSubscriptionsResponsePayload.decodeAndVerify(from: payload.response) - let (watchSubscriptionPayloadRequest, _) = try NotifyWatchSubscriptionsPayload.decodeAndVerify(from: payload.request) - - let account = watchSubscriptionPayloadRequest.subscriptionAccount - // TODO: varify signature with notify server diddoc authentication key - - let oldSubscriptions = notifyStorage.getSubscriptions(account: account) - let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) - - try Task.checkCancellation() - - let subscriptions = oldSubscriptions.difference(from: newSubscriptions) - - logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") - - if subscriptions.count > 0 { - // TODO: unsubscribe for oldSubscriptions topics that are not included in new subscriptions - try notifyStorage.replaceAllSubscriptions(newSubscriptions) - - for subscription in newSubscriptions { - let symKey = try SymmetricKey(hex: subscription.symKey) - try groupKeychainStorage.add(symKey, forKey: subscription.topic) - try kms.setSymmetricKey(symKey, for: subscription.topic) - } - - try await networkingInteractor.batchSubscribe(topics: newSubscriptions.map { $0.topic }) - - try Task.checkCancellation() - var logProperties = [String: String]() - for (index, subscription) in newSubscriptions.enumerated() { - let key = "subscription_\(index + 1)" - logProperties[key] = subscription.topic - } + let subscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) - logger.debug("Updated Subscriptions with Watch Subscriptions Update, number of subscriptions: \(newSubscriptions.count)", properties: logProperties) - } + try await notifySubscriptionsUpdater.update(subscriptions: subscriptions, for: requestPayload.subscriptionAccount) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift index d8aa56a39..d42e8b1f0 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift @@ -2,31 +2,22 @@ import Foundation import Combine class NotifySubscribeResponseSubscriber { - enum Errors: Error { - case couldNotCreateSubscription - } private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let notifyStorage: NotifyStorage - private let groupKeychainStorage: KeychainStorageProtocol - private let notifyConfigProvider: NotifyConfigProvider + private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, logger: ConsoleLogging, - groupKeychainStorage: KeychainStorageProtocol, - notifyStorage: NotifyStorage, - notifyConfigProvider: NotifyConfigProvider + notifySubscriptionsBuilder: NotifySubscriptionsBuilder, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater ) { self.networkingInteractor = networkingInteractor - self.kms = kms self.logger = logger - self.groupKeychainStorage = groupKeychainStorage - self.notifyStorage = notifyStorage - self.notifyConfigProvider = notifyConfigProvider + self.notifySubscriptionsBuilder = notifySubscriptionsBuilder + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater + subscribeForSubscriptionResponse() } @@ -39,7 +30,12 @@ class NotifySubscribeResponseSubscriber { ) { [unowned self] payload in logger.debug("Received Notify Subscribe response") - let _ = try NotifySubscriptionResponsePayload.decodeAndVerify(from: payload.response) + let (requestPayload, _) = try NotifySubscriptionPayload.decodeAndVerify(from: payload.request) + let (responsePayload, _) = try NotifySubscriptionResponsePayload.decodeAndVerify(from: payload.response) + + let subscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) + + try await notifySubscriptionsUpdater.update(subscriptions: subscriptions, for: requestPayload.subscriptionAccount) logger.debug("NotifySubscribeResponseSubscriber: unsubscribing from response topic: \(payload.topic)") diff --git a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifySubscriptionResponsePayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/NotifySubscriptionResponsePayload.swift index 8ed7e775e..d9f04440a 100644 --- a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifySubscriptionResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/JWTPayloads/NotifySubscriptionResponsePayload.swift @@ -18,6 +18,8 @@ struct NotifySubscriptionResponsePayload: JWTClaimsCodable { let sub: String /// Dapp's domain url let app: String + /// array of Notify Subscriptions + let sbs: [NotifyServerSubscription] static var action: String? { return "notify_subscription_response" @@ -39,22 +41,16 @@ struct NotifySubscriptionResponsePayload: JWTClaimsCodable { let account: Account let selfPubKey: DIDKey let app: String + let subscriptions: [NotifyServerSubscription] init(claims: Claims) throws { self.account = try Account(DIDPKHString: claims.sub) self.selfPubKey = try DIDKey(did: claims.aud) self.app = claims.app + self.subscriptions = claims.sbs } func encode(iss: String) throws -> Claims { - return Claims( - iat: defaultIat(), - exp: expiry(days: 1), - act: Claims.action, - iss: iss, - aud: selfPubKey.did(variant: .ED25519), - sub: account.did, - app: app - ) + fatalError("Client is not supposed to encode this JWT payload") } } From 1a012d2c7c7c2ab377ae9161acad3582035824be Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 21:46:23 +0300 Subject: [PATCH 143/169] NotifyUpdateResponsePayload with sbs --- .../NotifyUpdateResponseSubscriber.swift | 19 ++++++++++++++++--- .../NotifyUpdateResponsePayload.swift | 14 +++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift index 4843f5dbf..76b2a539b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -5,10 +5,19 @@ class NotifyUpdateResponseSubscriber { private let networkingInteractor: NetworkInteracting private let logger: ConsoleLogging - - init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging) { + private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater + + init( + networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + notifySubscriptionsBuilder: NotifySubscriptionsBuilder, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater + ) { self.networkingInteractor = networkingInteractor self.logger = logger + self.notifySubscriptionsBuilder = notifySubscriptionsBuilder + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater subscribeForUpdateResponse() } @@ -26,7 +35,11 @@ private extension NotifyUpdateResponseSubscriber { errorHandler: logger ) { [unowned self] payload in - let _ = try NotifyUpdateResponsePayload.decodeAndVerify(from: payload.response) + let (responsePayload, _) = try NotifyUpdateResponsePayload.decodeAndVerify(from: payload.response) + + let subscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) + + try await notifySubscriptionsUpdater.update(subscriptions: subscriptions, for: responsePayload.account) logger.debug("Received Notify Update response") } diff --git a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyUpdateResponsePayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyUpdateResponsePayload.swift index a03ca61f8..7e49d08c3 100644 --- a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyUpdateResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyUpdateResponsePayload.swift @@ -16,6 +16,8 @@ struct NotifyUpdateResponsePayload: JWTClaimsCodable { let aud: String /// Blockchain account that notify subscription has been proposed for -`did:pkh` let sub: String + /// array of Notify Server Subscriptions + let sbs: [NotifyServerSubscription] /// Dapp's domain url let app: String @@ -39,22 +41,16 @@ struct NotifyUpdateResponsePayload: JWTClaimsCodable { let account: Account let selfPubKey: DIDKey let app: DIDWeb + let subscriptions: [NotifyServerSubscription] init(claims: Claims) throws { self.account = try Account(DIDPKHString: claims.sub) self.selfPubKey = try DIDKey(did: claims.aud) self.app = try DIDWeb(did: claims.app) + self.subscriptions = claims.sbs } func encode(iss: String) throws -> Claims { - return Claims( - iat: defaultIat(), - exp: expiry(days: 1), - act: Claims.action, - iss: iss, - aud: selfPubKey.did(variant: .ED25519), - sub: account.did, - app: app.did - ) + fatalError() } } From feaa31452ad9f33f6669443f83c24924505918fd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 23 Jan 2024 22:31:26 +0300 Subject: [PATCH 144/169] Delete response subsciber --- .../Client/Wallet/NotifyClient.swift | 11 ++-- .../Client/Wallet/NotifyClientFactory.swift | 10 ++-- ...> NotifyDeleteSubscriptionRequester.swift} | 13 ++--- .../NotifyDeleteSubscriptionSubscriber.swift | 51 +++++++++++++++++++ .../NotifyDeletePayload.swift | 0 .../NotifyDeleteResponsePayload.swift | 14 ++--- 6 files changed, 72 insertions(+), 27 deletions(-) rename Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/{DeleteNotifySubscriptionRequester.swift => NotifyDeleteSubscriptionRequester.swift} (84%) create mode 100644 Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionSubscriber.swift rename Sources/WalletConnectNotify/Types/JWTPayloads/{ => notify_delete}/NotifyDeletePayload.swift (100%) rename Sources/WalletConnectNotify/Types/JWTPayloads/{ => notify_delete}/NotifyDeleteResponsePayload.swift (84%) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index c0c22af51..6b5aa22fc 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -17,7 +17,7 @@ public class NotifyClient { return logger.logsPublisher } - private let deleteNotifySubscriptionRequester: DeleteNotifySubscriptionRequester + private let notifyDeleteSubscriptionRequester: NotifyDeleteSubscriptionRequester private let notifySubscribeRequester: NotifySubscribeRequester public let logger: ConsoleLogging @@ -31,6 +31,7 @@ public class NotifyClient { private let notifyMessageSubscriber: NotifyMessageSubscriber private let resubscribeService: NotifyResubscribeService private let notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber + private let notifyDeleteSubscriptionSubscriber: NotifyDeleteSubscriptionSubscriber private let notifyUpdateRequester: NotifyUpdateRequester private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater @@ -48,10 +49,11 @@ public class NotifyClient { pushClient: PushClient, notifyMessageSubscriber: NotifyMessageSubscriber, notifyStorage: NotifyStorage, - deleteNotifySubscriptionRequester: DeleteNotifySubscriptionRequester, + notifyDeleteSubscriptionRequester: NotifyDeleteSubscriptionRequester, resubscribeService: NotifyResubscribeService, notifySubscribeRequester: NotifySubscribeRequester, notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber, + notifyDeleteSubscriptionSubscriber: NotifyDeleteSubscriptionSubscriber, notifyUpdateRequester: NotifyUpdateRequester, notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, notifyAccountProvider: NotifyAccountProvider, @@ -69,10 +71,11 @@ public class NotifyClient { self.historyService = historyService self.notifyMessageSubscriber = notifyMessageSubscriber self.notifyStorage = notifyStorage - self.deleteNotifySubscriptionRequester = deleteNotifySubscriptionRequester + self.notifyDeleteSubscriptionRequester = notifyDeleteSubscriptionRequester self.resubscribeService = resubscribeService self.notifySubscribeRequester = notifySubscribeRequester self.notifySubscribeResponseSubscriber = notifySubscribeResponseSubscriber + self.notifyDeleteSubscriptionSubscriber = notifyDeleteSubscriptionSubscriber self.notifyUpdateRequester = notifyUpdateRequester self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber self.notifyAccountProvider = notifyAccountProvider @@ -128,7 +131,7 @@ public class NotifyClient { } public func deleteSubscription(topic: String) async throws { - try await deleteNotifySubscriptionRequester.delete(topic: topic) + try await notifyDeleteSubscriptionRequester.delete(topic: topic) } public func deleteNotifyMessage(id: String) { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 8e7de5dae..14fa2fdef 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -45,7 +45,7 @@ public struct NotifyClientFactory { let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger) let webDidResolver = NotifyWebDidResolver() - let deleteNotifySubscriptionRequester = DeleteNotifySubscriptionRequester(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, kms: kms, logger: logger, notifyStorage: notifyStorage) + let notifyDeleteSubscriptionRequester = NotifyDeleteSubscriptionRequester(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, notifyStorage: notifyStorage) let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage, logger: logger) let notifyConfigProvider = NotifyConfigProvider(projectId: projectId, explorerHost: explorerHost) @@ -60,7 +60,7 @@ public struct NotifyClientFactory { let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, notifyConfigProvider: notifyConfigProvider, logger: logger, notifyStorage: notifyStorage) - let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger) + let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, notifySubscriptionsBuilder: notifySubscriptionsBuilder, notifySubscriptionsUpdater: notifySubscriptionsUpdater) let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, notifyStorage: notifyStorage) @@ -70,6 +70,7 @@ public struct NotifyClientFactory { let notifySubscriptionsChangedRequestSubscriber = NotifySubscriptionsChangedRequestSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, notifySubscriptionsUpdater: notifySubscriptionsUpdater, notifySubscriptionsBuilder: notifySubscriptionsBuilder) let subscriptionWatcher = SubscriptionWatcher(notifyWatchSubscriptionsRequester: notifyWatchSubscriptionsRequester, logger: logger) let historyService = HistoryService(keyserver: keyserverURL, networkingClient: networkInteractor, identityClient: identityClient) + let notifyDeleteSubscriptionSubscriber = NotifyDeleteSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifySubscriptionsBuilder: notifySubscriptionsBuilder, notifySubscriptionsUpdater: notifySubscriptionsUpdater) return NotifyClient( logger: logger, @@ -80,10 +81,11 @@ public struct NotifyClientFactory { pushClient: pushClient, notifyMessageSubscriber: notifyMessageSubscriber, notifyStorage: notifyStorage, - deleteNotifySubscriptionRequester: deleteNotifySubscriptionRequester, + notifyDeleteSubscriptionRequester: notifyDeleteSubscriptionRequester, resubscribeService: resubscribeService, notifySubscribeRequester: notifySubscribeRequester, - notifySubscribeResponseSubscriber: notifySubscribeResponseSubscriber, + notifySubscribeResponseSubscriber: notifySubscribeResponseSubscriber, + notifyDeleteSubscriptionSubscriber: notifyDeleteSubscriptionSubscriber, notifyUpdateRequester: notifyUpdateRequester, notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, notifyAccountProvider: notifyAccountProvider, diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionRequester.swift similarity index 84% rename from Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionRequester.swift index b9c86009c..762f4948d 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionRequester.swift @@ -1,13 +1,12 @@ import Foundation -class DeleteNotifySubscriptionRequester { +class NotifyDeleteSubscriptionRequester { enum Errors: Error { case notifySubscriptionNotFound } private let keyserver: URL private let networkingInteractor: NetworkInteracting private let identityClient: IdentityClient - private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging private let notifyStorage: NotifyStorage @@ -15,14 +14,12 @@ class DeleteNotifySubscriptionRequester { keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient, - kms: KeyManagementServiceProtocol, logger: ConsoleLogging, notifyStorage: NotifyStorage ) { self.keyserver = keyserver self.networkingInteractor = networkingInteractor self.identityClient = identityClient - self.kms = kms self.logger = logger self.notifyStorage = notifyStorage } @@ -49,15 +46,11 @@ class DeleteNotifySubscriptionRequester { try notifyStorage.deleteSubscription(topic: topic) try notifyStorage.deleteMessages(topic: topic) - networkingInteractor.unsubscribe(topic: topic) - - logger.debug("Subscription removed, topic: \(topic)") - - kms.deleteSymmetricKey(for: topic) + logger.debug("Subscription delete request sent, topic: \(topic)") } } -private extension DeleteNotifySubscriptionRequester { +private extension NotifyDeleteSubscriptionRequester { func createJWTWrapper(dappPubKey: DIDKey, reason: String, app: DIDWeb, account: Account) throws -> NotifyDeletePayload.Wrapper { let jwtPayload = NotifyDeletePayload(account: account, keyserver: keyserver, dappPubKey: dappPubKey, app: app) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionSubscriber.swift new file mode 100644 index 000000000..3768b6697 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/NotifyDeleteSubscriptionSubscriber.swift @@ -0,0 +1,51 @@ +import Foundation +import Combine + +class NotifyDeleteSubscriptionSubscriber { + + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder + private let notifySubscriptionsUpdater: NotifySubsctiptionsUpdater + + init( + networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + notifySubscriptionsBuilder: NotifySubscriptionsBuilder, + notifySubscriptionsUpdater: NotifySubsctiptionsUpdater + ) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.notifySubscriptionsBuilder = notifySubscriptionsBuilder + self.notifySubscriptionsUpdater = notifySubscriptionsUpdater + + subscribeForDeleteResponse() + } +} + +private extension NotifyDeleteSubscriptionSubscriber { + + func subscribeForDeleteResponse() { + networkingInteractor.subscribeOnResponse( + protocolMethod: NotifyDeleteProtocolMethod(), + requestOfType: NotifyDeletePayload.Wrapper.self, + responseOfType: NotifyDeleteResponsePayload.Wrapper.self, + errorHandler: logger + ) { [unowned self] payload in + + let (responsePayload, _) = try NotifyDeleteResponsePayload.decodeAndVerify(from: payload.response) + + let subscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) + + try await notifySubscriptionsUpdater.update(subscriptions: subscriptions, for: responsePayload.account) + + logger.debug("Received Notify Delete response") + + networkingInteractor.unsubscribe(topic: payload.topic) + kms.deleteSymmetricKey(for: payload.topic) + } + } +} diff --git a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyDeletePayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/notify_delete/NotifyDeletePayload.swift similarity index 100% rename from Sources/WalletConnectNotify/Types/JWTPayloads/NotifyDeletePayload.swift rename to Sources/WalletConnectNotify/Types/JWTPayloads/notify_delete/NotifyDeletePayload.swift diff --git a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyDeleteResponsePayload.swift b/Sources/WalletConnectNotify/Types/JWTPayloads/notify_delete/NotifyDeleteResponsePayload.swift similarity index 84% rename from Sources/WalletConnectNotify/Types/JWTPayloads/NotifyDeleteResponsePayload.swift rename to Sources/WalletConnectNotify/Types/JWTPayloads/notify_delete/NotifyDeleteResponsePayload.swift index b9f97cc43..ac7f76e0d 100644 --- a/Sources/WalletConnectNotify/Types/JWTPayloads/NotifyDeleteResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/JWTPayloads/notify_delete/NotifyDeleteResponsePayload.swift @@ -16,6 +16,8 @@ struct NotifyDeleteResponsePayload: JWTClaimsCodable { let aud: String /// Blockchain account that notify subscription has been proposed for -`did:pkh` let sub: String + /// array of Notify Server Subscriptions + let sbs: [NotifyServerSubscription] /// Dapp's domain url let app: String @@ -39,22 +41,16 @@ struct NotifyDeleteResponsePayload: JWTClaimsCodable { let account: Account let selfPubKey: DIDKey let app: DIDWeb + let subscriptions: [NotifyServerSubscription] init(claims: Claims) throws { self.account = try Account(DIDPKHString: claims.sub) self.selfPubKey = try DIDKey(did: claims.aud) self.app = try DIDWeb(did: claims.app) + self.subscriptions = claims.sbs } func encode(iss: String) throws -> Claims { - return Claims( - iat: defaultIat(), - exp: expiry(days: 1), - act: Claims.action, - iss: iss, - aud: selfPubKey.did(variant: .ED25519), - sub: account.did, - app: app.did - ) + fatalError() } } From e51db103109525252ea54d2b661601d05b402c65 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Tue, 23 Jan 2024 20:29:12 +0100 Subject: [PATCH 145/169] Bump w3m and include sample wallet in customWallets --- Example/DApp/SceneDelegate.swift | 12 +++++++++++- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index 489a1c2d0..521b89ff0 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -30,7 +30,17 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { Web3Modal.configure( projectId: InputConfig.projectId, - metadata: metadata + metadata: metadata, + customWallets: [ + .init( + id: "swift-sample", + name: "Swift Sample Wallet", + homepage: "https://walletconnect.com/", + imageUrl: "https://avatars.githubusercontent.com/u/37784886?s=200&v=4", + order: 1, + mobileLink: "walletapp://" + ) + ] ) Sign.instance.logsPublisher.sink { log in diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c46e295f2..07a8aa9dd 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -177,8 +177,8 @@ "repositoryURL": "https://github.com/WalletConnect/web3modal-swift", "state": { "branch": null, - "revision": "e84a07662d71721de4d0ccb2d3bb28fd993dd108", - "version": "1.0.14" + "revision": "296b2b72c116807a862e4c08ecf0a78ff044f87a", + "version": "1.0.16" } } ] From 04e8917e12dfbf6e4bbc9c6a5fbddd33ce82110a Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Thu, 14 Dec 2023 15:02:42 +0100 Subject: [PATCH 146/169] WCM using api.web3modal.com --- Example/DApp/Modules/Sign/SignPresenter.swift | 11 +++ Example/DApp/Modules/Sign/SignView.swift | 59 +++++++----- Example/DApp/SceneDelegate.swift | 7 ++ Example/ExampleApp.xcodeproj/project.pbxproj | 9 +- .../Extensions/View+Backport.swift | 2 +- .../Mocks/Listing+Mocks.swift | 70 +++++--------- .../Modal/ModalInteractor.swift | 27 +++--- .../WalletConnectModal/Modal/ModalSheet.swift | 6 +- .../Modal/ModalViewModel.swift | 81 +++++++++------- .../Modal/RecentWalletStorage.swift | 14 +-- .../Modal/Screens/GetAWalletView.swift | 6 +- .../WalletDetail/WalletDetailViewModel.swift | 12 +-- .../Modal/Screens/WalletList.swift | 94 ++++++++++++++----- .../Networking/Explorer/ExplorerAPI.swift | 55 ----------- .../Explorer/GetIosDataResponse.swift | 11 +++ .../Explorer/GetWalletsResponse.swift | 87 +++++++++++++++++ .../Explorer/ListingsResponse.swift | 74 --------------- .../Networking/Explorer/Web3ModalAPI.swift | 84 +++++++++++++++++ .../WalletConnectModal/UI/WalletImage.swift | 4 +- .../WalletConnectModal.swift | 3 + 20 files changed, 424 insertions(+), 292 deletions(-) delete mode 100644 Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift create mode 100644 Sources/WalletConnectModal/Networking/Explorer/GetIosDataResponse.swift create mode 100644 Sources/WalletConnectModal/Networking/Explorer/GetWalletsResponse.swift delete mode 100644 Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift create mode 100644 Sources/WalletConnectModal/Networking/Explorer/Web3ModalAPI.swift diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 1c5194e2c..896f50442 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -2,6 +2,7 @@ import UIKit import Combine import Web3Modal +import WalletConnectModal import WalletConnectSign final class SignPresenter: ObservableObject { @@ -52,6 +53,16 @@ final class SignPresenter: ObservableObject { Web3Modal.present(from: nil) } + func connectWalletWithWCM() { + Task { + WalletConnectModal.set(sessionParams: .init( + requiredNamespaces: Proposal.requiredNamespaces, + optionalNamespaces: Proposal.optionalNamespaces + )) + } + WalletConnectModal.present(from: nil) + } + @MainActor func connectWalletWithSign() { Task { diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift index 54c9d62e7..51d12a806 100644 --- a/Example/DApp/Modules/Sign/SignView.swift +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -18,29 +18,42 @@ struct SignView: View { Spacer() - Button { - presenter.connectWalletWithW3M() - } label: { - Text("Connect with Web3Modal") - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(Color(red: 95/255, green: 159/255, blue: 248/255)) - .cornerRadius(16) - } - .padding(.top, 20) - - Button { - presenter.connectWalletWithSign() - } label: { - Text("Connect with Sign API") - .font(.system(size: 16, weight: .semibold)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(Color(red: 95/255, green: 159/255, blue: 248/255)) - .cornerRadius(16) + VStack(spacing: 10) { + Button { + presenter.connectWalletWithW3M() + } label: { + Text("Connect with Web3Modal") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } + + Button { + presenter.connectWalletWithSign() + } label: { + Text("Connect with Sign API") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } + + Button { + presenter.connectWalletWithWCM() + } label: { + Text("Connect with WalletConnectModal") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } } .padding(.top, 10) } diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index 521b89ff0..38e007ed9 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -1,6 +1,7 @@ import UIKit import Web3Modal +import WalletConnectModal import Auth import WalletConnectRelay import WalletConnectNetworking @@ -42,6 +43,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) ] ) + + WalletConnectModal.configure( + projectId: InputConfig.projectId, + metadata: metadata + ) + Sign.instance.logsPublisher.sink { log in switch log { diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 24712ead6..900432d34 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -341,6 +341,7 @@ CF25F2892A432476009C7E49 /* WalletConnectModal in Frameworks */ = {isa = PBXBuildFile; productRef = CF25F2882A432476009C7E49 /* WalletConnectModal */; }; CF6704DF29E59DDC003326A4 /* XCUIElementQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704DE29E59DDC003326A4 /* XCUIElementQuery.swift */; }; CF6704E129E5A014003326A4 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704E029E5A014003326A4 /* XCTestCase.swift */; }; + CFDB50722B2869AA00A0CBC2 /* WalletConnectModal in Frameworks */ = {isa = PBXBuildFile; productRef = CFDB50712B2869AA00A0CBC2 /* WalletConnectModal */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -712,6 +713,7 @@ 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */, A573C53929EC365000E3CBFD /* HDWalletKit in Frameworks */, A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */, + CFDB50722B2869AA00A0CBC2 /* WalletConnectModal in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1961,6 +1963,7 @@ 84943C7A2A9BA206007EBAC2 /* Mixpanel */, C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */, C579FEB52AFA86CD008855EB /* Web3Modal */, + CFDB50712B2869AA00A0CBC2 /* WalletConnectModal */, 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */, ); productName = DApp; @@ -3348,7 +3351,7 @@ repositoryURL = "https://github.com/WalletConnect/web3modal-swift"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.9; + minimumVersion = 1.0.15; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -3567,6 +3570,10 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectModal; }; + CFDB50712B2869AA00A0CBC2 /* WalletConnectModal */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectModal; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 764E1D3426F8D3FC00A1FB15 /* Project object */; diff --git a/Sources/WalletConnectModal/Extensions/View+Backport.swift b/Sources/WalletConnectModal/Extensions/View+Backport.swift index 3c40e44ba..a2ed92408 100644 --- a/Sources/WalletConnectModal/Extensions/View+Backport.swift +++ b/Sources/WalletConnectModal/Extensions/View+Backport.swift @@ -32,7 +32,7 @@ extension View { @ViewBuilder func onTapGestureBackported(count: Int = 1, perform action: @escaping () -> Void) -> some View { - self + self.onTapGesture(count: count, perform: action) } #elseif os(tvOS) diff --git a/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift b/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift index 0c8af0885..721524d4e 100644 --- a/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift +++ b/Sources/WalletConnectModal/Mocks/Listing+Mocks.swift @@ -2,64 +2,40 @@ import Foundation #if DEBUG -extension Listing { - static let stubList: [Listing] = [ - Listing( +extension Wallet { + static let stubList: [Wallet] = [ + Wallet( id: UUID().uuidString, name: "Sample Wallet", - homepage: "https://example.com", + homepage: "https://example.com/cool", + imageId: "0528ee7e-16d1-4089-21e3-bbfb41933100", order: 1, - imageId: UUID().uuidString, - app: Listing.App( - ios: "https://example.com/download-ios", - browser: "https://example.com/download-safari" - ), - mobile: .init( - native: "sampleapp://deeplink", - universal: "https://example.com/universal" - ), - desktop: .init( - native: nil, - universal: "https://example.com/universal" - ) + mobileLink: "https://sample.com/foo/universal", + desktopLink: "sampleapp://deeplink", + webappLink: "https://sample.com/foo/webapp", + appStore: "" ), - Listing( + Wallet( id: UUID().uuidString, - name: "Awesome Wallet", - homepage: "https://example.com/awesome", + name: "Cool Wallet", + homepage: "https://example.com/cool", + imageId: "5195e9db-94d8-4579-6f11-ef553be95100", order: 2, - imageId: UUID().uuidString, - app: Listing.App( - ios: "https://example.com/download-ios", - browser: "https://example.com/download-safari" - ), - mobile: .init( - native: "awesomeapp://deeplink", - universal: "https://example.com/awesome/universal" - ), - desktop: .init( - native: nil, - universal: "https://example.com/awesome/universal" - ) + mobileLink: "awsomeapp://", + desktopLink: "awsomeapp://deeplink", + webappLink: "https://awesome.com/foo/webapp", + appStore: "" ), - Listing( + Wallet( id: UUID().uuidString, name: "Cool Wallet", homepage: "https://example.com/cool", + imageId: "3913df81-63c2-4413-d60b-8ff83cbed500", order: 3, - imageId: UUID().uuidString, - app: Listing.App( - ios: "https://example.com/download-ios", - browser: "https://example.com/download-safari" - ), - mobile: .init( - native: "coolapp://deeplink", - universal: "https://example.com/cool/universal" - ), - desktop: .init( - native: nil, - universal: "https://example.com/cool/universal" - ) + mobileLink: "https://cool.com/foo/universal", + desktopLink: "coolapp://deeplink", + webappLink: "https://cool.com/foo/webapp", + appStore: "" ) ] } diff --git a/Sources/WalletConnectModal/Modal/ModalInteractor.swift b/Sources/WalletConnectModal/Modal/ModalInteractor.swift index 19fc70c51..fe18b4f48 100644 --- a/Sources/WalletConnectModal/Modal/ModalInteractor.swift +++ b/Sources/WalletConnectModal/Modal/ModalInteractor.swift @@ -3,7 +3,7 @@ import Combine import Foundation protocol ModalSheetInteractor { - func getListings() async throws -> [Listing] + func getWallets(page: Int, entries: Int) async throws -> (Int, [Wallet]) func createPairingAndConnect() async throws -> WalletConnectURI? var sessionSettlePublisher: AnyPublisher { get } @@ -11,24 +11,27 @@ protocol ModalSheetInteractor { } final class DefaultModalSheetInteractor: ModalSheetInteractor { - lazy var sessionSettlePublisher: AnyPublisher = WalletConnectModal.instance.sessionSettlePublisher lazy var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never> = WalletConnectModal.instance.sessionRejectionPublisher - func getListings() async throws -> [Listing] { - - let httpClient = HTTPNetworkClient(host: "explorer-api.walletconnect.com") + func getWallets(page: Int, entries: Int) async throws -> (Int, [Wallet]) { + let httpClient = HTTPNetworkClient(host: "api.web3modal.org") let response = try await httpClient.request( - ListingsResponse.self, - at: ExplorerAPI.getListings( - projectId: WalletConnectModal.config.projectId, - metadata: WalletConnectModal.config.metadata, - recommendedIds: WalletConnectModal.config.recommendedWalletIds, - excludedIds: WalletConnectModal.config.excludedWalletIds + GetWalletsResponse.self, + at: Web3ModalAPI.getWallets( + params: Web3ModalAPI.GetWalletsParams( + page: page, + entries: entries, + search: nil, + projectId: WalletConnectModal.config.projectId, + metadata: WalletConnectModal.config.metadata, + recommendedIds: WalletConnectModal.config.recommendedWalletIds, + excludedIds: WalletConnectModal.config.excludedWalletIds + ) ) ) - return response.listings.values.compactMap { $0 } + return (response.count, response.data.compactMap { $0 }) } func createPairingAndConnect() async throws -> WalletConnectURI? { diff --git a/Sources/WalletConnectModal/Modal/ModalSheet.swift b/Sources/WalletConnectModal/Modal/ModalSheet.swift index b09226db0..1bfdbaa59 100644 --- a/Sources/WalletConnectModal/Modal/ModalSheet.swift +++ b/Sources/WalletConnectModal/Modal/ModalSheet.swift @@ -119,14 +119,12 @@ public struct ModalSheet: View { @ViewBuilder private func welcome() -> some View { WalletList( - wallets: .init(get: { - viewModel.filteredWallets - }, set: { _ in }), destination: .init(get: { viewModel.destination }, set: { _ in }), + viewModel: viewModel, navigateTo: viewModel.navigateTo(_:), - onListingTap: { viewModel.onListingTap($0) } + onWalletTap: { viewModel.onWalletTap($0) } ) } diff --git a/Sources/WalletConnectModal/Modal/ModalViewModel.swift b/Sources/WalletConnectModal/Modal/ModalViewModel.swift index 5c274468a..5232c1156 100644 --- a/Sources/WalletConnectModal/Modal/ModalViewModel.swift +++ b/Sources/WalletConnectModal/Modal/ModalViewModel.swift @@ -7,7 +7,7 @@ enum Destination: Equatable { case welcome case viewAll case qr - case walletDetail(Listing) + case walletDetail(Wallet) case getWallet var contentTitle: String { @@ -42,17 +42,21 @@ final class ModalViewModel: ObservableObject { @Published private(set) var destinationStack: [Destination] = [.welcome] @Published private(set) var uri: String? - @Published private(set) var wallets: [Listing] = [] - + @Published private(set) var wallets: [Wallet] = [] + @Published var searchTerm: String = "" @Published var toast: Toast? + @Published private(set) var isThereMoreWallets: Bool = true + private var maxPage = Int.max + private var currentPage: Int = 0 + var destination: Destination { destinationStack.last! } - var filteredWallets: [Listing] { + var filteredWallets: [Wallet] { wallets .sortByRecent() .filter(searchTerm: searchTerm) @@ -119,8 +123,8 @@ final class ModalViewModel: ObservableObject { uiApplicationWrapper.openURL(url, nil) } - func onListingTap(_ listing: Listing) { - setLastTimeUsed(listing.id) + func onWalletTap(_ wallet: Wallet) { + setLastTimeUsed(wallet.id) } func onBackButton() { @@ -162,17 +166,32 @@ final class ModalViewModel: ObservableObject { @MainActor func fetchWallets() async { + let entries = 40 + do { - let wallets = try await interactor.getListings() + guard currentPage <= maxPage else { + return + } + + currentPage += 1 + + if currentPage == maxPage { + isThereMoreWallets = false + } + + let (total, wallets) = try await interactor.getWallets(page: currentPage, entries: entries) + maxPage = Int(Double(total / entries).rounded(.up)) + // Small deliberate delay to ensure animations execute properly try await Task.sleep(nanoseconds: 500_000_000) - + loadRecentWallets() checkWhetherInstalled(wallets: wallets) - self.wallets = wallets + self.wallets.append(contentsOf: wallets .sortByOrder() .sortByInstalled() + ) } catch { toast = Toast(style: .error, message: error.localizedDescription) } @@ -181,28 +200,20 @@ final class ModalViewModel: ObservableObject { // MARK: - Sorting and filtering -private extension Array where Element: Listing { - func sortByOrder() -> [Listing] { +private extension Array where Element: Wallet { + func sortByOrder() -> [Wallet] { sorted { - guard let lhs = $0.order else { - return false - } - - guard let rhs = $1.order else { - return true - } - - return lhs < rhs + $0.order < $1.order } } - func sortByInstalled() -> [Listing] { + func sortByInstalled() -> [Wallet] { sorted { lhs, rhs in - if lhs.installed, !rhs.installed { + if lhs.isInstalled, !rhs.isInstalled { return true } - if !lhs.installed, rhs.installed { + if !lhs.isInstalled, rhs.isInstalled { return false } @@ -210,7 +221,7 @@ private extension Array where Element: Listing { } } - func sortByRecent() -> [Listing] { + func sortByRecent() -> [Wallet] { sorted { lhs, rhs in guard let lhsLastTimeUsed = lhs.lastTimeUsed else { return false @@ -224,7 +235,7 @@ private extension Array where Element: Listing { } } - func filter(searchTerm: String) -> [Listing] { + func filter(searchTerm: String) -> [Wallet] { if searchTerm.isEmpty { return self } return filter { @@ -236,18 +247,18 @@ private extension Array where Element: Listing { // MARK: - Recent & Installed Wallets private extension ModalViewModel { - func checkWhetherInstalled(wallets: [Listing]) { + func checkWhetherInstalled(wallets: [Wallet]) { guard let schemes = Bundle.main.object(forInfoDictionaryKey: "LSApplicationQueriesSchemes") as? [String] else { return } wallets.forEach { if - let walletScheme = $0.mobile.native, + let walletScheme = $0.mobileLink, !walletScheme.isEmpty, schemes.contains(walletScheme.replacingOccurrences(of: "://", with: "")) { - $0.installed = uiApplicationWrapper.canOpenURL(URL(string: walletScheme)!) + $0.isInstalled = uiApplicationWrapper.canOpenURL(URL(string: walletScheme)!) } } } @@ -270,24 +281,24 @@ private extension ModalViewModel { // MARK: - Deeplinking protocol WalletDeeplinkHandler { - func openAppstore(wallet: Listing) - func navigateToDeepLink(wallet: Listing, preferUniversal: Bool, preferBrowser: Bool) + func openAppstore(wallet: Wallet) + func navigateToDeepLink(wallet: Wallet, preferUniversal: Bool, preferBrowser: Bool) } extension ModalViewModel: WalletDeeplinkHandler { - func openAppstore(wallet: Listing) { + func openAppstore(wallet: Wallet) { guard - let storeLinkString = wallet.app.ios, + let storeLinkString = wallet.appStore, let storeLink = URL(string: storeLinkString) else { return } uiApplicationWrapper.openURL(storeLink, nil) } - func navigateToDeepLink(wallet: Listing, preferUniversal: Bool, preferBrowser: Bool) { + func navigateToDeepLink(wallet: Wallet, preferUniversal: Bool, preferBrowser: Bool) { do { - let nativeScheme = preferBrowser ? nil : wallet.mobile.native - let universalScheme = preferBrowser ? wallet.desktop.universal : wallet.mobile.universal + let nativeScheme = preferBrowser ? nil : wallet.mobileLink + let universalScheme = preferBrowser ? wallet.desktopLink : wallet.mobileLink let nativeUrlString = try formatNativeUrlString(nativeScheme) let universalUrlString = try formatUniversalUrlString(universalScheme) diff --git a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift index 00ccd5929..87ca09a09 100644 --- a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift +++ b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift @@ -7,7 +7,7 @@ final class RecentWalletsStorage { self.defaults = defaults } - var recentWallets: [Listing] { + var recentWallets: [Wallet] { get { loadRecentWallets() } @@ -16,16 +16,16 @@ final class RecentWalletsStorage { } } - func loadRecentWallets() -> [Listing] { + func loadRecentWallets() -> [Wallet] { guard let data = defaults.data(forKey: "recentWallets"), - let wallets = try? JSONDecoder().decode([Listing].self, from: data) + let wallets = try? JSONDecoder().decode([Wallet].self, from: data) else { return [] } - return wallets.filter { listing in - guard let lastTimeUsed = listing.lastTimeUsed else { + return wallets.filter { wallet in + guard let lastTimeUsed = wallet.lastTimeUsed else { assertionFailure("Shouldn't happen we stored wallet without `lastTimeUsed`") return false } @@ -35,9 +35,9 @@ final class RecentWalletsStorage { } } - func saveRecentWallets(_ listings: [Listing]) { + func saveRecentWallets(_ wallets: [Wallet]) { - let subset = Array(listings.filter { + let subset = Array(wallets.filter { $0.lastTimeUsed != nil }.prefix(5)) diff --git a/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift b/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift index b5f2c7438..0255cd648 100644 --- a/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift +++ b/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift @@ -1,8 +1,8 @@ import SwiftUI struct GetAWalletView: View { - let wallets: [Listing] - let onWalletTap: (Listing) -> Void + let wallets: [Wallet] + let onWalletTap: (Wallet) -> Void let navigateToExternalLink: (URL) -> Void var body: some View { @@ -71,7 +71,7 @@ struct GetAWalletView: View { struct GetAWalletView_Previews: PreviewProvider { static var previews: some View { GetAWalletView( - wallets: Listing.stubList, + wallets: Wallet.stubList, onWalletTap: { _ in }, navigateToExternalLink: { _ in } ) diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift index 4b146927c..a00d14731 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift @@ -15,22 +15,22 @@ final class WalletDetailViewModel: ObservableObject { case didTapAppStore } - let wallet: Listing + let wallet: Wallet let deeplinkHandler: WalletDeeplinkHandler @Published var preferredPlatform: Platform = .native - var showToggle: Bool { wallet.app.browser != nil && wallet.app.ios != nil } - var showUniversalLink: Bool { preferredPlatform == .native && wallet.mobile.universal?.isEmpty == false } - var hasNativeLink: Bool { wallet.mobile.native?.isEmpty == false } + var showToggle: Bool { wallet.webappLink != nil && wallet.appStore != nil } + var showUniversalLink: Bool { preferredPlatform == .native && wallet.mobileLink?.isEmpty == false } + var hasNativeLink: Bool { wallet.mobileLink?.isEmpty == false } init( - wallet: Listing, + wallet: Wallet, deeplinkHandler: WalletDeeplinkHandler ) { self.wallet = wallet self.deeplinkHandler = deeplinkHandler - preferredPlatform = wallet.app.ios != nil ? .native : .browser + preferredPlatform = wallet.appStore != nil ? .native : .browser } func handle(_ event: Event) { diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift index 55ba54c2a..7d4b58f0e 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift @@ -1,16 +1,45 @@ import SwiftUI struct WalletList: View { - @Binding var wallets: [Listing] + @Binding var destination: Destination + @ObservedObject var viewModel: ModalViewModel + var navigateTo: (Destination) -> Void - var onListingTap: (Listing) -> Void + var onWalletTap: (Wallet) -> Void @State var numberOfColumns = 4 - @State var availableSize: CGSize = .zero + init( + destination: Binding, + viewModel: ModalViewModel, + navigateTo: @escaping (Destination) -> Void, + onWalletTap: @escaping (Wallet) -> Void, + numberOfColumns: Int = 4, + availableSize: CGSize = .zero, + infiniteScrollLoading: Bool = false + ) { + self._destination = destination + self.viewModel = viewModel + self.navigateTo = navigateTo + self.onWalletTap = onWalletTap + self.numberOfColumns = numberOfColumns + self.availableSize = availableSize + self.infiniteScrollLoading = infiniteScrollLoading + + if #available(iOS 14.0, *) { + // iOS 14 doesn't have extra separators below the list by default. + } else { + // To remove only extra separators below the list: + UITableView.appearance().tableFooterView = UIView() + } + + // To remove all separators including the actual ones: + UITableView.appearance().separatorStyle = .none + } + var body: some View { ZStack { content() @@ -23,6 +52,7 @@ struct WalletList: View { numberOfColumns = Int(round(size.width / 100)) availableSize = size } + } } @@ -47,16 +77,16 @@ struct WalletList: View { VStack { HStack { - ForEach(wallets.prefix(numberOfColumns)) { wallet in + ForEach(viewModel.filteredWallets.prefix(numberOfColumns)) { wallet in gridItem(for: wallet) } } HStack { - ForEach(wallets.dropFirst(numberOfColumns).prefix(max(numberOfColumns - 1, 0))) { wallet in + ForEach(viewModel.filteredWallets.dropFirst(numberOfColumns).prefix(max(numberOfColumns - 1, 0))) { wallet in gridItem(for: wallet) } - if wallets.count > numberOfColumns * 2 { + if viewModel.filteredWallets.count > numberOfColumns * 2 { viewAllItem() .onTapGestureBackported { withAnimation { @@ -67,32 +97,52 @@ struct WalletList: View { } } - if wallets.isEmpty { + if viewModel.filteredWallets.isEmpty { ActivityIndicator(isAnimating: .constant(true)) } } } + @State var infiniteScrollLoading = false + @ViewBuilder private func viewAll() -> some View { ZStack { Spacer().frame(maxWidth: .infinity, maxHeight: 150) - ScrollView(.vertical) { - VStack(alignment: .leading) { - ForEach(Array(stride(from: 0, to: wallets.count, by: numberOfColumns)), id: \.self) { row in - HStack { - ForEach(row ..< (row + numberOfColumns), id: \.self) { index in - if let wallet = wallets[safe: index] { - gridItem(for: wallet) - } + List { + ForEach(Array(stride(from: 0, to: viewModel.filteredWallets.count, by: numberOfColumns)), id: \.self) { row in + HStack { + ForEach(row ..< (row + numberOfColumns), id: \.self) { index in + if let wallet = viewModel.filteredWallets[safe: index] { + gridItem(for: wallet) } } } } - .padding(.vertical) + .listRowInsets(EdgeInsets(top: 0, leading: 24, bottom: 8, trailing: 24)) + .transform { + if #available(iOS 15.0, *) { + $0.listRowSeparator(.hidden) + } + } + + if viewModel.isThereMoreWallets { + Color.clear.frame(height: 100) + .onAppear { + Task { + await viewModel.fetchWallets() + } + } + .transform { + if #available(iOS 15.0, *) { + $0.listRowSeparator(.hidden) + } + } + } } - + .listStyle(.plain) + LinearGradient( stops: [ .init(color: .background1, location: 0.0), @@ -112,7 +162,7 @@ struct WalletList: View { func viewAllItem() -> some View { VStack { VStack(spacing: 3) { - let viewAllWalletsFirstRow = wallets.dropFirst(2 * numberOfColumns - 1).prefix(2) + let viewAllWalletsFirstRow = viewModel.filteredWallets.dropFirst(2 * numberOfColumns - 1).prefix(2) HStack(spacing: 3) { ForEach(viewAllWalletsFirstRow) { wallet in @@ -123,7 +173,7 @@ struct WalletList: View { } .padding(.horizontal, 5) - let viewAllWalletsSecondRow = wallets.dropFirst(2 * numberOfColumns + 1).prefix(2) + let viewAllWalletsSecondRow = viewModel.filteredWallets.dropFirst(2 * numberOfColumns + 1).prefix(2) HStack(spacing: 3) { ForEach(viewAllWalletsSecondRow) { wallet in @@ -155,7 +205,7 @@ struct WalletList: View { } @ViewBuilder - func gridItem(for wallet: Listing) -> some View { + func gridItem(for wallet: Wallet) -> some View { VStack { WalletImage(wallet: wallet) .frame(width: 60, height: 60) @@ -171,7 +221,7 @@ struct WalletList: View { .multilineTextAlignment(.center) Text(wallet.lastTimeUsed != nil ? "RECENT" : "INSTALLED") - .opacity(wallet.lastTimeUsed != nil || wallet.installed ? 1 : 0) + .opacity(wallet.lastTimeUsed != nil || wallet.isInstalled ? 1 : 0) .font(.system(size: 10)) .foregroundColor(.foreground3) .padding(.horizontal, 12) @@ -183,7 +233,7 @@ struct WalletList: View { // Small delay to let detail screen present before actually deeplinking DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - onListingTap(wallet) + onWalletTap(wallet) } } } diff --git a/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift b/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift deleted file mode 100644 index f52a3db67..000000000 --- a/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation - -enum ExplorerAPI: HTTPService { - case getListings( - projectId: String, - metadata: AppMetadata, - recommendedIds: [String], - excludedIds: [String] - ) - - var path: String { - switch self { - case .getListings: return "/w3m/v1/getiOSListings" - } - } - - var method: HTTPMethod { - switch self { - case .getListings: return .get - } - } - - var body: Data? { - nil - } - - var queryParameters: [String: String]? { - switch self { - case let .getListings(projectId, _, recommendedIds, excludedIds): - return [ - "projectId": projectId, - "recommendedIds": recommendedIds.joined(separator: ","), - "excludedIds": excludedIds.joined(separator: ","), - "sdkType": "wcm", - "sdkVersion": EnvironmentInfo.sdkName, - ] - .compactMapValues { value in - value.isEmpty ? nil : value - } - } - } - - var scheme: String { - return "https" - } - - var additionalHeaderFields: [String: String]? { - switch self { - case let .getListings(_, metadata, _, _): - return [ - "Referer": metadata.name - ] - } - } -} diff --git a/Sources/WalletConnectModal/Networking/Explorer/GetIosDataResponse.swift b/Sources/WalletConnectModal/Networking/Explorer/GetIosDataResponse.swift new file mode 100644 index 000000000..31445bebd --- /dev/null +++ b/Sources/WalletConnectModal/Networking/Explorer/GetIosDataResponse.swift @@ -0,0 +1,11 @@ +import Foundation + +struct GetIosDataResponse: Codable { + let count: Int + let data: [WalletMetadata] + + struct WalletMetadata: Codable { + let id: String + let ios_schema: String + } +} diff --git a/Sources/WalletConnectModal/Networking/Explorer/GetWalletsResponse.swift b/Sources/WalletConnectModal/Networking/Explorer/GetWalletsResponse.swift new file mode 100644 index 000000000..02d84ed88 --- /dev/null +++ b/Sources/WalletConnectModal/Networking/Explorer/GetWalletsResponse.swift @@ -0,0 +1,87 @@ +import Foundation + +struct GetWalletsResponse: Codable { + let count: Int + let data: [Wallet] +} + +class Wallet: Codable, Identifiable, Hashable { + let id: String + let name: String + let homepage: String + let imageId: String + let order: Int + let mobileLink: String? + let desktopLink: String? + let webappLink: String? + let appStore: String? + + var lastTimeUsed: Date? + var isInstalled: Bool = false + + enum CodingKeys: String, CodingKey { + case id + case name + case homepage + case imageId = "image_id" + case order + case mobileLink = "mobile_link" + case desktopLink = "desktop_link" + case webappLink = "webapp_link" + case appStore = "app_store" + + // Decorated + case lastTimeUsed + case isInstalled + } + + init( + id: String, + name: String, + homepage: String, + imageId: String, + order: Int, + mobileLink: String? = nil, + desktopLink: String? = nil, + webappLink: String? = nil, + appStore: String? = nil, + lastTimeUsed: Date? = nil, + isInstalled: Bool = false + ) { + self.id = id + self.name = name + self.homepage = homepage + self.imageId = imageId + self.order = order + self.mobileLink = mobileLink + self.desktopLink = desktopLink + self.webappLink = webappLink + self.appStore = appStore + self.lastTimeUsed = lastTimeUsed + self.isInstalled = isInstalled + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.homepage = try container.decode(String.self, forKey: .homepage) + self.imageId = try container.decode(String.self, forKey: .imageId) + self.order = try container.decode(Int.self, forKey: .order) + self.mobileLink = try container.decodeIfPresent(String.self, forKey: .mobileLink) + self.desktopLink = try container.decodeIfPresent(String.self, forKey: .desktopLink) + self.webappLink = try container.decodeIfPresent(String.self, forKey: .webappLink) + self.appStore = try container.decodeIfPresent(String.self, forKey: .appStore) + self.lastTimeUsed = try container.decodeIfPresent(Date.self, forKey: .lastTimeUsed) + self.isInstalled = try container.decodeIfPresent(Bool.self, forKey: .isInstalled) ?? false + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(name) + } + + static func == (lhs: Wallet, rhs: Wallet) -> Bool { + lhs.id == rhs.id && lhs.name == rhs.name + } +} diff --git a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift b/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift deleted file mode 100644 index 0ddd4446c..000000000 --- a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift +++ /dev/null @@ -1,74 +0,0 @@ -import Foundation - -struct ListingsResponse: Codable { - let listings: [String: Listing] -} - -class Listing: Codable, Hashable, Identifiable { - init( - id: String, - name: String, - homepage: String, - order: Int? = nil, - imageId: String, - app: Listing.App, - mobile: Listing.Links, - desktop: Listing.Links, - lastTimeUsed: Date? = nil, - installed: Bool = false - ) { - self.id = id - self.name = name - self.homepage = homepage - self.order = order - self.imageId = imageId - self.app = app - self.mobile = mobile - self.desktop = desktop - self.lastTimeUsed = lastTimeUsed - self.installed = installed - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - hasher.combine(name) - } - - static func == (lhs: Listing, rhs: Listing) -> Bool { - lhs.id == rhs.id && lhs.name == rhs.name - } - - let id: String - let name: String - let homepage: String - let order: Int? - let imageId: String - let app: App - let mobile: Links - let desktop: Links - - var lastTimeUsed: Date? - var installed: Bool = false - - private enum CodingKeys: String, CodingKey { - case id - case name - case homepage - case order - case imageId = "image_id" - case app - case mobile - case desktop - case lastTimeUsed - } - - struct App: Codable, Hashable { - let ios: String? - let browser: String? - } - - struct Links: Codable, Hashable { - let native: String? - let universal: String? - } -} diff --git a/Sources/WalletConnectModal/Networking/Explorer/Web3ModalAPI.swift b/Sources/WalletConnectModal/Networking/Explorer/Web3ModalAPI.swift new file mode 100644 index 000000000..e2c63128a --- /dev/null +++ b/Sources/WalletConnectModal/Networking/Explorer/Web3ModalAPI.swift @@ -0,0 +1,84 @@ +import Foundation + +enum Web3ModalAPI: HTTPService { + struct GetWalletsParams { + let page: Int + let entries: Int + let search: String? + let projectId: String + let metadata: AppMetadata + let recommendedIds: [String] + let excludedIds: [String] + } + + struct GetIosDataParams { + let projectId: String + let metadata: AppMetadata + } + + case getWallets(params: GetWalletsParams) + case getIosData(params: GetIosDataParams) + + var path: String { + switch self { + case .getWallets: return "/getWallets" + case .getIosData: return "/getIosData" + } + } + + var method: HTTPMethod { + switch self { + case .getWallets: return .get + case .getIosData: return .get + } + } + + var body: Data? { + nil + } + + var queryParameters: [String: String]? { + switch self { + case let .getWallets(params): + return [ + "page": "\(params.page)", + "entries": "\(params.entries)", + "search": params.search ?? "", + "recommendedIds": params.recommendedIds.joined(separator: ","), + "excludedIds": params.excludedIds.joined(separator: ","), + "platform": "ios", + ] + .compactMapValues { value in + value.isEmpty ? nil : value + } + case let .getIosData(params): + return [ + "projectId": params.projectId, + "metadata": params.metadata.name + ] + } + } + + var scheme: String { + return "https" + } + + var additionalHeaderFields: [String: String]? { + switch self { + case let .getWallets(params): + return [ + "x-project-id": params.projectId, + "x-sdk-version": WalletConnectModal.Config.sdkVersion, + "x-sdk-type": WalletConnectModal.Config.sdkType, + "Referer": params.metadata.name + ] + case let .getIosData(params): + return [ + "x-project-id": params.projectId, + "x-sdk-version": WalletConnectModal.Config.sdkVersion, + "x-sdk-type": WalletConnectModal.Config.sdkType, + "Referer": params.metadata.name + ] + } + } +} diff --git a/Sources/WalletConnectModal/UI/WalletImage.swift b/Sources/WalletConnectModal/UI/WalletImage.swift index cd70dae0a..9b142eab0 100644 --- a/Sources/WalletConnectModal/UI/WalletImage.swift +++ b/Sources/WalletConnectModal/UI/WalletImage.swift @@ -10,7 +10,7 @@ struct WalletImage: View { @Environment(\.projectId) var projectId - var wallet: Listing? + var wallet: Wallet? var size: Size = .medium var body: some View { @@ -24,7 +24,7 @@ struct WalletImage: View { } } - private func imageURL(for wallet: Listing?) -> URL? { + private func imageURL(for wallet: Wallet?) -> URL? { guard let wallet else { return nil } diff --git a/Sources/WalletConnectModal/WalletConnectModal.swift b/Sources/WalletConnectModal/WalletConnectModal.swift index 87085fcf5..c74e5c884 100644 --- a/Sources/WalletConnectModal/WalletConnectModal.swift +++ b/Sources/WalletConnectModal/WalletConnectModal.swift @@ -34,6 +34,9 @@ public class WalletConnectModal { }() struct Config { + static let sdkVersion: String = "swift-\(EnvironmentInfo.packageVersion)" + static let sdkType = "wcm" + let projectId: String var metadata: AppMetadata var sessionParams: SessionParams From 2d087357b5b4409117c7e650ac0eb4d533ebaeaf Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Tue, 19 Dec 2023 12:28:19 +0100 Subject: [PATCH 147/169] Fix tests --- .../Modal/ModalViewModel.swift | 17 ++---- .../WalletDetail/WalletDetailViewModel.swift | 25 ++++----- .../ExplorerAPITests.swift | 14 ++++- .../Mocks/ModalSheetInteractorMock.swift | 10 ++-- .../ModalViewModelTests.swift | 55 +++++++------------ 5 files changed, 51 insertions(+), 70 deletions(-) diff --git a/Sources/WalletConnectModal/Modal/ModalViewModel.swift b/Sources/WalletConnectModal/Modal/ModalViewModel.swift index 5232c1156..e78aa56e1 100644 --- a/Sources/WalletConnectModal/Modal/ModalViewModel.swift +++ b/Sources/WalletConnectModal/Modal/ModalViewModel.swift @@ -282,7 +282,7 @@ private extension ModalViewModel { protocol WalletDeeplinkHandler { func openAppstore(wallet: Wallet) - func navigateToDeepLink(wallet: Wallet, preferUniversal: Bool, preferBrowser: Bool) + func navigateToDeepLink(wallet: Wallet, preferBrowser: Bool) } extension ModalViewModel: WalletDeeplinkHandler { @@ -295,26 +295,17 @@ extension ModalViewModel: WalletDeeplinkHandler { uiApplicationWrapper.openURL(storeLink, nil) } - func navigateToDeepLink(wallet: Wallet, preferUniversal: Bool, preferBrowser: Bool) { + func navigateToDeepLink(wallet: Wallet, preferBrowser: Bool) { do { - let nativeScheme = preferBrowser ? nil : wallet.mobileLink - let universalScheme = preferBrowser ? wallet.desktopLink : wallet.mobileLink - + let nativeScheme = preferBrowser ? wallet.webappLink : wallet.mobileLink let nativeUrlString = try formatNativeUrlString(nativeScheme) - let universalUrlString = try formatUniversalUrlString(universalScheme) - if let nativeUrl = nativeUrlString?.toURL(), !preferUniversal { + if let nativeUrl = nativeUrlString?.toURL() { uiApplicationWrapper.openURL(nativeUrl) { success in if !success { self.toast = Toast(style: .error, message: DeeplinkErrors.failedToOpen.localizedDescription) } } - } else if let universalUrl = universalUrlString?.toURL() { - uiApplicationWrapper.openURL(universalUrl) { success in - if !success { - self.toast = Toast(style: .error, message: DeeplinkErrors.failedToOpen.localizedDescription) - } - } } else { throw DeeplinkErrors.noWalletLinkFound } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift index a00d14731..96d5519d3 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift @@ -36,28 +36,23 @@ final class WalletDetailViewModel: ObservableObject { func handle(_ event: Event) { switch event { case .onAppear: - deeplinkHandler.navigateToDeepLink( - wallet: wallet, - preferUniversal: true, - preferBrowser: preferredPlatform == .browser - ) + deeplinkToWallet() case .didTapUniversalLink: - deeplinkHandler.navigateToDeepLink( - wallet: wallet, - preferUniversal: true, - preferBrowser: preferredPlatform == .browser - ) + deeplinkToWallet() case .didTapTryAgain: - deeplinkHandler.navigateToDeepLink( - wallet: wallet, - preferUniversal: false, - preferBrowser: preferredPlatform == .browser - ) + deeplinkToWallet() case .didTapAppStore: deeplinkHandler.openAppstore(wallet: wallet) } } + + func deeplinkToWallet() { + deeplinkHandler.navigateToDeepLink( + wallet: wallet, + preferBrowser: preferredPlatform == .browser + ) + } } diff --git a/Tests/WalletConnectModalTests/ExplorerAPITests.swift b/Tests/WalletConnectModalTests/ExplorerAPITests.swift index 14f0f6bf5..6ee709e9c 100644 --- a/Tests/WalletConnectModalTests/ExplorerAPITests.swift +++ b/Tests/WalletConnectModalTests/ExplorerAPITests.swift @@ -6,8 +6,18 @@ final class ExplorerAPITests: XCTestCase { func testCorrectMappingOfWalletIds() throws { - let request = ExplorerAPI - .getListings(projectId: "123", metadata: .stub(), recommendedIds: ["foo", "bar"], excludedIds: ["boo", "far"]) + let request = Web3ModalAPI + .getWallets( + params: .init( + page: 0, + entries: 0, + search: "", + projectId: "123", + metadata: .stub(), + recommendedIds: ["foo", "bar"], + excludedIds: ["boo", "far"] + ) + ) .resolve(for: "www.google.com") XCTAssertEqual(request?.allHTTPHeaderFields?["Referer"], "Wallet Connect") diff --git a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift index bfc9a34b6..23ed24b76 100644 --- a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift +++ b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift @@ -7,14 +7,14 @@ import WalletConnectSign final class ModalSheetInteractorMock: ModalSheetInteractor { - var listings: [Listing] + var wallets: [Wallet] - init(listings: [Listing] = Listing.stubList) { - self.listings = listings + init(wallets: [Wallet] = Wallet.stubList) { + self.wallets = wallets } - func getListings() async throws -> [Listing] { - listings + func getWallets(page: Int, entries: Int) async throws -> (Int, [Wallet]) { + (1, wallets) } func createPairingAndConnect() async throws -> WalletConnectURI? { diff --git a/Tests/WalletConnectModalTests/ModalViewModelTests.swift b/Tests/WalletConnectModalTests/ModalViewModelTests.swift index 2b9fd7c89..5af6a0e2d 100644 --- a/Tests/WalletConnectModalTests/ModalViewModelTests.swift +++ b/Tests/WalletConnectModalTests/ModalViewModelTests.swift @@ -17,44 +17,28 @@ final class ModalViewModelTests: XCTestCase { sut = .init( isShown: .constant(true), - interactor: ModalSheetInteractorMock(listings: [ - Listing( + interactor: ModalSheetInteractorMock(wallets: [ + Wallet( id: "1", name: "Sample App", - homepage: "https://example.com", + homepage: "https://example.com/cool", + imageId: "0528ee7e-16d1-4089-21e3-bbfb41933100", order: 1, - imageId: "1", - app: Listing.App( - ios: "https://example.com/download-ios", - browser: "https://example.com/wallet" - ), - mobile: Listing.Links( - native: nil, - universal: "https://example.com/universal" - ), - desktop: Listing.Links( - native: nil, - universal: "https://example.com/universal" - ) + mobileLink: "https://example.com/universal/", + desktopLink: "sampleapp://deeplink", + webappLink: "https://sample.com/foo/webapp", + appStore: "" ), - Listing( + Wallet( id: "2", name: "Awesome App", - homepage: "https://example.com/awesome", + homepage: "https://example.com/cool", + imageId: "5195e9db-94d8-4579-6f11-ef553be95100", order: 2, - imageId: "2", - app: Listing.App( - ios: "https://example.com/download-ios", - browser: "https://example.com/wallet" - ), - mobile: Listing.Links( - native: "awesomeapp://deeplink", - universal: "https://awesome.com/awesome/universal" - ), - desktop: Listing.Links( - native: "awesomeapp://deeplink", - universal: "https://awesome.com/awesome/desktop/universal" - ) + mobileLink: "awesomeapp://deeplink", + desktopLink: "awesomeapp://deeplink", + webappLink: "https://awesome.com/awesome/universal/", + appStore: "" ), ]), uiApplicationWrapper: .init( @@ -87,9 +71,9 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual(sut.wallets.map(\.id), ["1", "2"]) XCTAssertEqual(sut.wallets.map(\.name), ["Sample App", "Awesome App"]) - expectation = XCTestExpectation(description: "Wait for openUrl to be called") + expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link") - sut.navigateToDeepLink(wallet: sut.wallets[0], preferUniversal: true, preferBrowser: false) + sut.navigateToDeepLink(wallet: sut.wallets[1], preferBrowser: false) XCTWaiter.wait(for: [expectation], timeout: 3) XCTAssertEqual( @@ -105,11 +89,12 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! + URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! ) - expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link") + expectation = XCTestExpectation(description: "Wait for openUrl to be called using webapp link") - sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: true, preferBrowser: false) + sut.navigateToDeepLink(wallet: sut.wallets[1], preferBrowser: true) XCTWaiter.wait(for: [expectation], timeout: 3) XCTAssertEqual( From 49403f76771a171208d882390d3aed85f715ce98 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Thu, 28 Dec 2023 16:57:25 +0100 Subject: [PATCH 148/169] Fix tests --- .../WalletConnectModalTests/ExplorerAPITests.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Tests/WalletConnectModalTests/ExplorerAPITests.swift b/Tests/WalletConnectModalTests/ExplorerAPITests.swift index 6ee709e9c..26bdb83e9 100644 --- a/Tests/WalletConnectModalTests/ExplorerAPITests.swift +++ b/Tests/WalletConnectModalTests/ExplorerAPITests.swift @@ -9,8 +9,8 @@ final class ExplorerAPITests: XCTestCase { let request = Web3ModalAPI .getWallets( params: .init( - page: 0, - entries: 0, + page: 2, + entries: 40, search: "", projectId: "123", metadata: .stub(), @@ -21,13 +21,16 @@ final class ExplorerAPITests: XCTestCase { .resolve(for: "www.google.com") XCTAssertEqual(request?.allHTTPHeaderFields?["Referer"], "Wallet Connect") + XCTAssertEqual(request?.allHTTPHeaderFields?["x-sdk-version"], WalletConnectModal.Config.sdkVersion) + XCTAssertEqual(request?.allHTTPHeaderFields?["x-sdk-type"], "wcm") + XCTAssertEqual(request?.allHTTPHeaderFields?["x-project-id"], "123") XCTAssertEqual(request?.url?.queryParameters, [ - "projectId": "123", "recommendedIds": "foo,bar", + "page": "2", + "entries": "40", + "platform": "ios", "excludedIds": "boo,far", - "sdkVersion": EnvironmentInfo.sdkName, - "sdkType": "wcm" ]) } } From 33a036fbb1618804ccf99e13fa5c9e82e5ac06ff Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Mon, 22 Jan 2024 19:39:29 +0100 Subject: [PATCH 149/169] pr feedback --- Example/DApp/Modules/Sign/SignPresenter.swift | 10 ++++------ .../Screens/WalletDetail/WalletDetailViewModel.swift | 9 +-------- .../WalletConnectModal/Modal/Screens/WalletList.swift | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 896f50442..e96791894 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -54,12 +54,10 @@ final class SignPresenter: ObservableObject { } func connectWalletWithWCM() { - Task { - WalletConnectModal.set(sessionParams: .init( - requiredNamespaces: Proposal.requiredNamespaces, - optionalNamespaces: Proposal.optionalNamespaces - )) - } + WalletConnectModal.set(sessionParams: .init( + requiredNamespaces: Proposal.requiredNamespaces, + optionalNamespaces: Proposal.optionalNamespaces + )) WalletConnectModal.present(from: nil) } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift index 96d5519d3..f1ee61ac5 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetailViewModel.swift @@ -35,15 +35,8 @@ final class WalletDetailViewModel: ObservableObject { func handle(_ event: Event) { switch event { - case .onAppear: + case .onAppear, .didTapUniversalLink, .didTapTryAgain: deeplinkToWallet() - - case .didTapUniversalLink: - deeplinkToWallet() - - case .didTapTryAgain: - deeplinkToWallet() - case .didTapAppStore: deeplinkHandler.openAppstore(wallet: wallet) } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift index 7d4b58f0e..96efd6a13 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift @@ -33,11 +33,11 @@ struct WalletList: View { // iOS 14 doesn't have extra separators below the list by default. } else { // To remove only extra separators below the list: - UITableView.appearance().tableFooterView = UIView() + UITableView.appearance(whenContainedInInstancesOf: [WalletConnectModalSheetController.self]).tableFooterView = UIView() } // To remove all separators including the actual ones: - UITableView.appearance().separatorStyle = .none + UITableView.appearance(whenContainedInInstancesOf: [WalletConnectModalSheetController.self]).separatorStyle = .none } var body: some View { From b2b63d1eceab3dd0be0e2533dcecace09801e851 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Mon, 29 Jan 2024 12:54:31 +0100 Subject: [PATCH 150/169] Fix tests --- Tests/WalletConnectModalTests/ModalViewModelTests.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Tests/WalletConnectModalTests/ModalViewModelTests.swift b/Tests/WalletConnectModalTests/ModalViewModelTests.swift index 5af6a0e2d..a7ec21f6d 100644 --- a/Tests/WalletConnectModalTests/ModalViewModelTests.swift +++ b/Tests/WalletConnectModalTests/ModalViewModelTests.swift @@ -78,18 +78,17 @@ final class ModalViewModelTests: XCTestCase { XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "https://example.com/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! + URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) expectation = XCTestExpectation(description: "Wait for openUrl to be called using universal link") - sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: false, preferBrowser: false) + sut.navigateToDeepLink(wallet: sut.wallets[1], preferBrowser: false) XCTWaiter.wait(for: [expectation], timeout: 3) XCTAssertEqual( openURLFuncTest.currentValue, URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! - URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! ) expectation = XCTestExpectation(description: "Wait for openUrl to be called using webapp link") @@ -104,12 +103,12 @@ final class ModalViewModelTests: XCTestCase { expectation = XCTestExpectation(description: "Wait for openUrl to be called using native link") - sut.navigateToDeepLink(wallet: sut.wallets[1], preferUniversal: false, preferBrowser: true) + sut.navigateToDeepLink(wallet: sut.wallets[1], preferBrowser: true) XCTWaiter.wait(for: [expectation], timeout: 3) XCTAssertEqual( openURLFuncTest.currentValue, - URL(string: "https://awesome.com/awesome/desktop/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! + URL(string: "https://awesome.com/awesome/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn%26expiryTimestamp%3D1706001526")! ) } } From abaa83df342923e1cd3aa520d9e524623d4284e2 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 29 Jan 2024 19:36:01 +0300 Subject: [PATCH 151/169] Update NotifySubsctiptionsUpdater.swift --- .../Client/Wallet/NotifySubsctiptionsUpdater.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift index 367e3b23a..6b48492d0 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySubsctiptionsUpdater.swift @@ -42,9 +42,13 @@ final class NotifySubsctiptionsUpdater { try kms.setSymmetricKey(symKey, for: subscription.topic) } - let topics = newSubscriptions.map { $0.topic } + let topicsToSubscribe = newSubscriptions.map { $0.topic } - try await networkingInteractor.batchSubscribe(topics: topics) + let oldTopics = Set(oldSubscriptions.map { $0.topic }) + let topicsToUnsubscribe = Array(oldTopics.subtracting(topicsToSubscribe)) + + try await networkingInteractor.batchUnsubscribe(topics: topicsToUnsubscribe) + try await networkingInteractor.batchSubscribe(topics: topicsToSubscribe) try Task.checkCancellation() From 228613025e8f61610ca836a9b3e0d56b1b9ec039 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 29 Jan 2024 12:00:54 +0300 Subject: [PATCH 152/169] Listings query params updated --- .../WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift index c121e1e38..1d3eebf18 100644 --- a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift +++ b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift @@ -16,7 +16,7 @@ enum ListingsAPI: HTTPService { } var queryParameters: [String : String]? { - return ["projectId": InputConfig.projectId, "entries": "100"] + return ["projectId": InputConfig.projectId, "isVerified": "true", "isFeatured": "true"] } var additionalHeaderFields: [String : String]? { From 8889de6b5cbf6c367b368519e39782fe28bc7fe1 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 29 Jan 2024 18:31:51 +0300 Subject: [PATCH 153/169] Body urls --- .../Wallet/PushMessages/SubscriptionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index 890629513..647121270 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -77,7 +77,7 @@ struct SubscriptionView: View { .font(.system(size: 11)) } - Text(pushMessage.subtitle) + Text(.init(pushMessage.subtitle)) .foregroundColor(.Foreground175) .font(.system(size: 13)) From 28f667e2e52ae2ebef2aed3566b3f989d1d1d561 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 29 Jan 2024 21:38:12 +0300 Subject: [PATCH 154/169] Regression fixes --- .../Wallet/PushMessages/SubscriptionPresenter.swift | 3 ++- .../Wallet/PushMessages/SubscriptionView.swift | 3 ++- Sources/Database/SQLiteQuery.swift | 13 ++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index e5b69d57c..bfddfee6e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -64,7 +64,8 @@ final class SubscriptionPresenter: ObservableObject { case .idle: Task(priority: .high) { @MainActor in loadingState = .loading - isMoreDataAvailable = try await interactor.fetchHistory(after: messages.last?.id, limit: 50) + let isLoaded = try? await interactor.fetchHistory(after: messages.last?.id, limit: 50) + isMoreDataAvailable = isLoaded ?? false loadingState = .idle } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index 647121270..e68fcb5b6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -49,7 +49,7 @@ struct SubscriptionView: View { private func notificationView(pushMessage: NotifyMessageViewModel) -> some View { VStack(alignment: .center) { HStack(spacing: 12) { - CacheAsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in + CacheAsyncImage(url: URL(string: pushMessage.imageUrl) ?? presenter.subscriptionViewModel.imageUrl) { phase in if let image = phase.image { image .resizable() @@ -58,6 +58,7 @@ struct SubscriptionView: View { .cornerRadius(10, corners: .allCorners) } else { Color.black + .opacity(0.05) .frame(width: 48, height: 48) .cornerRadius(10, corners: .allCorners) } diff --git a/Sources/Database/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift index ce2d8d920..ce6078410 100644 --- a/Sources/Database/SQLiteQuery.swift +++ b/Sources/Database/SQLiteQuery.swift @@ -7,7 +7,7 @@ public struct SqliteQuery { for row in rows { values.append(row.encode().values - .map { "'\($0.value)'" } + .map { "'\($0.value.screen())'" } .joined(separator: ", ")) } @@ -34,7 +34,7 @@ public struct SqliteQuery { } public static func select(table: String, where argument: String, equals value: String) -> String { - return "SELECT * FROM \(table) WHERE \(argument) = '\(value)';" + return "SELECT * FROM \(table) WHERE \(argument) = '\(value.screen())';" } public static func delete(table: String) -> String { @@ -42,7 +42,7 @@ public struct SqliteQuery { } public static func delete(table: String, where argument: String, equals value: String) -> String { - return "DELETE FROM \(table) WHERE \(argument) = '\(value)';" + return "DELETE FROM \(table) WHERE \(argument) = '\(value.screen())';" } } @@ -52,3 +52,10 @@ extension SqliteQuery { case rowsNotFound } } + +private extension String { + + func screen() -> String { + return replacingOccurrences(of: "'", with: "''") + } +} From 81deb47a66d3a56bc79a6980e4cfd00192be7667 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 29 Jan 2024 22:04:10 +0300 Subject: [PATCH 155/169] Notify imports fixed --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d6d573c09..e3c3b7eb8 100644 --- a/Package.swift +++ b/Package.swift @@ -73,7 +73,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectIdentity", "WalletConnectPush", "WalletConnectSigner", "Database"], + dependencies: ["WalletConnectPairing", "WalletConnectIdentity", "WalletConnectPush", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", From 35049369732b5667298bb8c2981fa3bafe5a9dd3 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 14:43:33 +0300 Subject: [PATCH 156/169] NotificationService notofocation types icon --- .../NotificationService.swift | 110 +++++++++--------- .../Client/Wallet/NotifyClientFactory.swift | 18 +-- .../Wallet/NotifyDecryptionService.swift | 15 ++- .../Client/Wallet/NotifySqliteFactory.swift | 25 ++++ .../Client/Wallet/NotifyStorageFactory.swift | 8 ++ 5 files changed, 102 insertions(+), 74 deletions(-) create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 9fae80990..87ac13e5f 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -75,11 +75,11 @@ class NotificationService: UNNotificationServiceExtension { private func handleNotifyNotification(content: UNNotificationContent, topic: String, ciphertext: String) -> UNMutableNotificationContent { do { let service = NotifyDecryptionService(groupIdentifier: "group.com.walletconnect.sdk") - let (pushMessage, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext) + let (pushMessage, subscription, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext) log("message decrypted", account: account, topic: topic, message: pushMessage) - let updatedContent = handle(content: content, pushMessage: pushMessage, topic: topic) + let updatedContent = handle(content: content, pushMessage: pushMessage, subscription: subscription, topic: topic) let mutableContent = updatedContent.mutableCopy() as! UNMutableNotificationContent mutableContent.title = pushMessage.title @@ -114,62 +114,66 @@ class NotificationService: UNNotificationServiceExtension { private extension NotificationService { - func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) -> UNNotificationContent { - do { - let iconUrl = try pushMessage.icon.asURL() + func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent { + + let icon = subscription.scope[pushMessage.type]?.imageUrls?.sm ?? pushMessage.icon + var senderAvatar: INImage? + + do { + let iconUrl = try icon.asURL() let senderThumbnailImageData = try Data(contentsOf: iconUrl) let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) - let senderAvatar = INImage(imageData: senderThumbnailImageFileData) - - var personNameComponents = PersonNameComponents() - personNameComponents.nickname = pushMessage.title - - let senderPerson = INPerson( - personHandle: INPersonHandle(value: topic, type: .unknown), - nameComponents: personNameComponents, - displayName: pushMessage.title, - image: senderAvatar, - contactIdentifier: nil, - customIdentifier: topic, - isMe: false, - suggestionType: .none - ) - - let selfPerson = INPerson( - personHandle: INPersonHandle(value: "0", type: .unknown), - nameComponents: nil, - displayName: nil, - image: nil, - contactIdentifier: nil, - customIdentifier: nil, - isMe: true, - suggestionType: .none - ) - - let incomingMessagingIntent = INSendMessageIntent( - recipients: [selfPerson], - outgoingMessageType: .outgoingMessageText, - content: pushMessage.body, - speakableGroupName: nil, - conversationIdentifier: pushMessage.type, - serviceName: nil, - sender: senderPerson, - attachments: [] - ) - - incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) - - let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) - interaction.direction = .incoming - interaction.donate(completion: nil) - - return try content.updating(from: incomingMessagingIntent) - } - catch { - return content + senderAvatar = INImage(imageData: senderThumbnailImageFileData) + } catch { + log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) } + + var personNameComponents = PersonNameComponents() + personNameComponents.nickname = pushMessage.title + + let senderPerson = INPerson( + personHandle: INPersonHandle(value: topic, type: .unknown), + nameComponents: personNameComponents, + displayName: pushMessage.title, + image: senderAvatar, + contactIdentifier: nil, + customIdentifier: topic, + isMe: false, + suggestionType: .none + ) + + let selfPerson = INPerson( + personHandle: INPersonHandle(value: "0", type: .unknown), + nameComponents: nil, + displayName: nil, + image: nil, + contactIdentifier: nil, + customIdentifier: nil, + isMe: true, + suggestionType: .none + ) + + let incomingMessagingIntent = INSendMessageIntent( + recipients: [selfPerson], + outgoingMessageType: .outgoingMessageText, + content: pushMessage.body, + speakableGroupName: nil, + conversationIdentifier: pushMessage.type, + serviceName: nil, + sender: senderPerson, + attachments: [] + ) + + incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) + + let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) + interaction.direction = .incoming + interaction.donate(completion: nil) + + let updated = try? content.updating(from: incomingMessagingIntent) + return updated ?? content } func downloadAttachment(data: Data, fileName: String) throws -> URL { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 14fa2fdef..c18b8749f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -7,8 +7,7 @@ public struct NotifyClientFactory { let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier) let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier) - let databasePath = databasePath(appGroup: groupIdentifier, database: "notify_v\(version).db") - let sqlite = DiskSqlite(path: databasePath) + let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier) return NotifyClientFactory.create( projectId: projectId, @@ -97,19 +96,4 @@ public struct NotifyClientFactory { subscriptionWatcher: subscriptionWatcher ) } - - static func databasePath(appGroup: String, database: String) -> String { - guard let path = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? - .appendingPathComponent(database) else { - - fatalError("Database path not exists") - } - - return path.absoluteString - } - - static var version: String { - return "1" - } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift index 272a37cea..e53b53042 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift @@ -3,29 +3,36 @@ import Foundation public class NotifyDecryptionService { enum Errors: Error { case malformedNotifyMessage + case subsctiptionNotFound } private let serializer: Serializing + private let database: NotifyDatabase private static let notifyTags: [UInt] = [4002] - init(serializer: Serializing) { + init(serializer: Serializing, database: NotifyDatabase) { self.serializer = serializer + self.database = database } public init(groupIdentifier: String) { let keychainStorage = GroupKeychainStorage(serviceIdentifier: groupIdentifier) let kms = KeyManagementService(keychain: keychainStorage) - self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) + let logger = ConsoleLogger(prefix: "🔐", loggingLevel: .off) + let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier) + self.serializer = Serializer(kms: kms, logger: logger) + self.database = NotifyDatabase(sqlite: sqlite, logger: logger) } public static func canHandle(tag: UInt) -> Bool { return notifyTags.contains(tag) } - public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, Account) { + public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, NotifySubscription, Account) { let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage } let wrapper = try params.get(NotifyMessagePayload.Wrapper.self) let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper) - return (messagePayload.message, messagePayload.account) + guard let subscription = database.getSubscription(topic: topic) else { throw Errors.subsctiptionNotFound } + return (messagePayload.message, subscription, messagePayload.account) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift new file mode 100644 index 000000000..cd1b0dd59 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySqliteFactory.swift @@ -0,0 +1,25 @@ +import Foundation + +struct NotifySqliteFactory { + + static func create(appGroup: String) -> Sqlite { + let databasePath = databasePath(appGroup: appGroup, database: "notify_v\(version).db") + let sqlite = DiskSqlite(path: databasePath) + return sqlite + } + + static func databasePath(appGroup: String, database: String) -> String { + guard let path = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? + .appendingPathComponent(database) else { + + fatalError("Database path not exists") + } + + return path.absoluteString + } + + static var version: String { + return "1" + } +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift new file mode 100644 index 000000000..3d5e56c21 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift @@ -0,0 +1,8 @@ +import Foundation + +struct NotifyStorageFactory { + + static func create() -> NotifyStorage { + + } +} From 03fe849f3a2eba3926734229c7a3abd5add38dfa Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 30 Jan 2024 13:58:14 +0100 Subject: [PATCH 157/169] add getPendingRequestsSortedByTimestamp to history service --- .../Services/HistoryService.swift | 29 ++++++++++++++----- .../Sign/SessionRequestsProvider.swift | 6 ++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index a34f52bb5..dba1bdbb7 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -15,32 +15,45 @@ final class HistoryService { public func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? { guard let record = history.get(recordId: id) else { return nil } - guard let (request, recordId) = mapRequestRecord(record) else { + guard let (request, recordId, _) = mapRequestRecord(record) else { return nil } return (request, try? verifyContextStore.get(key: recordId.string)) } func getPendingRequests() -> [(request: Request, context: VerifyContext?)] { + getPendingRequestsSortedByTimestamp() + } + + func getPendingRequestsSortedByTimestamp() -> [(request: Request, context: VerifyContext?)] { let requests = history.getPending() .compactMap { mapRequestRecord($0) } - .filter { !$0.0.isExpired() } // Note the change here to access the Request part of the tuple - return requests.map { (request: $0.0, context: try? verifyContextStore.get(key: $0.1.string)) } - } + .filter { !$0.0.isExpired() } + .sorted { + switch ($0.2, $1.2) { + case let (date1?, date2?): return date1 < date2 // Both dates are present + case (nil, _): return false // First date is nil, so it should go last + case (_, nil): return true // Second date is nil, so the first one should come first + } + } + .map { (request: $0.0, context: try? verifyContextStore.get(key: $0.1.string)) } + return requests + } func getPendingRequestsWithRecordId() -> [(request: Request, recordId: RPCID)] { - history.getPending() + return history.getPending() .compactMap { mapRequestRecord($0) } + .map { (request: $0.0, recordId: $0.1) } } func getPendingRequests(topic: String) -> [(request: Request, context: VerifyContext?)] { - return getPendingRequests().filter { $0.request.topic == topic } + return getPendingRequestsSortedByTimestamp().filter { $0.request.topic == topic } } } private extension HistoryService { - func mapRequestRecord(_ record: RPCHistory.Record) -> (Request, RPCID)? { + func mapRequestRecord(_ record: RPCHistory.Record) -> (Request, RPCID, Date?)? { guard let request = try? record.request.params?.get(SessionType.RequestParams.self) else { return nil } @@ -53,6 +66,6 @@ private extension HistoryService { expiryTimestamp: request.request.expiryTimestamp ) - return (mappedRequest, record.id) + return (mappedRequest, record.id, record.timestamp) } } diff --git a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift new file mode 100644 index 000000000..d3387b6e7 --- /dev/null +++ b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift @@ -0,0 +1,6 @@ + +import Foundation + +//class SessionRequestsProvider { +// +//} From 96f2339f7195b26f24a4e5325e7ccd4e59c4aead Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:32:24 +0300 Subject: [PATCH 158/169] Icons from notification types --- .../NotificationService.swift | 20 +++++++++---------- .../Models/NotifyMessageViewModel.swift | 4 ++++ .../PushMessages/SubscriptionPresenter.swift | 5 +++++ .../PushMessages/SubscriptionView.swift | 4 ++-- .../Client/Wallet/NotifyImageUrls.swift | 14 +++++++++++++ .../Client/Wallet/NotifyStorageFactory.swift | 8 -------- .../DataStructures/NotifySubscription.swift | 4 ++++ 7 files changed, 39 insertions(+), 20 deletions(-) delete mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 87ac13e5f..d3f535f8f 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -115,19 +115,19 @@ class NotificationService: UNNotificationServiceExtension { private extension NotificationService { func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent { - - let icon = subscription.scope[pushMessage.type]?.imageUrls?.sm ?? pushMessage.icon var senderAvatar: INImage? - do { - let iconUrl = try icon.asURL() - let senderThumbnailImageData = try Data(contentsOf: iconUrl) - let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) - let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) - senderAvatar = INImage(imageData: senderThumbnailImageFileData) - } catch { - log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) + if let icon = subscription.messageIcons(ofType: pushMessage.type).md { + do { + let iconUrl = try icon.asURL() + let senderThumbnailImageData = try Data(contentsOf: iconUrl) + let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) + let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) + senderAvatar = INImage(imageData: senderThumbnailImageFileData) + } catch { + log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage) + } } var personNameComponents = PersonNameComponents() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift index 9e937154c..342bac31e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift @@ -24,4 +24,8 @@ struct NotifyMessageViewModel: Identifiable { var publishedAt: String { return pushMessageRecord.publishedAt.formatted(.relative(presentation: .named, unitsStyle: .wide)) } + + var type: String { + return pushMessageRecord.message.type + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index bfddfee6e..2f9bb1a21 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -52,6 +52,11 @@ final class SubscriptionPresenter: ObservableObject { } } + func messageIconUrl(message: NotifyMessageViewModel) -> URL? { + let icons = subscription.messageIcons(ofType: message.type) + return try? icons.md?.asURL() + } + func unsubscribe() { interactor.deleteSubscription(subscription) router.dismiss() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index e68fcb5b6..e168a8814 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -49,12 +49,12 @@ struct SubscriptionView: View { private func notificationView(pushMessage: NotifyMessageViewModel) -> some View { VStack(alignment: .center) { HStack(spacing: 12) { - CacheAsyncImage(url: URL(string: pushMessage.imageUrl) ?? presenter.subscriptionViewModel.imageUrl) { phase in + CacheAsyncImage(url: presenter.messageIconUrl(message: pushMessage)) { phase in if let image = phase.image { image .resizable() .frame(width: 48, height: 48) - .background(Color.black) + .background(Color.black.opacity(0.05)) .cornerRadius(10, corners: .allCorners) } else { Color.black diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift index 124dc1def..c5963bae2 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift @@ -1,7 +1,21 @@ import Foundation public struct NotifyImageUrls: Codable, Equatable { + public let sm: String? public let md: String? public let lg: String? + + public init(sm: String? = nil, md: String? = nil, lg: String? = nil) { + self.sm = sm + self.md = md + self.lg = lg + } + + public init?(icons: [String]) { + guard icons.count == 3 else { return nil } + self.sm = icons[0] + self.md = icons[1] + self.lg = icons[2] + } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift deleted file mode 100644 index 3d5e56c21..000000000 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorageFactory.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -struct NotifyStorageFactory { - - static func create() -> NotifyStorage { - - } -} diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index 0cc7f313e..54be8d136 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -15,6 +15,10 @@ public struct NotifySubscription: Codable, Equatable, SqliteRow { return "\(account.absoluteString)-\(metadata.url)" } + public func messageIcons(ofType type: String) -> NotifyImageUrls { + return scope[type]?.imageUrls ?? NotifyImageUrls(icons: metadata.icons) ?? NotifyImageUrls() + } + public init(decoder: SqliteRowDecoder) throws { self.topic = try decoder.decodeString(at: 0) self.account = try Account(decoder.decodeString(at: 1))! From 17107eab8d57751826c68c60efb19516cbdc2162 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:34:45 +0300 Subject: [PATCH 159/169] Badge disabled --- .../Wallet/Notifications/Models/SubscriptionsViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift index 6e0f12f42..cced6706c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift @@ -45,6 +45,7 @@ struct SubscriptionsViewModel: Identifiable { } var hasMessage: Bool { - return messagesCount != 0 + /* return messagesCount != 0 Badge disabled */ + return false } } From 6aa35d52828afe7e68c1defe381e8e8969be117a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 30 Jan 2024 18:41:09 +0300 Subject: [PATCH 160/169] Bottom padding --- .../Wallet/Notifications/NotificationsView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index a3146cbd8..7cf63ed8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -62,6 +62,9 @@ struct NotificationsView: View { .listRowSeparator(.hidden) } .listStyle(PlainListStyle()) + .safeAreaInset(edge: .bottom) { + Spacer().frame(height: 16) + } } .task { try? await presenter.fetch() From 39b004c8a2f05af9ccfe96744a159b80651af596 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 09:55:18 +0100 Subject: [PATCH 161/169] savepoint --- .../xcshareddata/xcschemes/Wallet.xcscheme | 20 +++++++- .../Wallet/Main/MainPresenter.swift | 12 +++-- .../Engine/Common/SessionEngine.swift | 21 +++++++-- .../Sign/SessionRequestsProvider.swift | 46 +++++++++++++++++-- .../WalletConnectSign/Sign/SignClient.swift | 6 +-- .../Sign/SignClientFactory.swift | 3 +- .../RPCHistory/RPCHistory.swift | 2 +- .../SessionRequestsProviderTests.swift | 18 ++++++++ 8 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Wallet.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Wallet.xcscheme index d786c3306..9619bb4be 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Wallet.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Wallet.xcscheme @@ -73,6 +73,15 @@ allowLocationSimulation = "YES"> + + + + - + + + + + diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 3d5c8a0b6..76726ac06 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -48,11 +48,15 @@ extension MainPresenter { interactor.sessionRequestPublisher .receive(on: DispatchQueue.main) - .sink { [unowned self] request, context in - router.dismiss() - router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) + .sink { [unowned self] (request, context) in + if let rootview = router.viewController.presentedViewController as? SessionViewController { + return + } else { + router.dismiss() + router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) + } }.store(in: &disposeBag) - + interactor.requestPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] result in diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 9d0c034f6..f9cbd1ad8 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -7,12 +7,16 @@ final class SessionEngine { } var onSessionsUpdate: (([Session]) -> Void)? - var onSessionRequest: ((Request, VerifyContext?) -> Void)? var onSessionResponse: ((Response) -> Void)? var onSessionRejected: ((String, SessionType.Reason) -> Void)? var onSessionDelete: ((String, SessionType.Reason) -> Void)? var onEventReceived: ((String, Session.Event, Blockchain?) -> Void)? + var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { + return sessionRequestsProvider.sessionRequestPublisher + } + + private let sessionStore: WCSessionStorage private let networkingInteractor: NetworkInteracting private let historyService: HistoryService @@ -21,6 +25,7 @@ final class SessionEngine { private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging + private let sessionRequestsProvider: SessionRequestsProvider init( networkingInteractor: NetworkInteracting, @@ -29,7 +34,8 @@ final class SessionEngine { verifyClient: VerifyClientProtocol, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, - logger: ConsoleLogging + logger: ConsoleLogging, + sessionRequestsProvider: SessionRequestsProvider ) { self.networkingInteractor = networkingInteractor self.historyService = historyService @@ -38,12 +44,16 @@ final class SessionEngine { self.kms = kms self.sessionStore = sessionStore self.logger = logger + self.sessionRequestsProvider = sessionRequestsProvider setupConnectionSubscriptions() setupRequestSubscriptions() setupResponseSubscriptions() setupUpdateSubscriptions() setupExpirationSubscriptions() + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [unowned self] in + sessionRequestsProvider.emitRequestIfPending() + } } func hasSession(for topic: String) -> Bool { @@ -95,6 +105,7 @@ final class SessionEngine { protocolMethod: protocolMethod ) verifyContextStore.delete(forKey: requestId.string) + sessionRequestsProvider.emitRequestIfPending() } func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws { @@ -249,12 +260,12 @@ private extension SessionEngine { let response = try await verifyClient.verifyOrigin(assertionId: assertionId) let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: session.peerParticipant.metadata.url, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) - onSessionRequest?(request, verifyContext) + + sessionRequestsProvider.emitRequestIfPending() } catch { let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url, isScam: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) - onSessionRequest?(request, verifyContext) - return + sessionRequestsProvider.emitRequestIfPending() } } } diff --git a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift index d3387b6e7..a8b91d325 100644 --- a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift +++ b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift @@ -1,6 +1,44 @@ - +import Combine import Foundation -//class SessionRequestsProvider { -// -//} +class SessionRequestsProvider { + private let historyService: HistoryService + private var sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() + private var cancellables = Set() + private var lastEmitDate: Date? + private var emitRequestSubject = PassthroughSubject() + + public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { + sessionRequestPublisherSubject.eraseToAnyPublisher() + } + + init(historyService: HistoryService) { + self.historyService = historyService + setupEmitRequestHandling() + } + + private func setupEmitRequestHandling() { + emitRequestSubject + .sink { [unowned self] _ in + + let now = Date() + if let lastEmitDate = self.lastEmitDate, now.timeIntervalSince(lastEmitDate) < 1 { + // If the last emit was less than 1 second ago, ignore this request. + return + } + + // Update the last emit time to now. + self.lastEmitDate = now + + // Fetch the oldest request and emit it. + if let oldestRequest = self.historyService.getPendingRequestsSortedByTimestamp().first { + self.sessionRequestPublisherSubject.send(oldestRequest) + } + } + .store(in: &cancellables) + } + + func emitRequestIfPending() { + emitRequestSubject.send(()) + } +} diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 9c4b5e14d..0122f0f82 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -24,7 +24,7 @@ public final class SignClient: SignClientProtocol { /// /// In most cases event will be emited on wallet public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { - sessionRequestPublisherSubject.eraseToAnyPublisher() + sessionEngine.sessionRequestPublisher } /// Publisher that sends web socket connection status @@ -138,7 +138,6 @@ public final class SignClient: SignClientProtocol { private let requestsExpiryWatcher: RequestsExpiryWatcher private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>() - private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() private let socketConnectionStatusPublisherSubject = PassthroughSubject() private let sessionSettlePublisherSubject = PassthroughSubject() private let sessionDeletePublisherSubject = PassthroughSubject<(String, Reason), Never>() @@ -359,9 +358,6 @@ public final class SignClient: SignClientProtocol { approveEngine.onSessionSettle = { [unowned self] settledSession in sessionSettlePublisherSubject.send(settledSession) } - sessionEngine.onSessionRequest = { [unowned self] (sessionRequest, context) in - sessionRequestPublisherSubject.send((sessionRequest, context)) - } sessionEngine.onSessionDelete = { [unowned self] topic, reason in sessionDeletePublisherSubject.send((topic, reason)) } diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 3d63bcf39..c01f9855a 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -43,7 +43,8 @@ public struct SignClientFactory { let verifyContextStore = CodableStore(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue) let historyService = HistoryService(history: rpcHistory, verifyContextStore: verifyContextStore) let verifyClient = VerifyClientFactory.create() - let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger) + let sessionRequestsProvider = SessionRequestsProvider(historyService: historyService) + let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let sessionExtendRequester = SessionExtendRequester(sessionStore: sessionStore, networkingInteractor: networkingClient) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 274122c3d..6e8fb9443 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -32,7 +32,7 @@ public final class RPCHistory { case .responseDuplicateNotAllowed: return "Response duplicates are not allowed." case .requestMatchingResponseNotFound: - return "Matching requesr for the response not found." + return "Matching request for the response not found." } } } diff --git a/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift b/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift new file mode 100644 index 000000000..8e2101290 --- /dev/null +++ b/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import WalletConnectSign + +class SessionRequestsProviderTests: XCTestCase { + + var sut: SessionRequestsProvider! + + func testEmitNewRequestWhenNoPending() { + + } + + func testEmitOldRequestOnNewWhenThereArePendingRequests() { + + } + +} + + From 7434413813bf451c69569b5877bc6830d0cfd379 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 11:25:35 +0100 Subject: [PATCH 162/169] clean up rpc hiistory handle session request screen --- .../PresentationLayer/Wallet/Main/MainPresenter.swift | 6 ++++-- .../Wallet/SessionRequest/SessionRequestModule.swift | 2 ++ .../Wallet/Settings/SettingsPresenter.swift | 2 ++ .../PresentationLayer/Wallet/Wallet/WalletRouter.swift | 2 +- Sources/WalletConnectSign/Engine/Common/SessionEngine.swift | 6 ++++-- Sources/WalletConnectSign/Services/SignCleanupService.swift | 6 +++++- Sources/WalletConnectSign/Sign/SignClientFactory.swift | 2 +- Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift | 4 ++++ 8 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 76726ac06..debbf14df 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -1,5 +1,6 @@ import UIKit import Combine +import SwiftUI final class MainPresenter { private let interactor: MainInteractor @@ -49,14 +50,15 @@ extension MainPresenter { interactor.sessionRequestPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] (request, context) in - if let rootview = router.viewController.presentedViewController as? SessionViewController { + if let vc = UIApplication.currentWindow.rootViewController?.topController, + vc.restorationIdentifier == SessionRequestModule.restorationIdentifier { return } else { router.dismiss() router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) } }.store(in: &disposeBag) - + interactor.requestPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] result in diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift index a12c75573..6cffd00ea 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift @@ -3,6 +3,7 @@ import SwiftUI import Web3Wallet final class SessionRequestModule { + static let restorationIdentifier = "SessionRequestViewController" @discardableResult static func create(app: Application, sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) -> UIViewController { let router = SessionRequestRouter(app: app) @@ -10,6 +11,7 @@ final class SessionRequestModule { let presenter = SessionRequestPresenter(interactor: interactor, router: router, sessionRequest: sessionRequest, importAccount: importAccount, context: sessionContext) let view = SessionRequestView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) + viewController.restorationIdentifier = Self.restorationIdentifier router.viewController = viewController diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift index d9c10848d..163799e3c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift @@ -1,6 +1,7 @@ import UIKit import Combine import WalletConnectNetworking +import Web3Wallet final class SettingsPresenter: ObservableObject { @@ -46,6 +47,7 @@ final class SettingsPresenter: ObservableObject { guard let account = accountStorage.importAccount?.account else { return } try await interactor.notifyUnregister(account: account) accountStorage.importAccount = nil + try await Web3Wallet.instance.cleanup() UserDefaults.standard.set(nil, forKey: "deviceToken") await router.presentWelcome() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift index c9907a0b5..694ddaab7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift @@ -13,7 +13,7 @@ final class WalletRouter { func present(sessionRequest: Request, importAccount: ImportAccount, sessionContext: VerifyContext?) { SessionRequestModule.create(app: app, sessionRequest: sessionRequest, importAccount: importAccount, sessionContext: sessionContext) - .presentFullScreen(from: viewController, transparentBackground: true) + .presentFullScreen(from: UIApplication.currentWindow.rootViewController!, transparentBackground: true) } func present(sessionProposal: Session.Proposal, importAccount: ImportAccount, sessionContext: VerifyContext?) { diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index f9cbd1ad8..429718b64 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -51,7 +51,7 @@ final class SessionEngine { setupResponseSubscriptions() setupUpdateSubscriptions() setupExpirationSubscriptions() - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [unowned self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [unowned self] in sessionRequestsProvider.emitRequestIfPending() } } @@ -105,7 +105,9 @@ final class SessionEngine { protocolMethod: protocolMethod ) verifyContextStore.delete(forKey: requestId.string) - sessionRequestsProvider.emitRequestIfPending() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [unowned self] in + sessionRequestsProvider.emitRequestIfPending() + } } func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws { diff --git a/Sources/WalletConnectSign/Services/SignCleanupService.swift b/Sources/WalletConnectSign/Services/SignCleanupService.swift index abee34063..5c2a6ec1c 100644 --- a/Sources/WalletConnectSign/Services/SignCleanupService.swift +++ b/Sources/WalletConnectSign/Services/SignCleanupService.swift @@ -7,13 +7,16 @@ final class SignCleanupService { private let kms: KeyManagementServiceProtocol private let sessionTopicToProposal: CodableStore private let networkInteractor: NetworkInteracting + private let rpcHistory: RPCHistory - init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionTopicToProposal: CodableStore, networkInteractor: NetworkInteracting) { + init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionTopicToProposal: CodableStore, networkInteractor: NetworkInteracting, + rpcHistory: RPCHistory) { self.pairingStore = pairingStore self.sessionStore = sessionStore self.sessionTopicToProposal = sessionTopicToProposal self.networkInteractor = networkInteractor self.kms = kms + self.rpcHistory = rpcHistory } func cleanup() async throws { @@ -39,6 +42,7 @@ private extension SignCleanupService { pairingStore.deleteAll() sessionStore.deleteAll() sessionTopicToProposal.deleteAll() + rpcHistory.deleteAll() try kms.deleteAll() } } diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index c01f9855a..7bba8b44b 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -65,7 +65,7 @@ public struct SignClientFactory { verifyClient: verifyClient, rpcHistory: rpcHistory ) - let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient) + let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient, rpcHistory: rpcHistory) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore) let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 6e8fb9443..ff6bda280 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -119,6 +119,10 @@ public final class RPCHistory { public func getPending() -> [Record] { storage.getAll().filter { $0.response == nil } } + + public func deleteAll() { + storage.deleteAll() + } } extension RPCHistory { From a73cadde56e21c8dfe8b62917491e1b91375118d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 11:34:03 +0100 Subject: [PATCH 163/169] remove tests --- .../SessionRequestsProviderTests.swift | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift diff --git a/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift b/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift deleted file mode 100644 index 8e2101290..000000000 --- a/Tests/WalletConnectSignTests/SessionRequestsProviderTests.swift +++ /dev/null @@ -1,18 +0,0 @@ -import XCTest -@testable import WalletConnectSign - -class SessionRequestsProviderTests: XCTestCase { - - var sut: SessionRequestsProvider! - - func testEmitNewRequestWhenNoPending() { - - } - - func testEmitOldRequestOnNewWhenThereArePendingRequests() { - - } - -} - - From 569c559c8a5306b07d00e470e427196a1a1ebf99 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 11:48:24 +0100 Subject: [PATCH 164/169] savepoint --- .../SessionEngineTests.swift | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index c4c7a1112..d2d68f9b2 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -9,27 +9,34 @@ final class SessionEngineTests: XCTestCase { var sessionStorage: WCSessionStorageMock! var verifyContextStore: CodableStore! var engine: SessionEngine! + var rpcHistory: RPCHistory! + var historyService: HistoryService! + var sessionRequestsProvider: SessionRequestsProvider! override func setUp() { + rpcHistory = RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ) + historyService = HistoryService( + history: rpcHistory, + verifyContextStore: verifyContextStore + ) + sessionRequestsProvider = SessionRequestsProvider(historyService: historyService) networkingInteractor = NetworkingInteractorMock() sessionStorage = WCSessionStorageMock() verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") engine = SessionEngine( networkingInteractor: networkingInteractor, - historyService: HistoryService( - history: RPCHistory( - keyValueStore: .init( - defaults: RuntimeKeyValueStorage(), - identifier: "" - ) - ), - verifyContextStore: verifyContextStore - ), + historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: VerifyClientMock(), kms: KeyManagementServiceMock(), sessionStore: sessionStorage, - logger: ConsoleLoggerMock() + logger: ConsoleLoggerMock(), + sessionRequestsProvider: sessionRequestsProvider ) } @@ -57,3 +64,4 @@ final class SessionEngineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } } + From 108b075364b41cc87ba365ced00df14dc1130059 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 11:50:36 +0100 Subject: [PATCH 165/169] fix tests --- .../SessionEngineTests.swift | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index d2d68f9b2..e3a42232e 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -9,34 +9,37 @@ final class SessionEngineTests: XCTestCase { var sessionStorage: WCSessionStorageMock! var verifyContextStore: CodableStore! var engine: SessionEngine! - var rpcHistory: RPCHistory! - var historyService: HistoryService! - var sessionRequestsProvider: SessionRequestsProvider! override func setUp() { - rpcHistory = RPCHistory( - keyValueStore: .init( - defaults: RuntimeKeyValueStorage(), - identifier: "" - ) - ) - historyService = HistoryService( - history: rpcHistory, - verifyContextStore: verifyContextStore - ) - sessionRequestsProvider = SessionRequestsProvider(historyService: historyService) networkingInteractor = NetworkingInteractorMock() sessionStorage = WCSessionStorageMock() verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") engine = SessionEngine( networkingInteractor: networkingInteractor, - historyService: historyService, + historyService: HistoryService( + history: RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ), + verifyContextStore: verifyContextStore + ), verifyContextStore: verifyContextStore, verifyClient: VerifyClientMock(), kms: KeyManagementServiceMock(), sessionStore: sessionStorage, logger: ConsoleLoggerMock(), - sessionRequestsProvider: sessionRequestsProvider + sessionRequestsProvider: SessionRequestsProvider( + historyService: HistoryService( + history: RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ), + verifyContextStore: verifyContextStore + )) ) } @@ -64,4 +67,3 @@ final class SessionEngineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } } - From b750f01d0ebba42ead70e2f6d9d3b4aee941dc3c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 12:09:23 +0100 Subject: [PATCH 166/169] fix tests --- Sources/WalletConnectSign/Engine/Common/SessionEngine.swift | 6 ++++-- .../WalletConnectSign/Sign/SessionRequestsProvider.swift | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 429718b64..daf11e954 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -51,7 +51,8 @@ final class SessionEngine { setupResponseSubscriptions() setupUpdateSubscriptions() setupExpirationSubscriptions() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [unowned self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + guard let self = self else {return} sessionRequestsProvider.emitRequestIfPending() } } @@ -105,7 +106,8 @@ final class SessionEngine { protocolMethod: protocolMethod ) verifyContextStore.delete(forKey: requestId.string) - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [unowned self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + guard let self = self else {return} sessionRequestsProvider.emitRequestIfPending() } } diff --git a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift index a8b91d325..0b986d73a 100644 --- a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift +++ b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift @@ -19,8 +19,8 @@ class SessionRequestsProvider { private func setupEmitRequestHandling() { emitRequestSubject - .sink { [unowned self] _ in - + .sink { [weak self] _ in + guard let self = self else { return } let now = Date() if let lastEmitDate = self.lastEmitDate, now.timeIntervalSince(lastEmitDate) < 1 { // If the last emit was less than 1 second ago, ignore this request. From 6300045c8f97c6eabb7e6cc8d91f2d3ebcf731c7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 12:34:28 +0100 Subject: [PATCH 167/169] review fix --- .../PresentationLayer/Wallet/Main/MainPresenter.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index debbf14df..57900bcad 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -50,14 +50,14 @@ extension MainPresenter { interactor.sessionRequestPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] (request, context) in - if let vc = UIApplication.currentWindow.rootViewController?.topController, - vc.restorationIdentifier == SessionRequestModule.restorationIdentifier { + guard let vc = UIApplication.currentWindow.rootViewController?.topController, + vc.restorationIdentifier != SessionRequestModule.restorationIdentifier else { return - } else { - router.dismiss() - router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) } + router.dismiss() + router.present(sessionRequest: request, importAccount: importAccount, sessionContext: context) }.store(in: &disposeBag) + interactor.requestPublisher .receive(on: DispatchQueue.main) From bf777cda1d2d1b61d3686781bdceccb5f10ab5c2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 31 Jan 2024 13:39:12 +0100 Subject: [PATCH 168/169] simplify SessionRequestsProvider --- .../Sign/SessionRequestsProvider.swift | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift index 0b986d73a..7838b65b7 100644 --- a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift +++ b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift @@ -4,41 +4,17 @@ import Foundation class SessionRequestsProvider { private let historyService: HistoryService private var sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() - private var cancellables = Set() - private var lastEmitDate: Date? - private var emitRequestSubject = PassthroughSubject() - public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { sessionRequestPublisherSubject.eraseToAnyPublisher() } init(historyService: HistoryService) { self.historyService = historyService - setupEmitRequestHandling() - } - - private func setupEmitRequestHandling() { - emitRequestSubject - .sink { [weak self] _ in - guard let self = self else { return } - let now = Date() - if let lastEmitDate = self.lastEmitDate, now.timeIntervalSince(lastEmitDate) < 1 { - // If the last emit was less than 1 second ago, ignore this request. - return - } - - // Update the last emit time to now. - self.lastEmitDate = now - - // Fetch the oldest request and emit it. - if let oldestRequest = self.historyService.getPendingRequestsSortedByTimestamp().first { - self.sessionRequestPublisherSubject.send(oldestRequest) - } - } - .store(in: &cancellables) } func emitRequestIfPending() { - emitRequestSubject.send(()) + if let oldestRequest = self.historyService.getPendingRequestsSortedByTimestamp().first { + self.sessionRequestPublisherSubject.send(oldestRequest) + } } } From e5ed0dde937596c784ab2ec45f0d015a4b879024 Mon Sep 17 00:00:00 2001 From: flypaper0 Date: Wed, 31 Jan 2024 15:32:48 +0100 Subject: [PATCH 169/169] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index c16d55635..ec1cdfd4c 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.11.0"} +{"version": "1.12.0"}