diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift index 01813b92f..131546b82 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift @@ -5,6 +5,9 @@ import Web3Wallet import WalletConnectRouter final class AuthRequestPresenter: ObservableObject { + enum Errors: Error { + case noCommonChains + } private let router: AuthRequestRouter let importAccount: ImportAccount @@ -73,6 +76,30 @@ final class AuthRequestPresenter: ObservableObject { } } + @MainActor + func signOne() async { + do { + ActivityIndicatorManager.shared.start() + + let auths = try buildOneAuthObject() + + _ = try await Web3Wallet.instance.approveSessionAuthenticate(requestId: request.id, auths: auths) + ActivityIndicatorManager.shared.stop() + + /* Redirect */ + if let uri = request.requester.redirect?.native { + WalletConnectRouter.goBack(uri: uri) + router.dismiss() + } else { + showSignedSheet.toggle() + } + + } catch { + ActivityIndicatorManager.shared.stop() + AlertPresenter.present(message: error.localizedDescription, type: .error) + } + } + @MainActor func reject() async { ActivityIndicatorManager.shared.start() @@ -98,34 +125,40 @@ final class AuthRequestPresenter: ObservableObject { router.dismiss() } - private func buildAuthObjects() throws -> [AuthObject] { - var auths = [AuthObject]() + private func createAuthObjectForChain(chain: Blockchain) throws -> AuthObject { + let account = Account(blockchain: chain, address: importAccount.account.address)! - try getCommonAndRequestedChainsIntersection().forEach { chain in + let supportedAuthPayload = try Web3Wallet.instance.buildAuthPayload(payload: request.payload, supportedEVMChains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!, Blockchain("eip155:69")!], supportedMethods: ["personal_sign", "eth_sendTransaction"]) - let account = Account(blockchain: chain, address: importAccount.account.address)! + let SIWEmessages = try Web3Wallet.instance.formatAuthMessage(payload: supportedAuthPayload, account: account) - var supportedAuthPayload: AuthPayload! - do { - supportedAuthPayload = try Web3Wallet.instance.buildAuthPayload(payload: request.payload, supportedEVMChains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!, Blockchain("eip155:69")!], supportedMethods: ["personal_sign", "eth_sendTransaction"]) - } catch { - Task { await reject() } - throw error - } - let SIWEmessages = try Web3Wallet.instance.formatAuthMessage(payload: supportedAuthPayload, account: account) + let signature = try messageSigner.sign(message: SIWEmessages, privateKey: Data(hex: importAccount.privateKey), type: .eip191) + + let auth = try Web3Wallet.instance.buildSignedAuthObject(authPayload: supportedAuthPayload, signature: signature, account: account) - let signature = try messageSigner.sign( - message: SIWEmessages, - privateKey: Data(hex: importAccount.privateKey), - type: .eip191) + return auth + } - let auth = try Web3Wallet.instance.buildSignedAuthObject(authPayload: supportedAuthPayload, signature: signature, account: account) + private func buildAuthObjects() throws -> [AuthObject] { + guard let chain = getCommonAndRequestedChainsIntersection().first else { + throw Errors.noCommonChains + } + + let auth = try createAuthObjectForChain(chain: chain) + return [auth] + } + private func buildOneAuthObject() throws -> [AuthObject] { + var auths = [AuthObject]() + + try getCommonAndRequestedChainsIntersection().forEach { chain in + let auth = try createAuthObjectForChain(chain: chain) auths.append(auth) } return auths } + func getCommonAndRequestedChainsIntersection() -> Set { let requestedChains: Set = Set(request.payload.chains.compactMap { Blockchain($0) }) let supportedChains: Set = [Blockchain("eip155:1")!, Blockchain("eip155:137")!] diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift index e2b81cd20..73ce87bc6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift @@ -93,20 +93,8 @@ struct AuthRequestView: View { } } - if case .scam = presenter.validationStatus { - VStack(spacing: 20) { - declineButton() - allowButton() - } - .padding(.top, 25) - } else { - HStack { - declineButton() - allowButton() - } - .padding(.top, 25) - } - + buttonGroup() + } .padding(20) @@ -241,7 +229,7 @@ struct AuthRequestView: View { presenter.approve() } } label: { - Text(presenter.validationStatus == .scam ? "Proceed anyway" : "Allow") + Text(presenter.validationStatus == .scam ? "Proceed anyway" : "Sign Multi") .frame(maxWidth: .infinity) .foregroundColor(presenter.validationStatus == .scam ? .grey50 : .white) .font(.system(size: 20, weight: .semibold, design: .rounded)) @@ -265,6 +253,50 @@ struct AuthRequestView: View { } .shadow(color: .white.opacity(0.25), radius: 8, y: 2) } + + private func signOneButton() -> some View { + Button { + Task(priority: .userInitiated) { + await presenter.signOne() + } + } label: { + Text("Sign One") + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .padding(.vertical, 11) + .background( + LinearGradient( + gradient: Gradient(colors: [.blue, .purple]), // Example gradient, adjust as needed + startPoint: .top, endPoint: .bottom + ) + ) + .cornerRadius(20) + } + .shadow(color: .white.opacity(0.25), radius: 8, y: 2) + } + + // Adjusted layout to include the signOneButton + private func buttonGroup() -> some View { + Group { + if case .scam = presenter.validationStatus { + VStack(spacing: 20) { + declineButton() + signOneButton() // Place the "Sign One" button between "Decline" and "Allow" + allowButton() + } + .padding(.top, 25) + } else { + HStack { + declineButton() + signOneButton() // Include the "Sign One" button in the horizontal stack + allowButton() + } + .padding(.top, 25) + } + } + } + } #if DEBUG diff --git a/Sources/WalletConnectSign/Auth/Services/SessionNamespaceBuilder.swift b/Sources/WalletConnectSign/Auth/Services/SessionNamespaceBuilder.swift index 3e1931910..343a66934 100644 --- a/Sources/WalletConnectSign/Auth/Services/SessionNamespaceBuilder.swift +++ b/Sources/WalletConnectSign/Auth/Services/SessionNamespaceBuilder.swift @@ -38,7 +38,7 @@ class SessionNamespaceBuilder { throw Errors.cannotCreateSessionNamespaceFromTheRecap } - let addresses = Set(cacaos.compactMap { try? DIDPKH(did: $0.p.iss).account.address }) + let addresses = getUniqueAddresses(from: cacaos) var accounts = [Account]() for address in addresses { @@ -56,4 +56,17 @@ class SessionNamespaceBuilder { return [chainsNamespace: sessionNamespace] } + func getUniqueAddresses(from cacaos: [Cacao]) -> [String] { + var seenAddresses = Set() + var uniqueAddresses = [String]() + + for cacao in cacaos { + if let address = try? DIDPKH(did: cacao.p.iss).account.address, !seenAddresses.contains(address) { + uniqueAddresses.append(address) + seenAddresses.insert(address) + } + } + return uniqueAddresses + } + } diff --git a/Tests/WalletConnectSignTests/SessionNamespaceBuilderTests.swift b/Tests/WalletConnectSignTests/SessionNamespaceBuilderTests.swift index d441cafb1..aadfd436c 100644 --- a/Tests/WalletConnectSignTests/SessionNamespaceBuilderTests.swift +++ b/Tests/WalletConnectSignTests/SessionNamespaceBuilderTests.swift @@ -66,10 +66,10 @@ class SessionNamespaceBuilderTests: XCTestCase { let expectedSessionNamespace = SessionNamespace( chains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!], accounts: [ - Account("eip155:1:0x990a10343Bcdebe21283c7172d67a9a113E819X5")!, - Account("eip155:137:0x990a10343Bcdebe21283c7172d67a9a113E819X5")!, Account("eip155:1:0x000a10343Bcdebe21283c7172d67a9a113E819C5")!, - Account("eip155:137:0x000a10343Bcdebe21283c7172d67a9a113E819C5")! + Account("eip155:137:0x000a10343Bcdebe21283c7172d67a9a113E819C5")!, + Account("eip155:1:0x990a10343Bcdebe21283c7172d67a9a113E819X5")!, + Account("eip155:137:0x990a10343Bcdebe21283c7172d67a9a113E819X5")! ], methods: Set(["personal_sign", "eth_signTypedData", "eth_sign"]), events: Set(["chainChanged", "accountsChanged"])