Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import SwiftUI
import CrossmintClient
import Wallet

// swiftlint:disable:next type_body_length
struct DashboardView: View {
@EnvironmentObject var sdk: CrossmintSDK

Expand All @@ -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: "<email to send the otp code>",
host: "wallets-ios.demos-crossmint.com"
)

enum Tab {
case balance, transfer, nft
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,3 @@ struct OTPValidatorView: NonCustodialSignerCallbackView {
showAlert = true
}
}

#Preview {
OTPValidatorView(
nonCustodialSignerCallback: NonCustodialSignerCallback.noOp
)
.environmentObject(CrossmintSDK.shared)
}
7 changes: 2 additions & 5 deletions Sources/CrossmintClient/SwiftUI/CrossmintEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<NCSView: NonCustodialSignerCallbackView>(
_ sdk: CrossmintSDK,
@ViewBuilder ncsViewBuilder: (NonCustodialSignerCallback) -> NCSView
Expand Down
2 changes: 1 addition & 1 deletion Sources/CrossmintClient/SwiftUI/CrossmintSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions Sources/CrossmintClient/SwiftUI/View+NonCustodialSigners.swift
Original file line number Diff line number Diff line change
@@ -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<NonCustodialSignerCallback?>) {
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<OTPView: NonCustodialSignerCallbackView>: 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<NonCustodialSignerCallback?>
) -> some View {
self.modifier(
CrossmintNonCustodialSignerViewModifier(sdk: sdk, presentingCallback: presentingCallback)
)
}

public func crossmintNonCustodialSignersSheet<Content: NonCustodialSignerCallbackView>(
_ sdk: CrossmintSDK,
@ViewBuilder otpView: @escaping (NonCustodialSignerCallback) -> Content
) -> some View {
self.modifier(
CrossmintNonCustodialSignerSheetModifier(sdk: sdk, otpView: otpView)
)
}
}
11 changes: 0 additions & 11 deletions Sources/Wallet/DefaultCrossmintWallets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
5 changes: 5 additions & 0 deletions Sources/Wallet/Model/Wallet/Wallet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/Wallet/NonCustodialSignerCallback.swift
Original file line number Diff line number Diff line change
@@ -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

Expand Down
10 changes: 10 additions & 0 deletions Sources/Wallet/TEEAuthProvider.swift
Original file line number Diff line number Diff line change
@@ -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 {}
Loading