diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe39b9775..d021cfad9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: ci on: - pull_request_target: + pull_request: branches: - develop - main @@ -69,7 +69,7 @@ jobs: - name: Run integration tests if: matrix.type == 'integration-tests' shell: bash - run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=notify.walletconnect.com EXPLORER_HOST=explorer-api.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }} GM_DAPP_HOST=gm.walletconnect.com JS_CLIENT_API_HOST=test-automation-api.walletconnect.com + run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=notify.walletconnect.com EXPLORER_HOST=explorer-api.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }} GM_DAPP_HOST=wc-notify-swift-integration-tests-prod.pages.dev JS_CLIENT_API_HOST=test-automation-api.walletconnect.com # Relay Integration tests - name: Run Relay integration tests diff --git a/Example/DApp/ApplicationLayer/Application.swift b/Example/DApp/ApplicationLayer/Application.swift new file mode 100644 index 000000000..feba5e373 --- /dev/null +++ b/Example/DApp/ApplicationLayer/Application.swift @@ -0,0 +1,8 @@ +import Foundation + +import WalletConnectUtils + +final class Application { + var uri: WalletConnectURI? + var requestSent = false +} diff --git a/Example/DApp/Assets.xcassets/auth.imageset/Contents.json b/Example/DApp/Assets.xcassets/auth.imageset/Contents.json new file mode 100644 index 000000000..5104439bf --- /dev/null +++ b/Example/DApp/Assets.xcassets/auth.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "auth.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Example/DApp/Assets.xcassets/auth.imageset/auth.pdf b/Example/DApp/Assets.xcassets/auth.imageset/auth.pdf new file mode 100644 index 000000000..541e3c064 Binary files /dev/null and b/Example/DApp/Assets.xcassets/auth.imageset/auth.pdf differ diff --git a/Example/DApp/Assets.xcassets/copy.imageset/Contents.json b/Example/DApp/Assets.xcassets/copy.imageset/Contents.json new file mode 100644 index 000000000..27aa2aacc --- /dev/null +++ b/Example/DApp/Assets.xcassets/copy.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "copy.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/copy.imageset/copy.pdf b/Example/DApp/Assets.xcassets/copy.imageset/copy.pdf new file mode 100644 index 000000000..42246103e Binary files /dev/null and b/Example/DApp/Assets.xcassets/copy.imageset/copy.pdf differ diff --git a/Example/DApp/Assets.xcassets/ethereum.imageset/Contents.json b/Example/DApp/Assets.xcassets/ethereum.imageset/Contents.json new file mode 100644 index 000000000..6c2ed21b5 --- /dev/null +++ b/Example/DApp/Assets.xcassets/ethereum.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "eth.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/ethereum.imageset/eth.pdf b/Example/DApp/Assets.xcassets/ethereum.imageset/eth.pdf new file mode 100644 index 000000000..68dfa2b24 Binary files /dev/null and b/Example/DApp/Assets.xcassets/ethereum.imageset/eth.pdf differ diff --git a/Example/DApp/Assets.xcassets/exit.imageset/Contents.json b/Example/DApp/Assets.xcassets/exit.imageset/Contents.json new file mode 100644 index 000000000..9ae84b125 --- /dev/null +++ b/Example/DApp/Assets.xcassets/exit.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "exit.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/exit.imageset/exit.pdf b/Example/DApp/Assets.xcassets/exit.imageset/exit.pdf new file mode 100644 index 000000000..5a61d8489 Binary files /dev/null and b/Example/DApp/Assets.xcassets/exit.imageset/exit.pdf differ diff --git a/Example/DApp/Assets.xcassets/pen.imageset/Contents.json b/Example/DApp/Assets.xcassets/pen.imageset/Contents.json new file mode 100644 index 000000000..b6f60efc6 --- /dev/null +++ b/Example/DApp/Assets.xcassets/pen.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "pen.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Example/DApp/Assets.xcassets/pen.imageset/pen.pdf b/Example/DApp/Assets.xcassets/pen.imageset/pen.pdf new file mode 100644 index 000000000..128845b3f Binary files /dev/null and b/Example/DApp/Assets.xcassets/pen.imageset/pen.pdf differ diff --git a/Example/DApp/Assets.xcassets/polygon.imageset/Contents.json b/Example/DApp/Assets.xcassets/polygon.imageset/Contents.json new file mode 100644 index 000000000..dcabf6968 --- /dev/null +++ b/Example/DApp/Assets.xcassets/polygon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "matic.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/polygon.imageset/matic.pdf b/Example/DApp/Assets.xcassets/polygon.imageset/matic.pdf new file mode 100644 index 000000000..c1274fc8a Binary files /dev/null and b/Example/DApp/Assets.xcassets/polygon.imageset/matic.pdf differ diff --git a/Example/DApp/Assets.xcassets/profile.imageset/Contents.json b/Example/DApp/Assets.xcassets/profile.imageset/Contents.json new file mode 100644 index 000000000..0e9b66a20 --- /dev/null +++ b/Example/DApp/Assets.xcassets/profile.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "profile.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/profile.imageset/profile.pdf b/Example/DApp/Assets.xcassets/profile.imageset/profile.pdf new file mode 100644 index 000000000..3c2150a39 Binary files /dev/null and b/Example/DApp/Assets.xcassets/profile.imageset/profile.pdf differ diff --git a/Example/DApp/Assets.xcassets/solana.imageset/Contents.json b/Example/DApp/Assets.xcassets/solana.imageset/Contents.json new file mode 100644 index 000000000..5228d26fa --- /dev/null +++ b/Example/DApp/Assets.xcassets/solana.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "sol.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/solana.imageset/sol.pdf b/Example/DApp/Assets.xcassets/solana.imageset/sol.pdf new file mode 100644 index 000000000..2a1762f8d Binary files /dev/null and b/Example/DApp/Assets.xcassets/solana.imageset/sol.pdf differ diff --git a/Example/DApp/Auth/AuthCoordinator.swift b/Example/DApp/Auth/AuthCoordinator.swift deleted file mode 100644 index 0577412f4..000000000 --- a/Example/DApp/Auth/AuthCoordinator.swift +++ /dev/null @@ -1,38 +0,0 @@ -import SwiftUI -import Auth -import WalletConnectPairing - -final class AuthCoordinator { - - let navigationController = UINavigationController() - - private lazy var tabBarItem: UITabBarItem = { - let item = UITabBarItem() - item.title = "Auth" - item.image = UIImage(systemName: "person") - return item - }() - - private lazy var authViewController: UIViewController = { - let viewModel = AuthViewModel() - let view = AuthView(viewModel: viewModel) - let controller = UIHostingController(rootView: view) - controller.title = "DApp" - return controller - }() - - func start() { - navigationController.tabBarItem = tabBarItem - navigationController.viewControllers = [UIViewController()] - - let metadata = AppMetadata( - name: "Swift Dapp", - description: "WalletConnect DApp sample", - url: "wallet.connect", - icons: ["https://avatars.githubusercontent.com/u/37784886"], - redirect: AppMetadata.Redirect(native: "dapp://", universal: nil)) - - Pair.configure(metadata: metadata) - navigationController.viewControllers = [authViewController] - } -} diff --git a/Example/DApp/Auth/AuthView.swift b/Example/DApp/Auth/AuthView.swift deleted file mode 100644 index d3e699ed3..000000000 --- a/Example/DApp/Auth/AuthView.swift +++ /dev/null @@ -1,102 +0,0 @@ -import SwiftUI - -struct AuthView: View { - - @ObservedObject var viewModel: AuthViewModel - - var body: some View { - VStack(spacing: 16.0) { - - Spacer() - - Image(uiImage: viewModel.qrImage ?? UIImage()) - .interpolation(.none) - .resizable() - .frame(width: 300, height: 300) - - signingLabel() - .frame(maxWidth: .infinity) - - Spacer() - - Button("Connect Wallet", action: { }) - .buttonStyle(CircleButtonStyle()) - - Button("Copy URI", action: { viewModel.copyDidPressed() }) - .buttonStyle(CircleButtonStyle()) - - Button("Deeplink", action: { viewModel.deeplinkPressed() }) - .buttonStyle(CircleButtonStyle()) - - - } - .padding(16.0) - .onAppear { Task(priority: .userInitiated) { - try await viewModel.setupInitialState() - }} - } - - @ViewBuilder - private func signingLabel() -> some View { - switch viewModel.state { - case .error(let error): - SigningLabel(state: .error(error.localizedDescription)) - .frame(height: 50) - case .signed: - SigningLabel(state: .signed) - .frame(height: 50) - case .none: - Spacer().frame(height: 50) - } - } -} - -struct SigningLabel: View { - enum State { - case signed - case error(String) - - var color: Color { - switch self { - case .signed: return .green.opacity(0.6) - case .error: return .red.opacity(0.6) - } - } - - var text: String { - switch self { - case .signed: - return "Authenticated" - case .error: - return "Authenticion error" - } - } - } - - let state: State - - var body: some View { - VStack { - Text(state.text) - .multilineTextAlignment(.center) - .foregroundColor(.white) - .font(.system(size: 14, weight: .semibold)) - .padding(16.0) - } - .fixedSize(horizontal: true, vertical: false) - .background(state.color) - .cornerRadius(4.0) - } -} - -struct CircleButtonStyle: ButtonStyle { - - func makeBody(configuration: Self.Configuration) -> some View { - configuration.label - .frame(width: 200, height: 44) - .foregroundColor(.white) - .background(configuration.isPressed ? Color.blue.opacity(0.5) : Color.blue) - .font(.system(size: 17, weight: .semibold)) - .cornerRadius(8.0) - } -} diff --git a/Example/DApp/Auth/AuthViewModel.swift b/Example/DApp/Auth/AuthViewModel.swift deleted file mode 100644 index 69b0fb46e..000000000 --- a/Example/DApp/Auth/AuthViewModel.swift +++ /dev/null @@ -1,88 +0,0 @@ -import UIKit -import Combine -import Auth -import WalletConnectPairing - -final class AuthViewModel: ObservableObject { - - enum SigningState { - case none - case signed(Cacao) - case error(Error) - } - - private var disposeBag = Set() - - @Published var state: SigningState = .none - @Published var uriString: String? - - var qrImage: UIImage? { - return uriString.map { QRCodeGenerator.generateQRCode(from: $0) } - } - - init() { - setupSubscriptions() - } - - @MainActor - func setupInitialState() async throws { - state = .none - uriString = nil - let uri = try! await Pair.instance.create() - uriString = uri.absoluteString - try await Auth.instance.request(.stub(), topic: uri.topic) - } - - func copyDidPressed() { - UIPasteboard.general.string = uriString - } - - func walletDidPressed() { - - } - - func deeplinkPressed() { - guard let uri = uriString else { return } - UIApplication.shared.open(URL(string: "showcase://wc?uri=\(uri)")!) - } -} - -private extension AuthViewModel { - - func setupSubscriptions() { - Auth.instance.authResponsePublisher.sink { [weak self] (_, result) in - switch result { - case .success(let cacao): - self?.state = .signed(cacao) - case .failure(let error): - self?.state = .error(error) - } - }.store(in: &disposeBag) - } -} - -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/Info.plist b/Example/DApp/Info.plist index b991963c0..20e5043da 100644 --- a/Example/DApp/Info.plist +++ b/Example/DApp/Info.plist @@ -9,7 +9,7 @@ Editor CFBundleURLSchemes - dapp + wcdapp diff --git a/Example/DApp/Modules/Auth/AuthInteractor.swift b/Example/DApp/Modules/Auth/AuthInteractor.swift new file mode 100644 index 000000000..ffddc61dd --- /dev/null +++ b/Example/DApp/Modules/Auth/AuthInteractor.swift @@ -0,0 +1,3 @@ +import Foundation + +final class AuthInteractor {} diff --git a/Example/DApp/Modules/Auth/AuthModule.swift b/Example/DApp/Modules/Auth/AuthModule.swift new file mode 100644 index 000000000..9252f89e3 --- /dev/null +++ b/Example/DApp/Modules/Auth/AuthModule.swift @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..8e01e32e6 --- /dev/null +++ b/Example/DApp/Modules/Auth/AuthPresenter.swift @@ -0,0 +1,116 @@ +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 new file mode 100644 index 000000000..3caacfd38 --- /dev/null +++ b/Example/DApp/Modules/Auth/AuthRouter.swift @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..8e15bacc0 --- /dev/null +++ b/Example/DApp/Modules/Auth/AuthView.swift @@ -0,0 +1,140 @@ +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 new file mode 100644 index 000000000..a3954796d --- /dev/null +++ b/Example/DApp/Modules/Main/MainInteractor.swift @@ -0,0 +1,3 @@ +import Foundation + +final class MainInteractor {} diff --git a/Example/DApp/Modules/Main/MainModule.swift b/Example/DApp/Modules/Main/MainModule.swift new file mode 100644 index 000000000..67a9d6060 --- /dev/null +++ b/Example/DApp/Modules/Main/MainModule.swift @@ -0,0 +1,15 @@ +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 new file mode 100644 index 000000000..c3c7d47ee --- /dev/null +++ b/Example/DApp/Modules/Main/MainPresenter.swift @@ -0,0 +1,32 @@ +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 new file mode 100644 index 000000000..4b3fef3de --- /dev/null +++ b/Example/DApp/Modules/Main/MainRouter.swift @@ -0,0 +1,19 @@ +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 new file mode 100644 index 000000000..539b2a789 --- /dev/null +++ b/Example/DApp/Modules/Main/MainViewController.swift @@ -0,0 +1,43 @@ +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 new file mode 100644 index 000000000..faec2ba34 --- /dev/null +++ b/Example/DApp/Modules/Main/Model/TabPage.swift @@ -0,0 +1,32 @@ +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/NewPairingInteractor.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingInteractor.swift new file mode 100644 index 000000000..976caef32 --- /dev/null +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingInteractor.swift @@ -0,0 +1,3 @@ +import Foundation + +final class NewPairingInteractor {} diff --git a/Example/DApp/Modules/Sign/NewPairing/NewPairingModule.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingModule.swift new file mode 100644 index 000000000..70fa7b63c --- /dev/null +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingModule.swift @@ -0,0 +1,18 @@ +import SwiftUI + +import WalletConnectSign + +final class NewPairingModule { + @discardableResult + static func create(app: Application, walletConnectUri: WalletConnectURI) -> UIViewController { + let router = NewPairingRouter(app: app) + let interactor = NewPairingInteractor() + let presenter = NewPairingPresenter(interactor: interactor, router: router, walletConnectUri: walletConnectUri) + let view = NewPairingView().environmentObject(presenter) + let viewController = UIHostingController(rootView: view) + + router.viewController = viewController + + return viewController + } +} diff --git a/Example/DApp/Modules/Sign/NewPairing/NewPairingPresenter.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingPresenter.swift new file mode 100644 index 000000000..1a111425d --- /dev/null +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingPresenter.swift @@ -0,0 +1,54 @@ +import UIKit +import Combine + +import WalletConnectSign + +final class NewPairingPresenter: ObservableObject { + @Published var qrCodeImageData: Data? + + private let interactor: NewPairingInteractor + private let router: NewPairingRouter + + var walletConnectUri: WalletConnectURI + + private var subscriptions = Set() + + init( + interactor: NewPairingInteractor, + router: NewPairingRouter, + walletConnectUri: WalletConnectURI + ) { + self.interactor = interactor + self.router = router + self.walletConnectUri = walletConnectUri + } + + func onAppear() { + generateQR() + } + + func connectWallet() { + let url = URL(string: "walletapp://wc?uri=\(walletConnectUri.deeplinkUri.removingPercentEncoding!)")! + DispatchQueue.main.async { + UIApplication.shared.open(url) + } + router.dismiss() + } + + func copyUri() { + UIPasteboard.general.string = walletConnectUri.absoluteString + } +} + +// MARK: - Private functions +extension NewPairingPresenter { + private func generateQR() { + Task { @MainActor in + let qrCodeImage = QRCodeGenerator.generateQRCode(from: walletConnectUri.absoluteString) + qrCodeImageData = qrCodeImage.pngData() + } + } +} + +// MARK: - SceneViewModel +extension NewPairingPresenter: SceneViewModel {} diff --git a/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift new file mode 100644 index 000000000..51fc5b2f2 --- /dev/null +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingRouter.swift @@ -0,0 +1,16 @@ +import UIKit + +final class NewPairingRouter { + 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/Sign/NewPairing/NewPairingView.swift b/Example/DApp/Modules/Sign/NewPairing/NewPairingView.swift new file mode 100644 index 000000000..69231ad3e --- /dev/null +++ b/Example/DApp/Modules/Sign/NewPairing/NewPairingView.swift @@ -0,0 +1,83 @@ +import SwiftUI + +import WalletConnectSign + +struct NewPairingView: View { + @EnvironmentObject var presenter: NewPairingPresenter + + 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) + + Text(presenter.walletConnectUri.absoluteString) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .opacity(0) + + Spacer() + } + } + .navigationTitle("New Pairing") + .navigationBarTitleDisplayMode(.inline) + .toolbarColorScheme(.dark, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbarBackground( + Color(red: 25/255, green: 26/255, blue: 26/255), + for: .navigationBar + ) + .toolbarRole(.editor) + .onAppear { + presenter.onAppear() + } + } + } +} + +// MARK: - Previews +struct SignNewPairingView_Previews: PreviewProvider { + static var previews: some View { + NewPairingView() + } +} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift new file mode 100644 index 000000000..d861b6ceb --- /dev/null +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift @@ -0,0 +1,13 @@ +import Foundation + +import WalletConnectSign + + +struct AccountDetails { + let chain: String + let methods: [String] + let account: String +} + + +final class SessionAccountInteractor {} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountModule.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountModule.swift new file mode 100644 index 000000000..94bdd3043 --- /dev/null +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountModule.swift @@ -0,0 +1,18 @@ +import SwiftUI + +import WalletConnectSign + +final class SessionAccountModule { + @discardableResult + static func create(app: Application, sessionAccount: AccountDetails, session: Session) -> UIViewController { + let router = SessionAccountRouter(app: app) + let interactor = SessionAccountInteractor() + let presenter = SessionAccountPresenter(interactor: interactor, router: router, sessionAccount: sessionAccount, session: session) + let view = SessionAccountView().environmentObject(presenter) + let viewController = UIHostingController(rootView: view) + + router.viewController = viewController + + return viewController + } +} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift new file mode 100644 index 000000000..15b0b7e74 --- /dev/null +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -0,0 +1,197 @@ +import UIKit +import Combine + +import WalletConnectSign + +final class SessionAccountPresenter: ObservableObject { + enum Errors: Error { + case notImplemented + } + + @Published var showResponse = false + @Published var showError = false + @Published var errorMessage = String.empty + @Published var showRequestSent = false + + private let interactor: SessionAccountInteractor + private let router: SessionAccountRouter + private let session: Session + + var sessionAccount: AccountDetails + var response: Response? + + private var subscriptions = Set() + + init( + interactor: SessionAccountInteractor, + router: SessionAccountRouter, + sessionAccount: AccountDetails, + session: Session + ) { + defer { setupInitialState() } + self.interactor = interactor + self.router = router + self.sessionAccount = sessionAccount + self.session = session + } + + func onAppear() {} + + func onMethod(method: String) { + do { + let requestParams = try getRequest(for: method) + + let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(sessionAccount.chain)!) + Task { + do { + try await Sign.instance.request(params: request) + DispatchQueue.main.async { [weak self] in + self?.openWallet() + } + } catch { + showError.toggle() + errorMessage = error.localizedDescription + } + } + } catch { + showError.toggle() + errorMessage = error.localizedDescription + } + } + + func copyUri() { + UIPasteboard.general.string = sessionAccount.account + } +} + +// MARK: - Private functions +extension SessionAccountPresenter { + private func setupInitialState() { + Sign.instance.sessionResponsePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] response in + presentResponse(response: response) + } + .store(in: &subscriptions) + } + + private func getRequest(for method: String) throws -> AnyCodable { + let account = session.namespaces.first!.value.accounts.first!.absoluteString + if method == "eth_sendTransaction" { + let tx = Stub.tx + return AnyCodable(tx) + } else if method == "personal_sign" { + return AnyCodable(["0x4d7920656d61696c206973206a6f686e40646f652e636f6d202d2031363533333933373535313531", account]) + } else if method == "eth_signTypedData" { + return AnyCodable([account, Stub.eth_signTypedData]) + } + throw Errors.notImplemented + } + + private func presentResponse(response: Response) { + self.response = response + showResponse.toggle() + } + + private func openWallet() { + if let nativeUri = session.peer.redirect?.native { + UIApplication.shared.open(URL(string: "\(nativeUri)wc?requestSent")!) + } else { + showRequestSent.toggle() + } + } +} + +// MARK: - SceneViewModel +extension SessionAccountPresenter: SceneViewModel {} + +// MARK: Errors +extension SessionAccountPresenter.Errors: LocalizedError { + var errorDescription: String? { + switch self { + case .notImplemented: return "Requested method is not implemented" + } + } +} + +// MARK: - Transaction Stub +private enum Stub { + struct Transaction: Codable { + let from, to, data, gas: String + let gasPrice, value, nonce: String + } + + static let tx = [Transaction(from: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", + to: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", + data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", + gas: "0x76c0", + gasPrice: "0x9184e72a000", + value: "0x9184e72a", + nonce: "0x117")] + static let eth_signTypedData = """ +{ +"types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] +}, +"primaryType": "Mail", +"domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" +}, +"message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" +} +} +""" +} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountRouter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountRouter.swift new file mode 100644 index 000000000..9da7d698d --- /dev/null +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountRouter.swift @@ -0,0 +1,17 @@ +import UIKit + +import WalletConnectSign + +final class SessionAccountRouter { + weak var viewController: UIViewController! + + private let app: Application + + init(app: Application) { + self.app = app + } + + func dismiss() { + viewController.dismiss(animated: true) + } +} diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift new file mode 100644 index 000000000..1b8a3ebb8 --- /dev/null +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -0,0 +1,254 @@ +import SwiftUI + +import WalletConnectSign + +struct SessionAccountView: View { + @EnvironmentObject var presenter: SessionAccountPresenter + + 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 ?? "")) + accountView(address: presenter.sessionAccount.account) + methodsView(methods: presenter.sessionAccount.methods) + + Spacer() + } + .padding(12) + } + } + .navigationTitle(presenter.sessionAccount.chain) + .navigationBarTitleDisplayMode(.inline) + .toolbarColorScheme(.dark, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbarRole(.editor) + .toolbarBackground( + Color(red: 25/255, green: 26/255, blue: 26/255), + for: .navigationBar + ) + .sheet(isPresented: $presenter.showResponse) { + ZStack { + Color(red: 25/255, green: 26/255, blue: 26/255) + .ignoresSafeArea() + + ScrollView { + responseView(response: presenter.response!) + .padding(12) + } + } + .presentationDetents([.medium]) + } + .alert(presenter.errorMessage, isPresented: $presenter.showError) { + Button("OK", role: .cancel) {} + } + .alert("Request sent. Check your wallet.", isPresented: $presenter.showRequestSent) { + Button("OK", role: .cancel) {} + } + } + } + + private func networkView(title: String) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + VStack(spacing: 5) { + Text(title) + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .frame(maxWidth: .infinity, alignment: .topLeading) + .padding(12) + + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + HStack(spacing: 10) { + Image(title == "eip155" ? "ethereum" : title.lowercased()) + .resizable() + .frame(width: 40, height: 40) + + VStack(alignment: .leading, spacing: 5) { + Text(title) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(red: 228/255, green: 231/255, blue: 231/255)) + } + + Spacer() + } + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + .padding(.bottom, 12) + .padding(.horizontal, 8) + } + } + } + + private func accountView(address: String) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + VStack(spacing: 5) { + Text("Address") + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .frame(maxWidth: .infinity, alignment: .topLeading) + .padding(12) + + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + HStack(spacing: 10) { + VStack(alignment: .leading, spacing: 5) { + Text(address) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(.vertical, 12) + } + + Spacer() + + Button { + presenter.copyUri() + } label: { + Image("copy") + .resizable() + .frame(width: 14, height: 14) + .padding(.trailing, 18) + } + } + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + .padding(.bottom, 12) + .padding(.horizontal, 8) + } + } + } + + private func methodsView(methods: [String]) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + VStack(spacing: 5) { + Text("Methods") + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .frame(maxWidth: .infinity, alignment: .topLeading) + .padding(12) + + ForEach(Array(methods.enumerated()), id: \.offset) { index, method in + Button { + presenter.onMethod(method: method) + } label: { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + HStack(spacing: 10) { + Text(method) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .padding(.vertical, 12) + + Spacer() + } + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + .padding(.bottom, 12) + .padding(.horizontal, 8) + } + .accessibilityIdentifier("method-\(index)") + } + } + } + } + + private func responseView(response: Response) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + VStack(spacing: 5) { + HStack { + RoundedRectangle(cornerRadius: 2) + .fill(.gray.opacity(0.5)) + .frame(width: 30, height: 4) + + } + .padding(20) + + HStack { + Group { + if case RPCResult.error(_) = response.result { + Text("❌ Response") + } else { + Text("✅ Response") + } + } + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(12) + + Spacer() + + let record = Sign.instance.getSessionRequestRecord(id: response.id)! + Text(record.request.method) + .font( + Font.system(size: 14, weight: .medium) + ) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(12) + } + + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + switch response.result { + case .response(let response): + Text(try! response.get(String.self).description) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .padding(.vertical, 12) + .padding(.horizontal, 8) + + case .error(let error): + Text(error.message) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + } + .padding(.bottom, 12) + .padding(.horizontal, 8) + } + } + } +} + +// MARK: - Previews +struct SessionAccountView_Previews: PreviewProvider { + static var previews: some View { + SessionAccountView() + } +} diff --git a/Example/DApp/Modules/Sign/SignInteractor.swift b/Example/DApp/Modules/Sign/SignInteractor.swift new file mode 100644 index 000000000..8fa7d0e2b --- /dev/null +++ b/Example/DApp/Modules/Sign/SignInteractor.swift @@ -0,0 +1,38 @@ +import Foundation + +import WalletConnectSign + +enum Proposal { + static let requiredNamespaces: [String: ProposalNamespace] = [ + "eip155": ProposalNamespace( + chains: [ + Blockchain("eip155:1")!, + Blockchain("eip155:137")! + ], + methods: [ + "eth_sendTransaction", + "personal_sign", + "eth_signTypedData" + ], events: [] + ) + ] + + static let optionalNamespaces: [String: ProposalNamespace] = [ + "solana": ProposalNamespace( + chains: [ + Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! + ], + methods: [ + "solana_signMessage", + "solana_signTransaction" + ], events: [] + ) + ] +} + +struct Chain { + let name: String + let id: String +} + +final class SignInteractor {} diff --git a/Example/DApp/Modules/Sign/SignModule.swift b/Example/DApp/Modules/Sign/SignModule.swift new file mode 100644 index 000000000..4e2efd83e --- /dev/null +++ b/Example/DApp/Modules/Sign/SignModule.swift @@ -0,0 +1,16 @@ +import SwiftUI + +final class SignModule { + @discardableResult + static func create(app: Application) -> UIViewController { + let router = SignRouter(app: app) + let interactor = SignInteractor() + let presenter = SignPresenter(interactor: interactor, router: router) + let view = SignView().environmentObject(presenter) + + let viewController = SceneViewController(viewModel: presenter, content: view) + router.viewController = viewController + + return viewController + } +} diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift new file mode 100644 index 000000000..b42d2663c --- /dev/null +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -0,0 +1,130 @@ +import UIKit +import Combine + +import Web3Modal +import WalletConnectSign + +final class SignPresenter: ObservableObject { + @Published var accountsDetails = [AccountDetails]() + + @Published var showError = false + @Published var errorMessage = String.empty + + var walletConnectUri: WalletConnectURI? + + let chains = [ + Chain(name: "Ethereum", id: "eip155:1"), + Chain(name: "Polygon", id: "eip155:137"), + Chain(name: "Solana", id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ") + ] + + private let interactor: SignInteractor + private let router: SignRouter + + private var session: Session? + + private var subscriptions = Set() + + init( + interactor: SignInteractor, + router: SignRouter + ) { + defer { setupInitialState() } + self.interactor = interactor + self.router = router + } + + func onAppear() { + + } + + func copyUri() { + UIPasteboard.general.string = walletConnectUri?.absoluteString + } + + func connectWalletWithW3M() { + Task { + Web3Modal.set(sessionParams: .init( + requiredNamespaces: Proposal.requiredNamespaces, + optionalNamespaces: Proposal.optionalNamespaces + )) + } + Web3Modal.present(from: nil) + } + + @MainActor + func connectWalletWithSign() { + 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) + } + } + + func disconnect() { + if let session { + Task { @MainActor in + do { + try await Sign.instance.disconnect(topic: session.topic) + accountsDetails.removeAll() + } catch { + showError.toggle() + errorMessage = error.localizedDescription + } + } + } + } + + func presentSessionAccount(sessionAccount: AccountDetails) { + if let session { + router.presentSessionAccount(sessionAccount: sessionAccount, session: session) + } + } +} + +// MARK: - Private functions +extension SignPresenter { + private func setupInitialState() { + Sign.instance.sessionSettlePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] _ in + self.router.dismiss() + self.getSession() + } + .store(in: &subscriptions) + + getSession() + + Sign.instance.sessionDeletePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] _ in + self.accountsDetails.removeAll() + } + .store(in: &subscriptions) + } + + private func getSession() { + if let session = Sign.instance.getSessions().first { + self.session = session + session.namespaces.values.forEach { namespace in + namespace.accounts.forEach { account in + accountsDetails.append( + AccountDetails( + chain: account.blockchainIdentifier, + methods: Array(namespace.methods), + account: account.address + ) + ) + } + } + } + } +} + +// MARK: - SceneViewModel +extension SignPresenter: SceneViewModel {} diff --git a/Example/DApp/Modules/Sign/SignRouter.swift b/Example/DApp/Modules/Sign/SignRouter.swift new file mode 100644 index 000000000..60da1928c --- /dev/null +++ b/Example/DApp/Modules/Sign/SignRouter.swift @@ -0,0 +1,33 @@ +import UIKit + +import WalletConnectSign + +final class SignRouter { + weak var viewController: UIViewController! + + private var newPairingViewController: UIViewController? + + private let app: Application + + init(app: Application) { + self.app = app + } + + func presentNewPairing(walletConnectUri: WalletConnectURI) { + newPairingViewController = NewPairingModule.create(app: app, walletConnectUri: walletConnectUri) + newPairingViewController?.present(from: viewController) + } + + func presentSessionAccount(sessionAccount: AccountDetails, session: Session) { + SessionAccountModule.create(app: app, sessionAccount: sessionAccount, session: session) + .present(from: viewController) + } + + func dismissNewPairing() { + newPairingViewController?.dismiss() + } + + func dismiss() { + viewController.dismiss(animated: true) + } +} diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift new file mode 100644 index 000000000..fc60a1754 --- /dev/null +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -0,0 +1,171 @@ +import SwiftUI + +struct SignView: View { + @EnvironmentObject var presenter: SignPresenter + + var body: some View { + NavigationStack { + ZStack { + Color(red: 25/255, green: 26/255, blue: 26/255) + .ignoresSafeArea() + + ScrollView { + if presenter.accountsDetails.isEmpty { + VStack { + ForEach(presenter.chains, id: \.name) { chain in + networkItem(title: chain.name, icon: chain.name.lowercased(), id: chain.id) + } + + Spacer() + + Button { + presenter.connectWalletWithW3M() + } label: { + Text("Connect with Web3Modal") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } + .padding(.top, 20) + + Button { + presenter.connectWalletWithSign() + } label: { + Text("Connect with Sign API") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } + .padding(.top, 10) + } + .padding(12) + } else { + VStack { + ForEach(presenter.accountsDetails, id: \.chain) { account in + Button { + presenter.presentSessionAccount(sessionAccount: account) + } label: { + networkItem(title: account.account, icon: String(account.chain.split(separator: ":").first ?? ""), id: account.chain) + } + .accessibilityIdentifier(account.account) + } + } + .padding(12) + } + } + .onAppear { + presenter.onAppear() + } + + if !presenter.accountsDetails.isEmpty { + VStack { + Spacer() + + Button { + presenter.disconnect() + } label: { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(.white.opacity(0.02)) + + HStack { + ZStack { + Circle() + .fill(.white.opacity(0.05)) + .frame(width: 32, height: 32) + + Circle() + .fill(.white.opacity(0.1)) + .frame(width: 30, height: 30) + + Image("exit") + .resizable() + .frame(width: 14, height: 14) + } + + Text("Disconnect") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + + Spacer() + } + .padding(.horizontal, 12) + } + .frame(height: 56) + + } + .padding(20) + } + } + } + .navigationTitle(presenter.accountsDetails.isEmpty ? "Available Chains" : "Session Accounts") + .navigationBarTitleDisplayMode(.inline) + .toolbarColorScheme(.dark, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbarBackground( + Color(red: 25/255, green: 26/255, blue: 26/255), + for: .navigationBar + ) + .alert(presenter.errorMessage, isPresented: $presenter.showError) { + Button("OK", role: .cancel) {} + } + } + } + + private func networkItem(title: String, icon: String, id: String) -> some View { + ZStack { + RoundedRectangle(cornerRadius: 16) + .fill(Color(red: 30/255, green: 31/255, blue: 31/255)) + + HStack(spacing: 10) { + Image(icon == "eip155" ? "ethereum" : icon) + .resizable() + .frame(width: 40, height: 40) + + VStack(alignment: .leading, spacing: 5) { + HStack { + Text(title) + .lineLimit(1) + .truncationMode(.middle) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(red: 228/255, green: 231/255, blue: 231/255)) + + Spacer() + } + + HStack { + Text(id) + .lineLimit(1) + .truncationMode(.middle) + .font(.system(size: 13, weight: .regular)) + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + + Spacer() + } + } + + Spacer() + + if !presenter.accountsDetails.isEmpty { + Image(systemName: "chevron.right") + .foregroundColor(Color(red: 0.58, green: 0.62, blue: 0.62)) + .padding(.trailing, 16) + } + } + .padding(.vertical, 12) + .padding(.horizontal, 8) + } + } +} + +struct SignView_Previews: PreviewProvider { + static var previews: some View { + SignView() + } +} diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index b5a8df419..2586a7fd9 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -1,15 +1,14 @@ import UIKit + +import Web3Modal import Auth import WalletConnectRelay import WalletConnectNetworking -import WalletConnectModal class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - private let signCoordinator = SignCoordinator() - private let authCoordinator = AuthCoordinator() + private let app = Application() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory()) @@ -20,13 +19,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { description: "WalletConnect DApp sample", url: "wallet.connect", icons: ["https://avatars.githubusercontent.com/u/37784886"], - redirect: AppMetadata.Redirect(native: "dapp://", universal: nil) + redirect: AppMetadata.Redirect(native: "wcdapp://", universal: nil) ) - WalletConnectModal.configure( + Web3Modal.configure( projectId: InputConfig.projectId, - metadata: metadata, - accentColor: .green + metadata: metadata ) setupWindow(scene: scene) @@ -36,16 +34,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let tabController = UITabBarController() - tabController.viewControllers = [ - signCoordinator.navigationController, - authCoordinator.navigationController - ] - - signCoordinator.start() - authCoordinator.start() + let viewController = MainModule.create(app: app) - window?.rootViewController = tabController + window?.rootViewController = viewController window?.makeKeyAndVisible() } } diff --git a/Example/DApp/Sign/AccountRequest/AccountRequestView.swift b/Example/DApp/Sign/AccountRequest/AccountRequestView.swift deleted file mode 100644 index d7f2aab76..000000000 --- a/Example/DApp/Sign/AccountRequest/AccountRequestView.swift +++ /dev/null @@ -1,68 +0,0 @@ -import UIKit -import Foundation - -class AccountRequestView: UIView { - let iconView: UIImageView = { - let imageView = UIImageView() - imageView.contentMode = .scaleAspectFit - imageView.backgroundColor = .systemFill - imageView.layer.cornerRadius = 32 - return imageView - }() - - let chainLabel: UILabel = { - let label = UILabel() - label.font = UIFont.systemFont(ofSize: 17.0, weight: .heavy) - return label - }() - let accountLabel: UILabel = { - let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .subheadline) - label.textColor = .secondaryLabel - label.numberOfLines = 0 - label.textAlignment = .center - return label - }() - - let headerStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 16 - stackView.alignment = .center - return stackView - }() - - let tableView = UITableView() - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = .systemBackground - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "method_cell") - addSubview(iconView) - addSubview(headerStackView) - addSubview(tableView) - - headerStackView.addArrangedSubview(chainLabel) - headerStackView.addArrangedSubview(accountLabel) - - subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - - NSLayoutConstraint.activate([ - iconView.topAnchor.constraint(equalTo: topAnchor, constant: 64), - iconView.centerXAnchor.constraint(equalTo: centerXAnchor), - - headerStackView.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 32), - headerStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), - headerStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32), - - tableView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 0), - tableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0), - tableView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), - tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor) - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift b/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift deleted file mode 100644 index 325d0378a..000000000 --- a/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift +++ /dev/null @@ -1,176 +0,0 @@ -import Foundation -import UIKit -import WalletConnectSign -import WalletConnectUtils - -class AccountRequestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { - private let session: Session - private let chainId: String - private let account: String - private let methods: [String] - private let accountRequestView = { - AccountRequestView() - }() - - init(session: Session, accountDetails: AccountDetails) { - self.session = session - self.chainId = accountDetails.chain - self.account = accountDetails.account - self.methods = accountDetails.methods - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = accountRequestView - } - - override func viewDidLoad() { - super.viewDidLoad() - accountRequestView.tableView.delegate = self - accountRequestView.tableView.dataSource = self - accountRequestView.iconView.image = UIImage(named: chainId) - accountRequestView.chainLabel.text = chainId - accountRequestView.accountLabel.text = account - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return methods.count - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return "Methods" - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "method_cell", for: indexPath) - cell.textLabel?.text = methods[indexPath.row] - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let method = methods[indexPath.row] - let requestParams = getRequest(for: method) - - let request = Request(topic: session.topic, method: method, params: requestParams, chainId: Blockchain(chainId)!) - Task { - do { - try await Sign.instance.request(params: request) - DispatchQueue.main.async { [weak self] in - self?.openWallet() - } - } catch { - print(error) - // show failure alert - } - } - } - - private func openWallet() { - UIApplication.shared.open(URL(string: "walletapp://wc?requestSent")!) - } - - private func presentConfirmationAlert() { - let alert = UIAlertController(title: "Request Sent", message: nil, preferredStyle: .alert) - let action = UIAlertAction(title: "OK", style: .cancel) - alert.addAction(action) - present(alert, animated: true) - } - - private func getRequest(for method: String) -> AnyCodable { - let account = session.namespaces.first!.value.accounts.first!.absoluteString - if method == "eth_sendTransaction" { - let tx = Stub.tx - return AnyCodable(tx) - } else if method == "personal_sign" { - return AnyCodable(["0x4d7920656d61696c206973206a6f686e40646f652e636f6d202d2031363533333933373535313531", account]) - } else if method == "eth_signTypedData" { - return AnyCodable([account, Stub.eth_signTypedData]) - } - fatalError("not implemented") - } -} - -struct Transaction: Codable { - let from, to, data, gas: String - let gasPrice, value, nonce: String -} - -private enum Stub { - static let tx = [Transaction(from: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", - to: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", - data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", - gas: "0x76c0", - gasPrice: "0x9184e72a000", - value: "0x9184e72a", - nonce: "0x117")] - static let eth_signTypedData = """ -{ -"types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Person": [ - { - "name": "name", - "type": "string" - }, - { - "name": "wallet", - "type": "address" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person" - }, - { - "name": "contents", - "type": "string" - } - ] -}, -"primaryType": "Mail", -"domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" -}, -"message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" -} -} -""" -} diff --git a/Example/DApp/Sign/Accounts/AccountsView.swift b/Example/DApp/Sign/Accounts/AccountsView.swift deleted file mode 100644 index 4c544cf3f..000000000 --- a/Example/DApp/Sign/Accounts/AccountsView.swift +++ /dev/null @@ -1,29 +0,0 @@ -import UIKit - -final class AccountsView: UIView { - let tableView: UITableView = { - let tableView = UITableView(frame: .zero, style: .plain) - tableView.backgroundColor = .tertiarySystemBackground - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "accountCell") - return tableView - }() - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = .systemBackground - addSubview(tableView) - - subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16), - tableView.leadingAnchor.constraint(equalTo: leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor) - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift deleted file mode 100644 index 631a0725a..000000000 --- a/Example/DApp/Sign/Accounts/AccountsViewController.swift +++ /dev/null @@ -1,99 +0,0 @@ -import UIKit -import WalletConnectSign -import WalletConnectNotify -import Combine - -struct AccountDetails { - let chain: String - let methods: [String] - let account: String -} - -final class AccountsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - - let session: Session - var accountsDetails: [AccountDetails] = [] - var onDisconnect: (() -> Void)? - var notifySubscription: NotifySubscription? - - private var publishers = [AnyCancellable]() - private let accountsView: AccountsView = { - AccountsView() - }() - - init(session: Session) { - self.session = session - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = accountsView - } - - override func viewDidLoad() { - super.viewDidLoad() - navigationItem.title = "Accounts" - - - navigationItem.rightBarButtonItem = UIBarButtonItem( - title: "Disconnect", - style: .plain, - target: self, - action: #selector(disconnect) - ) - - accountsView.tableView.dataSource = self - accountsView.tableView.delegate = self - session.namespaces.values.forEach { namespace in - namespace.accounts.forEach { account in - accountsDetails.append(AccountDetails(chain: account.blockchainIdentifier, methods: Array(namespace.methods), account: account.address)) // TODO: Rethink how this info is displayed on example - } - } - } - - @objc - private func disconnect() { - Task { - do { - try await Sign.instance.disconnect(topic: session.topic) - DispatchQueue.main.async { [weak self] in - self?.onDisconnect?() - } - } catch { - print(error) - // show failure alert - } - } - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - accountsDetails.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "accountCell", for: indexPath) - let details = accountsDetails[indexPath.row] - cell.textLabel?.text = details.account - cell.imageView?.image = UIImage(named: details.chain) - cell.textLabel?.numberOfLines = 0 - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - showAccountRequestScreen(accountsDetails[indexPath.row]) - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 70 - } - - func showAccountRequestScreen(_ details: AccountDetails) { - let vc = AccountRequestViewController(session: session, accountDetails: details) - navigationController?.pushViewController(vc, animated: true) - } - -} diff --git a/Example/DApp/Sign/Connect/ConnectView.swift b/Example/DApp/Sign/Connect/ConnectView.swift deleted file mode 100644 index 7004411a0..000000000 --- a/Example/DApp/Sign/Connect/ConnectView.swift +++ /dev/null @@ -1,78 +0,0 @@ -import Foundation -import UIKit - -final class ConnectView: UIView { - let tableView = UITableView() - - let qrCodeView: UIImageView = { - let imageView = UIImageView() - imageView.contentMode = .center - return imageView - }() - - let copyButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Copy", for: .normal) - button.setImage(UIImage(systemName: "doc.on.doc"), for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 17.0, weight: .semibold) - button.backgroundColor = .systemBlue - button.tintColor = .white - button.layer.cornerRadius = 8 - return button - }() - - let connectWalletButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Connect Wallet", for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 17.0, weight: .semibold) - button.backgroundColor = .systemBlue - button.tintColor = .white - button.layer.cornerRadius = 8 - return button - }() - - let invisibleUriLabel: UILabel = { - let label = UILabel(frame: CGRect(origin: .zero, size: .init(width: 1, height: 1))) - label.numberOfLines = 0 - label.textColor = .clear - return label - }() - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = .systemBackground - addSubview(qrCodeView) - addSubview(invisibleUriLabel) - addSubview(copyButton) - addSubview(connectWalletButton) - addSubview(tableView) - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "pairing_cell") - subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), - - qrCodeView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 50), - qrCodeView.centerXAnchor.constraint(equalTo: centerXAnchor), - qrCodeView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6), - qrCodeView.widthAnchor.constraint(equalTo: qrCodeView.heightAnchor), - - copyButton.topAnchor.constraint(equalTo: qrCodeView.bottomAnchor, constant: 16), - copyButton.centerXAnchor.constraint(equalTo: centerXAnchor), - copyButton.widthAnchor.constraint(equalTo: qrCodeView.widthAnchor), - copyButton.heightAnchor.constraint(equalToConstant: 44), - - connectWalletButton.topAnchor.constraint(equalTo: copyButton.bottomAnchor, constant: 16), - connectWalletButton.centerXAnchor.constraint(equalTo: centerXAnchor), - connectWalletButton.widthAnchor.constraint(equalTo: copyButton.widthAnchor), - connectWalletButton.heightAnchor.constraint(equalToConstant: 44) - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Sign/Connect/ConnectViewController.swift b/Example/DApp/Sign/Connect/ConnectViewController.swift deleted file mode 100644 index 59fae59ec..000000000 --- a/Example/DApp/Sign/Connect/ConnectViewController.swift +++ /dev/null @@ -1,123 +0,0 @@ -import Foundation -import UIKit -import WalletConnectModal - -class ConnectViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - let uri: WalletConnectURI - let activePairings: [Pairing] = Pair.instance.getPairings() - let segmentedControl = UISegmentedControl(items: ["Pairings", "New Pairing"]) - - init(uri: WalletConnectURI) { - self.uri = uri - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private let connectView: ConnectView = { - ConnectView() - }() - - override func loadView() { - view = connectView - } - - override func viewDidLoad() { - super.viewDidLoad() - DispatchQueue.global().async { [unowned self] in - let qrImage = QRCodeGenerator.generateQRCode(from: uri.absoluteString) - DispatchQueue.main.async { [self] in - self.connectView.qrCodeView.image = qrImage - self.connectView.copyButton.isHidden = false - } - } - - connectView.invisibleUriLabel.text = uri.absoluteString - connectView.copyButton.addTarget(self, action: #selector(copyURI), for: .touchUpInside) - connectView.connectWalletButton.addTarget(self, action: #selector(connectWithExampleWallet), for: .touchUpInside) - connectView.tableView.dataSource = self - connectView.tableView.delegate = self - connectView.copyButton.isHidden = true - setUpSegmentedControl() - } - - func setUpSegmentedControl() { - segmentedControl.selectedSegmentIndex = 0 - self.navigationItem.titleView = segmentedControl - segmentedControl.addTarget(self, action: #selector(segmentAction), for: .valueChanged) - } - - @objc func segmentAction() { - if segmentedControl.selectedSegmentIndex == 0 { - connectView.tableView.isHidden = false - } else { - connectView.tableView.isHidden = true - } - } - - @objc func copyURI() { - UIPasteboard.general.string = uri.absoluteString - } - - @objc func connectWithExampleWallet() { - let url = URL(string: "walletapp://wc?uri=\(uri.deeplinkUri.removingPercentEncoding!)")! - DispatchQueue.main.async { - UIApplication.shared.open(url, options: [:]) { [weak self] _ in - self?.dismiss(animated: true, completion: nil) - } - } - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - activePairings.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "pairing_cell", for: indexPath) - cell.textLabel?.text = activePairings[indexPath.row].peer?.name ?? "" - return cell - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let pairingTopic = activePairings[indexPath.row].topic - let requiredNamespaces: [String: ProposalNamespace] = [ - "eip155": ProposalNamespace( - chains: [ - Blockchain("eip155:1")!, - Blockchain("eip155:137")! - ], - methods: [ - "eth_sendTransaction", - "personal_sign", - "eth_signTypedData" - ], events: [] - ), - "solana": ProposalNamespace( - chains: [ - Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! - ], - methods: [ - "solana_signMessage", - "solana_signTransaction" - ], events: [] - ) - ] - let optionalNamespaces: [String: ProposalNamespace] = [ - "eip155:42161": ProposalNamespace( - methods: [ - "eth_sendTransaction", - "eth_signTransaction", - "get_balance", - "personal_sign" - ], - events: ["accountsChanged", "chainChanged"] - ) - ] - Task { - _ = try await Sign.instance.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces, topic: pairingTopic) - connectWithExampleWallet() - } - } -} diff --git a/Example/DApp/Sign/ResponseViewController.swift b/Example/DApp/Sign/ResponseViewController.swift deleted file mode 100644 index 9d817014b..000000000 --- a/Example/DApp/Sign/ResponseViewController.swift +++ /dev/null @@ -1,106 +0,0 @@ -import Foundation -import WalletConnectSign -import UIKit - -class ResponseViewController: UIViewController { - let response: Response - private let responseView = { - ResponseView() - }() - - init(response: Response) { - self.response = response - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func loadView() { - view = responseView - } - - override func viewDidLoad() { - super.viewDidLoad() - let record = Sign.instance.getSessionRequestRecord(id: response.id)! - switch response.result { - case .response(let response): - responseView.nameLabel.text = "Received Response\n\(record.request.method)" - responseView.descriptionLabel.text = try! response.get(String.self).description - case .error(let error): - responseView.nameLabel.text = "Received Error\n\(record.request.method)" - responseView.descriptionLabel.text = error.message - } - responseView.dismissButton.addTarget(self, action: #selector(dismissSelf), for: .touchUpInside) - } - - @objc func dismissSelf() { - dismiss(animated: true, completion: nil) - } -} - -final class ResponseView: UIView { - - let nameLabel: UILabel = { - let label = UILabel() - label.numberOfLines = 0 - label.font = UIFont.systemFont(ofSize: 17.0, weight: .heavy) - return label - }() - - let descriptionLabel: UILabel = { - let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .subheadline) - label.textColor = .secondaryLabel - label.numberOfLines = 0 - label.textAlignment = .center - return label - }() - - let dismissButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Dismiss", for: .normal) - button.backgroundColor = .systemBlue - button.tintColor = .white - button.layer.cornerRadius = 8 - button.titleLabel?.font = UIFont.systemFont(ofSize: 17.0, weight: .semibold) - return button - }() - - let headerStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 16 - stackView.alignment = .center - return stackView - }() - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = .systemBackground - - addSubview(headerStackView) - addSubview(dismissButton) - headerStackView.addArrangedSubview(nameLabel) - headerStackView.addArrangedSubview(descriptionLabel) - - subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - - NSLayoutConstraint.activate([ - - headerStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 32), - headerStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), - headerStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32), - - dismissButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), - dismissButton.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: 16), - dismissButton.heightAnchor.constraint(equalToConstant: 44), - dismissButton.widthAnchor.constraint(equalToConstant: 120) - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Sign/SelectChain/SelectChainView.swift b/Example/DApp/Sign/SelectChain/SelectChainView.swift deleted file mode 100644 index 934bfe45c..000000000 --- a/Example/DApp/Sign/SelectChain/SelectChainView.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation -import UIKit - -class SelectChainView: UIView { - - let tableView: UITableView = { - let tableView = UITableView(frame: .zero, style: .insetGrouped) - tableView.backgroundColor = .tertiarySystemBackground - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "chain") - return tableView - }() - - let connectButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Connect", for: .normal) - button.backgroundColor = .systemBlue - button.tintColor = .white - button.layer.cornerRadius = 8 - return button - }() - - let openWallet: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Open Wallet", for: .normal) - button.backgroundColor = .systemFill - button.tintColor = .white - button.layer.cornerRadius = 8 - return button - }() - - override init(frame: CGRect) { - super.init(frame: frame) - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "chain_cell") - backgroundColor = .systemBackground - addSubview(tableView) - addSubview(connectButton) - addSubview(openWallet) - - subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16), - tableView.leadingAnchor.constraint(equalTo: leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), - - connectButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), - connectButton.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), - connectButton.heightAnchor.constraint(equalToConstant: 44), - connectButton.widthAnchor.constraint(equalToConstant: 120), - - openWallet.bottomAnchor.constraint(equalTo: connectButton.topAnchor, constant: -16), - openWallet.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), - openWallet.heightAnchor.constraint(equalToConstant: 44), - openWallet.widthAnchor.constraint(equalToConstant: 120) - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift deleted file mode 100644 index bd577d639..000000000 --- a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift +++ /dev/null @@ -1,108 +0,0 @@ -import Foundation -import WalletConnectModal -import UIKit -import Combine - -struct Chain { - let name: String - let id: String -} - -class SelectChainViewController: UIViewController, UITableViewDataSource { - private let selectChainView: SelectChainView = { - SelectChainView() - }() - private var publishers = [AnyCancellable]() - - let chains = [ - Chain(name: "Ethereum", id: "eip155:1"), - Chain(name: "Polygon", id: "eip155:137"), - Chain(name: "Solana", id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ") - ] - - override func viewDidLoad() { - super.viewDidLoad() - navigationItem.title = "Available Chains" - selectChainView.tableView.dataSource = self - selectChainView.connectButton.addTarget(self, action: #selector(connect), for: .touchUpInside) - selectChainView.openWallet.addTarget(self, action: #selector(openWallet), for: .touchUpInside) - } - - override func loadView() { - view = selectChainView - } - - @objc - private func connect() { - print("[PROPOSER] Connecting to a pairing...") - let namespaces: [String: ProposalNamespace] = [ - "eip155": ProposalNamespace( - chains: [ - Blockchain("eip155:137")! - ], - methods: [ - "eth_sendTransaction", - "personal_sign", - "eth_signTypedData" - ], events: [] - ), - "eip155:1": ProposalNamespace( - methods: [ - "eth_sendTransaction", - "personal_sign", - "eth_signTypedData" - ], - events: [] - ) - ] - let optionalNamespaces: [String: ProposalNamespace] = [ - "solana": ProposalNamespace( - chains: [ - Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! - ], - methods: [ - "solana_signMessage", - "solana_signTransaction" - ], events: [] - ) - ] - let sessionProperties: [String: String] = [ - "caip154-mandatory": "true" - ] - - Task { - WalletConnectModal.set(sessionParams: .init( - requiredNamespaces: namespaces, - optionalNamespaces: optionalNamespaces, - sessionProperties: sessionProperties - )) - } - - WalletConnectModal.present(from: self) - } - - @objc - private func openWallet() { - UIApplication.shared.open(URL(string: "walletapp://")!) - } - - private func showConnectScreen(uri: WalletConnectURI) { - DispatchQueue.main.async { [unowned self] in - let vc = UINavigationController(rootViewController: ConnectViewController(uri: uri)) - present(vc, animated: true, completion: nil) - } - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - chains.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "chain_cell", for: indexPath) - let chain = chains[indexPath.row] - cell.textLabel?.text = chain.name - cell.imageView?.image = UIImage(named: chain.id) - cell.selectionStyle = .none - return cell - } -} diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift deleted file mode 100644 index 23e0a1011..000000000 --- a/Example/DApp/Sign/SignCoordinator.swift +++ /dev/null @@ -1,76 +0,0 @@ -import UIKit -import Combine -import WalletConnectSign -import WalletConnectRelay -import WalletConnectPairing - -final class SignCoordinator { - - private var publishers = Set() - - let navigationController = UINavigationController() - - lazy var tabBarItem: UITabBarItem = { - let item = UITabBarItem() - item.title = "Sign" - item.image = UIImage(systemName: "signature") - return item - }() - - func start() { - navigationController.tabBarItem = tabBarItem - - let metadata = AppMetadata( - name: "Swift Dapp", - description: "WalletConnect DApp sample", - url: "wallet.connect", - icons: ["https://avatars.githubusercontent.com/u/37784886"], - redirect: AppMetadata.Redirect(native: "dapp://", universal: nil) - ) - - Pair.configure(metadata: metadata) -#if DEBUG - if CommandLine.arguments.contains("-cleanInstall") { - try? Sign.instance.cleanup() - } -#endif - - Sign.instance.sessionDeletePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] _ in - showSelectChainScreen() - }.store(in: &publishers) - - Sign.instance.sessionResponsePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] response in - presentResponse(for: response) - }.store(in: &publishers) - - if let session = Sign.instance.getSessions().first { - _ = showAccountsScreen(session) - } else { - showSelectChainScreen() - } - } - - private func showSelectChainScreen() { - let controller = SelectChainViewController() - navigationController.viewControllers = [controller] - } - - private func showAccountsScreen(_ session: Session) -> AccountsViewController { - let controller = AccountsViewController(session: session) - controller.onDisconnect = { [unowned self] in - showSelectChainScreen() - } - navigationController.presentedViewController?.dismiss(animated: false) - navigationController.viewControllers = [controller] - return controller - } - - private func presentResponse(for response: Response) { - let controller = UINavigationController(rootViewController: ResponseViewController(response: response)) - navigationController.present(controller, animated: true, completion: nil) - } -} diff --git a/Example/EchoUITests/Engine/DAppEngine.swift b/Example/EchoUITests/Engine/DAppEngine.swift index 5711ccce8..48c3c2205 100644 --- a/Example/EchoUITests/Engine/DAppEngine.swift +++ b/Example/EchoUITests/Engine/DAppEngine.swift @@ -10,17 +10,17 @@ struct DAppEngine { // Main screen var connectButton: XCUIElement { - instance.buttons["Connect"] + instance.buttons["Connect with Sign API"] } // Accounts screen var accountRow: XCUIElement { - instance.tables.cells.containing("0x").firstMatch + instance.buttons.containing("0x").firstMatch } var methodRow: XCUIElement { - instance.tables.cells.firstMatch + instance.firstMatch.buttons.element(matching: .button, identifier: "method-0") } // Pairing screen @@ -30,6 +30,6 @@ struct DAppEngine { } var copyURIButton: XCUIElement { - instance.buttons["Copy"] + instance.buttons["Copy link"] } } diff --git a/Example/EchoUITests/Engine/WalletEngine.swift b/Example/EchoUITests/Engine/WalletEngine.swift index c45d327a8..a5d5a904f 100644 --- a/Example/EchoUITests/Engine/WalletEngine.swift +++ b/Example/EchoUITests/Engine/WalletEngine.swift @@ -10,13 +10,17 @@ struct WalletEngine { // Onboarding var getStartedButton: XCUIElement { - instance.buttons["Get Started"] + instance.buttons["Create new account"] } // MainScreen - var pasteURIButton: XCUIElement { - instance.buttons["copy"] + var allow: XCUIElement { + instance.buttons["Allow"] + } + + var copyURIButton: XCUIElement { + instance.firstMatch.buttons.element(matching: .button, identifier: "copy") } var alertUriTextField: XCUIElement { diff --git a/Example/EchoUITests/Extensions/XCTestCase.swift b/Example/EchoUITests/Extensions/XCTestCase.swift index f2698a34b..770242148 100644 --- a/Example/EchoUITests/Extensions/XCTestCase.swift +++ b/Example/EchoUITests/Extensions/XCTestCase.swift @@ -4,15 +4,19 @@ import XCTest extension XCTestCase { func allowPushNotificationsIfNeeded(app: XCUIApplication) { + /** iOS 17 bug: https://developer.apple.com/forums/thread/737880 let pnPermission = addUIInterruptionMonitor(withDescription: "Push Notification Monitor") { alerts -> Bool in if alerts.buttons["Allow"].exists { alerts.buttons["Allow"].tap() } - return true } app.swipeUp() self.removeUIInterruptionMonitor(pnPermission) + */ + + let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") + springboard.buttons["Allow"].tap() } } diff --git a/Example/EchoUITests/Tests/PushNotificationTests.swift b/Example/EchoUITests/Tests/PushNotificationTests.swift index c9db0aeca..7d4c31ffb 100644 --- a/Example/EchoUITests/Tests/PushNotificationTests.swift +++ b/Example/EchoUITests/Tests/PushNotificationTests.swift @@ -12,12 +12,10 @@ class PushNotificationTests: XCTestCase { } func testPushNotification() { - // Initiate connection & copy URI from dApp engine.routing.activate(app: .dapp) engine.dapp.connectButton.wait(until: \.exists).tap() - engine.dapp.newPairingButton.wait(until: \.exists).tap() - + // Relies on existence of invisible label with uri in Dapp let uri = engine.dapp.instance.staticTexts.containing("wc:").firstMatch.label @@ -26,10 +24,10 @@ class PushNotificationTests: XCTestCase { // Paste URI into Wallet & and allow connect engine.routing.activate(app: .wallet) - allowPushNotificationsIfNeeded(app: engine.wallet.instance) - engine.wallet.getStartedButton.wait(until: \.exists).tap() - engine.wallet.pasteURIButton.wait(until: \.exists).tap() + + allowPushNotificationsIfNeeded(app: engine.wallet.instance) + engine.wallet.copyURIButton.wait(until: \.exists).tap() engine.wallet.alertUriTextField.wait(until: \.exists).tap() engine.wallet.alertUriTextField.typeText(uri) @@ -47,9 +45,10 @@ class PushNotificationTests: XCTestCase { engine.routing.activate(app: .springboard) // Assert notification + let notification = engine.routing.springboard.otherElements.descendants(matching: .any)["NotificationShortLookView"] notification - .wait(until: \.exists, timeout: 15) + .wait(until: \.exists, timeout: 60) .tap() engine.wallet.instance.wait(until: \.exists) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 5f3a5bfe7..4f5a2101c 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; }; 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7A2A9BA206007EBAC2 /* Mixpanel */; }; 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7C2A9BA328007EBAC2 /* Mixpanel */; }; - 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */; }; 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 */; }; @@ -46,15 +45,6 @@ 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE642027981DED00142511 /* SceneDelegate.swift */; }; 84CE642827981DF000142511 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642727981DF000142511 /* Assets.xcassets */; }; 84CE642B27981DF000142511 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642927981DF000142511 /* LaunchScreen.storyboard */; }; - 84CE6430279820F600142511 /* AccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761C649926FB7ABB004239D1 /* AccountsViewController.swift */; }; - 84CE6431279820F600142511 /* AccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7603D74C2703429A00DD27A2 /* AccountsView.swift */; }; - 84CE643D2798322600142511 /* ConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE643C2798322600142511 /* ConnectViewController.swift */; }; - 84CE6444279AB5AD00142511 /* SelectChainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6443279AB5AD00142511 /* SelectChainViewController.swift */; }; - 84CE6448279AE68600142511 /* AccountRequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6447279AE68600142511 /* AccountRequestViewController.swift */; }; - 84CE644B279EA1FA00142511 /* AccountRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644A279EA1FA00142511 /* AccountRequestView.swift */; }; - 84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644D279ED2FF00142511 /* SelectChainView.swift */; }; - 84CE6452279ED42B00142511 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6451279ED42B00142511 /* ConnectView.swift */; }; - 84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; }; 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; }; 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; }; 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */; }; @@ -63,7 +53,8 @@ 84E6B84E29787A8000428BAF /* PNDecryptionService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A507BE1A29E8032E0038EF70 /* EIP55Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507BE1929E8032E0038EF70 /* EIP55Tests.swift */; }; - A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; + A50B6A362B06683800162B01 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE25D293F56D6004840D1 /* InputConfig.swift */; }; + A50B6A382B06697B00162B01 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B6A372B06697B00162B01 /* ProfilingService.swift */; }; A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BC2ABA055700A4FD8B /* NotifyPreferencesModule.swift */; }; A50D53C22ABA055700A4FD8B /* NotifyPreferencesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BD2ABA055700A4FD8B /* NotifyPreferencesPresenter.swift */; }; A50D53C32ABA055700A4FD8B /* NotifyPreferencesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */; }; @@ -169,6 +160,7 @@ A5A0843F29D2F625000B9B17 /* DefaultCryptoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A0843B29D2F60A000B9B17 /* DefaultCryptoProvider.swift */; }; A5A0844029D2F626000B9B17 /* DefaultCryptoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A0843B29D2F60A000B9B17 /* DefaultCryptoProvider.swift */; }; A5A4FC772840C12C00BBEC1E /* RegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC762840C12C00BBEC1E /* RegressionTests.swift */; }; + A5A650CA2B062A1400F9AD4B /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = A5A650C92B062A1400F9AD4B /* Mixpanel */; }; A5A8E47A293A1C9B00FEB97D /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; A5A8E47D293A1CFE00FEB97D /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; }; @@ -184,11 +176,7 @@ A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; }; A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; }; A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */; }; - A5BB7F9F28B69B7100707FC6 /* SignCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */; }; - A5BB7FA128B69F3400707FC6 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */; }; A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */; }; - A5BB7FA728B6A5F600707FC6 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FA628B6A5F600707FC6 /* AuthView.swift */; }; - A5BB7FA928B6A5FD00707FC6 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FA828B6A5FD00707FC6 /* AuthViewModel.swift */; }; A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */; }; A5C2020B287D9DEE007E3188 /* WelcomeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C20206287D9DEE007E3188 /* WelcomeModule.swift */; }; A5C2020C287D9DEE007E3188 /* WelcomePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C20207287D9DEE007E3188 /* WelcomePresenter.swift */; }; @@ -287,6 +275,8 @@ C56EE28E293F5757004840D1 /* ApplicationConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE284293F5757004840D1 /* ApplicationConfigurator.swift */; }; C56EE28F293F5757004840D1 /* MigrationConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE283293F5757004840D1 /* MigrationConfigurator.swift */; }; C56EE2A3293F6BAF004840D1 /* UIPasteboardWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE2A2293F6BAF004840D1 /* UIPasteboardWrapper.swift */; }; + C579FEB62AFA86CD008855EB /* Web3Modal in Frameworks */ = {isa = PBXBuildFile; productRef = C579FEB52AFA86CD008855EB /* Web3Modal */; }; + C579FEBA2AFCDFA6008855EB /* ConnectedSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C579FEB92AFCDFA6008855EB /* ConnectedSheetView.swift */; }; C58099352A543CD000AB58F5 /* BlinkAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58099342A543CD000AB58F5 /* BlinkAnimation.swift */; }; C5B2F6F629705293000DBA0E /* SessionRequestModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B2F6F12970511B000DBA0E /* SessionRequestModule.swift */; }; C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B2F6F32970511B000DBA0E /* SessionRequestRouter.swift */; }; @@ -298,6 +288,40 @@ C5B2F6FD297055B0000DBA0E /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; 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 */; }; + C5BE01E42AF697100064FC88 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE26B293F56D6004840D1 /* UIColor.swift */; }; + C5BE01E52AF697470064FC88 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE268293F56D6004840D1 /* Color.swift */; }; + C5BE01E62AF697FA0064FC88 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE26A293F56D6004840D1 /* String.swift */; }; + C5BE01F72AF6CB250064FC88 /* SignModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01EE2AF6C9DF0064FC88 /* SignModule.swift */; }; + C5BE01F82AF6CB270064FC88 /* SignInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01EF2AF6C9DF0064FC88 /* SignInteractor.swift */; }; + C5BE01FA2AF6CB270064FC88 /* SignPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01ED2AF6C9DF0064FC88 /* SignPresenter.swift */; }; + C5BE01FB2AF6CB270064FC88 /* SignRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01F12AF6C9DF0064FC88 /* SignRouter.swift */; }; + C5BE02002AF774CB0064FC88 /* NewPairingRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01F32AF6CA2B0064FC88 /* NewPairingRouter.swift */; }; + C5BE02012AF774CB0064FC88 /* NewPairingModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE01F52AF6CA2B0064FC88 /* NewPairingModule.swift */; }; + 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 */; }; + C5BE021D2AF79B9A0064FC88 /* SessionAccountInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02182AF79B950064FC88 /* SessionAccountInteractor.swift */; }; + C5BE021E2AF79B9A0064FC88 /* SessionAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE02192AF79B950064FC88 /* SessionAccountView.swift */; }; + C5BE021F2AF79B9A0064FC88 /* SessionAccountModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BE021A2AF79B960064FC88 /* SessionAccountModule.swift */; }; C5D4603A29687A5700302C7E /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */ = {isa = PBXBuildFile; productRef = C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */; }; C5F32A2C2954814200A6476E /* ConnectionDetailsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F32A2B2954814200A6476E /* ConnectionDetailsModule.swift */; }; @@ -321,7 +345,6 @@ CF1A594C29E5876600AAC16B /* RoutingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF1A594329E5876600AAC16B /* RoutingEngine.swift */; }; CF1A594D29E5876600AAC16B /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF1A594429E5876600AAC16B /* App.swift */; }; CF25F2892A432476009C7E49 /* WalletConnectModal in Frameworks */ = {isa = PBXBuildFile; productRef = CF25F2882A432476009C7E49 /* WalletConnectModal */; }; - CF25F28B2A432488009C7E49 /* WalletConnectModal in Frameworks */ = {isa = PBXBuildFile; productRef = CF25F28A2A432488009C7E49 /* WalletConnectModal */; }; CF6704DF29E59DDC003326A4 /* XCUIElementQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704DE29E59DDC003326A4 /* XCUIElementQuery.swift */; }; CF6704E129E5A014003326A4 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704E029E5A014003326A4 /* XCTestCase.swift */; }; /* End PBXBuildFile section */ @@ -367,7 +390,7 @@ /* Begin PBXCopyFilesBuildPhase section */ 84E6B85229787A8000428BAF /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 13; files = ( @@ -379,8 +402,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 7603D74C2703429A00DD27A2 /* AccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsView.swift; sourceTree = ""; }; - 761C649926FB7ABB004239D1 /* AccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsViewController.swift; sourceTree = ""; }; 764E1D5426F8DAC800A1FB15 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; 764E1D5526F8DADE00A1FB15 /* WalletConnectSwiftV2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WalletConnectSwiftV2; path = ..; sourceTree = ""; }; 764E1D5626F8DB6000A1FB15 /* WalletConnectSwiftV2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WalletConnectSwiftV2; path = ..; sourceTree = ""; }; @@ -407,7 +428,6 @@ 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPlatformW3WTests.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 = ""; }; - 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilingService.swift; sourceTree = ""; }; 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; 849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionServiceRelease.entitlements; sourceTree = ""; }; 849D7A92292E2169006A2BD4 /* NotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyTests.swift; sourceTree = ""; }; @@ -421,14 +441,7 @@ 84CE642727981DF000142511 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 84CE642A27981DF000142511 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 84CE642C27981DF000142511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 84CE643C2798322600142511 /* ConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewController.swift; sourceTree = ""; }; - 84CE6443279AB5AD00142511 /* SelectChainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectChainViewController.swift; sourceTree = ""; }; - 84CE6447279AE68600142511 /* AccountRequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRequestViewController.swift; sourceTree = ""; }; - 84CE644A279EA1FA00142511 /* AccountRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRequestView.swift; sourceTree = ""; }; - 84CE644D279ED2FF00142511 /* SelectChainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectChainView.swift; sourceTree = ""; }; - 84CE6451279ED42B00142511 /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; }; 84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = ""; }; - 84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = ""; }; 84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; }; 84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = ""; }; 84DB38F029828A7C00BFEE37 /* WalletApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletApp.entitlements; sourceTree = ""; }; @@ -441,7 +454,7 @@ 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; A507BE1929E8032E0038EF70 /* EIP55Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EIP55Tests.swift; sourceTree = ""; }; - A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; + A50B6A372B06697B00162B01 /* ProfilingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfilingService.swift; sourceTree = ""; }; A50D53BC2ABA055700A4FD8B /* NotifyPreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesModule.swift; sourceTree = ""; }; A50D53BD2ABA055700A4FD8B /* NotifyPreferencesPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesPresenter.swift; sourceTree = ""; }; A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesRouter.swift; sourceTree = ""; }; @@ -537,10 +550,6 @@ A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRouter.swift; sourceTree = ""; }; A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; A5B4F7C72ABB21190099AF7C /* CacheAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheAsyncImage.swift; sourceTree = ""; }; - A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignCoordinator.swift; sourceTree = ""; }; - A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; - A5BB7FA628B6A5F600707FC6 /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; - A5BB7FA828B6A5FD00707FC6 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = ""; }; A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = ""; }; A5C20206287D9DEE007E3188 /* WelcomeModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeModule.swift; sourceTree = ""; }; A5C20207287D9DEE007E3188 /* WelcomePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePresenter.swift; sourceTree = ""; }; @@ -630,12 +639,40 @@ C56EE287293F5757004840D1 /* AppearanceConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceConfigurator.swift; sourceTree = ""; }; C56EE29F293F5C4F004840D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C56EE2A2293F6BAF004840D1 /* UIPasteboardWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPasteboardWrapper.swift; sourceTree = ""; }; + C579FEB92AFCDFA6008855EB /* ConnectedSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedSheetView.swift; sourceTree = ""; }; C58099342A543CD000AB58F5 /* BlinkAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlinkAnimation.swift; sourceTree = ""; }; C5B2F6F12970511B000DBA0E /* SessionRequestModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRequestModule.swift; sourceTree = ""; }; C5B2F6F22970511B000DBA0E /* SessionRequestPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRequestPresenter.swift; sourceTree = ""; }; C5B2F6F32970511B000DBA0E /* SessionRequestRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRequestRouter.swift; sourceTree = ""; }; 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 = ""; }; + C5BE01EF2AF6C9DF0064FC88 /* SignInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInteractor.swift; sourceTree = ""; }; + C5BE01F12AF6C9DF0064FC88 /* SignRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignRouter.swift; sourceTree = ""; }; + C5BE01F32AF6CA2B0064FC88 /* NewPairingRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPairingRouter.swift; sourceTree = ""; }; + 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 = ""; }; + C5BE02192AF79B950064FC88 /* SessionAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAccountView.swift; sourceTree = ""; }; + C5BE021A2AF79B960064FC88 /* SessionAccountModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAccountModule.swift; sourceTree = ""; }; C5F32A2B2954814200A6476E /* ConnectionDetailsModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionDetailsModule.swift; sourceTree = ""; }; C5F32A2D2954814A00A6476E /* ConnectionDetailsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionDetailsRouter.swift; sourceTree = ""; }; C5F32A2F2954816100A6476E /* ConnectionDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionDetailsInteractor.swift; sourceTree = ""; }; @@ -678,7 +715,8 @@ buildActionMask = 2147483647; files = ( 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */, - CF25F28B2A432488009C7E49 /* WalletConnectModal in Frameworks */, + C579FEB62AFA86CD008855EB /* Web3Modal in Frameworks */, + C5BE01DF2AF692D80064FC88 /* WalletConnectRouter in Frameworks */, A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */, A54195A52934E83F0035AD19 /* Web3 in Frameworks */, 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */, @@ -693,6 +731,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A5A650CA2B062A1400F9AD4B /* Mixpanel in Frameworks */, A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -818,7 +857,6 @@ 767DC83328997F7600080FA9 /* Helpers */ = { isa = PBXGroup; children = ( - A50C036428AAD32200FE72D3 /* ClientDelegate.swift */, 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */, 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */, ); @@ -934,11 +972,11 @@ 84CE641D27981DED00142511 /* DApp */ = { isa = PBXGroup; children = ( + C5BE01E02AF692F80064FC88 /* ApplicationLayer */, + C5BE02202AF7DDE70064FC88 /* Modules */, A5BB7FAB28B6AA7100707FC6 /* Common */, 84CE641E27981DED00142511 /* AppDelegate.swift */, 84CE642027981DED00142511 /* SceneDelegate.swift */, - A5BB7FAA28B6A64A00707FC6 /* Sign */, - A5BB7FA528B6A5DC00707FC6 /* Auth */, 84CE642727981DF000142511 /* Assets.xcassets */, 84CE642927981DF000142511 /* LaunchScreen.storyboard */, 84CE642C27981DF000142511 /* Info.plist */, @@ -946,42 +984,6 @@ path = DApp; sourceTree = ""; }; - 84CE6449279EA1E600142511 /* AccountRequest */ = { - isa = PBXGroup; - children = ( - 84CE6447279AE68600142511 /* AccountRequestViewController.swift */, - 84CE644A279EA1FA00142511 /* AccountRequestView.swift */, - ); - path = AccountRequest; - sourceTree = ""; - }; - 84CE644C279ED2EC00142511 /* SelectChain */ = { - isa = PBXGroup; - children = ( - 84CE6443279AB5AD00142511 /* SelectChainViewController.swift */, - 84CE644D279ED2FF00142511 /* SelectChainView.swift */, - ); - path = SelectChain; - sourceTree = ""; - }; - 84CE644F279ED3FB00142511 /* Accounts */ = { - isa = PBXGroup; - children = ( - 761C649926FB7ABB004239D1 /* AccountsViewController.swift */, - 7603D74C2703429A00DD27A2 /* AccountsView.swift */, - ); - path = Accounts; - sourceTree = ""; - }; - 84CE6450279ED41D00142511 /* Connect */ = { - isa = PBXGroup; - children = ( - 84CE643C2798322600142511 /* ConnectViewController.swift */, - 84CE6451279ED42B00142511 /* ConnectView.swift */, - ); - path = Connect; - sourceTree = ""; - }; 84CEC64728D8A98900D081A8 /* Pairing */ = { isa = PBXGroup; children = ( @@ -1406,33 +1408,11 @@ A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */, A5417BBD299BFC3E00B469F3 /* ImportAccount.swift */, A5C20228287EB34C007E3188 /* AccountStorage.swift */, + C56EE25D293F56D6004840D1 /* InputConfig.swift */, ); path = Shared; sourceTree = ""; }; - A5BB7FA528B6A5DC00707FC6 /* Auth */ = { - isa = PBXGroup; - children = ( - A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */, - A5BB7FA628B6A5F600707FC6 /* AuthView.swift */, - A5BB7FA828B6A5FD00707FC6 /* AuthViewModel.swift */, - ); - path = Auth; - sourceTree = ""; - }; - A5BB7FAA28B6A64A00707FC6 /* Sign */ = { - isa = PBXGroup; - children = ( - 84CE645427A29D4C00142511 /* ResponseViewController.swift */, - 84CE6449279EA1E600142511 /* AccountRequest */, - 84CE644C279ED2EC00142511 /* SelectChain */, - 84CE6450279ED41D00142511 /* Connect */, - 84CE644F279ED3FB00142511 /* Accounts */, - A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */, - ); - path = Sign; - sourceTree = ""; - }; A5BB7FAB28B6AA7100707FC6 /* Common */ = { isa = PBXGroup; children = ( @@ -1678,7 +1658,7 @@ C56EE25C293F56D6004840D1 /* Common */ = { isa = PBXGroup; children = ( - C56EE25D293F56D6004840D1 /* InputConfig.swift */, + C579FEB82AFCDF83008855EB /* Views */, 84B8154D2991099000FAD54E /* BuildConfiguration.swift */, C56EE267293F56D6004840D1 /* Style */, C56EE2A1293F6B9E004840D1 /* Helpers */, @@ -1741,10 +1721,10 @@ C56EE280293F5757004840D1 /* Application.swift */, C56EE27F293F5757004840D1 /* AppDelegate.swift */, C56EE281293F5757004840D1 /* SceneDelegate.swift */, + A50B6A372B06697B00162B01 /* ProfilingService.swift */, 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */, A51811972A52E21A00A52B15 /* ConfigurationService.swift */, 8487A9472A83AD680003D5AF /* LoggingService.swift */, - 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */, ); path = ApplicationLayer; sourceTree = ""; @@ -1792,6 +1772,14 @@ path = Helpers; sourceTree = ""; }; + C579FEB82AFCDF83008855EB /* Views */ = { + isa = PBXGroup; + children = ( + C579FEB92AFCDFA6008855EB /* ConnectedSheetView.swift */, + ); + path = Views; + sourceTree = ""; + }; C5B2F6F029705111000DBA0E /* SessionRequest */ = { isa = PBXGroup; children = ( @@ -1804,6 +1792,95 @@ path = SessionRequest; sourceTree = ""; }; + C5B4C4C52AF12C2900B4274A /* Sign */ = { + isa = PBXGroup; + children = ( + C5BE01EB2AF6C9BF0064FC88 /* NewPairing */, + C5BE02152AF79B860064FC88 /* SessionAccount */, + C5BE01EE2AF6C9DF0064FC88 /* SignModule.swift */, + C5BE01ED2AF6C9DF0064FC88 /* SignPresenter.swift */, + C5BE01F12AF6C9DF0064FC88 /* SignRouter.swift */, + C5BE01EF2AF6C9DF0064FC88 /* SignInteractor.swift */, + C5B4C4C32AF11C8B00B4274A /* SignView.swift */, + ); + 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 = ( + C5BE01E12AF693080064FC88 /* Application.swift */, + ); + path = ApplicationLayer; + sourceTree = ""; + }; + C5BE01EB2AF6C9BF0064FC88 /* NewPairing */ = { + isa = PBXGroup; + children = ( + C5BE01F52AF6CA2B0064FC88 /* NewPairingModule.swift */, + C5BE01F42AF6CA2B0064FC88 /* NewPairingPresenter.swift */, + C5BE01F32AF6CA2B0064FC88 /* NewPairingRouter.swift */, + C5BE01F62AF6CA2B0064FC88 /* NewPairingInteractor.swift */, + C5BE01D02AF661D70064FC88 /* NewPairingView.swift */, + ); + 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 = ( + C5BE021A2AF79B960064FC88 /* SessionAccountModule.swift */, + C5BE02172AF79B950064FC88 /* SessionAccountPresenter.swift */, + C5BE02162AF79B950064FC88 /* SessionAccountRouter.swift */, + C5BE02182AF79B950064FC88 /* SessionAccountInteractor.swift */, + C5BE02192AF79B950064FC88 /* SessionAccountView.swift */, + ); + path = SessionAccount; + sourceTree = ""; + }; + C5BE02202AF7DDE70064FC88 /* Modules */ = { + isa = PBXGroup; + children = ( + C5BE02062AF777AD0064FC88 /* Main */, + C5B4C4C52AF12C2900B4274A /* Sign */, + C5B4C4CD2AF12F0B00B4274A /* Auth */, + ); + path = Modules; + sourceTree = ""; + }; C5F32A2A2954812900A6476E /* ConnectionDetails */ = { isa = PBXGroup; children = ( @@ -1929,10 +2006,11 @@ A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */, A54195A42934E83F0035AD19 /* Web3 */, A573C53829EC365000E3CBFD /* HDWalletKit */, - CF25F28A2A432488009C7E49 /* WalletConnectModal */, 8487A9432A836C2A0003D5AF /* Sentry */, A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */, 84943C7A2A9BA206007EBAC2 /* Mixpanel */, + C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */, + C579FEB52AFA86CD008855EB /* Web3Modal */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; @@ -1953,6 +2031,7 @@ name = PNDecryptionService; packageProductDependencies = ( A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */, + A5A650C92B062A1400F9AD4B /* Mixpanel */, ); productName = PNDecryptionService; productReference = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; @@ -2168,6 +2247,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C5BE02142AF77A940064FC88 /* Colors.xcassets in Resources */, 84CE642B27981DF000142511 /* LaunchScreen.storyboard in Resources */, 84CE642827981DF000142511 /* Assets.xcassets in Resources */, ); @@ -2237,27 +2317,46 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A5BB7FA728B6A5F600707FC6 /* AuthView.swift in Sources */, - 84CE6430279820F600142511 /* AccountsViewController.swift in Sources */, - A5BB7FA128B69F3400707FC6 /* AuthCoordinator.swift in Sources */, - 84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */, + 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 */, + 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 */, - A5BB7FA928B6A5FD00707FC6 /* AuthViewModel.swift in Sources */, - 84CE6452279ED42B00142511 /* ConnectView.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 */, - 84CE6448279AE68600142511 /* AccountRequestViewController.swift in Sources */, - A5BB7F9F28B69B7100707FC6 /* SignCoordinator.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 */, + C5BE01D12AF661D70064FC88 /* NewPairingView.swift in Sources */, 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */, - 84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */, - 84CE644B279EA1FA00142511 /* AccountRequestView.swift in Sources */, - 84CE6431279820F600142511 /* AccountsView.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 */, - 84CE643D2798322600142511 /* ConnectViewController.swift in Sources */, - 84CE6444279AB5AD00142511 /* SelectChainViewController.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; }; @@ -2266,6 +2365,7 @@ buildActionMask = 2147483647; files = ( 84E6B84A29787A8000428BAF /* NotificationService.swift in Sources */, + A50B6A362B06683800162B01 /* InputConfig.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2386,7 +2486,6 @@ 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, 7694A5262874296A0001257E /* RegistryTests.swift in Sources */, A541959F2934BFEF0035AD19 /* SignerTests.swift in Sources */, - A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */, A5321C2B2A250367006CADC3 /* HistoryTests.swift in Sources */, A58A1ECC29BF458600A82A20 /* ENSResolverTests.swift in Sources */, A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */, @@ -2460,6 +2559,7 @@ A51811A12A52E83100A52B15 /* SettingsRouter.swift in Sources */, C56EE279293F56D7004840D1 /* Color.swift in Sources */, 847BD1E6298A806800076C90 /* NotificationsRouter.swift in Sources */, + C579FEBA2AFCDFA6008855EB /* ConnectedSheetView.swift in Sources */, C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */, C56EE243293F566D004840D1 /* ScanView.swift in Sources */, 84310D05298BC980000C15B6 /* MainInteractor.swift in Sources */, @@ -2498,13 +2598,13 @@ A5D610CA2AB3249100C20083 /* ListingViewModel.swift in Sources */, 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */, A5D610CE2AB3594100C20083 /* ListingsAPI.swift in Sources */, - 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */, C5B2F6FB297055B0000DBA0E /* ETHSigner.swift in Sources */, C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */, A5D610D42AB35BED00C20083 /* FailableDecodable.swift in Sources */, A56AC8F22AD88A5A001C8FAA /* Sequence.swift in Sources */, 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */, + A50B6A382B06697B00162B01 /* ProfilingService.swift in Sources */, A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, @@ -2762,11 +2862,12 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 18; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = dApp; + INFOPLIST_KEY_NSCameraUsageDescription = "Enable camera access to be able to scan QR codes."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7; @@ -2796,11 +2897,12 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 18; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = dApp; + INFOPLIST_KEY_NSCameraUsageDescription = "Enable camera access to be able to scan QR codes."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7; @@ -3290,8 +3392,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/WalletConnect/web3modal-swift"; requirement = { - branch = develop; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 1.0.9; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -3399,6 +3501,11 @@ package = A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */; productName = Web3; }; + A5A650C92B062A1400F9AD4B /* Mixpanel */ = { + isa = XCSwiftPackageProductDependency; + package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */; + productName = Mixpanel; + }; A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectNotify; @@ -3473,11 +3580,20 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectChat; }; + C579FEB52AFA86CD008855EB /* Web3Modal */ = { + isa = XCSwiftPackageProductDependency; + package = A5F1526D2ACDC46B00D745A6 /* XCRemoteSwiftPackageReference "web3modal-swift" */; + productName = Web3Modal; + }; C5B2F7042970573D000DBA0E /* SolanaSwift */ = { isa = XCSwiftPackageProductDependency; package = A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */; productName = SolanaSwift; }; + C5BE01DE2AF692D80064FC88 /* WalletConnectRouter */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectRouter; + }; C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */ = { isa = XCSwiftPackageProductDependency; productName = Web3Wallet; @@ -3486,10 +3602,6 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectModal; }; - CF25F28A2A432488009C7E49 /* WalletConnectModal */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectModal; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 764E1D3426F8D3FC00A1FB15 /* Project object */; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 600596394..dceba93f5 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -167,9 +167,9 @@ "package": "swift-web3modal", "repositoryURL": "https://github.com/WalletConnect/web3modal-swift", "state": { - "branch": "develop", - "revision": "24602e2acee171fac26aa978b30666deec68a08f", - "version": null + "branch": null, + "revision": "831410cfd6e68afa7212a5547483fb2d180f0fa7", + "version": "1.0.10" } } ] diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme index 66122a584..07e071581 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme @@ -2,7 +2,7 @@ + version = "1.3"> @@ -58,18 +58,25 @@ + allowLocationSimulation = "YES"> + + + + - + + + + + diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index af668e8c5..bbea6aa46 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -63,7 +63,7 @@ final class PairingTests: XCTestCase { appPairingClient = pairingClient appAuthClient = AuthClientFactory.create( - metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "dapp://", universal: nil)), + metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "wcdapp://", universal: nil)), projectId: InputConfig.projectId, crypto: DefaultCryptoProvider(), logger: notifyLogger, diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 18408e9af..108ba3e37 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -95,6 +95,7 @@ final class NotifyTests: XCTestCase { override func setUp() { pk = try! EthereumPrivateKey() walletNotifyClientA = makeWalletClient() + publishers.removeAll() } func testWalletCreatesSubscription() async throws { @@ -235,7 +236,11 @@ final class NotifyTests: XCTestCase { walletNotifyClientA.messagesPublisher .sink { messages in guard let newNotifyMessageRecord = messages.first else { return } - XCTAssertEqual(newNotifyMessageRecord.message, notifyMessage) + // ID's is not equal because server generates a new one + XCTAssertEqual(newNotifyMessageRecord.message.title, notifyMessage.title) + XCTAssertEqual(newNotifyMessageRecord.message.body, notifyMessage.body) + XCTAssertEqual(newNotifyMessageRecord.message.icon, notifyMessage.icon) + XCTAssertEqual(newNotifyMessageRecord.message.type, notifyMessage.type) notifyMessageRecord = newNotifyMessageRecord messageExpectation.fulfill() }.store(in: &publishers) diff --git a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift deleted file mode 100644 index e812661e2..000000000 --- a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Foundation -@testable import WalletConnectSign -import Combine - -class ClientDelegate { - - var client: SignClient - var onSessionSettled: ((Session) -> Void)? - var onConnected: (() -> Void)? - var onSessionProposal: ((Session.Proposal) -> Void)? - var onSessionRequest: ((Request) -> Void)? - var onSessionResponse: ((Response) -> Void)? - var onSessionRejected: ((Session.Proposal, Reason) -> Void)? - var onSessionDelete: (() -> Void)? - var onSessionUpdateNamespaces: ((String, [String: SessionNamespace]) -> Void)? - var onSessionExtend: ((String, Date) -> Void)? - var onPing: ((String) -> Void)? - var onEventReceived: ((Session.Event, String) -> Void)? - - private var publishers = Set() - - init(client: SignClient) { - self.client = client - setupSubscriptions() - } - - private func setupSubscriptions() { - client.sessionSettlePublisher.sink { session in - self.onSessionSettled?(session) - }.store(in: &publishers) - - client.socketConnectionStatusPublisher.sink { _ in - self.onConnected?() - }.store(in: &publishers) - - client.sessionProposalPublisher.sink { result in - self.onSessionProposal?(result.proposal) - }.store(in: &publishers) - - client.sessionRequestPublisher.sink { result in - self.onSessionRequest?(result.request) - }.store(in: &publishers) - - client.sessionResponsePublisher.sink { response in - self.onSessionResponse?(response) - }.store(in: &publishers) - - client.sessionRejectionPublisher.sink { (proposal, reason) in - self.onSessionRejected?(proposal, reason) - }.store(in: &publishers) - - client.sessionDeletePublisher.sink { _ in - self.onSessionDelete?() - }.store(in: &publishers) - - client.sessionUpdatePublisher.sink { (topic, namespaces) in - self.onSessionUpdateNamespaces?(topic, namespaces) - }.store(in: &publishers) - - client.sessionEventPublisher.sink { (event, topic, _) in - self.onEventReceived?(event, topic) - }.store(in: &publishers) - - client.sessionExtendPublisher.sink { (topic, date) in - self.onSessionExtend?(topic, date) - }.store(in: &publishers) - - client.pingResponsePublisher.sink { topic in - self.onPing?(topic) - }.store(in: &publishers) - } -} diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index f6e0a9dd8..4cecd7bb0 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -6,12 +6,17 @@ import JSONRPC @testable import WalletConnectRelay import WalletConnectPairing import WalletConnectNetworking +import Combine final class SignClientTests: XCTestCase { - var dapp: ClientDelegate! - var wallet: ClientDelegate! + var dapp: SignClient! + var dappPairingClient: PairingClient! + var wallet: SignClient! + var walletPairingClient: PairingClient! + private var publishers = Set() - static private func makeClientDelegate(name: String) -> ClientDelegate { + + static private func makeClients(name: String) -> (PairingClient, SignClient) { let logger = ConsoleLogger(prefix: name, loggingLevel: .debug) let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() @@ -48,12 +53,12 @@ final class SignClientTests: XCTestCase { let clientId = try! networkingClient.getClientId() logger.debug("My client id is: \(clientId)") - return ClientDelegate(client: client) + return (pairingClient, client) } override func setUp() async throws { - dapp = Self.makeClientDelegate(name: "🍏P") - wallet = Self.makeClientDelegate(name: "🍎R") + (dappPairingClient, dapp) = Self.makeClients(name: "🍏P") + (walletPairingClient, wallet) = Self.makeClients(name: "🍎R") } override func tearDown() { @@ -67,48 +72,51 @@ final class SignClientTests: XCTestCase { let requiredNamespaces = ProposalNamespace.stubRequired() let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { _ in dappSettlementExpectation.fulfill() - } - wallet.onSessionSettled = { _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { _ in walletSettlementExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } func testSessionReject() async throws { let sessionRejectExpectation = expectation(description: "Proposer is notified on session rejection") + let requiredNamespaces = ProposalNamespace.stubRequired() class Store { var rejectedProposal: Session.Proposal? } let store = Store() - let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired()) - try await wallet.client.pair(uri: uri!) + let uri = try! await dappPairingClient.create() + try await dapp.connect(requiredNamespaces: requiredNamespaces, topic: uri.topic) + try await walletPairingClient.pair(uri: uri) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.reject(proposalId: proposal.id, reason: .userRejectedChains) // TODO: Review reason + try await wallet.reject(proposalId: proposal.id, reason: .userRejectedChains) // TODO: Review reason store.rejectedProposal = proposal } catch { XCTFail("\(error)") } } - } - dapp.onSessionRejected = { proposal, _ in + }.store(in: &publishers) + dapp.sessionRejectionPublisher.sink { proposal, _ in XCTAssertEqual(store.rejectedProposal, proposal) sessionRejectExpectation.fulfill() // TODO: Assert reason code - } + }.store(in: &publishers) wait(for: [sessionRejectExpectation], timeout: InputConfig.defaultTimeout) } @@ -117,22 +125,23 @@ final class SignClientTests: XCTestCase { let requiredNamespaces = ProposalNamespace.stubRequired() let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - do { try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } + do { try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { [unowned self] settledSession in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - try await dapp.client.disconnect(topic: settledSession.topic) + try await dapp.disconnect(topic: settledSession.topic) } - } - wallet.onSessionDelete = { + }.store(in: &publishers) + wallet.sessionDeletePublisher.sink { _ in sessionDeleteExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -142,26 +151,27 @@ final class SignClientTests: XCTestCase { let requiredNamespaces = ProposalNamespace.stubRequired() let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - wallet.onSessionProposal = { proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try! await self.wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try! await self.wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } + }.store(in: &publishers) - dapp.onSessionSettled = { sessionSettled in + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - try! await self.dapp.client.ping(topic: sessionSettled.topic) + try! await dapp.ping(topic: settledSession.topic) } - } + }.store(in: &publishers) - dapp.onPing = { topic in - let session = self.wallet.client.getSessions().first! + dapp.pingResponsePublisher.sink { topic in + let session = self.wallet.getSessions().first! XCTAssertEqual(topic, session.topic) expectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)! - try await wallet.client.pair(uri: uri) + 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) } @@ -177,30 +187,30 @@ final class SignClientTests: XCTestCase { let responseParams = "0xdeadbeef" let chain = Blockchain("eip155:1")! - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { [unowned self] settledSession in + }.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) - try await dapp.client.request(params: request) + try await dapp.request(params: request) } - } - wallet.onSessionRequest = { [unowned self] sessionRequest in + }.store(in: &publishers) + wallet.sessionRequestPublisher.sink { [unowned self] (sessionRequest, _) in let receivedParams = try! sessionRequest.params.get([EthSendTransaction].self) XCTAssertEqual(receivedParams, requestParams) XCTAssertEqual(sessionRequest.method, requestMethod) requestExpectation.fulfill() Task(priority: .high) { - try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(AnyCodable(responseParams))) + try await wallet.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(AnyCodable(responseParams))) } - } - dapp.onSessionResponse = { response in + }.store(in: &publishers) + dapp.sessionResponsePublisher.sink { response in switch response.result { case .response(let response): XCTAssertEqual(try! response.get(String.self), responseParams) @@ -208,10 +218,11 @@ final class SignClientTests: XCTestCase { XCTFail() } responseExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -226,23 +237,23 @@ final class SignClientTests: XCTestCase { let chain = Blockchain("eip155:1")! - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } - dapp.onSessionSettled = { [unowned self] settledSession in + }.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) - try await dapp.client.request(params: request) + try await dapp.request(params: request) } - } - wallet.onSessionRequest = { [unowned self] sessionRequest in + }.store(in: &publishers) + wallet.sessionRequestPublisher.sink { [unowned self] (sessionRequest, _) in Task(priority: .high) { - try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .error(error)) + try await wallet.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .error(error)) } - } - dapp.onSessionResponse = { response in + }.store(in: &publishers) + dapp.sessionResponsePublisher.sink { response in switch response.result { case .response: XCTFail() @@ -250,14 +261,15 @@ final class SignClientTests: XCTestCase { XCTAssertEqual(error, receivedError) } expectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } - func testNewSessionOnExistingPairing() async { + func testNewSessionOnExistingPairing() async throws { let dappSettlementExpectation = expectation(description: "Dapp settles session") dappSettlementExpectation.expectedFulfillmentCount = 2 let walletSettlementExpectation = expectation(description: "Wallet settles session") @@ -266,86 +278,89 @@ final class SignClientTests: XCTestCase { let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) var initiatedSecondSession = false - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { [unowned self] _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { [unowned self] _ in dappSettlementExpectation.fulfill() - let pairingTopic = dapp.client.getPairings().first!.topic + let pairingTopic = dappPairingClient.getPairings().first!.topic if !initiatedSecondSession { Task(priority: .high) { - _ = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic) + _ = try! await dapp.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic) } initiatedSecondSession = true } - } - wallet.onSessionSettled = { _ in + }.store(in: &publishers) + wallet.sessionSettlePublisher.sink { _ in walletSettlementExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) + 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) } - func testSuccessfulSessionUpdateNamespaces() async { + func testSuccessfulSessionUpdateNamespaces() async throws { let expectation = expectation(description: "Dapp updates namespaces") let requiredNamespaces = ProposalNamespace.stubRequired() let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } - dapp.onSessionSettled = { [unowned self] settledSession in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - try! await wallet.client.update(topic: settledSession.topic, namespaces: sessionNamespaces) + try! await wallet.update(topic: settledSession.topic, namespaces: sessionNamespaces) } - } - dapp.onSessionUpdateNamespaces = { _, _ in + }.store(in: &publishers) + dapp.sessionUpdatePublisher.sink { _, _ in expectation.fulfill() - } - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) + }.store(in: &publishers) + 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) } - func testSuccessfulSessionExtend() async { + func testSuccessfulSessionExtend() async throws { let expectation = expectation(description: "Dapp extends session") let requiredNamespaces = ProposalNamespace.stubRequired() let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } + }.store(in: &publishers) - dapp.onSessionExtend = { _, _ in + dapp.sessionExtendPublisher.sink { _, _ in expectation.fulfill() - } + }.store(in: &publishers) - dapp.onSessionSettled = { [unowned self] settledSession in + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - try! await wallet.client.extend(topic: settledSession.topic) + try! await wallet.extend(topic: settledSession.topic) } - } + }.store(in: &publishers) - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) + 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) } - func testSessionEventSucceeds() async { + func testSessionEventSucceeds() async throws { let expectation = expectation(description: "Dapp receives session event") let requiredNamespaces = ProposalNamespace.stubRequired() @@ -353,29 +368,30 @@ final class SignClientTests: XCTestCase { let event = Session.Event(name: "any", data: AnyCodable("event_data")) let chain = Blockchain("eip155:1")! - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } + }.store(in: &publishers) - dapp.onEventReceived = { _, _ in + dapp.sessionEventPublisher.sink { _, _, _ in expectation.fulfill() - } + }.store(in: &publishers) - dapp.onSessionSettled = { [unowned self] settledSession in + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - try! await wallet.client.emit(topic: settledSession.topic, event: event, chainId: chain) + try! await wallet.emit(topic: settledSession.topic, event: event, chainId: chain) } - } + }.store(in: &publishers) - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) + 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) } - func testSessionEventFails() async { + func testSessionEventFails() async throws { let expectation = expectation(description: "Dapp receives session event") let requiredNamespaces = ProposalNamespace.stubRequired() @@ -383,21 +399,22 @@ final class SignClientTests: XCTestCase { let event = Session.Event(name: "unknown", data: AnyCodable("event_data")) let chain = Blockchain("eip155:1")! - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } - } + }.store(in: &publishers) - dapp.onSessionSettled = { [unowned self] settledSession in + dapp.sessionSettlePublisher.sink { [unowned self] settledSession in Task(priority: .high) { - await XCTAssertThrowsErrorAsync(try await wallet.client.emit(topic: settledSession.topic, event: event, chainId: chain)) + await XCTAssertThrowsErrorAsync(try await wallet.emit(topic: settledSession.topic, event: event, chainId: chain)) expectation.fulfill() } - } + }.store(in: &publishers) - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) + 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) } @@ -458,24 +475,25 @@ final class SignClientTests: XCTestCase { ] ) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { settledSession in dappSettlementExpectation.fulfill() - } - wallet.onSessionSettled = { _ in + }.store(in: &publishers) + wallet.sessionSettlePublisher.sink { _ in walletSettlementExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -526,24 +544,25 @@ final class SignClientTests: XCTestCase { ] ) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { [unowned self] _ in dappSettlementExpectation.fulfill() - } - wallet.onSessionSettled = { _ in + }.store(in: &publishers) + wallet.sessionSettlePublisher.sink { _ in walletSettlementExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -584,24 +603,25 @@ final class SignClientTests: XCTestCase { ] ) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } - } - dapp.onSessionSettled = { _ in + }.store(in: &publishers) + dapp.sessionSettlePublisher.sink { _ in dappSettlementExpectation.fulfill() - } - wallet.onSessionSettled = { _ in + }.store(in: &publishers) + wallet.sessionSettlePublisher.sink { _ in walletSettlementExpectation.fulfill() - } + }.store(in: &publishers) - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -649,21 +669,22 @@ final class SignClientTests: XCTestCase { ] ) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { settlementFailedExpectation.fulfill() } } - } + }.store(in: &publishers) } catch { settlementFailedExpectation.fulfill() } - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } @@ -714,21 +735,22 @@ final class SignClientTests: XCTestCase { ] ) - wallet.onSessionProposal = { [unowned self] proposal in + wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { settlementFailedExpectation.fulfill() } } - } + }.store(in: &publishers) } catch { settlementFailedExpectation.fulfill() } - let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces, optionalNamespaces: optionalNamespaces) - try await wallet.client.pair(uri: uri!) + 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) } } diff --git a/Example/IntegrationTests/Stubs/PushMessage.swift b/Example/IntegrationTests/Stubs/PushMessage.swift index 18387013b..634d78ea1 100644 --- a/Example/IntegrationTests/Stubs/PushMessage.swift +++ b/Example/IntegrationTests/Stubs/PushMessage.swift @@ -4,6 +4,7 @@ import WalletConnectNotify extension NotifyMessage { static func stub(type: String) -> NotifyMessage { return NotifyMessage( + id: UUID().uuidString, title: "swift_test", body: "body", icon: "https://images.unsplash.com/photo-1581224463294-908316338239?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80", diff --git a/Example/IntegrationTests/Stubs/Stubs.swift b/Example/IntegrationTests/Stubs/Stubs.swift index 555206db2..366d8970f 100644 --- a/Example/IntegrationTests/Stubs/Stubs.swift +++ b/Example/IntegrationTests/Stubs/Stubs.swift @@ -30,7 +30,7 @@ extension AppMetadata { description: "WalletConnectSwift", url: "https://walletconnect.com", icons: [], - redirect: AppMetadata.Redirect(native: "dapp://", universal: nil) + redirect: AppMetadata.Redirect(native: "wcdapp://", universal: nil) ) } } diff --git a/Example/PNDecryptionService/Info.plist b/Example/PNDecryptionService/Info.plist index cb9e830fd..82ce6e8c5 100644 --- a/Example/PNDecryptionService/Info.plist +++ b/Example/PNDecryptionService/Info.plist @@ -1,20 +1,22 @@ - - NSExtension - - NSExtensionAttributes - - IntentsSupported - - INSendMessageIntent - - - NSExtensionPointIdentifier - com.apple.usernotifications.service - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).NotificationService - - + + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + MIXPANEL_TOKEN + $(MIXPANEL_TOKEN) + diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 70232ec62..708444594 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -1,6 +1,7 @@ import UserNotifications import WalletConnectNotify import Intents +import Mixpanel class NotificationService: UNNotificationServiceExtension { @@ -11,22 +12,36 @@ class NotificationService: UNNotificationServiceExtension { self.contentHandler = contentHandler self.bestAttemptContent = request.content + log("didReceive(_:) fired") + if let content = bestAttemptContent, let topic = content.userInfo["topic"] as? String, let ciphertext = content.userInfo["blob"] as? String { + log("topic and blob found") + do { let service = NotifyDecryptionService(groupIdentifier: "group.com.walletconnect.sdk") - let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext) - let updatedContent = try handle(content: content, pushMessage: pushMessage, topic: topic) + let (pushMessage, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext) + + log("message decrypted", account: account, topic: topic, message: pushMessage) + + let updatedContent = handle(content: content, pushMessage: pushMessage, topic: topic) let mutableContent = updatedContent.mutableCopy() as! UNMutableNotificationContent mutableContent.title = pushMessage.title + mutableContent.subtitle = pushMessage.url mutableContent.body = pushMessage.body + log("message handled", account: account, topic: topic, message: pushMessage) + contentHandler(mutableContent) + + log("content handled", account: account, topic: topic, message: pushMessage) } catch { + log("error: \(error.localizedDescription)") + let mutableContent = content.mutableCopy() as! UNMutableNotificationContent mutableContent.title = "Error" mutableContent.body = error.localizedDescription @@ -47,57 +62,62 @@ class NotificationService: UNNotificationServiceExtension { private extension NotificationService { - func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) throws -> UNNotificationContent { - let iconUrl = try pushMessage.icon.asURL() - - let senderThumbnailImageData = try Data(contentsOf: iconUrl) - let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) - let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) - let senderAvatar = INImage(imageData: senderThumbnailImageFileData) - - var personNameComponents = PersonNameComponents() - personNameComponents.nickname = pushMessage.title - - let senderPerson = INPerson( - personHandle: INPersonHandle(value: topic, type: .unknown), - nameComponents: personNameComponents, - displayName: pushMessage.title, - image: senderAvatar, - contactIdentifier: nil, - customIdentifier: topic, - isMe: false, - suggestionType: .none - ) - - let selfPerson = INPerson( - personHandle: INPersonHandle(value: "0", type: .unknown), - nameComponents: nil, - displayName: nil, - image: nil, - contactIdentifier: nil, - customIdentifier: nil, - isMe: true, - suggestionType: .none - ) - - let incomingMessagingIntent = INSendMessageIntent( - recipients: [selfPerson], - outgoingMessageType: .outgoingMessageText, - content: pushMessage.body, - speakableGroupName: nil, - conversationIdentifier: pushMessage.type, - serviceName: nil, - sender: senderPerson, - attachments: [] - ) - - incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) - - let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) - interaction.direction = .incoming - interaction.donate(completion: nil) - - return try content.updating(from: incomingMessagingIntent) + func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) -> UNNotificationContent { + do { + let iconUrl = try pushMessage.icon.asURL() + + let senderThumbnailImageData = try Data(contentsOf: iconUrl) + let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent) + let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl) + let senderAvatar = INImage(imageData: senderThumbnailImageFileData) + + var personNameComponents = PersonNameComponents() + personNameComponents.nickname = pushMessage.title + + let senderPerson = INPerson( + personHandle: INPersonHandle(value: topic, type: .unknown), + nameComponents: personNameComponents, + displayName: pushMessage.title, + image: senderAvatar, + contactIdentifier: nil, + customIdentifier: topic, + isMe: false, + suggestionType: .none + ) + + let selfPerson = INPerson( + personHandle: INPersonHandle(value: "0", type: .unknown), + nameComponents: nil, + displayName: nil, + image: nil, + contactIdentifier: nil, + customIdentifier: nil, + isMe: true, + suggestionType: .none + ) + + let incomingMessagingIntent = INSendMessageIntent( + recipients: [selfPerson], + outgoingMessageType: .outgoingMessageText, + content: pushMessage.body, + speakableGroupName: nil, + conversationIdentifier: pushMessage.type, + serviceName: nil, + sender: senderPerson, + attachments: [] + ) + + incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender) + + let interaction = INInteraction(intent: incomingMessagingIntent, response: nil) + interaction.direction = .incoming + interaction.donate(completion: nil) + + return try content.updating(from: incomingMessagingIntent) + } + catch { + return content + } } func downloadAttachment(data: Data, fileName: String) throws -> URL { @@ -112,4 +132,36 @@ private extension NotificationService { return fileURL } + + func log(_ event: String, account: Account? = nil, topic: String? = nil, message: NotifyMessage? = nil) { + let keychain = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") + + guard let clientId: String = try? keychain.read(key: "clientId") else { + return + } + + guard let token = InputConfig.mixpanelToken, !token.isEmpty else { return } + + Mixpanel.initialize(token: token, trackAutomaticEvents: true) + + if let account { + let mixpanel = Mixpanel.mainInstance() + mixpanel.alias = account.absoluteString + mixpanel.identify(distinctId: clientId) + mixpanel.people.set(properties: ["$name": account.absoluteString, "account": account.absoluteString]) + } + + Mixpanel.mainInstance().track( + event: "💬 APNS: " + event, + properties: [ + "title": message?.title, + "body": message?.body, + "icon": message?.icon, + "url": message?.url, + "type": message?.type, + "topic": topic, + "source": "NotificationService" + ] + ) + } } diff --git a/Example/WalletApp/Common/InputConfig.swift b/Example/Shared/InputConfig.swift similarity index 100% rename from Example/WalletApp/Common/InputConfig.swift rename to Example/Shared/InputConfig.swift diff --git a/Example/Showcase/Common/VIPER/SceneViewController.swift b/Example/Showcase/Common/VIPER/SceneViewController.swift index 7558ef776..664eee5d1 100644 --- a/Example/Showcase/Common/VIPER/SceneViewController.swift +++ b/Example/Showcase/Common/VIPER/SceneViewController.swift @@ -1,15 +1,10 @@ import SwiftUI -enum NavigationBarStyle { - case translucent(UIColor) -} - protocol SceneViewModel { var sceneTitle: String? { get } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { get } var leftBarButtonItem: UIBarButtonItem? { get } var rightBarButtonItem: UIBarButtonItem? { get } - var navigationBarStyle: NavigationBarStyle { get } var preferredStatusBarStyle: UIStatusBarStyle { get } var isNavigationBarTranslucent: Bool { get } @@ -28,9 +23,6 @@ extension SceneViewModel { var rightBarButtonItem: UIBarButtonItem? { return .none } - var navigationBarStyle: NavigationBarStyle { - return .translucent(.w_background) - } var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -56,7 +48,6 @@ class SceneViewController: UIHostingCo super.viewDidLoad() setupView() setupNavigation() - setupNavigationBarStyle() } @objc required dynamic init?(coder aDecoder: NSCoder) { @@ -79,12 +70,4 @@ private extension SceneViewController { navigationItem.rightBarButtonItem = viewModel.rightBarButtonItem navigationItem.leftBarButtonItem = viewModel.leftBarButtonItem } - - func setupNavigationBarStyle() { - switch viewModel.navigationBarStyle { - case .translucent(let color): - navigationController?.navigationBar.barTintColor = color - navigationController?.navigationBar.isTranslucent = true - } - } } diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 4e5f57c3e..01fe59d9c 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -30,6 +30,8 @@ final class ConfigurationService { if let clientId = try? Networking.interactor.getClientId() { LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId) ProfilingService.instance.setUpProfiling(account: importAccount.account.absoluteString, clientId: clientId) + let groupKeychain = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") + try! groupKeychain.add(clientId, forKey: "clientId") } LoggingService.instance.startLogging() diff --git a/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift index ca41f7f63..15fe7fecb 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -3,6 +3,6 @@ import UIKit struct AppearanceConfigurator: Configurator { func configure() { - + } } diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index 4a98cfa19..c36e6b4f6 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -1,8 +1,9 @@ import Auth +import SafariServices import UIKit import WalletConnectPairing -final class SceneDelegate: UIResponder, UIWindowSceneDelegate { +final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? private let app = Application() @@ -32,17 +33,43 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { app.requestSent = (connectionOptions.urlContexts.first?.url.absoluteString.replacingOccurrences(of: "walletapp://wc?", with: "") == "requestSent") configurators.configure() + + 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 { - Task { - try await Pair.instance.pair(uri: uri) - } - } + let uri = WalletConnectURI(urlContext: context) + + if let uri { + Task { + try await Pair.instance.pair(uri: uri) + } + } + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { + open(notification: notification) + return [.sound, .banner, .badge] + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { + open(notification: response.notification) + } +} + +private extension SceneDelegate { + + func open(notification: UNNotification) { + let popupTag: Int = 1020 + if let url = URL(string: notification.request.content.subtitle), + let topController = window?.rootViewController?.topController, topController.view.tag != popupTag + { + let safari = SFSafariViewController(url: url) + safari.modalPresentationStyle = .formSheet + safari.view.tag = popupTag + window?.rootViewController?.topController.present(safari, animated: true) + } } } diff --git a/Example/WalletApp/Common/VIPER/SceneViewController.swift b/Example/WalletApp/Common/VIPER/SceneViewController.swift index 33b846ed3..5652fcdba 100644 --- a/Example/WalletApp/Common/VIPER/SceneViewController.swift +++ b/Example/WalletApp/Common/VIPER/SceneViewController.swift @@ -1,16 +1,10 @@ import SwiftUI -enum NavigationBarStyle { - case solid(UIColor) - case clear -} - protocol SceneViewModel { var sceneTitle: String? { get } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { get } var leftBarButtonItem: UIBarButtonItem? { get } var rightBarButtonItem: UIBarButtonItem? { get } - var navigationBarStyle: NavigationBarStyle { get } var preferredStatusBarStyle: UIStatusBarStyle { get } var isNavigationBarTranslucent: Bool { get } @@ -29,9 +23,6 @@ extension SceneViewModel { var rightBarButtonItem: UIBarButtonItem? { return .none } - var navigationBarStyle: NavigationBarStyle { - return .solid(.w_background) - } var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -58,11 +49,6 @@ class SceneViewController: UIHostingCo setupNavigation() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigationBarStyle() - } - @objc required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -81,21 +67,4 @@ private extension SceneViewController { navigationItem.rightBarButtonItem = viewModel.rightBarButtonItem navigationItem.leftBarButtonItem = viewModel.leftBarButtonItem } - - func setupNavigationBarStyle() { - switch viewModel.navigationBarStyle { - case .solid(let color): - navigationController?.navigationBar.setBackgroundImage(color.image(), for: .default) - navigationController?.navigationBar.isTranslucent = false - navigationController?.navigationBar.backgroundColor = color - navigationController?.navigationBar.barTintColor = color - case .clear: - navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - navigationController?.navigationBar.shadowImage = UIImage() - navigationController?.navigationBar.isTranslucent = true - navigationController?.navigationBar.backgroundColor = .clear - navigationController?.navigationBar.barTintColor = .clear - navigationController?.navigationBar.tintColor = .w_foreground - } - } } diff --git a/Example/WalletApp/Common/Views/ConnectedSheetView.swift b/Example/WalletApp/Common/Views/ConnectedSheetView.swift new file mode 100644 index 000000000..ca04d4301 --- /dev/null +++ b/Example/WalletApp/Common/Views/ConnectedSheetView.swift @@ -0,0 +1,38 @@ +import SwiftUI + +struct ConnectedSheetView: View { + let title: String + + var body: some View { + ZStack { + VStack { + Image("connected") + + Spacer() + } + + VStack(spacing: 8) { + Rectangle() + .foregroundColor(.clear) + .frame(width: 48, height: 4) + .background(Color(red: 0.02, green: 0.17, blue: 0.17).opacity(0.2)) + .cornerRadius(100) + .padding(.top, 8) + + Text(title) + .foregroundColor(.grey8) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .padding(.top, 168) + + + Text("You can go back to your browser now") + .foregroundColor(Color(red: 0.47, green: 0.53, blue: 0.53)) + .font(.system(size: 16, weight: .medium, design: .rounded)) + + Spacer() + } + } + .presentationDetents([.height(254)]) + .presentationDragIndicator(.hidden) + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/AccentColor.colorset/Contents.json index eb8789700..7e65d72f8 100644 --- a/Example/WalletApp/Other/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Example/WalletApp/Other/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,33 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.078", + "green" : "0.078", + "red" : "0.078" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, "idiom" : "universal" } ], diff --git a/Example/WalletApp/Other/Assets.xcassets/connected.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/connected.imageset/Contents.json new file mode 100644 index 000000000..a0c9ba91a --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/connected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "connected.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/connected.imageset/connected.pdf b/Example/WalletApp/Other/Assets.xcassets/connected.imageset/connected.pdf new file mode 100644 index 000000000..082ee55f8 Binary files /dev/null and b/Example/WalletApp/Other/Assets.xcassets/connected.imageset/connected.pdf differ diff --git a/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/Contents.json new file mode 100644 index 000000000..9f8a19fe6 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "connections_tab.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/connections_tab.svg b/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/connections_tab.svg new file mode 100644 index 000000000..5e2cc7dd8 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/connections_tab.imageset/connections_tab.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/Contents.json new file mode 100644 index 000000000..905e0be62 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "copy.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/copy.svg b/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/copy.svg new file mode 100644 index 000000000..06308be11 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/copy_small.imageset/copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/Contents.json new file mode 100644 index 000000000..7f65e61dc --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "inbox_tab.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/inbox_tab.svg b/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/inbox_tab.svg new file mode 100644 index 000000000..e944a8f5e --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/inbox_tab.imageset/inbox_tab.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/Contents.json new file mode 100644 index 000000000..2d928d41c --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "settings_tab.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/settings_tab.svg b/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/settings_tab.svg new file mode 100644 index 000000000..b55eba524 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/settings_tab.imageset/settings_tab.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist index 25606209f..f542d355a 100644 --- a/Example/WalletApp/Other/Info.plist +++ b/Example/WalletApp/Other/Info.plist @@ -2,10 +2,6 @@ - NSUserActivityTypes - - INSendMessageIntent - CFBundleIconName AppIcon CFBundleURLTypes @@ -23,19 +19,21 @@ $(CONFIGURATION) ITSAppUsesNonExemptEncryption + MIXPANEL_TOKEN + $(MIXPANEL_TOKEN) NSAppTransportSecurity NSAllowsArbitraryLoads + NSUserActivityTypes + + INSendMessageIntent + PROJECT_ID $(PROJECT_ID) - WALLETAPP_SENTRY_DSN - $(WALLETAPP_SENTRY_DSN) SIMULATOR_IDENTIFIER $(SIMULATOR_IDENTIFIER) - MIXPANEL_TOKEN - $(MIXPANEL_TOKEN) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -53,9 +51,7 @@ - UIBackgroundModes - - remote-notification - + WALLETAPP_SENTRY_DSN + $(WALLETAPP_SENTRY_DSN) diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift index 3e70d3826..dafe7d283 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift @@ -11,7 +11,7 @@ final class AuthRequestInteractor { self.messageSigner = messageSigner } - func approve(request: AuthRequest, importAccount: ImportAccount) async throws { + func approve(request: AuthRequest, importAccount: ImportAccount) async throws -> Bool { let account = importAccount.account let signature = try messageSigner.sign( payload: request.payload.cacaoPayload(address: account.address), @@ -22,6 +22,9 @@ final class AuthRequestInteractor { /* Redirect */ if let uri = request.requester.redirect?.native { WalletConnectRouter.goBack(uri: uri) + return false + } else { + return true } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift index f1fe362ef..d9aa3125d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift @@ -15,6 +15,8 @@ final class AuthRequestPresenter: ObservableObject { return interactor.formatted(request: request, account: importAccount.account) } + @Published var showSignedSheet = false + private var disposeBag = Set() init( @@ -34,8 +36,8 @@ final class AuthRequestPresenter: ObservableObject { @MainActor func onApprove() async throws { - try await interactor.approve(request: request, importAccount: importAccount) - router.dismiss() + let showConnected = try await interactor.approve(request: request, importAccount: importAccount) + showConnected ? showSignedSheet.toggle() : router.dismiss() } @MainActor @@ -43,6 +45,10 @@ final class AuthRequestPresenter: ObservableObject { try await interactor.reject(request: request) router.dismiss() } + + func onSignedSheetDismiss() { + router.dismiss() + } } // MARK: - Private functions diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift index 97d40b444..46e38408f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift @@ -103,6 +103,12 @@ struct AuthRequestView: View { Spacer() } } + .sheet( + isPresented: $presenter.showSignedSheet, + onDismiss: presenter.onSignedSheetDismiss + ) { + ConnectedSheetView(title: "Request is signed") + } .edgesIgnoringSafeArea(.all) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Browser/BrowserPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Browser/BrowserPresenter.swift index a3ffcc4cc..6163252e5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Browser/BrowserPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Browser/BrowserPresenter.swift @@ -38,7 +38,7 @@ extension BrowserPresenter: SceneViewModel { } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { - return .always + return .never } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 451cce287..762bdf97c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -17,7 +17,6 @@ final class MainPresenter { return [ router.walletViewController(importAccount: importAccount), router.notificationsViewController(importAccount: importAccount), - router.browserViewController(), router.settingsViewController() ] } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index 6e67c078c..211a1fc62 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -27,11 +27,6 @@ final class MainRouter { .wrapToNavigationController() } - func browserViewController() -> UIViewController { - return BrowserModule.create(app: app) - .wrapToNavigationController() - } - func present(proposal: Session.Proposal, importAccount: ImportAccount, context: VerifyContext?) { SessionProposalModule.create(app: app, importAccount: importAccount, proposal: proposal, context: context) .presentFullScreen(from: viewController, transparentBackground: true) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift index d79c5a4e4..e391d283e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift @@ -3,32 +3,27 @@ import UIKit enum TabPage: CaseIterable { case wallet case notifications - case browser case settings var title: String { switch self { case .wallet: - return "Apps" + return "Connections" case .notifications: - return "Notifications" + return "Inbox" case .settings: return "Settings" - case .browser: - return "Browser" } } var icon: UIImage { switch self { case .wallet: - return UIImage(systemName: "house.fill")! + return UIImage(named: "connections_tab")! case .notifications: - return UIImage(systemName: "bell.fill")! - case .browser: - return UIImage(systemName: "network")! + return UIImage(named: "inbox_tab")! case .settings: - return UIImage(systemName: "gearshape.fill")! + return UIImage(named: "settings_tab")! } } @@ -37,6 +32,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.wallet, .notifications, .browser, .settings] + return [.wallet, .notifications, .settings] } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift index 843fcc14d..a7bc7b2ac 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift @@ -12,7 +12,8 @@ final class NotificationsRouter { } func presentNotifications(subscription: NotifySubscription) { - SubscriptionModule.create(app: app, subscription: subscription) - .push(from: viewController) + let module = SubscriptionModule.create(app: app, subscription: subscription) + module.hidesBottomBarWhenPushed = true + module.push(from: viewController) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 343eac466..2daa40746 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -52,7 +52,7 @@ struct NotificationsView: View { } Rectangle() - .foregroundColor(.black.opacity(0.03)) + .foregroundColor(Color.Foreground100.opacity(0.05)) .frame(maxWidth: .infinity) .frame(height: 1) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index 441c001e0..23f9729f9 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -62,10 +62,6 @@ extension SubscriptionPresenter: SceneViewModel { return .never } - var navigationBarStyle: NavigationBarStyle { - return .clear - } - var rightBarButtonItem: UIBarButtonItem? { return UIBarButtonItem( image: UIImage(systemName: "gearshape"), diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index 260a1905a..34c0f9880 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift @@ -4,7 +4,7 @@ import Web3Wallet import WalletConnectRouter final class SessionProposalInteractor { - func approve(proposal: Session.Proposal, account: Account) async throws { + func approve(proposal: Session.Proposal, account: Account) async throws -> Bool { // Following properties are used to support all the required and optional namespaces for the testing purposes let supportedMethods = Set(proposal.requiredNamespaces.flatMap { $0.value.methods } + (proposal.optionalNamespaces?.flatMap { $0.value.methods } ?? [])) let supportedEvents = Set(proposal.requiredNamespaces.flatMap { $0.value.events } + (proposal.optionalNamespaces?.flatMap { $0.value.events } ?? [])) @@ -29,10 +29,12 @@ final class SessionProposalInteractor { accounts: supportedAccounts ) try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) - - /* Redirect */ + if let uri = proposal.proposer.redirect?.native { WalletConnectRouter.goBack(uri: uri) + return false + } else { + return true } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index be919a381..523158eee 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -13,6 +13,7 @@ final class SessionProposalPresenter: ObservableObject { @Published var showError = false @Published var errorMessage = "Error" + @Published var showConnectedSheet = false private var disposeBag = Set() @@ -34,8 +35,8 @@ final class SessionProposalPresenter: ObservableObject { @MainActor func onApprove() async throws { do { - try await interactor.approve(proposal: sessionProposal, account: importAccount.account) - router.dismiss() + let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account) + showConnected ? showConnectedSheet.toggle() : router.dismiss() } catch { errorMessage = error.localizedDescription showError.toggle() @@ -52,6 +53,10 @@ final class SessionProposalPresenter: ObservableObject { showError.toggle() } } + + func onConnectedSheetDismiss() { + router.dismiss() + } } // MARK: - Private functions diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index cd595a158..19ee52d1e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -143,6 +143,12 @@ struct SessionProposalView: View { .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) {} } + .sheet( + isPresented: $presenter.showConnectedSheet, + onDismiss: presenter.onConnectedSheetDismiss + ) { + ConnectedSheetView(title: "Connected") + } .edgesIgnoringSafeArea(.all) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift index b2933c2b6..59886fbfb 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 { + func approve(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool { do { let result = try Signer.sign(request: sessionRequest, importAccount: importAccount) try await Web3Wallet.instance.respond( @@ -17,6 +17,9 @@ final class SessionRequestInteractor { let session = getSession(topic: sessionRequest.topic) if let uri = session?.peer.redirect?.native { WalletConnectRouter.goBack(uri: uri) + return false + } else { + return true } } catch { throw error diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index 103002dd6..d8c00b710 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -20,6 +20,7 @@ final class SessionRequestPresenter: ObservableObject { @Published var showError = false @Published var errorMessage = "Error" + @Published var showSignedSheet = false private var disposeBag = Set() @@ -42,8 +43,8 @@ final class SessionRequestPresenter: ObservableObject { @MainActor func onApprove() async throws { do { - try await interactor.approve(sessionRequest: sessionRequest, importAccount: importAccount) - router.dismiss() + let showConnected = try await interactor.approve(sessionRequest: sessionRequest, importAccount: importAccount) + showConnected ? showSignedSheet.toggle() : router.dismiss() } catch { errorMessage = error.localizedDescription showError.toggle() @@ -55,6 +56,14 @@ final class SessionRequestPresenter: ObservableObject { try await interactor.reject(sessionRequest: sessionRequest) router.dismiss() } + + func onSignedSheetDismiss() { + dismiss() + } + + func dismiss() { + router.dismiss() + } } // MARK: - Private functions diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index c8ce70396..d64b1f82a 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -97,7 +97,15 @@ struct SessionRequestView: View { Spacer() } .alert(presenter.errorMessage, isPresented: $presenter.showError) { - Button("OK", role: .cancel) {} + Button("OK", role: .cancel) { + presenter.dismiss() + } + } + .sheet( + isPresented: $presenter.showSignedSheet, + onDismiss: presenter.onSignedSheetDismiss + ) { + ConnectedSheetView(title: "Request is signed") } } .edgesIgnoringSafeArea(.all) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift index fb827251e..d9c10848d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift @@ -38,6 +38,10 @@ final class SettingsPresenter: ObservableObject { return deviceToken } + func browserPressed() { + router.presentBrowser() + } + func logoutPressed() async throws { guard let account = accountStorage.importAccount?.account else { return } try await interactor.notifyUnregister(account: account) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsRouter.swift index 7ef69186e..5e78f55b6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsRouter.swift @@ -13,4 +13,9 @@ final class SettingsRouter { @MainActor func presentWelcome() async { WelcomeModule.create(app: app).present() } + + func presentBrowser() { + BrowserModule.create(app: app) + .push(from: viewController) + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift index 137296793..73a5fdbcd 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift @@ -1,5 +1,6 @@ import SwiftUI import AsyncButton +import Web3ModalUI struct SettingsView: View { @@ -8,28 +9,54 @@ struct SettingsView: View { @State private var copyAlert: Bool = false var body: some View { - List { - Section(header: Text("Account")) { - row(title: "CAIP-10", subtitle: viewModel.account) - row(title: "Private key", subtitle: viewModel.privateKey) - } + ScrollView { + VStack(spacing: 12) { + separator() - Section(header: Text("Device")) { - row(title: "Client ID", subtitle: viewModel.clientId) - row(title: "Device Token", subtitle: viewModel.deviceToken) - } + Group { + header(title: "Account") + row(title: "CAIP-10", subtitle: viewModel.account) + row(title: "Private key", subtitle: viewModel.privateKey) + } + .padding(.horizontal, 20) + + separator() + + Group { + header(title: "Device") + row(title: "Client ID", subtitle: viewModel.clientId) + row(title: "Device Token", subtitle: viewModel.deviceToken) + } + .padding(.horizontal, 20) + + separator() + + Group { + Button { + viewModel.browserPressed() + } label: { + Text("Browser") + .frame(maxWidth: .infinity) + } + .frame(height: 44.0) - Section { - AsyncButton { - try await viewModel.logoutPressed() - } label: { - Text("Log out") - .foregroundColor(.red) - .frame(maxWidth: .infinity) + AsyncButton { + try await viewModel.logoutPressed() + } label: { + Text("Log out") + .foregroundColor(.red) + .frame(maxWidth: .infinity) + } + .frame(height: 44.0) + .overlay( + RoundedRectangle(cornerRadius: Radius.m) + .stroke(Color.red, lineWidth: 1) + ) + .padding(.bottom, 24) } + .padding(.horizontal, 20) } } - .listStyle(.insetGrouped) .alert("Value copied to clipboard", isPresented: $copyAlert) { Button("OK", role: .cancel) { } } @@ -38,22 +65,53 @@ struct SettingsView: View { } } + func header(title: String) -> some View { + HStack { + Text(title) + .foregroundColor(.Foreground100) + .font(.large700) + .padding(.vertical, 6) + Spacer() + } + } + func row(title: String, subtitle: String) -> some View { return Button(action: { UIPasteboard.general.string = subtitle copyAlert = true }) { - HStack(spacing: 16) { - VStack { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { Text(title) + .multilineTextAlignment(.leading) + .foregroundColor(.Foreground100) + .font(.paragraph700) + + Image("copy_small") + .foregroundColor(.Foreground100) + Spacer() } - .padding(.vertical, 8.0) + .padding(.horizontal, 12) + .padding(.top, 16) Text(subtitle) - .multilineTextAlignment(.trailing) - .foregroundColor(.gray) + .multilineTextAlignment(.leading) + .foregroundColor(.Foreground150) + .font(.paragraph500) + .padding(.horizontal, 12) + .padding(.bottom, 16) } + .background(Color.Foreground100.opacity(0.05).cornerRadius(12)) } + .frame(maxWidth: .infinity) + } + + func separator() -> some View { + Rectangle() + .foregroundColor(.Foreground100.opacity(0.05)) + .frame(maxWidth: .infinity) + .frame(height: 1) + .padding(.top, 8) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 5a1bc3b85..59d5c1fc2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -19,6 +19,7 @@ final class WalletPresenter: ObservableObject { @Published var showPairingLoading = false @Published var showError = false @Published var errorMessage = "Error" + @Published var showConnectedSheet = false private var disposeBag = Set() diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift index e8c976f31..db735a7cf 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletView.swift @@ -81,6 +81,7 @@ struct WalletView: View { .frame(width: 56, height: 56) } .shadow(color: .black.opacity(0.25), radius: 8, y: 4) + .accessibilityIdentifier("copy") Button { presenter.onScanUri() @@ -100,6 +101,36 @@ struct WalletView: View { .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) {} } + .sheet(isPresented: $presenter.showConnectedSheet) { + ZStack { + VStack { + Image("connected") + + Spacer() + } + + VStack(spacing: 8) { + Rectangle() + .foregroundColor(.clear) + .frame(width: 48, height: 4) + .background(Color(red: 0.02, green: 0.17, blue: 0.17).opacity(0.2)) + .cornerRadius(100) + .padding(.top, 8) + + Text("Connected") + .foregroundColor(.grey8) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .padding(.top, 168) + + + Text("You can go back to your browser now") + .foregroundColor(Color(red: 0.47, green: 0.53, blue: 0.53)) + .font(.system(size: 16, weight: .medium, design: .rounded)) + + Spacer() + } + } + } .onAppear { presenter.onAppear() } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift index 7931911ed..296e515e9 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDecryptionService.swift @@ -16,11 +16,11 @@ public class NotifyDecryptionService { self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) } - public func decryptMessage(topic: String, ciphertext: String) throws -> NotifyMessage { + public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, Account) { let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage } let wrapper = try params.get(NotifyMessagePayload.Wrapper.self) let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper) - return messagePayload.message + return (messagePayload.message, messagePayload.account) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift index cf7b39101..0ae95400b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyIdentityService.swift @@ -35,9 +35,9 @@ private extension NotifyIdentityService { func makeStatement(isLimited: Bool) -> String { switch isLimited { case true: - return "I further authorize this app to send and receive messages on my behalf for THIS domain using my WalletConnect identity. Read more at https://walletconnect.com/identity" + return "I further authorize this app to send me notifications. Read more at https://walletconnect.com/notifications" case false: - return "I further authorize this app to send and receive messages on my behalf for ALL domains using my WalletConnect identity. Read more at https://walletconnect.com/identity" + return "I further authorize this app to view and manage my notifications for ALL apps. Read more at https://walletconnect.com/notifications" } } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift index d12331d34..9fc7b1c2b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift @@ -1,27 +1,25 @@ import Foundation public struct NotifyMessageRecord: Codable, Equatable, SqliteRow { - public let id: String public let topic: String public let message: NotifyMessage public let publishedAt: Date - public var databaseId: String { - return id + public var id: String { + return message.id } - public init(id: String, topic: String, message: NotifyMessage, publishedAt: Date) { - self.id = id + public init(topic: String, message: NotifyMessage, publishedAt: Date) { self.topic = topic self.message = message self.publishedAt = publishedAt } public init(decoder: SqliteRowDecoder) throws { - self.id = try decoder.decodeString(at: 0) self.topic = try decoder.decodeString(at: 1) self.message = NotifyMessage( + id: try decoder.decodeString(at: 0), title: try decoder.decodeString(at: 2), body: try decoder.decodeString(at: 3), icon: try decoder.decodeString(at: 4), @@ -34,7 +32,7 @@ public struct NotifyMessageRecord: Codable, Equatable, SqliteRow { public func encode() -> SqliteRowEncoder { var encoder = SqliteRowEncoder() - encoder.encodeString(id, for: "id") + encoder.encodeString(message.id, for: "id") encoder.encodeString(topic, for: "topic") encoder.encodeString(message.title, for: "title") encoder.encodeString(message.body, for: "body") diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift index 4199bfb6f..22f470d81 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift @@ -32,7 +32,7 @@ class NotifyMessageSubscriber { logger.debug("Decoded Notify Message: \(payload.topic)", properties: ["topic": payload.topic, "messageBody": messagePayload.message.body, "messageTitle": messagePayload.message.title, "publishedAt": payload.publishedAt.description, "id": payload.id.string]) let dappPubKey = try DIDKey(did: claims.iss) - let record = NotifyMessageRecord(id: payload.id.string, topic: payload.topic, message: messagePayload.message, publishedAt: payload.publishedAt) + let record = NotifyMessageRecord(topic: payload.topic, message: messagePayload.message, publishedAt: payload.publishedAt) try notifyStorage.setMessage(record) let receiptPayload = NotifyMessageReceiptPayload( diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift index 2cd90ad8c..e61d46223 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift @@ -1,13 +1,15 @@ import Foundation public struct NotifyMessage: Codable, Equatable { + public let id: String public let title: String public let body: String public let icon: String public let url: String public let type: String - public init(title: String, body: String, icon: String, url: String, type: String) { + public init(id: String, title: String, body: String, icon: String, url: String, type: String) { + self.id = id self.title = title self.body = body self.icon = icon diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index b109e63f6..bf78f5622 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.9.8"} +{"version": "1.9.9"} diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 185d0d444..f2598b074 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -3,7 +3,6 @@ import Combine final class SessionEngine { enum Errors: Error { - case sessionNotFound(topic: String) case sessionRequestExpired } @@ -73,7 +72,7 @@ final class SessionEngine { func respondSessionRequest(topic: String, requestId: RPCID, response: RPCResult) async throws { guard sessionStore.hasSession(forTopic: topic) else { - throw Errors.sessionNotFound(topic: topic) + throw WalletConnectError.noSessionMatchingTopic(topic) } let protocolMethod = SessionRequestProtocolMethod() @@ -281,3 +280,11 @@ private extension SessionEngine { onEventReceived?(topic, event.publicRepresentation(), payload.request.chainId) } } + +extension SessionEngine.Errors: LocalizedError { + var errorDescription: String? { + switch self { + case .sessionRequestExpired: return "Session request expired" + } + } +} diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 1ea172041..299e6c4b1 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -173,43 +173,6 @@ public final class SignClient: SignClientProtocol { // MARK: - Public interface - /// For a dApp to propose a session to a wallet. - /// Function will create pairing and propose session or propose a session on existing pairing. - /// - Parameters: - /// - requiredNamespaces: required namespaces for a session - /// - topic: Optional parameter - use it if you already have an established pairing with peer client. - /// - Returns: Pairing URI that should be shared with responder out of bound. Common way is to present it as a QR code. Pairing URI will be nil if you are going to establish a session on existing Pairing and `topic` function parameter was provided. - @available(*, deprecated, message: "use Pair.instance.create() and connect(requiredNamespaces: [String: ProposalNamespace]): instead") - public func connect( - requiredNamespaces: [String: ProposalNamespace], - optionalNamespaces: [String: ProposalNamespace]? = nil, - sessionProperties: [String: String]? = nil, - topic: String? = nil - ) async throws -> WalletConnectURI? { - logger.debug("Connecting Application") - if let topic = topic { - try pairingClient.validatePairingExistance(topic) - try await appProposeService.propose( - pairingTopic: topic, - namespaces: requiredNamespaces, - optionalNamespaces: optionalNamespaces, - sessionProperties: sessionProperties, - relay: RelayProtocolOptions(protocol: "irn", data: nil) - ) - return nil - } else { - let pairingURI = try await pairingClient.create() - try await appProposeService.propose( - pairingTopic: pairingURI.topic, - namespaces: requiredNamespaces, - optionalNamespaces: optionalNamespaces, - sessionProperties: sessionProperties, - relay: RelayProtocolOptions(protocol: "irn", data: nil) - ) - return pairingURI - } - } - /// For a dApp to propose a session to a wallet. /// Function will propose a session on existing pairing. /// - Parameters: @@ -232,17 +195,6 @@ public final class SignClient: SignClientProtocol { ) } - /// For wallet to receive a session proposal from a dApp - /// Responder should call this function in order to accept peer's pairing and be able to subscribe for future session proposals. - /// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp. - /// - /// Should Error: - /// - When URI has invalid format or missing params - /// - When topic is already in use - @available(*, deprecated, message: "use Pair.instance.pair(uri: WalletConnectURI): instead") - public func pair(uri: WalletConnectURI) async throws { - try await pairingClient.pair(uri: uri) - } /// For a wallet to approve a session proposal. /// - Parameters: @@ -337,13 +289,6 @@ public final class SignClient: SignClientProtocol { sessionEngine.getSessions() } - /// Query pairings - /// - Returns: All pairings - @available(*, deprecated, message: "use Pair.instance.getPairings(uri: WalletConnectURI): instead") - public func getPairings() -> [Pairing] { - pairingClient.getPairings() - } - /// Query pending requests /// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method /// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions. diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 95f56ee76..0d83845b1 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -20,7 +20,6 @@ public protocol SignClientProtocol { func extend(topic: String) async throws func respond(topic: String, requestId: RPCID, response: RPCResult) async throws func emit(topic: String, event: Session.Event, chainId: Blockchain) async throws - func pair(uri: WalletConnectURI) async throws func disconnect(topic: String) async throws func getSessions() -> [Session] func cleanup() async throws diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index e528f2b60..855901655 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -34,7 +34,7 @@ public actor VerifyClient: VerifyClientProtocol { } nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { - guard isScam == nil else { + guard isScam != true else { return VerifyContext( origin: origin, validation: .scam