diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme index 14b8eeb45..204ab8061 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect-Package.xcscheme @@ -482,6 +482,90 @@ 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 c16bbe68c..8c95927cb 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -160,6 +160,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 @@ -218,7 +225,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 7be67ee89..bad0d9b54 100644 --- a/Example/DApp/Modules/Sign/SignView.swift +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -84,7 +84,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 988d32e57..1e805fb68 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -38,7 +38,9 @@ 84474A0129B9EB74005F520B /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 84474A0029B9EB74005F520B /* Starscream */; }; 84474A0229B9ECA2005F520B /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 8448F1D327E4726F0000B866 /* WalletConnect */; }; + 8454EF0A2C9421B600B5529E /* SmartAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454EF092C9421B600B5529E /* SmartAccountManager.swift */; }; 845B8D8C2934B36C0084A966 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845B8D8B2934B36C0084A966 /* Account.swift */; }; + 846CA5C82CB901F8005E8C15 /* Yttrium in Frameworks */ = {isa = PBXBuildFile; productRef = 846CA5C72CB901F8005E8C15 /* Yttrium */; }; 846E359F2C00654F00E63DF4 /* ConfigModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E359E2C00654F00E63DF4 /* ConfigModule.swift */; }; 846E35A22C0065AD00E63DF4 /* ConfigRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E35A12C0065AD00E63DF4 /* ConfigRouter.swift */; }; 846E35A42C0065B600E63DF4 /* ConfigPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E35A32C0065B600E63DF4 /* ConfigPresenter.swift */; }; @@ -47,6 +49,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 /* SmartAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84733CD92C258BDB001B2850 /* SmartAccount.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 */; }; @@ -339,6 +342,7 @@ 844511C92C8BA69D00A6A86C /* AppKitLab.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppKitLab.entitlements; sourceTree = ""; }; 844749F329B9E5B9005F520B /* RelayIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RelayIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 844749F529B9E5B9005F520B /* RelayClientEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayClientEndToEndTests.swift; sourceTree = ""; }; + 8454EF092C9421B600B5529E /* SmartAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartAccountManager.swift; sourceTree = ""; }; 845AA7D929BA1EBA00F33739 /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = IntegrationTests.xctestplan; path = ExampleApp.xcodeproj/IntegrationTests.xctestplan; sourceTree = ""; }; 845AA7DC29BB424800F33739 /* SmokeTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SmokeTests.xctestplan; sourceTree = ""; }; 845B8D8B2934B36C0084A966 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; @@ -347,6 +351,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 /* SmartAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartAccount.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 = ""; }; @@ -629,6 +634,7 @@ A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */, 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */, 8421447F2C81863A004FF494 /* ReownWalletKit in Frameworks */, + 846CA5C82CB901F8005E8C15 /* Yttrium in Frameworks */, 8421447D2C80A544004FF494 /* ReownAppKitUI in Frameworks */, 84CA52172C88965C0069BB33 /* ReownRouter in Frameworks */, ); @@ -1008,6 +1014,8 @@ isa = PBXGroup; children = ( A5D610CC2AB3592F00C20083 /* ListingsSertice */, + 84733CD92C258BDB001B2850 /* SmartAccount.swift */, + 8454EF092C9421B600B5529E /* SmartAccountManager.swift */, ); path = BusinessLayer; sourceTree = ""; @@ -1630,6 +1638,7 @@ 8421447C2C80A544004FF494 /* ReownAppKitUI */, 8421447E2C81863A004FF494 /* ReownWalletKit */, 84CA52162C88965C0069BB33 /* ReownRouter */, + 846CA5C72CB901F8005E8C15 /* Yttrium */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -1708,6 +1717,7 @@ 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */, 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */, 844511B82C8B6BC800A6A86C /* XCRemoteSwiftPackageReference "atlantis" */, + 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */, ); productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */; projectDirPath = ""; @@ -1989,6 +1999,7 @@ C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */, C56EE24F293F566D004840D1 /* WalletView.swift in Sources */, C55D34B22965FB750004314A /* SessionProposalView.swift in Sources */, + 84733CDA2C258BDB001B2850 /* SmartAccount.swift in Sources */, C56EE248293F566D004840D1 /* ScanQR.swift in Sources */, 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */, C55D349B2965BC2F0004314A /* TagsView.swift in Sources */, @@ -2020,6 +2031,7 @@ 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */, A50B6A382B06697B00162B01 /* ProfilingService.swift in Sources */, + 8454EF0A2C9421B600B5529E /* SmartAccountManager.swift in Sources */, A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, @@ -2564,6 +2576,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = ""; INFOPLIST_KEY_NSCameraUsageDescription = "Camera access for scanning QR code"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -2602,6 +2615,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = ""; INFOPLIST_KEY_NSCameraUsageDescription = "Camera access for scanning QR code"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -2755,6 +2769,14 @@ minimumVersion = 1.24.0; }; }; + 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/WalletConnect/yttrium"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.1.0; + }; + }; 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/mixpanel/mixpanel-swift"; @@ -2880,6 +2902,11 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnect; }; + 846CA5C72CB901F8005E8C15 /* Yttrium */ = { + isa = XCSwiftPackageProductDependency; + package = 846CA5C62CB901BC005E8C15 /* XCRemoteSwiftPackageReference "yttrium" */; + productName = Yttrium; + }; 8486EDD22B4F2EA6008E53C3 /* SwiftMessages */ = { isa = XCSwiftPackageProductDependency; package = 84AEC2522B4D43CD00E27A5B /* XCRemoteSwiftPackageReference "SwiftMessages" */; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 01ca2540d..866c577c3 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/ProxymanApp/atlantis", "state": { "branch": null, - "revision": "5145a7041ec71421d09653db87dcc80c81792004", - "version": "1.24.0" + "revision": "523dd773538e1e20036cb2d28f8b9947448c2d20", + "version": "1.25.1" } }, { @@ -96,8 +96,8 @@ "repositoryURL": "https://github.com/apple/swift-docc-plugin", "state": { "branch": null, - "revision": "2eb22993b3dfd0c0d32729b357c8dabb6cd44680", - "version": "1.4.2" + "revision": "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", + "version": "1.4.3" } }, { @@ -110,21 +110,21 @@ } }, { - "package": "swift-qrcode-generator", - "repositoryURL": "https://github.com/dagronf/swift-qrcode-generator", + "package": "swift-dotenv", + "repositoryURL": "https://github.com/thebarndog/swift-dotenv.git", "state": { "branch": null, - "revision": "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", - "version": "1.0.3" + "revision": "f6e7ca817d35eeccb20b62c87ee75963b01b29dc", + "version": "2.0.1" } }, { - "package": "swift-snapshot-testing", - "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing", + "package": "swift-qrcode-generator", + "repositoryURL": "https://github.com/dagronf/swift-qrcode-generator", "state": { "branch": null, - "revision": "f29e2014f6230cf7d5138fc899da51c7f513d467", - "version": "1.10.0" + "revision": "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", + "version": "1.0.3" } }, { @@ -189,6 +189,15 @@ "revision": "569255adcfff0b37e4cb8004aea29d0e2d6266df", "version": "1.0.2" } + }, + { + "package": "yttrium", + "repositoryURL": "https://github.com/WalletConnect/yttrium", + "state": { + "branch": null, + "revision": "04d1589c42c510d0f2487e58b0af9619a6f070b4", + "version": "0.1.0" + } } ] }, diff --git a/Example/Shared/InputConfig.swift b/Example/Shared/InputConfig.swift index b8ce51193..1deb5cf71 100644 --- a/Example/Shared/InputConfig.swift +++ b/Example/Shared/InputConfig.swift @@ -16,7 +16,15 @@ struct InputConfig { static var mixpanelToken: String? { return config(for: "MIXPANEL_TOKEN") } - + + static var pimlicoBundlerUrl: String? { + return config(for: "PIMLICO_BUNDLER_URL") + } + + static var rpcUrl: String? { + return config(for: "RPC_URL") + } + private static func config(for key: String) -> String? { return Bundle.main.object(forInfoDictionaryKey: key) as? String } diff --git a/Example/Shared/Signer/ETHSigner.swift b/Example/Shared/Signer/ETHSigner.swift index 232dcffc2..0d940c821 100644 --- a/Example/Shared/Signer/ETHSigner.swift +++ b/Example/Shared/Signer/ETHSigner.swift @@ -39,6 +39,24 @@ struct ETHSigner { return AnyCodable(result) } + func signHash(_ hashToSign: String) throws -> String { + + let dataToSign: Bytes + if hashToSign.hasPrefix("0x") { + // Hex-encoded message, remove "0x" and convert + let messageData = Data(hex: String(hashToSign.dropFirst(2))) + dataToSign = messageData.bytes + } else { + // Plain text message, convert directly to data + let messageData = Data(hashToSign.utf8) + dataToSign = messageData.bytes + } + + let (v, r, s) = try! privateKey.sign(hash: dataToSign) + let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) + return result + } + func signTypedData(_ params: AnyCodable) -> AnyCodable { let result = "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" return AnyCodable(result) diff --git a/Example/Shared/Signer/Signer.swift b/Example/Shared/Signer/Signer.swift index 3091c6c98..3075d82df 100644 --- a/Example/Shared/Signer/Signer.swift +++ b/Example/Shared/Signer/Signer.swift @@ -1,15 +1,87 @@ import Foundation import Commons import WalletConnectSign +import Yttrium + +struct SendCallsParams: Codable { + let version: String + let from: String + let calls: [Call] + + struct Call: Codable { + let to: String? + let value: String? + let data: String? + let chainId: String? + } +} + +enum SmartAccountType { + case simple + case safe +} final class Signer { enum Errors: Error { case notImplemented + case unknownSmartAccountType } - + private init() {} - static func sign(request: Request, importAccount: ImportAccount) throws -> AnyCodable { + static func sign(request: Request, importAccount: ImportAccount) async throws -> AnyCodable { + if let accountType = try await getRequestedSmartAccountType(request) { + return try await signWithSmartAccount(request: request, accountType: accountType) + } else { + return try signWithEOA(request: request, importAccount: importAccount) + } + } + + private static func getRequestedSmartAccountType(_ request: Request) async throws -> SmartAccountType? { + let account = try await getRequestedAccount(request) + if account == nil { + return nil + } + + let safeSmartAccountAddress = try await SmartAccountSafe.instance.getClient().getAddress() + + if account?.lowercased() == safeSmartAccountAddress.lowercased() { + return .safe + } + + return nil + } + + private static func getRequestedAccount(_ request: Request) async throws -> String? { + // 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 { + return account + } + + // 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 { + return account + } + } + // Handle the `wallet_sendCalls` method + if request.method == "wallet_sendCalls" { + if let sendCallsParams = paramsArray.first?.value as? [String: Any], + let account = sendCallsParams["from"] as? String { + return account + } + } + } + + return nil + } + + private static func signWithEOA(request: Request, importAccount: ImportAccount) throws -> AnyCodable { let signer = ETHSigner(importAccount: importAccount) switch request.method { @@ -24,9 +96,65 @@ final class Signer { case "solana_signTransaction": return SOLSigner.signTransaction(request.params) - + + default: + throw Errors.notImplemented + } + } + + private static func signWithSmartAccount(request: Request, accountType: SmartAccountType) async throws -> AnyCodable { + let client: AccountClientProtocol + switch accountType { + case .safe: + client = await SmartAccountSafe.instance.getClient() + default: + fatalError("Only safe is currently supported") + } + + switch request.method { + case "personal_sign": + let params = try request.params.get([String].self) + let message = params[0] + let signedMessage = try client.signMessage(message) + return AnyCodable(signedMessage) + + case "eth_signTypedData": + let params = try request.params.get([String].self) + let message = params[0] + let signedMessage = try client.signMessage(message) + return AnyCodable(signedMessage) + + case "eth_sendTransaction": + let params = try request.params.get([Yttrium.Transaction].self) + let result = try await client.sendTransactions(params) + return AnyCodable(result) + + case "wallet_sendCalls": + let params = try request.params.get([SendCallsParams].self) + guard let calls = params.first?.calls else { + fatalError() + } + + let transactions = calls.map { + Yttrium.Transaction( + to: $0.to!, + value: $0.value!, + data: $0.data! + ) + } + + let userOpHash = try await client.sendTransactions(transactions) + + Task { + let userOpReceipt = try await SmartAccountSafe.instance.getClient().waitForUserOperationReceipt(userOperationHash: userOpHash) + guard let userOpReceiptSting = userOpReceipt.jsonString else { return } + AlertPresenter.present(message: userOpReceiptSting, type: .info) + } + + return AnyCodable(userOpHash) + default: - throw Signer.Errors.notImplemented + throw Errors.notImplemented } } } @@ -35,6 +163,7 @@ extension Signer.Errors: LocalizedError { var errorDescription: String? { switch self { case .notImplemented: return "Requested method is not implemented" + case .unknownSmartAccountType: return "Unknown smart account type" } } } diff --git a/Example/WalletApp/ApplicationLayer/AppDelegate.swift b/Example/WalletApp/ApplicationLayer/AppDelegate.swift index f2f961b36..993235565 100644 --- a/Example/WalletApp/ApplicationLayer/AppDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/AppDelegate.swift @@ -8,6 +8,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + let entryPointAddress = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" // v0.7 on Sepolia + let chainId = 11155111 // Sepolia + SmartAccountSafe.instance.configure(entryPoint: entryPointAddress, chainId: chainId) return true } @@ -39,5 +42,4 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { ) { print("Failed to register: \(error)") } - } diff --git a/Example/WalletApp/BusinessLayer/SmartAccount.swift b/Example/WalletApp/BusinessLayer/SmartAccount.swift new file mode 100644 index 000000000..88ccede51 --- /dev/null +++ b/Example/WalletApp/BusinessLayer/SmartAccount.swift @@ -0,0 +1,94 @@ +import Foundation +import Yttrium +import WalletConnectUtils + +extension Yttrium.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 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 = Yttrium.Config.local() + + let pimlicoBundlerUrl = "https://\(InputConfig.pimlicoBundlerUrl!)" + let rpcUrl = "https://\(InputConfig.rpcUrl!)" + let pimlicoSepolia = Yttrium.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: pickedConfig, + safe: true + ) + 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 + } +} diff --git a/Example/WalletApp/BusinessLayer/SmartAccountManager.swift b/Example/WalletApp/BusinessLayer/SmartAccountManager.swift new file mode 100644 index 000000000..60fec7c37 --- /dev/null +++ b/Example/WalletApp/BusinessLayer/SmartAccountManager.swift @@ -0,0 +1,42 @@ +import Foundation + +class SmartAccountManager { + enum Errors: Error { + case smartAccountNotEnabled + } + + // Singleton instance + static let shared = SmartAccountManager() + + // Use a private queue for thread-safe access to the isSmartAccountEnabled property + private let queue = DispatchQueue(label: "com.smartaccount.manager", attributes: .concurrent) + + // A private backing variable for the thread-safe property + private var _isSmartAccountEnabled: Bool = false + + // Thread-safe access for setting and getting isSmartAccountEnabled + var isSmartAccountEnabled: Bool { + get { + return queue.sync { + _isSmartAccountEnabled + } + } + set { + queue.async(flags: .barrier) { + self._isSmartAccountEnabled = newValue + } + } + } + + // Private initializer to ensure it cannot be instantiated externally + private init() {} + + // Function to get smart account addresses + func getSmartAccountsAddresses() async throws -> [String] { + guard isSmartAccountEnabled else { + throw Errors.smartAccountNotEnabled + } + let safeAccountAddress = try await SmartAccountSafe.instance.getClient().getAddress() + return [safeAccountAddress] + } +} diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist index f542d355a..12593cb44 100644 --- a/Example/WalletApp/Other/Info.plist +++ b/Example/WalletApp/Other/Info.plist @@ -30,8 +30,12 @@ INSendMessageIntent + PIMLICO_BUNDLER_URL + $(PIMLICO_BUNDLER_URL) PROJECT_ID $(PROJECT_ID) + RPC_URL + $(RPC_URL) SIMULATOR_IDENTIFIER $(SIMULATOR_IDENTIFIER) UIApplicationSceneManifest diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift index d2186d935..06be96dd0 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainModule.swift @@ -1,4 +1,8 @@ import SwiftUI +import Web3 +import Yttrium + +let mnemonic = "test test test test test test test test test test test junk" final class MainModule { @discardableResult @@ -8,8 +12,45 @@ 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) { + let privateKey = importAccount.privateKey + let ownerAddress = String(importAccount.account.address.dropFirst(2)) + 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" +// 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/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 372a3437d..dfc484842 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -19,7 +19,7 @@ final class MainPresenter { return [ router.walletViewController(importAccount: importAccount), router.notificationsViewController(importAccount: importAccount), - router.settingsViewController() + router.settingsViewController(importAccount: importAccount) ] } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index c85eb61b9..f1ea36065 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -22,8 +22,8 @@ final class MainRouter { .wrapToNavigationController() } - func settingsViewController() -> UIViewController { - return SettingsModule.create(app: app) + func settingsViewController(importAccount: ImportAccount) -> UIViewController { + return SettingsModule.create(app: app, importAccount: importAccount) .wrapToNavigationController() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index c5580bda0..68a010e41 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift @@ -4,16 +4,26 @@ import ReownWalletKit import ReownRouter final class SessionProposalInteractor { - func approve(proposal: Session.Proposal, account: Account) async throws -> Bool { + func approve(proposal: Session.Proposal, EOAAccount: 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 } ?? [])) let supportedRequiredChains = proposal.requiredNamespaces["eip155"]?.chains ?? [] let supportedOptionalChains = proposal.optionalNamespaces?["eip155"]?.chains ?? [] - var supportedChains = supportedRequiredChains + supportedOptionalChains + var supportedChains = supportedRequiredChains + supportedOptionalChains - let supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: account.address)! } + var supportedAccounts: [Account] + var sessionProperties = [String: String]() + + if SmartAccountManager.shared.isSmartAccountEnabled { + let sepolia = Blockchain("eip155:11155111")! + let smartAccountAddresses = try await SmartAccountManager.shared.getSmartAccountsAddresses() + supportedAccounts = smartAccountAddresses.map { Account(blockchain: sepolia, address: $0)! } + sessionProperties = getSessionProperties(addresses: smartAccountAddresses) + } else { + supportedAccounts = Array(supportedChains).map { Account(blockchain: $0, address: EOAAccount.address)! } + } /* Use only supported values for production. I.e: let supportedMethods = ["eth_signTransaction", "personal_sign", "eth_signTypedData", "eth_sendTransaction", "eth_sign"] @@ -40,7 +50,8 @@ final class SessionProposalInteractor { AlertPresenter.present(message: error.localizedDescription, type: .error) return false } - _ = try await WalletKit.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) + + _ = try await WalletKit.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: sessionProperties) if let uri = proposal.proposer.redirect?.native { ReownRouter.goBack(uri: uri) return false @@ -56,4 +67,34 @@ final class SessionProposalInteractor { ReownRouter.goBack(uri: uri) } } + + private func getSessionProperties(addresses: [String]) -> [String: String] { + var addressCapabilities: [String] = [] + + // Iterate over the addresses and construct JSON strings for each address + for address in addresses { + let capability = """ + "\(address)":{ + "0xaa36a7":{ + "atomicBatch":{ + "supported":true + } + } + } + """ + addressCapabilities.append(capability) + } + + // Join all the address capabilities into one JSON-like structure + let sepoliaAtomicBatchCapabilities = "{\(addressCapabilities.joined(separator: ","))}" + + let sessionProperties: [String: String] = [ + "bundler_name": "pimlico", + "capabilities": sepoliaAtomicBatchCapabilities + ] + + print(sessionProperties) + return sessionProperties + } } + diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index c9c658c83..8060bf9d3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -36,7 +36,7 @@ final class SessionProposalPresenter: ObservableObject { func onApprove() async throws { do { ActivityIndicatorManager.shared.start() - let showConnected = try await interactor.approve(proposal: sessionProposal, account: importAccount.account) + let showConnected = try await interactor.approve(proposal: sessionProposal, EOAAccount: importAccount.account) 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 b33e79088..2d186abd6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestInteractor.swift @@ -6,7 +6,7 @@ import ReownRouter 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 WalletKit.instance.respond( topic: sessionRequest.topic, requestId: sessionRequest.id, diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift index a29152e51..04db03bfa 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsModule.swift @@ -3,10 +3,10 @@ import SwiftUI final class SettingsModule { @discardableResult - static func create(app: Application) -> UIViewController { + static func create(app: Application, importAccount: ImportAccount) -> UIViewController { let router = SettingsRouter(app: app) let interactor = SettingsInteractor() - let presenter = SettingsPresenter(interactor: interactor, router: router, accountStorage: app.accountStorage) + let presenter = SettingsPresenter(interactor: interactor, router: router, accountStorage: app.accountStorage, importAccount: importAccount) let view = SettingsView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift index 937e80b01..630080d24 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsPresenter.swift @@ -2,22 +2,50 @@ import UIKit import Combine import WalletConnectNetworking import ReownWalletKit +import Yttrium final class SettingsPresenter: ObservableObject { private let interactor: SettingsInteractor + private let importAccount: ImportAccount private let router: SettingsRouter 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) { + init(interactor: SettingsInteractor, router: SettingsRouter, accountStorage: AccountStorage, importAccount: ImportAccount) { defer { setupInitialState() } self.interactor = interactor self.router = router self.accountStorage = accountStorage + self.importAccount = importAccount + fetchSmartAccountSafe() + } + + 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)") + } + } } + func enableSmartAccount(_ enable: Bool) { + SmartAccountManager.shared.isSmartAccountEnabled = enable + } + + private func getSmartAccountSafe() async throws -> String { + try await SmartAccountSafe.instance.getClient().getAccount().absoluteString + } var account: String { guard let importAccount = accountStorage.importAccount else { return .empty } @@ -43,6 +71,26 @@ final class SettingsPresenter: ObservableObject { router.presentBrowser() } + func sendTransaction() async throws -> String { + let client = await SmartAccountSafe.instance.getClient() + + let prepareSendTransactions = try await client.prepareSendTransactions([.init( + to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + value: "0", + data: "0x68656c6c6f" + )]) + + let owner = client.ownerAddress + + let signer = ETHSigner(importAccount: importAccount) + + let signature = try signer.signHash(prepareSendTransactions.hash) + + let ownerSignature = OwnerSignature(owner: owner, signature: signature) + + return try await client.doSendTransaction(signatures: [ownerSignature], params: prepareSendTransactions.doSendTransactionParams) + } + func logoutPressed() async throws { guard let account = accountStorage.importAccount?.account else { return } try? await interactor.notifyUnregister(account: account) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift index 30835d1ab..938702d6f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Settings/SettingsView.swift @@ -2,11 +2,11 @@ import SwiftUI import AsyncButton import ReownAppKitUI -struct SettingsView: View { +struct SettingsView: View { @EnvironmentObject var viewModel: SettingsPresenter - @State private var copyAlert: Bool = false + @State private var isSmartAccountEnabled: Bool = false // State for the toggle switch var body: some View { ScrollView { @@ -16,7 +16,26 @@ struct SettingsView: View { Group { header(title: "Account") row(title: "CAIP-10", subtitle: viewModel.account) + row(title: "Smart Account Safe", subtitle: viewModel.smartAccountSafe) row(title: "Private key", subtitle: viewModel.privateKey) + + // New Smart Account Toggle Row + HStack { + Text("Smart Account") + .foregroundColor(.Foreground100) + .font(.paragraph700) + + Spacer() + + Toggle("", isOn: $isSmartAccountEnabled) + .onChange(of: isSmartAccountEnabled) { newValue in + viewModel.enableSmartAccount(newValue) + } + .labelsHidden() + } + .padding(.horizontal, 12) + .padding(.vertical, 16) + .background(Color.Foreground100.opacity(0.05).cornerRadius(12)) } .padding(.horizontal, 20) @@ -40,6 +59,20 @@ struct SettingsView: View { } .frame(height: 44.0) + 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() } label: { @@ -62,9 +95,15 @@ struct SettingsView: View { } .onAppear { viewModel.objectWillChange.send() + isSmartAccountEnabled = SmartAccountManager.shared.isSmartAccountEnabled } } + @discardableResult + func sendTransactionSafe() async throws -> String { + try await viewModel.sendTransaction() + } + func header(title: String) -> some View { HStack { Text(title) @@ -108,10 +147,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.resolved b/Package.resolved index acfc8dd9a..64598b065 100644 --- a/Package.resolved +++ b/Package.resolved @@ -28,6 +28,15 @@ "version": "1.0.0" } }, + { + "package": "swift-dotenv", + "repositoryURL": "https://github.com/thebarndog/swift-dotenv.git", + "state": { + "branch": null, + "revision": "f6e7ca817d35eeccb20b62c87ee75963b01b29dc", + "version": "2.0.1" + } + }, { "package": "swift-qrcode-generator", "repositoryURL": "https://github.com/dagronf/swift-qrcode-generator", @@ -37,6 +46,15 @@ "version": "1.0.3" } }, + { + "package": "swift-snapshot-testing", + "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing", + "state": { + "branch": null, + "revision": "f29e2014f6230cf7d5138fc899da51c7f513d467", + "version": "1.10.0" + } + }, { "package": "SwiftImageReadWrite", "repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite", @@ -45,6 +63,15 @@ "revision": "5596407d1cf61b953b8e658fa8636a471df3c509", "version": "1.1.6" } + }, + { + "package": "CoinbaseWalletSDK", + "repositoryURL": "https://github.com/WalletConnect/wallet-mobile-sdk", + "state": { + "branch": null, + "revision": "b6dfb7d6b8447c7c5b238a10443a1ac28223f38f", + "version": "1.0.0" + } } ] }, diff --git a/Package.swift b/Package.swift index 3a37d537d..0ae1fb5af 100644 --- a/Package.swift +++ b/Package.swift @@ -2,6 +2,38 @@ import PackageDescription +// Determine if Yttrium should be used in debug (local) mode +let yttriumDebug = false + + +// Define dependencies array +var dependencies: [Package.Dependency] = [ + .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(name: "CoinbaseWalletSDK", url: "https://github.com/MobileWalletProtocol/wallet-mobile-sdk", .upToNextMinor(from: "1.0.0")), +// .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", .upToNextMinor(from: "1.10.0")), +] +//var yttriumTarget: Target! +//// Conditionally add Yttrium dependency +//if yttriumDebug { +// var yttriumSwiftSettings: [SwiftSetting] = [] +// dependencies.append(.package(path: "../yttrium/crates/ffi/YttriumCore")) +// yttriumSwiftSettings.append(.define("YTTRIUM_DEBUG")) +// yttriumTarget = .target( +// name: "YttriumWrapper", +// dependencies: [.product(name: "YttriumCore", package: "YttriumCore")], +// path: "Sources/YttriumWrapper", +// swiftSettings: yttriumSwiftSettings +// ) +//} else { +// dependencies.append(.package(url: "https://github.com/reown-com/yttrium", .upToNextMinor(from: "0.1.0"))) +// yttriumTarget = .target( +// name: "YttriumWrapper", +// dependencies: [.product(name: "Yttrium", package: "yttrium")], +// path: "Sources/YttriumWrapper" +// ) +//} + let package = Package( name: "reown", platforms: [ @@ -42,14 +74,12 @@ let package = Package( targets: ["ReownAppKit"]), .library( name: "ReownAppKitUI", - targets: ["ReownAppKitUI"]) - ], - 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(name: "CoinbaseWalletSDK", url: "https://github.com/MobileWalletProtocol/wallet-mobile-sdk", .upToNextMinor(from: "1.0.0")), - .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", .upToNextMinor(from: "1.10.0")), + targets: ["ReownAppKitUI"]), +// .library( +// name: "YttriumWrapper", +// targets: ["YttriumWrapper"]) ], + dependencies: dependencies, targets: [ .target( name: "WalletConnectSign", @@ -156,6 +186,7 @@ let package = Package( name: "ReownAppKitBackport", path: "Sources/ReownAppKitBackport" ), +// yttriumTarget, .testTarget( name: "WalletConnectSignTests", dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]), diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index ceda7ba04..07ed632d7 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -3,13 +3,13 @@ import Combine public class Serializer: Serializing { - enum Errors: Error, CustomStringConvertible { + public enum Errors: Error, CustomStringConvertible { case symmetricKeyForTopicNotFound(String) case publicKeyForTopicNotFound case invalidType2Envelope case topicNotFound - var description: String { + public var description: String { switch self { case .symmetricKeyForTopicNotFound(let topic): return "Error: Symmetric key for topic '\(topic)' was not found." diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index f212de827..1f6b5d28d 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.0.4"} +{"version": "1.0.5"} diff --git a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift index a1c617e49..990e1ed8a 100644 --- a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift +++ b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift @@ -22,10 +22,20 @@ class DeleteSessionService { let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message) logger.debug("Will delete session for reason: message: \(reason.message) code: \(reason.code)") let request = RPCRequest(method: protocolMethod.method, params: reason) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + + do { + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + } catch let error as Serializer.Errors { + switch error { + case .symmetricKeyForTopicNotFound, .publicKeyForTopicNotFound: + logger.debug("Encountered Serializer error during session deletion: \(error)") + default: + throw error + } + } + sessionStore.delete(topic: topic) logger.debug("Session disconnected") kms.deleteSymmetricKey(for: topic) networkingInteractor.unsubscribe(topic: topic) - } -} + }} 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..b37426a43 --- /dev/null +++ b/Sources/YttriumWrapper/YttriumWrapper.swift @@ -0,0 +1,7 @@ +import Foundation + +#if YTTRIUM_DEBUG +@_exported import YttriumCore +#else +@_exported import Yttrium +#endif