diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 401cba6b..c40c2116 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -96,13 +96,13 @@ "repositoryURL": "https://github.com/apple/swift-docc-plugin", "state": { "branch": null, - "revision": "26ac5758409154cc448d7ab82389c520fa8a8247", - "version": "1.3.0" + "revision": "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", + "version": "1.4.3" } }, { "package": "SymbolKit", - "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", + "repositoryURL": "https://github.com/swiftlang/swift-docc-symbolkit", "state": { "branch": null, "revision": "b45d1f2ed151d057b54504d653e0da5552844e34", diff --git a/Example/WalletApp/ApplicationLayer/AppDelegate.swift b/Example/WalletApp/ApplicationLayer/AppDelegate.swift index 7b3607e9..34f4e4e2 100644 --- a/Example/WalletApp/ApplicationLayer/AppDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/AppDelegate.swift @@ -11,6 +11,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { let entryPointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" // v0.7 on Sepolia let chainId = 11155111 // Sepolia SmartAccount.instance.configure(entryPoint: entryPointAddress, chainId: chainId) + SmartAccountSafe.instance.configure(entryPoint: entryPointAddress, chainId: chainId) return true } diff --git a/Example/WalletApp/BusinessLayer/SmartAccount.swift b/Example/WalletApp/BusinessLayer/SmartAccount.swift index 16d90f45..e58a0a3e 100644 --- a/Example/WalletApp/BusinessLayer/SmartAccount.swift +++ b/Example/WalletApp/BusinessLayer/SmartAccount.swift @@ -56,12 +56,100 @@ class SmartAccount { ) ) + let pickedConfig = if !(InputConfig.pimlicoBundlerUrl?.isEmpty ?? true) && !(InputConfig.rpcUrl?.isEmpty ?? true) { + pimlicoSepolia + } else { + localConfig + } + + let client = AccountClient( + ownerAddress: owner, + entryPoint: config.entryPoint, + chainId: config.chainId, + config: pickedConfig, + safe: false + ) + client.register(privateKey: privateKey) + + self.client = client + } + + + public func getClient() async -> AccountClient { + if let client = client { + return client + } + + await withCheckedContinuation { continuation in + self.clientSetContinuation = continuation + } + + return client! + } + + struct Config { + let entryPoint: String + let chainId: Int + } +} + +class SmartAccountSafe { + + static var instance = SmartAccountSafe() + + private var client: AccountClient? { + didSet { + if let _ = client { + clientSetContinuation?.resume() + } + } + } + + private var clientSetContinuation: CheckedContinuation? + + private var config: Config? + + private init() { + + } + + public func configure(entryPoint: String, chainId: Int) { + self.config = Config( + entryPoint: entryPoint, + chainId: chainId + ) + } + + public func register(owner: String, privateKey: String) { + guard let config = self.config else { + fatalError("Error - you must call SmartAccount.configure(entryPoint:chainId:onSign:) before accessing the shared instance.") + } + assert(owner.count == 40) + + let localConfig = YttriumWrapper.Config.local() + + let pimlicoBundlerUrl = "https://\(InputConfig.pimlicoBundlerUrl!)" + let rpcUrl = "https://\(InputConfig.rpcUrl!)" + let pimlicoSepolia = YttriumWrapper.Config( + endpoints: .init( + rpc: .init(baseURL: rpcUrl), + bundler: .init(baseURL: pimlicoBundlerUrl), + paymaster: .init(baseURL: pimlicoBundlerUrl) + ) + ) + + let pickedConfig = if !(InputConfig.pimlicoBundlerUrl?.isEmpty ?? true) && !(InputConfig.rpcUrl?.isEmpty ?? true) { + pimlicoSepolia + } else { + localConfig + } + let client = AccountClient( ownerAddress: owner, entryPoint: config.entryPoint, chainId: config.chainId, - config: pimlicoSepolia -// config: localConfig + config: pickedConfig, + safe: true ) client.register(privateKey: privateKey) @@ -100,7 +188,7 @@ class AccountClientMock: YttriumWrapper.AccountClientProtocol { private var config: Yttrium.Config - required init(ownerAddress: String, entryPoint: String, chainId: Int, config: Yttrium.Config) { + required init(ownerAddress: String, entryPoint: String, chainId: Int, config: Yttrium.Config, safe: Bool) { self.ownerAddress = ownerAddress self.entryPoint = entryPoint self.chainId = chainId diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift index e53fe133..dbbbfda5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift @@ -25,6 +25,10 @@ final class MainModule { owner: ownerAddress, privateKey: privateKey ) + SmartAccountSafe.instance.register( + owner: ownerAddress, + privateKey: privateKey + ) // SmartAccount.instance.register(onSign: { (messageToSign: String) in // func dataToHash(_ data: Data) -> Bytes { // let prefix = "\u{19}Ethereum Signed Message:\n" diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift index 746cdce8..9774c353 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift @@ -10,6 +10,7 @@ final class SettingsPresenter: ObservableObject { private let accountStorage: AccountStorage private var disposeBag = Set() @Published var smartAccount: String = "Loading..." + @Published var smartAccountSafe: String = "Loading..." init(interactor: SettingsInteractor, router: SettingsRouter, accountStorage: AccountStorage) { defer { setupInitialState() } @@ -17,9 +18,9 @@ final class SettingsPresenter: ObservableObject { self.router = router self.accountStorage = accountStorage fetchSmartAccount() - + fetchSmartAccountSafe() } - + func fetchSmartAccount() { Task { do { @@ -39,6 +40,26 @@ final class SettingsPresenter: ObservableObject { private func getSmartAccount() async throws -> String { try await SmartAccount.instance.getClient().getAccount().absoluteString } + + func fetchSmartAccountSafe() { + Task { + do { + let smartAccount = try await getSmartAccountSafe() + DispatchQueue.main.async { + self.smartAccountSafe = smartAccount + } + } catch { + DispatchQueue.main.async { + self.smartAccountSafe = "Failed to load" + } + print("Failed to get smart account safe: \(error)") + } + } + } + + private func getSmartAccountSafe() async throws -> String { + try await SmartAccountSafe.instance.getClient().getAccount().absoluteString + } var account: String { guard let importAccount = accountStorage.importAccount else { return .empty } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift index ecb15075..cd5084ca 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift @@ -15,6 +15,7 @@ struct SettingsView: View { header(title: "Account") row(title: "CAIP-10", subtitle: viewModel.account) row(title: "Smart Account", subtitle: viewModel.smartAccount) + row(title: "Smart Account Safe", subtitle: viewModel.smartAccountSafe) row(title: "Private key", subtitle: viewModel.privateKey) } .padding(.horizontal, 20) @@ -52,6 +53,20 @@ struct SettingsView: View { .stroke(Color.green, lineWidth: 1) ) .padding(.bottom, 24) + + AsyncButton { + try await sendTransactionSafe() + } label: { + Text("Send Transaction Safe") + .foregroundColor(.green) + .frame(maxWidth: .infinity) + } + .frame(height: 44.0) + .overlay( + RoundedRectangle(cornerRadius: Radius.m) + .stroke(Color.green, lineWidth: 1) + ) + .padding(.bottom, 24) AsyncButton { try await viewModel.logoutPressed() @@ -87,6 +102,16 @@ struct SettingsView: View { data: "0x68656c6c6f" )) } + + @discardableResult + func sendTransactionSafe() async throws -> String { + let client = await SmartAccountSafe.instance.getClient() + return try await client.sendTransaction(.init( + to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + value: "0", + data: "0x68656c6c6f" + )) + } func header(title: String) -> some View { HStack {