diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme index 14b8eeb45..058e64dd2 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme @@ -482,6 +482,48 @@ ReferencedContainer = "container:"> + + + + + + + + + + + + + + + + Bool { // Override point for customization after application launch. + return true } diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift index d861b6ceb..ce02d82c9 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountInteractor.swift @@ -6,7 +6,10 @@ import WalletConnectSign struct AccountDetails { let chain: String let methods: [String] - let account: String + let address: String + var account: String { + "\(chain):\(address)" + } } diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift index e89ea8a8e..09c318daf 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountPresenter.swift @@ -70,7 +70,7 @@ final class SessionAccountPresenter: ObservableObject { } func copyUri() { - UIPasteboard.general.string = sessionAccount.account + UIPasteboard.general.string = sessionAccount.address } } @@ -128,17 +128,17 @@ extension SessionAccountPresenter.Errors: LocalizedError { // MARK: - Transaction Stub private enum Stub { struct Transaction: Codable { - let from, to, data, gas: String + let from, to, data, gasLimit: String let gasPrice, value, nonce: String } static let tx = [Transaction(from: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", to: "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", - data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", - gas: "0x76c0", - gasPrice: "0x9184e72a000", - value: "0x9184e72a", - nonce: "0x117")] + data: "0x", + gasLimit: "0x5208", + gasPrice: "0x013e3d2ed4", + value: "0x00", + nonce: "0x09")] static let eth_signTypedData = """ { "types": { diff --git a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift index 939a9edb6..247bc800a 100644 --- a/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift +++ b/Example/DApp/Modules/Sign/SessionAccount/SessionAccountView.swift @@ -16,7 +16,7 @@ struct SessionAccountView: View { ScrollView { VStack(spacing: 12) { networkView(title: String(presenter.sessionAccount.chain.split(separator: ":").first ?? "")) - accountView(address: presenter.sessionAccount.account) + accountView(address: presenter.sessionAccount.address) methodsView(methods: presenter.sessionAccount.methods) Spacer() diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 0b2e86e7d..86f10339b 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -169,6 +169,13 @@ extension SignPresenter { } .store(in: &subscriptions) + Sign.instance.sessionSettlePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] _ in + self.getSession() + } + .store(in: &subscriptions) + Sign.instance.authResponsePublisher .receive(on: DispatchQueue.main) .sink { [unowned self] response in @@ -227,7 +234,7 @@ extension SignPresenter { AccountDetails( chain: account.blockchainIdentifier, methods: Array(namespace.methods), - account: account.address + address: account.address ) ) } diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift index 03b4003a9..b080ee2eb 100644 --- a/Example/DApp/Modules/Sign/SignView.swift +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -96,7 +96,7 @@ struct SignView: View { .padding(12) } else { VStack { - ForEach(presenter.accountsDetails, id: \.chain) { account in + ForEach(presenter.accountsDetails, id: \.account) { account in Button { presenter.presentSessionAccount(sessionAccount: account) } label: { diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 2447d9efa..29561390d 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 61DAC4AB2C65897800BAF3F8 /* YttriumWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 61DAC4AA2C65897800BAF3F8 /* YttriumWrapper */; }; 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */; }; 84310D05298BC980000C15B6 /* MainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84310D04298BC980000C15B6 /* MainInteractor.swift */; }; 8439CB89293F658E00F2F2E2 /* PushMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8439CB88293F658E00F2F2E2 /* PushMessage.swift */; }; @@ -27,6 +28,7 @@ 84733CD32C1C2A4B001B2850 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AEC2502B4D42C100E27A5B /* AlertPresenter.swift */; }; 84733CD42C1C2C24001B2850 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50B6A372B06697B00162B01 /* ProfilingService.swift */; }; 84733CD52C1C2CEB001B2850 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE25D293F56D6004840D1 /* InputConfig.swift */; }; + 84733CDA2C258BDB001B2850 /* AccountMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84733CD92C258BDB001B2850 /* AccountMock.swift */; }; 847BD1D62989492500076C90 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D12989492500076C90 /* MainViewController.swift */; }; 847BD1D82989492500076C90 /* MainModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D32989492500076C90 /* MainModule.swift */; }; 847BD1D92989492500076C90 /* MainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D42989492500076C90 /* MainPresenter.swift */; }; @@ -408,6 +410,7 @@ 846E35A32C0065B600E63DF4 /* ConfigPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigPresenter.swift; sourceTree = ""; }; 846E35A52C0065C100E63DF4 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; }; 846E35A72C006C5600E63DF4 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 84733CD92C258BDB001B2850 /* AccountMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMock.swift; sourceTree = ""; }; 847BD1D12989492500076C90 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 847BD1D32989492500076C90 /* MainModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainModule.swift; sourceTree = ""; }; 847BD1D42989492500076C90 /* MainPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainPresenter.swift; sourceTree = ""; }; @@ -763,6 +766,7 @@ A59D25EE2AB3672700D7EA3A /* AsyncButton in Frameworks */, C5133A78294125CC00A8314C /* Web3 in Frameworks */, C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */, + 61DAC4AB2C65897800BAF3F8 /* YttriumWrapper in Frameworks */, 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */, C55D349929630D440004314A /* Web3Wallet in Frameworks */, C56EE255293F569A004840D1 /* Starscream in Frameworks */, @@ -1438,6 +1442,7 @@ isa = PBXGroup; children = ( A5D610CC2AB3592F00C20083 /* ListingsSertice */, + 84733CD92C258BDB001B2850 /* AccountMock.swift */, ); path = BusinessLayer; sourceTree = ""; @@ -2068,6 +2073,7 @@ C54C248F2AEB1B5600DA4BF6 /* WalletConnectRouter */, 84AEC2532B4D43CD00E27A5B /* SwiftMessages */, 84F391F92BA87CEB00FDC20A /* Web3ModalUI */, + 61DAC4AA2C65897800BAF3F8 /* YttriumWrapper */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -2495,6 +2501,7 @@ C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */, C56EE24F293F566D004840D1 /* WalletView.swift in Sources */, C55D34B22965FB750004314A /* SessionProposalView.swift in Sources */, + 84733CDA2C258BDB001B2850 /* AccountMock.swift in Sources */, C56EE248293F566D004840D1 /* ScanQR.swift in Sources */, 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */, C55D349B2965BC2F0004314A /* TagsView.swift in Sources */, @@ -3324,6 +3331,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 61DAC4AA2C65897800BAF3F8 /* YttriumWrapper */ = { + isa = XCSwiftPackageProductDependency; + productName = YttriumWrapper; + }; 844749FC29B9E6B2005F520B /* WalletConnectNetworking */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectNetworking; diff --git a/Example/Shared/Signer/Signer.swift b/Example/Shared/Signer/Signer.swift index 3091c6c98..819c909dd 100644 --- a/Example/Shared/Signer/Signer.swift +++ b/Example/Shared/Signer/Signer.swift @@ -1,6 +1,7 @@ import Foundation import Commons import WalletConnectSign +import YttriumWrapper final class Signer { enum Errors: Error { @@ -9,7 +10,39 @@ final class Signer { private init() {} - static func sign(request: Request, importAccount: ImportAccount) throws -> AnyCodable { + static func sign(request: Request, importAccount: ImportAccount) async throws -> AnyCodable { + if try await didRequestSmartAccount(request) { + return try await signWithSmartAccount(request: request) + } else { + return try signWithEOA(request: request, importAccount: importAccount) + } + } + + + private static func didRequestSmartAccount(_ request: Request) async throws -> Bool { + // Attempt to decode params for transaction requests encapsulated in an array of dictionaries + if let paramsArray = try? request.params.get([AnyCodable].self), + let firstParam = paramsArray.first?.value as? [String: Any], + let account = firstParam["from"] as? String { + let smartAccountAddress = try await SmartAccount.instance.getAddress() + return account.lowercased() == smartAccountAddress.lowercased() + } + + // Attempt to decode params for signing message requests + if let paramsArray = try? request.params.get([AnyCodable].self) { + if request.method == "personal_sign" || request.method == "eth_signTypedData" { + // Typically, the account address is the second parameter for personal_sign and eth_signTypedData + if paramsArray.count > 1, + let account = paramsArray[1].value as? String { + let smartAccountAddress = try await SmartAccount.instance.getAddress() + return account.lowercased() == smartAccountAddress.lowercased() + } + } + } + + return false + } + private static func signWithEOA(request: Request, importAccount: ImportAccount) throws -> AnyCodable { let signer = ETHSigner(importAccount: importAccount) switch request.method { @@ -24,11 +57,41 @@ final class Signer { case "solana_signTransaction": return SOLSigner.signTransaction(request.params) - + default: throw Signer.Errors.notImplemented } } + + + private static func signWithSmartAccount(request: Request) async throws -> AnyCodable { + switch request.method { + case "personal_sign": + let params = try request.params.get([String].self) + let message = params[0] + let signed = try SmartAccount.mockInstance.signMessage(message) + return AnyCodable(signed) + + case "eth_signTypedData": + let params = try request.params.get([String].self) + let message = params[0] + let signed = try SmartAccount.mockInstance.signMessage(message) + return AnyCodable(signed) + + case "eth_sendTransaction": + let params = try request.params.get([YttriumWrapper.Transaction].self) + let transaction = params[0] + let result = try await SmartAccount.mockInstance.sendTransaction(transaction) + return AnyCodable(result) + + // case "wallet_sendCalls": + // let transactions = + // return try await SmartAccount.mockInstance.sendBatchTransaction(<#T##batch: [Transaction]##[Transaction]#>) + + default: + throw Signer.Errors.notImplemented + } + } } extension Signer.Errors: LocalizedError { @@ -38,3 +101,4 @@ extension Signer.Errors: LocalizedError { } } } + diff --git a/Example/WalletApp/ApplicationLayer/AppDelegate.swift b/Example/WalletApp/ApplicationLayer/AppDelegate.swift index f2f961b36..0c125d80f 100644 --- a/Example/WalletApp/ApplicationLayer/AppDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/AppDelegate.swift @@ -8,6 +8,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + SmartAccount.configure(entryPoint: "", chainId: 11155111) return true } diff --git a/Example/WalletApp/BusinessLayer/AccountMock.swift b/Example/WalletApp/BusinessLayer/AccountMock.swift new file mode 100644 index 000000000..f28bf893b --- /dev/null +++ b/Example/WalletApp/BusinessLayer/AccountMock.swift @@ -0,0 +1,101 @@ +import Foundation +import YttriumWrapper +import WalletConnectUtils + +class AccountClientMock: YttriumWrapper.AccountClientProtocol { + + var onSign: OnSign? + + var chainId: Int + + required init(entryPoint: String, chainId: Int, onSign: OnSign?) { + self.chainId = chainId + self.onSign = onSign + } + + // prepares UserOp + func sendTransaction(_ transaction: YttriumWrapper.Transaction) async throws -> String { + guard let onSign = onSign else { + fatalError("Error - onSign closure must be set before calling signMessage") + } + let _ = onSign("UserOp") + return "txHash" + } + + func sendBatchTransaction(_ batch: [YttriumWrapper.Transaction]) async throws -> String { + return "userOpReceipt" + } + + func getAddress() async throws -> String { + return "0xF4D7560648F1252FD7501863355AEaBfb9d3b7c3" + } + + func getAccount() async throws -> Account { + let chain = try Blockchain(namespace: "eip155", reference: chainId) + let address = try await getAddress() + return try Account(blockchain: chain, accountAddress: address) + } + + func signMessage(_ message: String) throws -> String { + guard let onSign = onSign else { + fatalError("Error - onSign closure must be set before calling signMessage") + } + return try! onSign(message).get() + } +} + +extension YttriumWrapper.AccountClient { + + func getAccount() async throws -> Account { + let chain = try Blockchain(namespace: "eip155", reference: chainId) + let address = try await getAddress() + return try Account(blockchain: chain, accountAddress: address) + } +} + +class SmartAccount { + static var mockInstance: AccountClientMock = { + guard let config = SmartAccount.config else { + fatalError("Error - you must call SmartAccount.configure(entryPoint:chainId:onSign:) before accessing the shared instance.") + } + return AccountClientMock( + entryPoint: config.entryPoint, + chainId: config.chainId, + onSign: config.onSign + ) + }() + + static var instance: AccountClient = { + guard let config = SmartAccount.config else { + fatalError("Error - you must call SmartAccount.configure(entryPoint:chainId:onSign:) before accessing the shared instance.") + } + return AccountClient( + entryPoint: config.entryPoint, + chainId: config.chainId, + onSign: config.onSign + ) + }() + + private static var config: Config? + + private init() {} + + struct Config { + let entryPoint: String + let chainId: Int + var onSign: OnSign? + } + + /// SmartAccount instance config method + /// - Parameters: + /// - entryPoint: Entry point + /// - chainId: Chain ID + /// - onSign: Closure for signing messages (optional) + static public func configure( + entryPoint: String, + chainId: Int, + onSign: OnSign? = nil + ) { + SmartAccount.config = Config(entryPoint: entryPoint, chainId: chainId, onSign: onSign) + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift index d2186d935..d2534dffb 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift @@ -1,4 +1,5 @@ import SwiftUI +import Web3 final class MainModule { @discardableResult @@ -8,8 +9,39 @@ final class MainModule { let presenter = MainPresenter(router: router, interactor: interactor, importAccount: importAccount, pushRegisterer: app.pushRegisterer, configurationService: app.configurationService) let viewController = MainViewController(presenter: presenter) + configureSmartAccountOnSign(importAccount: importAccount) router.viewController = viewController return viewController } + + static func configureSmartAccountOnSign(importAccount: ImportAccount) { + SmartAccount.instance.onSign = { messageToSign in + func dataToHash(_ data: Data) -> Bytes { + let prefix = "\u{19}Ethereum Signed Message:\n" + let prefixData = (prefix + String(data.count)).data(using: .utf8)! + let prefixedMessageData = prefixData + data + return .init(hex: prefixedMessageData.toHexString()) + } + + let prvKey = try! EthereumPrivateKey(hexPrivateKey: importAccount.privateKey) + + // Determine if the message is hex-encoded or plain text + let dataToSign: Bytes + if messageToSign.hasPrefix("0x") { + // Hex-encoded message, remove "0x" and convert + let messageData = Data(hex: String(messageToSign.dropFirst(2))) + dataToSign = dataToHash(messageData) + } else { + // Plain text message, convert directly to data + let messageData = Data(messageToSign.utf8) + dataToSign = dataToHash(messageData) + } + + // Sign the data + let (v, r, s) = try! prvKey.sign(message: .init(Data(dataToSign))) + let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) + return .success(result) + } + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index ceeef4bc2..155192e9e 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 -> Bool { + func approve(proposal: Session.Proposal, EOAAccount: Account, smartAccount: 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 } ?? [])) @@ -13,7 +13,9 @@ final class SessionProposalInteractor { let supportedOptionalChains = proposal.optionalNamespaces?["eip155"]?.chains ?? [] var supportedChains = supportedRequiredChains + supportedOptionalChains - let supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: account.address)! } + var supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: EOAAccount.address)! } + + supportedAccounts.append(smartAccount) /* Use only supported values for production. I.e: let supportedMethods = ["eth_signTransaction", "personal_sign", "eth_signTypedData", "eth_sendTransaction", "eth_sign"] diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index e494039a2..8381e4c4d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -36,7 +36,8 @@ final class SessionProposalPresenter: ObservableObject { func onApprove() async throws { do { ActivityIndicatorManager.shared.start() - let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account) + let smartAccount = try await SmartAccount.instance.getAccount() + let showConnected = try await interactor.approve(proposal: sessionProposal, EOAAccount: importAccount.account, smartAccount: smartAccount) showConnected ? showConnectedSheet.toggle() : router.dismiss() ActivityIndicatorManager.shared.stop() } catch { diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift index edc2ed4df..93335feaa 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift @@ -6,7 +6,7 @@ import WalletConnectRouter final class SessionRequestInteractor { func respondSessionRequest(sessionRequest: Request, importAccount: ImportAccount) async throws -> Bool { do { - let result = try Signer.sign(request: sessionRequest, importAccount: importAccount) + let result = try await Signer.sign(request: sessionRequest, importAccount: importAccount) try await Web3Wallet.instance.respond( topic: sessionRequest.topic, requestId: sessionRequest.id, diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift index 87a048768..6be579ffa 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift @@ -8,16 +8,37 @@ final class SettingsPresenter: ObservableObject { private let interactor: SettingsInteractor private let router: SettingsRouter private let accountStorage: AccountStorage - private var disposeBag = Set() + @Published var smartAccount: String = "Loading..." init(interactor: SettingsInteractor, router: SettingsRouter, accountStorage: AccountStorage) { defer { setupInitialState() } self.interactor = interactor self.router = router self.accountStorage = accountStorage + fetchSmartAccount() + + } + + func fetchSmartAccount() { + Task { + do { + let smartAccount = try await getSmartAccount() + DispatchQueue.main.async { + self.smartAccount = smartAccount + } + } catch { + DispatchQueue.main.async { + self.smartAccount = "Failed to load" + } + print("Failed to get smart account: \(error)") + } + } } + private func getSmartAccount() async throws -> String { + return try await SmartAccount.instance.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 73a5fdbcd..d4547b3c6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift @@ -3,9 +3,7 @@ import AsyncButton import Web3ModalUI struct SettingsView: View { - @EnvironmentObject var viewModel: SettingsPresenter - @State private var copyAlert: Bool = false var body: some View { @@ -16,6 +14,7 @@ struct SettingsView: View { Group { header(title: "Account") row(title: "CAIP-10", subtitle: viewModel.account) + row(title: "Smart Account", subtitle: viewModel.smartAccount) row(title: "Private key", subtitle: viewModel.privateKey) } .padding(.horizontal, 20) @@ -108,10 +107,10 @@ struct SettingsView: View { } func separator() -> some View { - Rectangle() - .foregroundColor(.Foreground100.opacity(0.05)) - .frame(maxWidth: .infinity) - .frame(height: 1) - .padding(.top, 8) + Rectangle() + .foregroundColor(.Foreground100.opacity(0.05)) + .frame(maxWidth: .infinity) + .frame(height: 1) + .padding(.top, 8) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift index f97cfe29f..c5480c919 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Welcome/WelcomePresenter.swift @@ -24,9 +24,9 @@ final class WelcomePresenter: ObservableObject { func onImport() { guard let account = ImportAccount(input: input) else { return input = .empty } - importAccount(account) } + } // MARK: Private functions diff --git a/Package.swift b/Package.swift index b255c6b99..6031d73a0 100644 --- a/Package.swift +++ b/Package.swift @@ -43,10 +43,14 @@ let package = Package( .library( name: "WalletConnectIdentity", targets: ["WalletConnectIdentity"]), + .library( + name: "YttriumWrapper", + targets: ["YttriumWrapper"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"), - .package(url: "https://github.com/WalletConnect/QRCode", from: "14.3.1") + .package(url: "https://github.com/WalletConnect/QRCode", from: "14.3.1"), + .package(path: "../yttrium") ], targets: [ .target( @@ -136,6 +140,13 @@ let package = Package( .process("Resources/PrivacyInfo.xcprivacy"), ] ), + .target( + name: "YttriumWrapper", + dependencies: [ + .productItem(name: "Yttrium", package: "yttrium") + ], + path: "Sources/YttriumWrapper" + ), .testTarget( name: "WalletConnectSignTests", dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]), diff --git a/README.md b/README.md index e07ccaa25..2048a8dce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Wallet Connect v.2 - Swift +# WalletConnect v.2 - Swift ![CI main](https://github.com/WalletConnect/WalletConnectSwiftV2/actions/workflows/ci.yml/badge.svg?branch=main) ![CI develop](https://github.com/WalletConnect/WalletConnectSwiftV2/actions/workflows/ci.yml/badge.svg?branch=develop) @@ -58,4 +58,4 @@ Apache 2.0 ## Guides -- [Artifacts sometimes not available in Actions -> Build name -> Artifacts?](./docs/guides/downloading_artifacts.md) \ No newline at end of file +- [Artifacts sometimes not available in Actions -> Build name -> Artifacts?](./docs/guides/downloading_artifacts.md) diff --git a/Sources/WalletConnectUtils/Account.swift b/Sources/WalletConnectUtils/Account.swift index 7d5c2cbee..eb1b7615d 100644 --- a/Sources/WalletConnectUtils/Account.swift +++ b/Sources/WalletConnectUtils/Account.swift @@ -11,7 +11,9 @@ [CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md */ public struct Account: Equatable, Hashable { - + public enum Errors: Error { + case invalidInitFormat + } /// A blockchain namespace. Usually describes an ecosystem or standard. public let namespace: String @@ -71,6 +73,13 @@ public struct Account: Equatable, Hashable { public init?(blockchain: Blockchain, address: String) { self.init("\(blockchain.absoluteString):\(address)") } + + public init(blockchain: Blockchain, accountAddress: String) throws { + guard let instance = Self("\(blockchain.absoluteString):\(accountAddress)") else { + throw Errors.invalidInitFormat + } + self = instance + } } extension Account: LosslessStringConvertible { diff --git a/Sources/WalletConnectUtils/Blockchain.swift b/Sources/WalletConnectUtils/Blockchain.swift index 8d920f01d..9e839bdb9 100644 --- a/Sources/WalletConnectUtils/Blockchain.swift +++ b/Sources/WalletConnectUtils/Blockchain.swift @@ -9,7 +9,9 @@ [CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md */ public struct Blockchain: Equatable, Hashable { - + public enum Errors: Error { + case invalidInitFormat + } /// A blockchain namespace. Usually describes an ecosystem or standard. public let namespace: String @@ -43,6 +45,13 @@ public struct Blockchain: Equatable, Hashable { public init?(namespace: String, reference: String) { self.init("\(namespace):\(reference)") } + + public init(namespace: String, reference: Int) throws { + guard let instance = Self("\(namespace):\(reference)") else { + throw Errors.invalidInitFormat + } + self = instance + } } extension Blockchain: LosslessStringConvertible { diff --git a/Sources/YttriumWrapper/YttriumWrapper.swift b/Sources/YttriumWrapper/YttriumWrapper.swift new file mode 100644 index 000000000..d7d761cd4 --- /dev/null +++ b/Sources/YttriumWrapper/YttriumWrapper.swift @@ -0,0 +1,3 @@ +import Foundation + +@_exported import Yttrium