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..63a10ccb4 --- /dev/null +++ b/Example/DApp/Modules/Main/MainViewController.swift @@ -0,0 +1,45 @@ +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() { + tabBar.unselectedItemTintColor = .white + + 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..faba03264 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,13 @@ 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 + chainId: Blockchain("eip155:1")!, + metadata: metadata ) setupWindow(scene: scene) @@ -36,16 +35,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..ab2b202dd 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -46,15 +46,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 */; }; @@ -184,11 +175,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 +274,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 +287,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 +344,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 */ @@ -379,8 +401,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 = ""; }; @@ -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 = ""; }; @@ -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 */, @@ -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 = ( @@ -1410,29 +1412,6 @@ 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,6 +1657,7 @@ C56EE25C293F56D6004840D1 /* Common */ = { isa = PBXGroup; children = ( + C579FEB82AFCDF83008855EB /* Views */, C56EE25D293F56D6004840D1 /* InputConfig.swift */, 84B8154D2991099000FAD54E /* BuildConfiguration.swift */, C56EE267293F56D6004840D1 /* Style */, @@ -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 */; @@ -2168,6 +2246,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C5BE02142AF77A940064FC88 /* Colors.xcassets in Resources */, 84CE642B27981DF000142511 /* LaunchScreen.storyboard in Resources */, 84CE642827981DF000142511 /* Assets.xcassets in Resources */, ); @@ -2237,27 +2316,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; }; @@ -2460,6 +2558,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 */, @@ -2762,11 +2861,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 +2896,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 +3391,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/WalletConnect/web3modal-swift"; requirement = { - branch = develop; - kind = branch; + kind = exactVersion; + version = 1.0.9; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -3473,11 +3574,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 +3596,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..229e0f8ef 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": "e68c1b1560264965ca13608db44294d301c6404f", + "version": "1.0.9" } } ] 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/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/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/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/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/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..163135ba0 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,10 @@ final class SessionRequestPresenter: ObservableObject { try await interactor.reject(sessionRequest: sessionRequest) router.dismiss() } + + func onSignedSheetDismiss() { + 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..a183d2c57 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -99,6 +99,12 @@ struct SessionRequestView: View { .alert(presenter.errorMessage, isPresented: $presenter.showError) { Button("OK", role: .cancel) {} } + .sheet( + isPresented: $presenter.showSignedSheet, + onDismiss: presenter.onSignedSheetDismiss + ) { + ConnectedSheetView(title: "Request is signed") + } } .edgesIgnoringSafeArea(.all) } 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() }