From cc7a1f54cecf581853a8d23c0886325fd89f2968 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Sat, 6 Jan 2024 18:42:23 +0300 Subject: [PATCH 01/97] 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 02/97] 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 03/97] 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 04/97] 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 05/97] 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 06/97] 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 07/97] 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 08/97] 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 09/97] 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 10/97] 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 11/97] 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 12/97] 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 13/97] 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 14/97] 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 15/97] 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 16/97] 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 17/97] 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 18/97] 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 19/97] 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 20/97] 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 21/97] 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 22/97] 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 23/97] 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 24/97] 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 25/97] 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 26/97] 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 27/97] 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 28/97] 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 29/97] 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 30/97] 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 31/97] 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 32/97] 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 33/97] 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 34/97] 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 35/97] 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 36/97] 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 37/97] 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 38/97] 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 39/97] 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 40/97] 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 41/97] 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 42/97] 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 43/97] 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 44/97] 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 45/97] 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 46/97] 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 47/97] 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 48/97] 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 49/97] 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 50/97] 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 51/97] 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 52/97] 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 53/97] 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 54/97] 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 55/97] 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 56/97] 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 57/97] 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 58/97] 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 59/97] 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 60/97] 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 61/97] 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 62/97] 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 63/97] 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 64/97] 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 65/97] 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 66/97] 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 67/97] 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 feeca0cece77d23520c6d8935ec7294473dc63c7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 11:00:44 +0100 Subject: [PATCH 68/97] 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 69/97] 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 70/97] 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 71/97] 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 a93ba0b150e61249a1d13d171f4be8ffb436c599 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 Jan 2024 14:49:02 +0100 Subject: [PATCH 72/97] 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 73/97] 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 74/97] 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 d08497513af4a702cb577f3c2f235a54754b5ce1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 Jan 2024 12:16:38 +0100 Subject: [PATCH 75/97] 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 3b3ebcc9717d96a9da985e6a83824ab290949a41 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 Jan 2024 14:24:35 +0100 Subject: [PATCH 76/97] 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 a25e2cf6ba108e61439e74a2c6c24a98d7f4fc78 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 08:51:07 +0100 Subject: [PATCH 77/97] 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 78/97] 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 79/97] 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 80/97] 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 81/97] 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 82/97] 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 5f8ab6263d395cab492735b7008233599aa7f77b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jan 2024 18:46:42 +0100 Subject: [PATCH 83/97] 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 84/97] 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 85/97] 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 86/97] 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 87/97] 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 88/97] 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 89/97] 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 90/97] 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 91/97] 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 bb2f36db277a5fcd1ff9949395749bab33635af6 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 Jan 2024 11:33:16 +0100 Subject: [PATCH 92/97] 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 39085d52d16de98c027de3077ca30978bb15a638 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 08:31:50 +0100 Subject: [PATCH 93/97] 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 94/97] 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 95/97] 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 96/97] 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 2dce12f1c1ee1fa1b32cf6da5ae201b29ba436f2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jan 2024 15:15:23 +0100 Subject: [PATCH 97/97] 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,