diff --git a/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/DashboardView.swift b/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/DashboardView.swift index efd2eff..8d8e801 100644 --- a/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/DashboardView.swift +++ b/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/DashboardView.swift @@ -2,7 +2,6 @@ import SwiftUI import CrossmintClient import Wallet -// swiftlint:disable:next type_body_length struct DashboardView: View { @EnvironmentObject var sdk: CrossmintSDK @@ -20,9 +19,11 @@ struct DashboardView: View { @State private var isShaking: Bool = false private let hapticFeedback = UINotificationFeedbackGenerator() - private var authManager: AuthManager { - sdk.authManager - } + // Use this instead of the email signer to enable Passkeys signing. + private let passkeySigner: EVMSigners = .passkey( + name: "", + host: "wallets-ios.demos-crossmint.com" + ) enum Tab { case balance, transfer, nft @@ -233,23 +234,10 @@ struct DashboardView: View { } private func obtainOrCreateWallet(_ updateLoadingStatus: Bool = false) async { - guard let email = await authManager.email else { - await MainActor.run { - if updateLoadingStatus { - creatingWallet = false - } - showAlert(with: "There was a problem creating the wallet.\nLogout and try again.") - } - return - } - do { let wallet = try await sdk.crossmintWallets.getOrCreateWallet( chain: .baseSepolia, - signer: .passkey( - name: email, - host: "wallets-ios.demos-crossmint.com" - ) + signer: .email ) await MainActor.run { diff --git a/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/TransferDashboardView.swift b/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/TransferDashboardView.swift index 71cfedb..68e680e 100644 --- a/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/TransferDashboardView.swift +++ b/Examples/SmartWalletsDemo/SmartWalletsDemo/Dashboard/TransferDashboardView.swift @@ -19,6 +19,8 @@ struct TransferDashboardView: View { @State private var showTokenSelectionMenu: Bool = false @State private var isSendingTransaction: Bool = false + @State private var nonCustodialSignerCallback: NonCustodialSignerCallback? + private let evmBlockchain: EVMChain = .baseSepolia var body: some View { @@ -110,6 +112,17 @@ struct TransferDashboardView: View { .padding() .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(UIColor.systemBackground)) + .crossmintNonCustodialSigners(sdk, presentingCallback: $nonCustodialSignerCallback) + .sheet(item: $nonCustodialSignerCallback) { callback in + OTPValidatorView(nonCustodialSignerCallback: callback) + } + /* + These two lines above could be replaced with if the sheet is good enough to render the OTP view: + + .crossmintNonCustodialSignersSheet(sdk, otpView: { callback in + OTPValidatorView(nonCustodialSignerCallback: callback) + }) + */ .onChange(of: balances) { _, newValue in if let newValue { // Collect all available tokens with balance diff --git a/Examples/SmartWalletsDemo/SmartWalletsDemo/NonCustodialSigners/OTPValidatorView.swift b/Examples/SmartWalletsDemo/SmartWalletsDemo/NonCustodialSigners/OTPValidatorView.swift index f7804eb..ed98ca7 100644 --- a/Examples/SmartWalletsDemo/SmartWalletsDemo/NonCustodialSigners/OTPValidatorView.swift +++ b/Examples/SmartWalletsDemo/SmartWalletsDemo/NonCustodialSigners/OTPValidatorView.swift @@ -78,10 +78,3 @@ struct OTPValidatorView: NonCustodialSignerCallbackView { showAlert = true } } - -#Preview { - OTPValidatorView( - nonCustodialSignerCallback: NonCustodialSignerCallback.noOp - ) - .environmentObject(CrossmintSDK.shared) -} diff --git a/Sources/CrossmintClient/SwiftUI/CrossmintEnvironment.swift b/Sources/CrossmintClient/SwiftUI/CrossmintEnvironment.swift index d1fa8a9..da493f4 100644 --- a/Sources/CrossmintClient/SwiftUI/CrossmintEnvironment.swift +++ b/Sources/CrossmintClient/SwiftUI/CrossmintEnvironment.swift @@ -11,13 +11,10 @@ extension View { public func crossmintEnvironmentObject( _ sdk: CrossmintSDK ) -> some View { - ZStack { - self.environmentObject(sdk) - }.onAppear { - Logger.sdk.info("Initializing the environment without non-custodial signers setup. This might cause trouble if a signer of that type is required later on.") - } + self.environmentObject(sdk) } + @available(*, deprecated, message: "Use the view modifier on the view that will be requiring non-custodial signing operations.") public func crossmintEnvironmentObject( _ sdk: CrossmintSDK, @ViewBuilder ncsViewBuilder: (NonCustodialSignerCallback) -> NCSView diff --git a/Sources/CrossmintClient/SwiftUI/CrossmintSDK.swift b/Sources/CrossmintClient/SwiftUI/CrossmintSDK.swift index a0eb319..48c1bab 100644 --- a/Sources/CrossmintClient/SwiftUI/CrossmintSDK.swift +++ b/Sources/CrossmintClient/SwiftUI/CrossmintSDK.swift @@ -34,7 +34,7 @@ final public class CrossmintSDK: ObservableObject { public let authManager: AuthManager public let crossmintService: CrossmintService - let crossmintTEE: CrossmintTEE + public let crossmintTEE: CrossmintTEE public var isProductionEnvironment: Bool { crossmintService.isProductionEnvironment diff --git a/Sources/CrossmintClient/SwiftUI/View+NonCustodialSigners.swift b/Sources/CrossmintClient/SwiftUI/View+NonCustodialSigners.swift new file mode 100644 index 0000000..1bb9306 --- /dev/null +++ b/Sources/CrossmintClient/SwiftUI/View+NonCustodialSigners.swift @@ -0,0 +1,90 @@ +import SwiftUI + +private struct HiddenEmailSignersView: View { + private var crossmintTEE: CrossmintTEE + + init(crossmintTEE: CrossmintTEE) { + self.crossmintTEE = crossmintTEE + } + + var body: some View { + EmailSignersView( + webViewCommunicationProxy: crossmintTEE.webProxy + ) + .frame(width: 1, height: 1) + .allowsHitTesting(false) + .accessibilityHidden(true) + .task { + try? await crossmintTEE.load() + } + } +} + +private struct CrossmintNonCustodialSignerViewModifier: ViewModifier { + @ObservedObject private var crossmintTEE: CrossmintTEE + @Binding private var presentingCallback: NonCustodialSignerCallback? + + init(sdk: CrossmintSDK, presentingCallback: Binding) { + crossmintTEE = sdk.crossmintTEE + _presentingCallback = presentingCallback + } + + func body(content: Content) -> some View { + ZStack { + HiddenEmailSignersView(crossmintTEE: crossmintTEE) + content + } + .onChange(of: crossmintTEE.isOTPRequired) { newValue in + if newValue { + presentingCallback = crossmintTEE.getCallback() + } else { + presentingCallback = nil + } + } + } +} + +private struct CrossmintNonCustodialSignerSheetModifier: ViewModifier { + @ObservedObject private var crossmintTEE: CrossmintTEE + private let otpView: (NonCustodialSignerCallback) -> OTPView + + @State private var callback: NonCustodialSignerCallback? + + init(sdk: CrossmintSDK, @ViewBuilder otpView: @escaping (NonCustodialSignerCallback) -> OTPView) { + crossmintTEE = sdk.crossmintTEE + self.otpView = otpView + } + + func body(content: Content) -> some View { + ZStack { + HiddenEmailSignersView(crossmintTEE: crossmintTEE) + content + } + .onChange(of: crossmintTEE.isOTPRequired) { newValue in + callback = newValue ? crossmintTEE.getCallback() : nil + } + .sheet(item: $callback) { cb in + otpView(cb) + } + } +} + +extension View { + public func crossmintNonCustodialSigners( + _ sdk: CrossmintSDK, + presentingCallback: Binding + ) -> some View { + self.modifier( + CrossmintNonCustodialSignerViewModifier(sdk: sdk, presentingCallback: presentingCallback) + ) + } + + public func crossmintNonCustodialSignersSheet( + _ sdk: CrossmintSDK, + @ViewBuilder otpView: @escaping (NonCustodialSignerCallback) -> Content + ) -> some View { + self.modifier( + CrossmintNonCustodialSignerSheetModifier(sdk: sdk, otpView: otpView) + ) + } +} diff --git a/Sources/Wallet/DefaultCrossmintWallets.swift b/Sources/Wallet/DefaultCrossmintWallets.swift index 81c43ae..f998cb2 100644 --- a/Sources/Wallet/DefaultCrossmintWallets.swift +++ b/Sources/Wallet/DefaultCrossmintWallets.swift @@ -67,17 +67,6 @@ public final class DefaultCrossmintWallets: CrossmintWallets, Sendable { throw .walletGeneric("Unknown wallet chain") } - do { - try await (signer as? any EmailSigner)?.load() - } catch { - Logger.smartWallet.warn( - """ -There was an error initializing the Email signer. \(error.errorDescription) -Review if the .crossmintEnvironmentObject modifier is used as expected. -""" - ) - } - return wallet } diff --git a/Sources/Wallet/Model/Wallet/Wallet.swift b/Sources/Wallet/Model/Wallet/Wallet.swift index 1beffe7..71439aa 100644 --- a/Sources/Wallet/Model/Wallet/Wallet.swift +++ b/Sources/Wallet/Model/Wallet/Wallet.swift @@ -229,6 +229,11 @@ open class Wallet: @unchecked Sendable { ) } } + + if let email = config.adminSigner as? any EmailSigner { + try? await email.load() + updatedSigner = email + } return updatedSigner } diff --git a/Sources/Wallet/NonCustodialSignerCallback.swift b/Sources/Wallet/NonCustodialSignerCallback.swift index a7e1f69..5cb4f88 100644 --- a/Sources/Wallet/NonCustodialSignerCallback.swift +++ b/Sources/Wallet/NonCustodialSignerCallback.swift @@ -1,4 +1,7 @@ -public struct NonCustodialSignerCallback { +import Foundation + +public struct NonCustodialSignerCallback: Identifiable { + public let id = UUID() public let otpCode: (String) -> Void public let otpCancelled: () -> Void diff --git a/Sources/Wallet/TEEAuthProvider.swift b/Sources/Wallet/TEEAuthProvider.swift new file mode 100644 index 0000000..827ba18 --- /dev/null +++ b/Sources/Wallet/TEEAuthProvider.swift @@ -0,0 +1,10 @@ +import Foundation +import Auth + +// Minimal auth abstraction for TEE usage without full AuthManager integration +public protocol TEEAuthProvider: Sendable { + var jwt: String? { get async } + var email: String? { get async } +} + +extension AuthManager where Self: TEEAuthProvider {}