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/.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/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/DApp/Common/ActivityIndicatorManager.swift b/Example/DApp/Common/ActivityIndicatorManager.swift
new file mode 100644
index 000000000..2405ea052
--- /dev/null
+++ b/Example/DApp/Common/ActivityIndicatorManager.swift
@@ -0,0 +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() {
+ 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
+ }
+ }
+ }
+
+ func stop() {
+ serialQueue.async {
+ self.stopInternal()
+ }
+ }
+
+ private func stopInternal() {
+ DispatchQueue.main.sync {
+ self.activityIndicator?.stopAnimating()
+ self.activityIndicator?.removeFromSuperview()
+ self.activityIndicator = nil
+ }
+ }
+}
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/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 c3c7d47ee..000000000
--- a/Example/DApp/Modules/Main/MainPresenter.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import UIKit
-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
- }
-}
-
-// MARK: - Private functions
-extension MainPresenter {
- private func setupInitialState() {}
-}
diff --git a/Example/DApp/Modules/Main/MainRouter.swift b/Example/DApp/Modules/Main/MainRouter.swift
deleted file mode 100644
index 4b3fef3de..000000000
--- a/Example/DApp/Modules/Main/MainRouter.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import UIKit
-
-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)
- }
-}
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/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/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
index 15b0b7e74..31920ce46 100644
--- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
+++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift
@@ -12,7 +12,10 @@ final class SessionAccountPresenter: ObservableObject {
@Published var showError = false
@Published var errorMessage = String.empty
@Published var showRequestSent = false
-
+ @Published var requesting = false
+ var lastRequest: Request?
+
+
private let interactor: SessionAccountInteractor
private let router: SessionAccountRouter
private let session: Session
@@ -41,14 +44,21 @@ 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 ttl: TimeInterval = 300
+ let request = try Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!, ttl: ttl)
Task {
do {
+ ActivityIndicatorManager.shared.start()
try await Sign.instance.request(params: request)
+ lastRequest = request
+ ActivityIndicatorManager.shared.stop()
+ requesting = true
DispatchQueue.main.async { [weak self] in
self?.openWallet()
}
} catch {
+ ActivityIndicatorManager.shared.stop()
+ requesting = false
showError.toggle()
errorMessage = error.localizedDescription
}
@@ -70,6 +80,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..939a9edb6 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)
@@ -209,14 +230,14 @@ struct SessionAccountView: View {
.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)
+ 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 {
diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift
index b42d2663c..1c5194e2c 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 {
+ ActivityIndicatorManager.shared.start()
+ try await Sign.instance.connect(
+ requiredNamespaces: Proposal.requiredNamespaces,
+ optionalNamespaces: Proposal.optionalNamespaces,
+ topic: uri.topic
+ )
+ ActivityIndicatorManager.shared.stop()
+ router.presentNewPairing(walletConnectUri: uri)
+ } catch {
+ ActivityIndicatorManager.shared.stop()
+ }
}
}
@@ -70,9 +76,12 @@ final class SignPresenter: ObservableObject {
if let session {
Task { @MainActor in
do {
+ ActivityIndicatorManager.shared.start()
try await Sign.instance.disconnect(topic: session.topic)
+ ActivityIndicatorManager.shared.stop()
accountsDetails.removeAll()
} catch {
+ ActivityIndicatorManager.shared.stop()
showError.toggle()
errorMessage = error.localizedDescription
}
@@ -104,8 +113,26 @@ extension SignPresenter {
.receive(on: DispatchQueue.main)
.sink { [unowned self] _ in
self.accountsDetails.removeAll()
+ router.popToRoot()
+ Task(priority: .high) { ActivityIndicatorManager.shared.stop() }
+ }
+ .store(in: &subscriptions)
+
+ Sign.instance.sessionResponsePublisher
+ .receive(on: DispatchQueue.main)
+ .sink { response in
+ Task(priority: .high) { ActivityIndicatorManager.shared.stop() }
}
.store(in: &subscriptions)
+
+ Sign.instance.requestExpirationPublisher
+ .receive(on: DispatchQueue.main)
+ .sink { _ in
+ Task(priority: .high) { ActivityIndicatorManager.shared.stop() }
+ AlertPresenter.present(message: "Session Request has expired", type: .warning)
+ }
+ .store(in: &subscriptions)
+
}
private func getSession() {
diff --git a/Example/DApp/Modules/Sign/SignRouter.swift b/Example/DApp/Modules/Sign/SignRouter.swift
index 60da1928c..a68ad5f9a 100644
--- a/Example/DApp/Modules/Sign/SignRouter.swift
+++ b/Example/DApp/Modules/Sign/SignRouter.swift
@@ -20,14 +20,14 @@ 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)
}
+
+ func popToRoot() {
+ viewController.popToRoot()
+ }
}
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()
}
diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift
index b52f47555..489a1c2d0 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,24 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
projectId: InputConfig.projectId,
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 {
+ 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)
}
@@ -38,7 +57,8 @@ 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)
+ .wrapToNavigationController()
window?.rootViewController = viewController
window?.makeKeyAndVisible()
diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj
index a5ee276b5..24712ead6 100644
--- a/Example/ExampleApp.xcodeproj/project.pbxproj
+++ b/Example/ExampleApp.xcodeproj/project.pbxproj
@@ -32,6 +32,8 @@
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 */; };
+ 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 */; };
@@ -40,6 +42,9 @@
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 */; };
+ 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 */; };
@@ -47,6 +52,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 */; };
@@ -288,12 +294,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 */; };
@@ -309,12 +310,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 */; };
@@ -425,6 +420,7 @@
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 = ""; };
+ 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 = ""; };
@@ -432,6 +428,8 @@
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 = ""; };
+ 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; };
@@ -442,6 +440,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 = ""; };
@@ -646,12 +645,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 = ""; };
@@ -661,12 +655,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 = ""; };
@@ -720,6 +708,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 */,
@@ -787,6 +776,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 */,
@@ -1410,6 +1400,8 @@
children = (
A51AC0D828E436A3001BACF9 /* InputConfig.swift */,
A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */,
+ 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */,
+ 8486EDD02B4F2DC1008E53C3 /* AlertPresenter.swift */,
);
path = Common;
sourceTree = "";
@@ -1655,6 +1647,8 @@
C56EE2A1293F6B9E004840D1 /* Helpers */,
C56EE262293F56D6004840D1 /* Extensions */,
C56EE263293F56D6004840D1 /* VIPER */,
+ 84AEC24E2B4D1EE400E27A5B /* ActivityIndicatorManager.swift */,
+ 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */,
);
path = Common;
sourceTree = "";
@@ -1797,18 +1791,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 = (
@@ -1829,27 +1811,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 = (
@@ -1865,9 +1826,7 @@
C5BE02202AF7DDE70064FC88 /* Modules */ = {
isa = PBXGroup;
children = (
- C5BE02062AF777AD0064FC88 /* Main */,
C5B4C4C52AF12C2900B4274A /* Sign */,
- C5B4C4CD2AF12F0B00B4274A /* Auth */,
);
path = Modules;
sourceTree = "";
@@ -2002,6 +1961,7 @@
84943C7A2A9BA206007EBAC2 /* Mixpanel */,
C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */,
C579FEB52AFA86CD008855EB /* Web3Modal */,
+ 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */,
);
productName = DApp;
productReference = 84CE641C27981DED00142511 /* DApp.app */;
@@ -2131,6 +2091,7 @@
A59D25ED2AB3672700D7EA3A /* AsyncButton */,
A5F1526E2ACDC46B00D745A6 /* Web3ModalUI */,
C54C248F2AEB1B5600DA4BF6 /* WalletConnectRouter */,
+ 84AEC2532B4D43CD00E27A5B /* SwiftMessages */,
);
productName = ChatWallet;
productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */;
@@ -2209,6 +2170,7 @@
8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */,
A5F1526D2ACDC46B00D745A6 /* XCRemoteSwiftPackageReference "web3modal-swift" */,
+ 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */,
);
productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */;
projectDirPath = "";
@@ -2309,45 +2271,36 @@
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 */,
+ 84D093EB2B4EA6CB005B1925 /* ActivityIndicatorManager.swift in Sources */,
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 */,
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 */,
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 */,
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 */,
- C5BE01D92AF691FE0064FC88 /* AuthPresenter.swift in Sources */,
- C5BE02112AF777AD0064FC88 /* MainViewController.swift in Sources */,
C5BE021D2AF79B9A0064FC88 /* SessionAccountInteractor.swift in Sources */,
A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */,
C5BE01FB2AF6CB270064FC88 /* SignRouter.swift in Sources */,
- C5BE01D72AF691CD0064FC88 /* AuthModule.swift in Sources */,
C5BE01F72AF6CB250064FC88 /* SignModule.swift in Sources */,
C5BE01FA2AF6CB270064FC88 /* SignPresenter.swift in Sources */,
- C5BE02132AF777AD0064FC88 /* MainInteractor.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2500,10 +2453,12 @@
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 */,
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 */,
@@ -3340,6 +3295,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";
@@ -3408,6 +3371,11 @@
isa = XCSwiftPackageProductDependency;
productName = Web3Wallet;
};
+ 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */;
+ productName = SwiftMessages;
+ };
8487A9432A836C2A0003D5AF /* Sentry */ = {
isa = XCSwiftPackageProductDependency;
package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
@@ -3428,6 +3396,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 f11eb5be8..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",
@@ -168,8 +177,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/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">
+
+
+
+
()
- 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/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift
index 9e0fc6867..b1cfb2978 100644
--- a/Example/IntegrationTests/Sign/SignClientTests.swift
+++ b/Example/IntegrationTests/Sign/SignClientTests.swift
@@ -92,7 +92,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 {
@@ -121,7 +121,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 {
@@ -146,7 +146,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 {
@@ -177,7 +177,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 {
@@ -201,7 +201,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)
@@ -227,7 +227,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 {
@@ -248,7 +248,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)
@@ -270,7 +270,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 {
@@ -308,7 +308,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 {
@@ -332,7 +332,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 {
@@ -361,7 +361,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 {
@@ -392,7 +392,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 {
@@ -420,7 +420,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 {
@@ -498,7 +498,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 {
@@ -567,7 +567,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 {
@@ -626,7 +626,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 {
@@ -689,7 +689,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 {
@@ -755,6 +755,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)
}
}
diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift
index 1267374f7..51d1f0c28 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,42 @@ final class ConfigurationService {
}
LoggingService.instance.startLogging()
+ Web3Wallet.instance.socketConnectionStatusPublisher
+ .receive(on: DispatchQueue.main)
+ .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)
+
+ Web3Wallet.instance.logsPublisher
+ .receive(on: DispatchQueue.main)
+ .sink { log in
+ switch log {
+ case .error(let logMessage):
+ AlertPresenter.present(message: logMessage.message, type: .error)
+ default: return
+ }
+ }.store(in: &publishers)
+
+ 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)
+
+ 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")
diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift
index c36e6b4f6..ae9208eff 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?
@@ -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,25 @@ 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 Pair.instance.pair(uri: uri)
+ try await Web3Wallet.instance.pair(uri: uri)
+ }
+ } catch {
+ if case WalletConnectURI.Errors.expired = error {
+ AlertPresenter.present(message: error.localizedDescription, type: .error)
+ } else {
+ 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/Common/ActivityIndicatorManager.swift b/Example/WalletApp/Common/ActivityIndicatorManager.swift
new file mode 100644
index 000000000..9022a6f41
--- /dev/null
+++ b/Example/WalletApp/Common/ActivityIndicatorManager.swift
@@ -0,0 +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() {
+ 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 = .blue
+ activityIndicator.startAnimating()
+ window.addSubview(activityIndicator)
+
+ self.activityIndicator = activityIndicator
+ }
+ }
+ }
+
+ func stop() {
+ serialQueue.async {
+ self.stopInternal()
+ }
+ }
+
+ private func stopInternal() {
+ DispatchQueue.main.sync {
+ self.activityIndicator?.stopAnimating()
+ self.activityIndicator?.removeFromSuperview()
+ self.activityIndicator = nil
+ }
+ }
+}
diff --git a/Example/WalletApp/Common/AlertPresenter.swift b/Example/WalletApp/Common/AlertPresenter.swift
new file mode 100644
index 000000000..5da5d4668
--- /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.5)
+ SwiftMessages.show(config: config, view: view)
+ }
+ }
+}
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://")!)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsPresenter.swift
index 5312123a4..8ba46dc19 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 {
+ ActivityIndicatorManager.shared.start()
try await interactor.disconnectSession(session: session)
+ ActivityIndicatorManager.shared.stop()
DispatchQueue.main.async {
self.router.dismiss()
}
} catch {
+ ActivityIndicatorManager.shared.stop()
print(error)
}
}
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/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
index 523158eee..e494039a2 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift
@@ -35,9 +35,12 @@ final class SessionProposalPresenter: ObservableObject {
@MainActor
func onApprove() async throws {
do {
+ ActivityIndicatorManager.shared.start()
let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account)
showConnected ? showConnectedSheet.toggle() : router.dismiss()
+ ActivityIndicatorManager.shared.stop()
} catch {
+ ActivityIndicatorManager.shared.stop()
errorMessage = error.localizedDescription
showError.toggle()
}
@@ -46,9 +49,12 @@ final class SessionProposalPresenter: ObservableObject {
@MainActor
func onReject() async throws {
do {
+ ActivityIndicatorManager.shared.start()
try await interactor.reject(proposal: sessionProposal)
+ ActivityIndicatorManager.shared.stop()
router.dismiss()
} catch {
+ ActivityIndicatorManager.shared.stop()
errorMessage = error.localizedDescription
showError.toggle()
}
@@ -57,12 +63,31 @@ final class SessionProposalPresenter: ObservableObject {
func onConnectedSheetDismiss() {
router.dismiss()
}
+
+ func dismiss() {
+ router.dismiss()
+ }
}
// MARK: - Private functions
private extension SessionProposalPresenter {
func setupInitialState() {
+ 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()
+ }
+ }.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)
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalRouter.swift
index cd9de7971..f03cce6db 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()
+ }
}
}
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()
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
index 59886fbfb..edc2ed4df 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift
@@ -4,7 +4,7 @@ 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)
try await Web3Wallet.instance.respond(
@@ -12,7 +12,6 @@ final class SessionRequestInteractor {
requestId: sessionRequest.id,
response: .response(result)
)
-
/* Redirect */
let session = getSession(topic: sessionRequest.topic)
if let uri = session?.peer.redirect?.native {
@@ -26,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..17d2f9b49 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)
+ ActivityIndicatorManager.shared.start()
+ let showConnected = try await interactor.respondSessionRequest(sessionRequest: sessionRequest, importAccount: importAccount)
showConnected ? showSignedSheet.toggle() : router.dismiss()
+ ActivityIndicatorManager.shared.stop()
} catch {
+ ActivityIndicatorManager.shared.stop()
errorMessage = error.localizedDescription
showError.toggle()
}
@@ -53,8 +56,16 @@ final class SessionRequestPresenter: ObservableObject {
@MainActor
func onReject() async throws {
- try await interactor.reject(sessionRequest: sessionRequest)
- router.dismiss()
+ do {
+ ActivityIndicatorManager.shared.start()
+ try await interactor.respondError(sessionRequest: sessionRequest)
+ ActivityIndicatorManager.shared.stop()
+ router.dismiss()
+ } catch {
+ ActivityIndicatorManager.shared.stop()
+ errorMessage = error.localizedDescription
+ showError.toggle()
+ }
}
func onSignedSheetDismiss() {
@@ -68,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()
+ }
}
}
diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift
index d64b1f82a..ebc9557b2 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(
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/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift
index 59d5c1fc2..0affd430f 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
@@ -40,7 +45,7 @@ final class WalletPresenter: ObservableObject {
func onAppear() {
showPairingLoading = app.requestSent
- removePairingIndicator()
+ setUpPairingIndicatorRemoval()
let pendingRequests = interactor.getPendingRequests()
if let request = pendingRequests.first(where: { $0.context != nil }) {
@@ -53,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(uriString: 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()
@@ -69,24 +74,44 @@ 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(uriString: 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 {
- try? await interactor.disconnectSession(session: sessions[index])
+ do {
+ ActivityIndicatorManager.shared.start()
+ try await interactor.disconnectSession(session: sessions[index])
+ ActivityIndicatorManager.shared.stop()
+ } catch {
+ ActivityIndicatorManager.shared.stop()
+ sessions = sessions
+ AlertPresenter.present(message: error.localizedDescription, type: .error)
+ }
+ }
+ }
+
+ private func handlePairingLoadingChanged() {
+ isPairingTimer?.invalidate()
+
+ if showPairingLoading {
+ 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)
+ }
}
}
}
@@ -109,11 +134,8 @@ extension WalletPresenter {
private func pair(uri: WalletConnectURI) {
Task.detached(priority: .high) { @MainActor [unowned self] in
do {
- self.showPairingLoading = true
- self.removePairingIndicator()
try await self.interactor.pair(uri: uri)
} catch {
- self.showPairingLoading = false
self.errorMessage = error.localizedDescription
self.showError.toggle()
}
@@ -126,11 +148,13 @@ extension WalletPresenter {
}
pair(uri: uri)
}
-
- private func removePairingIndicator() {
- DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
- self.showPairingLoading = false
- }
+
+ private func setUpPairingIndicatorRemoval() {
+ Web3Wallet.instance.pairingStatePublisher
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] isPairing in
+ self?.showPairingLoading = isPairing
+ }.store(in: &disposeBag)
}
}
@@ -153,3 +177,4 @@ extension WalletPresenter.Errors: LocalizedError {
}
}
}
+
diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift
index db735a7cf..e053493b4 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift
@@ -35,7 +35,7 @@ struct WalletView: View {
}
.onDelete { indexSet in
Task(priority: .high) {
- await presenter.removeSession(at: indexSet)
+ try await presenter.removeSession(at: indexSet)
}
}
}
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,
diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift
index 16d8f0076..5637ba3e6 100644
--- a/Sources/WalletConnectPairing/PairingClient.swift
+++ b/Sources/WalletConnectPairing/PairingClient.swift
@@ -9,7 +9,14 @@ 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
+ }
private let pairingStorage: WCPairingStorage
private let walletPairService: WalletPairService
@@ -25,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
@@ -47,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
@@ -64,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 7edd05b30..905c1b4e8 100644
--- a/Sources/WalletConnectPairing/PairingClientProtocol.swift
+++ b/Sources/WalletConnectPairing/PairingClientProtocol.swift
@@ -3,6 +3,8 @@ 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
func getPairings() -> [Pairing]
diff --git a/Sources/WalletConnectPairing/Services/App/AppPairService.swift b/Sources/WalletConnectPairing/Services/App/AppPairService.swift
index 7dd5deb09..0ecb3a64e 100644
--- a/Sources/WalletConnectPairing/Services/App/AppPairService.swift
+++ b/Sources/WalletConnectPairing/Services/App/AppPairService.swift
@@ -15,8 +15,9 @@ actor AppPairService {
let topic = String.generateTopic()
try await networkingInteractor.subscribe(topic: topic)
let symKey = try! kms.createSymmetricKey(topic)
- let pairing = WCPairing(topic: topic)
- let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay)
+ let relay = RelayProtocolOptions(protocol: "irn", data: nil)
+ let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: relay)
+ let pairing = WCPairing(uri: uri)
pairingStorage.setPairing(pairing)
return uri
}
diff --git a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift
index 4a8850cbe..0df4caf7e 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 {
+ self?.pairingExpirationPublisherSubject.send(Pairing(pairing))
+ }
}
}
}
diff --git a/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift
new file mode 100644
index 000000000..ff917e33d
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/Common/PairingStateProvider.swift
@@ -0,0 +1,33 @@
+import Combine
+import Foundation
+
+class PairingStateProvider {
+ private let pairingStorage: WCPairingStorage
+ private var pairingStatePublisherSubject = PassthroughSubject()
+ private var checkTimer: Timer?
+ private var lastPairingState: Bool?
+
+ 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 }
+
+ if lastPairingState != pairingStateActive {
+ pairingStatePublisherSubject.send(pairingStateActive)
+ lastPairingState = pairingStateActive
+ }
+ }
+}
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 03ed01a41..9edd37941 100644
--- a/Sources/WalletConnectPairing/Types/Pairing.swift
+++ b/Sources/WalletConnectPairing/Types/Pairing.swift
@@ -6,16 +6,20 @@ 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
+ }
+}
+
+#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..a44ee01fa 100644
--- a/Sources/WalletConnectPairing/Types/WCPairing.swift
+++ b/Sources/WalletConnectPairing/Types/WCPairing.swift
@@ -27,29 +27,12 @@ public struct WCPairing: SequenceObject {
30 * .day
}
- public 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
- }
-
- public init(topic: String) {
- self.topic = topic
- self.relay = RelayProtocolOptions(protocol: "irn", data: nil)
- self.active = false
- self.requestReceived = false
- self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
- }
-
public init(uri: WalletConnectURI) {
self.topic = uri.topic
self.relay = uri.relay
self.active = false
self.requestReceived = false
- self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
+ self.expiryDate = Date(timeIntervalSince1970: TimeInterval(uri.expiryTimestamp))
}
public mutating func activate() {
@@ -75,3 +58,31 @@ 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)
+ }
+
+ 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/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/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
index 8c994c094..899b384cc 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
+ case emtySessionNamespacesForbidden
}
var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)?
@@ -27,6 +28,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 +43,8 @@ final class ApproveEngine {
logger: ConsoleLogging,
pairingStore: WCPairingStorage,
sessionStore: WCSessionStorage,
- verifyClient: VerifyClientProtocol
+ verifyClient: VerifyClientProtocol,
+ rpcHistory: RPCHistory
) {
self.networkingInteractor = networkingInteractor
self.proposalPayloadsStore = proposalPayloadsStore
@@ -54,6 +57,7 @@ final class ApproveEngine {
self.pairingStore = pairingStore
self.sessionStore = sessionStore
self.verifyClient = verifyClient
+ self.rpcHistory = rpcHistory
setupRequestSubscriptions()
setupResponseSubscriptions()
@@ -62,21 +66,28 @@ 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.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)
- verifyContextStore.delete(forKey: proposerPubKey)
-
try Namespace.validate(sessionNamespaces)
try Namespace.validateApproved(sessionNamespaces, against: proposal.requiredNamespaces)
@@ -97,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,
@@ -111,10 +122,16 @@ 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)
+ verifyContextStore.delete(forKey: proposerPubKey)
+
pairingRegisterer.activate(
pairingTopic: payload.topic,
peerMetadata: payload.request.proposer.metadata
@@ -123,15 +140,23 @@ 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
}
+
+ 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 {
+ 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 {
+ 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
}
@@ -169,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)
@@ -178,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
}
}
@@ -408,8 +432,22 @@ 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."
+ case .emtySessionNamespacesForbidden:
+ return "Session Namespaces Cannot Be Empty"
}
}
}
diff --git a/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift b/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift
new file mode 100644
index 000000000..4af7e3808
--- /dev/null
+++ b/Sources/WalletConnectSign/Engine/Common/PendingProposalsProvider.swift
@@ -0,0 +1,49 @@
+import Foundation
+import Combine
+
+class PendingProposalsProvider {
+
+ private let proposalPayloadsStore: CodableStore>
+ private let verifyContextStore: CodableStore
+ private var publishers = Set()
+ private let pendingProposalsPublisherSubject = CurrentValueSubject<[(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
+ updatePendingProposals()
+ setUpPendingProposalsPublisher()
+ }
+
+ private func updatePendingProposals() {
+ let proposalsWithVerifyContext = getPendingProposals()
+ pendingProposalsPublisherSubject.send(proposalsWithVerifyContext)
+ }
+
+ func setUpPendingProposalsPublisher() {
+ proposalPayloadsStore.storeUpdatePublisher.sink { [unowned self] _ in
+ updatePendingProposals()
+ }.store(in: &publishers)
+ }
+
+ 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)) }
+ }
+
+ 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/Engine/Common/ProposalExpiryWatcher.swift b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift
new file mode 100644
index 000000000..94bc407ca
--- /dev/null
+++ b/Sources/WalletConnectSign/Engine/Common/ProposalExpiryWatcher.swift
@@ -0,0 +1,41 @@
+import Foundation
+import Combine
+
+class ProposalExpiryWatcher {
+
+ private let sessionProposalExpirationPublisherSubject: PassthroughSubject = .init()
+ private let rpcHistory: RPCHistory
+
+ var sessionProposalExpirationPublisher: AnyPublisher {
+ return sessionProposalExpirationPublisherSubject.eraseToAnyPublisher()
+ }
+
+ private let proposalPayloadsStore: CodableStore>
+ private var checkTimer: Timer?
+
+ internal init(
+ proposalPayloadsStore: CodableStore>,
+ rpcHistory: RPCHistory
+ ) {
+ self.proposalPayloadsStore = proposalPayloadsStore
+ self.rpcHistory = rpcHistory
+ setUpExpiryCheckTimer()
+ }
+
+ func setUpExpiryCheckTimer() {
+ checkTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [unowned self] _ in
+ checkForProposalsExpiry()
+ }
+ }
+
+ func checkForProposalsExpiry() {
+ let proposals = proposalPayloadsStore.getAll()
+ 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/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/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
index f2598b074..9d0c034f6 100644
--- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
@@ -63,9 +63,10 @@ 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, expiryTimestamp: request.expiryTimestamp)
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())
}
@@ -228,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/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift
index 6de73e1cc..08ded8326 100644
--- a/Sources/WalletConnectSign/Namespace.swift
+++ b/Sources/WalletConnectSign/Namespace.swift
@@ -1,8 +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 {
@@ -148,9 +166,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],
@@ -325,7 +340,8 @@ public enum AutoNamespaces {
}
}
}
-
+ guard !sessionNamespaces.isEmpty else { throw AutoNamespacesError.emtySessionNamespacesForbidden }
+
return sessionNamespaces
}
}
diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift
index 1cae4e0cd..3898d45df 100644
--- a/Sources/WalletConnectSign/Request.swift
+++ b/Sources/WalletConnectSign/Request.swift
@@ -1,62 +1,72 @@
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 expiryTimestamp: UInt64?
+
+ // TTL bounds
+ 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, 10 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
+ }
+
+ let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl)
+ self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiryTimestamp: calculatedExpiry)
+ }
- internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64?) {
+ 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
+ }
+
+ let calculatedExpiry = UInt64(Date().timeIntervalSince1970) + UInt64(ttl)
+ 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, expiryTimestamp: UInt64?) {
self.id = id
self.topic = topic
self.method = method
self.params = params
self.chainId = chainId
- 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)
+ self.expiryTimestamp = expiryTimestamp
}
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))
-
- guard
- abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry,
- abs(currentDate.distance(to: expiryDate)) > Constants.minExpiry
- 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 = expiryTimestamp 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/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift
index 2a5974471..a34f52bb5 100644
--- a/Sources/WalletConnectSign/Services/HistoryService.swift
+++ b/Sources/WalletConnectSign/Services/HistoryService.swift
@@ -3,98 +3,56 @@ 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
}
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?)] {
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 {
- 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,
params: request.request.params,
chainId: request.chainId,
- 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
+ expiryTimestamp: request.request.expiryTimestamp
)
+
+ return (mappedRequest, record.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/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift
index b87864de1..9c4b5e14d 100644
--- a/Sources/WalletConnectSign/Sign/SignClient.swift
+++ b/Sources/WalletConnectSign/Sign/SignClient.swift
@@ -99,6 +99,20 @@ public final class SignClient: SignClientProtocol {
return logger.logsPublisher
}
+ /// Publisher that sends session proposal expiration
+ public var sessionProposalExpirationPublisher: AnyPublisher {
+ return proposalExpiryWatcher.sessionProposalExpirationPublisher
+ }
+
+ public var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> {
+ return pendingProposalsProvider.pendingProposalsPublisher
+ }
+
+ public var requestExpirationPublisher: AnyPublisher {
+ return requestsExpiryWatcher.requestExpirationPublisher
+ }
+
+
/// An object that loggs SDK's errors and info messages
public let logger: ConsoleLogging
@@ -119,6 +133,9 @@ public final class SignClient: SignClientProtocol {
private let appProposeService: AppProposeService
private let historyService: HistoryService
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>()
@@ -152,7 +169,10 @@ public final class SignClient: SignClientProtocol {
disconnectService: DisconnectService,
historyService: HistoryService,
cleanupService: SignCleanupService,
- pairingClient: PairingClient
+ pairingClient: PairingClient,
+ proposalExpiryWatcher: ProposalExpiryWatcher,
+ pendingProposalsProvider: PendingProposalsProvider,
+ requestsExpiryWatcher: RequestsExpiryWatcher
) {
self.logger = logger
self.networkingClient = networkingClient
@@ -170,6 +190,9 @@ public final class SignClient: SignClientProtocol {
self.cleanupService = cleanupService
self.disconnectService = disconnectService
self.pairingClient = pairingClient
+ self.proposalExpiryWatcher = proposalExpiryWatcher
+ self.pendingProposalsProvider = pendingProposalsProvider
+ self.requestsExpiryWatcher = requestsExpiryWatcher
setUpConnectionObserving()
setUpEnginesCallbacks()
@@ -303,21 +326,9 @@ 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
- public func getSessionRequestRecord(id: RPCID) -> (request: Request, context: VerifyContext?)? {
- return historyService.getSessionRequest(id: id)
+ public func getPendingProposals(topic: String? = nil) -> [(proposal: Session.Proposal, context: VerifyContext?)] {
+ pendingProposalsProvider.getPendingProposals()
}
/// Delete all stored data such as: pairings, sessions, keys
diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
index 1d9adf073..3d63bcf39 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)
@@ -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)
@@ -69,6 +70,9 @@ 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, rpcHistory: rpcHistory)
+ let pendingProposalsProvider = PendingProposalsProvider(proposalPayloadsStore: proposalPayloadsStore, verifyContextStore: verifyContextStore)
+ let requestsExpiryWatcher = RequestsExpiryWatcher(proposalPayloadsStore: proposalPayloadsStore, rpcHistory: rpcHistory, historyService: historyService)
let client = SignClient(
logger: logger,
@@ -86,7 +90,10 @@ public struct SignClientFactory {
disconnectService: disconnectService,
historyService: historyService,
cleanupService: cleanupService,
- pairingClient: pairingClient
+ pairingClient: pairingClient,
+ proposalExpiryWatcher: proposalExpiryWatcher,
+ pendingProposalsProvider: pendingProposalsProvider,
+ requestsExpiryWatcher: requestsExpiryWatcher
)
return client
}
diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
index 2b174ddec..4aecfac04 100644
--- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
+++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift
@@ -12,6 +12,9 @@ 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 }
+ 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
@@ -27,5 +30,5 @@ public protocol SignClientProtocol {
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/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/SessionProposal.swift b/Sources/WalletConnectSign/Types/Session/SessionProposal.swift
index fa1ee979a..2723ef46c 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 expiryTimestamp: UInt64?
+
+ static let proposalTtl: 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.expiryTimestamp = UInt64(Date().timeIntervalSince1970 + Self.proposalTtl)
+ }
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 = expiryTimestamp else { return false }
+
+ let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry))
+
+ return expiryDate < currentDate
+ }
}
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?
}
}
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/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) {
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/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
diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift
index 2f068a67f..4e8701b50 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=\(expiryTimestamp)"
}
public var deeplinkUri: String {
@@ -20,12 +25,26 @@ 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
}
+ @available(*, deprecated, message: "Use the throwing initializer instead")
public init?(string: String) {
- guard let components = Self.parseURIComponents(from: string) else {
+ do {
+ try self.init(uriString: string)
+ } catch {
+ print("Initialization failed: \(error.localizedDescription)")
return nil
}
+ }
+
+ public init(uriString: String) throws {
+ let decodedString = uriString.removingPercentEncoding ?? uriString
+ guard let components = Self.parseURIComponents(from: decodedString) else {
+ throw Errors.invalidFormat
+ }
let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value }
guard
@@ -34,22 +53,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) {
- if let deeplinkUri = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") {
- self.init(string: deeplinkUri)
- } else {
- return nil
- }
+
+
+ public init(deeplinkUri: URL) throws {
+ let uriString = deeplinkUri.query?.replacingOccurrences(of: "uri=", with: "") ?? ""
+ try self.init(uriString: uriString)
}
private var relayQuery: String {
@@ -61,34 +89,64 @@ 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)
}
}
+extension WalletConnectURI.Errors: LocalizedError {
+ public var errorDescription: String? {
+ switch self {
+ case .expired:
+ return NSLocalizedString("The WalletConnect Pairing URI has expired.", comment: "Expired URI Error")
+ case .invalidFormat:
+ return NSLocalizedString("The format of the WalletConnect Pairing 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(uriString: 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(uriString: uri)
} else {
- return nil
+ throw Errors.invalidFormat
}
}
}
+#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/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift
index 5b70a374a..0930b8443 100644
--- a/Sources/Web3Wallet/Web3WalletClient.swift
+++ b/Sources/Web3Wallet/Web3WalletClient.swift
@@ -67,12 +67,33 @@ public class Web3WalletClient {
pairingClient.pairingDeletePublisher
}
+ public var pairingStatePublisher: AnyPublisher {
+ pairingClient.pairingStatePublisher
+ }
+
+ public var pairingExpirationPublisher: AnyPublisher {
+ return pairingClient.pairingExpirationPublisher
+ }
+
public var logsPublisher: AnyPublisher {
return signClient.logsPublisher
.merge(with: pairingClient.logsPublisher)
.eraseToAnyPublisher()
}
+ /// Publisher that sends session proposal expiration
+ public var sessionProposalExpirationPublisher: AnyPublisher {
+ return signClient.sessionProposalExpirationPublisher
+ }
+
+ public var pendingProposalsPublisher: AnyPublisher<[(proposal: Session.Proposal, context: VerifyContext?)], Never> {
+ return signClient.pendingProposalsPublisher
+ }
+
+ public var requestExpirationPublisher: AnyPublisher {
+ return signClient.requestExpirationPublisher
+ }
+
// MARK: - Private Properties
private let authClient: AuthClientProtocol
private let signClient: SignClientProtocol
@@ -203,19 +224,11 @@ 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?)? {
- signClient.getSessionRequestRecord(id: id)
- }
-
+
/// Query pending authentication requests
/// - Returns: Pending authentication requests
public func getPendingRequests() throws -> [(AuthRequest, VerifyContext?)] {
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/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/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")!
)
}
}
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/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..c840d0ad0 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 WalletConnectPairing
@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
)
}
@@ -73,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)
}
@@ -98,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")
}
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)")
+ }
+ }
+ }
}
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/SessionProposalTests.swift b/Tests/WalletConnectSignTests/SessionProposalTests.swift
new file mode 100644
index 000000000..e490ba5be
--- /dev/null
+++ b/Tests/WalletConnectSignTests/SessionProposalTests.swift
@@ -0,0 +1,50 @@
+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.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.expiryTimestamp! - 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.expiryTimestamp, "Expiry should be nil if not provided in JSON.")
+ }
+}
diff --git a/Tests/WalletConnectSignTests/SessionRequestTests.swift b/Tests/WalletConnectSignTests/SessionRequestTests.swift
index e89ff347d..3321f2fe1 100644
--- a/Tests/WalletConnectSignTests/SessionRequestTests.swift
+++ b/Tests/WalletConnectSignTests/SessionRequestTests.swift
@@ -1,83 +1,71 @@
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 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 testCalculateTtlForNonExpiredRequest() {
+ let request = try! Request.stub(ttl: 3600) // 1 hour
+ XCTAssertNoThrow(try request.calculateTtl())
}
- 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 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)
+ }
}
+}
- 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))
- }
-}
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"),
chainId: Blockchain("eip155:1")!,
- expiry: expiry
+ expiryTimestamp: expiry
)
}
}
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))
}
}
diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift
index 2f7961e22..9c5de2ccb 100644
--- a/Tests/WalletConnectSignTests/Stub/Stubs.swift
+++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift
@@ -4,19 +4,7 @@ import JSONRPC
import WalletConnectKMS
import WalletConnectUtils
import TestingUtils
-import WalletConnectPairing
-
-extension Pairing {
- static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing {
- Pairing(topic: topic, peer: nil, expiryDate: expiryDate)
- }
-}
-
-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)
- }
-}
+@testable import WalletConnectPairing
extension ProposalNamespace {
static func stubDictionary() -> [String: ProposalNamespace] {
@@ -68,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)
}
diff --git a/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift b/Tests/WalletConnectUtilsTests/WalletConnectURITests.swift
index 5f65b5c28..be9913382 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(uriString: 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(uriString: 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(uriString: inputURIString)
+ let outputURIString = uri.absoluteString
XCTAssertEqual(expectedString, outputURIString)
}
@@ -44,27 +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(uriString: inputURIString))
}
func testInitFailsMalformedURL() {
let inputURIString = "wc://<"
- let uri = WalletConnectURI(string: inputURIString)
- XCTAssertNil(uri)
+ XCTAssertThrowsError(try WalletConnectURI(uriString: 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(uriString: 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(uriString: inputURIString))
}
+
+ func testInitHandlesURLEncodedString() throws {
+ let input = stubURI()
+ let encodedURIString = input.string
+ .addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""
+ let uri = try WalletConnectURI(uriString: encodedURIString)
+
+ // Assert that the initializer can handle encoded URI and it matches the expected URI
+ XCTAssertEqual(input.uri, uri)
+ 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(uriString: 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(uriString: uriStringWithoutExpiry)
+
+ // Check if the expiryTimestamp is set to 5 minutes in the future
+ let expectedExpiryTimestamp = UInt64(Date().timeIntervalSince1970) + 5 * 60
+ XCTAssertTrue(uri.expiryTimestamp >= expectedExpiryTimestamp)
+ }
+
}
diff --git a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift
index 0f0012d59..3e752da08 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()
}
@@ -29,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..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: "", 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> {
@@ -63,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(
(
@@ -74,7 +84,11 @@ final class SignClientMock: SignClientProtocol {
)
.eraseToAnyPublisher()
}
-
+
+ var sessionProposalExpirationPublisher: AnyPublisher {
+ fatalError()
+ }
+
var sessionRejectionPublisher: AnyPublisher<(Session.Proposal, Reason), Never> {
let sessionProposal = Session.Proposal(
id: "",
@@ -138,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
}
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)