diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index 4e0b9eb212..a9be02e4aa 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2150,6 +2150,8 @@ 6BAAF3472B9B245C00EFE5B2 /* ShimmerEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAAF3442B9B245C00EFE5B2 /* ShimmerEffect.swift */; }; 6BAAF3492B9B245C00EFE5B2 /* SlideButtonStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAAF3452B9B245C00EFE5B2 /* SlideButtonStyling.swift */; }; 6BAAF34B2B9B245C00EFE5B2 /* SlideButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAAF3462B9B245C00EFE5B2 /* SlideButton.swift */; }; + 6BBCE4A32BDA419200ABBD55 /* Web3Wallet in Frameworks */ = {isa = PBXBuildFile; productRef = 6BBCE4A22BDA419200ABBD55 /* Web3Wallet */; }; + 6BBCE4A52BDA419B00ABBD55 /* Web3Wallet in Frameworks */ = {isa = PBXBuildFile; productRef = 6BBCE4A42BDA419B00ABBD55 /* Web3Wallet */; }; 6BCD53002A161F4100993F20 /* BackupCloudModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD52F72A161F4100993F20 /* BackupCloudModule.swift */; }; 6BCD53012A161F4100993F20 /* BackupCloudModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD52F72A161F4100993F20 /* BackupCloudModule.swift */; }; 6BCD53022A161F4100993F20 /* ICloudBackupTermsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD52F92A161F4100993F20 /* ICloudBackupTermsViewModel.swift */; }; @@ -4940,6 +4942,7 @@ D3604E9C28F03DC00066C366 /* FeeRateKit in Frameworks */, D3C187E2290FD00E00FE1900 /* ComponentKit in Frameworks */, D339A93F29126D2A00B895BE /* HsCryptoKit in Frameworks */, + 6BBCE4A32BDA419200ABBD55 /* Web3Wallet in Frameworks */, D08C93B12B91E3B400A7D1D5 /* Hodler in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4988,6 +4991,7 @@ D3604E8228F03C6B0066C366 /* FeeRateKit in Frameworks */, D3C187D2290FCF3D00FE1900 /* ComponentKit in Frameworks */, D339A93D29126D0F00B895BE /* HsCryptoKit in Frameworks */, + 6BBCE4A52BDA419B00ABBD55 /* Web3Wallet in Frameworks */, D08C93AF2B91E39E00A7D1D5 /* Hodler in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -9260,6 +9264,7 @@ D3E323C72AE7B8E400F73914 /* KeychainAccess */, 6B55E33C2AF26D7A00616B60 /* Starscream */, D08C93B02B91E3B400A7D1D5 /* Hodler */, + 6BBCE4A22BDA419200ABBD55 /* Web3Wallet */, ); productName = Wallet; productReference = D38405CE218317DF007D50AD /* Unstoppable D.app */; @@ -9321,6 +9326,7 @@ D3E323C92AE7B8F400F73914 /* KeychainAccess */, 6B55E33A2AF26D6400616B60 /* Starscream */, D08C93AE2B91E39E00A7D1D5 /* Hodler */, + 6BBCE4A42BDA419B00ABBD55 /* Web3Wallet */, ); productName = Wallet; productReference = D38406BE21831B3D007D50AD /* Unstoppable.app */; @@ -13563,7 +13569,7 @@ repositoryURL = "https://github.com/zcash/ZcashLightClientKit"; requirement = { kind = exactVersion; - version = 2.1.3; + version = 2.1.5; }; }; D3993DAA28F42549008720FB /* XCRemoteSwiftPackageReference "WalletConnectSwiftV2" */ = { @@ -13571,7 +13577,7 @@ repositoryURL = "https://github.com/WalletConnect/WalletConnectSwiftV2"; requirement = { kind = exactVersion; - version = 1.6.10; + version = 1.18.5; }; }; D3993DC028F42992008720FB /* XCRemoteSwiftPackageReference "resolution-swift" */ = { @@ -13709,6 +13715,16 @@ package = 6B55E3392AF26D6400616B60 /* XCRemoteSwiftPackageReference "Starscream" */; productName = Starscream; }; + 6BBCE4A22BDA419200ABBD55 /* Web3Wallet */ = { + isa = XCSwiftPackageProductDependency; + package = D3993DAA28F42549008720FB /* XCRemoteSwiftPackageReference "WalletConnectSwiftV2" */; + productName = Web3Wallet; + }; + 6BBCE4A42BDA419B00ABBD55 /* Web3Wallet */ = { + isa = XCSwiftPackageProductDependency; + package = D3993DAA28F42549008720FB /* XCRemoteSwiftPackageReference "WalletConnectSwiftV2" */; + productName = Web3Wallet; + }; 6BDA29AA29D6F37C003847ED /* ECashKit */ = { isa = XCSwiftPackageProductDependency; package = 6BDA29A929D6EA9B003847ED /* XCRemoteSwiftPackageReference "ECashKit.Swift" */; diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift index d50a9b2015..fa4742eba9 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift @@ -4,6 +4,7 @@ import RxRelay import RxSwift class DeepLinkManager { + static let deepLinkScheme = "unstoppable.money" static let tonDeepLinkScheme = "ton" private let newSchemeRelay = BehaviorRelay(value: nil) @@ -24,7 +25,7 @@ extension DeepLinkManager { let path = urlComponents.path let queryItems = urlComponents.queryItems - if (scheme == "unstoppable.money" && host == "wc") || (scheme == "https" && host == "unstoppable.money" && path == "/wc"), + if (scheme == DeepLinkManager.deepLinkScheme && host == "wc") || (scheme == "https" && host == DeepLinkManager.deepLinkScheme && path == "/wc"), let uri = queryItems?.first(where: { $0.name == "uri" })?.value { newSchemeRelay.accept(.walletConnect(url: uri)) @@ -41,7 +42,7 @@ extension DeepLinkManager { } } - if scheme == "unstoppable.money", host == "coin" { + if scheme == DeepLinkManager.deepLinkScheme, host == "coin" { let uid = path.replacingOccurrences(of: "/", with: "") newSchemeRelay.accept(.coin(uid: uid)) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/Proposal/ProposalChain.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/Proposal/ProposalChain.swift index d171158542..7433d25426 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/Proposal/ProposalChain.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/Proposal/ProposalChain.swift @@ -19,9 +19,9 @@ extension Session: INamespaceProvider { var proposalNamespaces: [String: ProposalNamespace] { namespaces.reduce(into: [:]) { $0[$1.key] = ProposalNamespace( - chains: Set($1.value.accounts.compactMap { account in + chains: $1.value.accounts.compactMap { account in Blockchain(namespace: account.namespace, reference: account.reference) - }), + }, methods: $1.value.methods, events: $1.value.events ) diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainService.swift index f61d0f2f47..8aeb73729a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/Main/WalletConnectMainService.swift @@ -241,7 +241,7 @@ extension WalletConnectMainService { Task { [weak self, service, blockchains] in do { - try await service.approve(proposal: proposal, accounts: Set(accounts), methods: blockchains.methods, events: blockchains.events) + try await service.approve(proposal: proposal, accounts: accounts, methods: blockchains.methods, events: blockchains.events) } catch { self?.errorRelay.accept(error) } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift index a4dee9ece7..bf358022b4 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectService.swift @@ -11,6 +11,8 @@ import WalletConnectPairing import WalletConnectRelay import WalletConnectSign import WalletConnectUtils +import Web3Wallet +import HsCryptoKit extension Starscream.WebSocket: WebSocketConnecting {} @@ -43,39 +45,53 @@ class WalletConnectService { private var publishers = [AnyCancellable]() init(connectionService: WalletConnectSocketConnectionService, info: WalletConnectClientInfo, logger: Logger? = nil) { + let bundleIdentifier: String = Bundle.main.bundleIdentifier ?? "" + + Networking.configure( + groupIdentifier: "group.\(bundleIdentifier))", + projectId: info.projectId, + socketFactory: SocketFactory(), + socketConnectionType: .manual + ) + self.connectionService = connectionService + self.logger = logger let metadata = WalletConnectSign.AppMetadata( name: info.name, description: info.description, url: info.url, - icons: info.icons + icons: info.icons, + redirect: .init(native: DeepLinkManager.deepLinkScheme + "://", universal: nil) ) - Networking.configure(projectId: info.projectId, socketFactory: SocketFactory(), socketConnectionType: .manual) - Pair.configure(metadata: metadata) - setUpAuthSubscribing() + Web3Wallet.configure( + metadata: metadata, + crypto: DefaultCryptoProvider() + ) - connectionService.relayClient = Relay.instance + setUpAuthSubscribing() + connectionService.relayClient = Networking.instance + updateSessions() updatePairings() } func setUpAuthSubscribing() { - Sign.instance.socketConnectionStatusPublisher + Web3Wallet.instance.socketConnectionStatusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self?.didChangeSocketConnectionStatus(status) }.store(in: &publishers) - Sign.instance.sessionProposalPublisher + Web3Wallet.instance.sessionProposalPublisher .receive(on: DispatchQueue.main) .sink { [weak self] sessionProposal in self?.didReceive(sessionProposal: sessionProposal.proposal) }.store(in: &publishers) - Sign.instance.sessionSettlePublisher + Web3Wallet.instance.sessionSettlePublisher .receive(on: DispatchQueue.main) .sink { [weak self] session in self?.didSettle(session: session) @@ -87,13 +103,13 @@ class WalletConnectService { self?.didUpdate(sessionTopic: pair.sessionTopic, namespaces: pair.namespaces) }.store(in: &publishers) - Sign.instance.sessionRequestPublisher + Web3Wallet.instance.sessionRequestPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] sessionRequest in - self?.didReceive(sessionRequest: sessionRequest.request) + .sink { [weak self] pair in + self?.didReceive(sessionRequest: pair.request) }.store(in: &publishers) - Sign.instance.sessionDeletePublisher + Web3Wallet.instance.sessionDeletePublisher .receive(on: DispatchQueue.main) .sink { [weak self] tuple in self?.didDelete(sessionTopic: tuple.0, reason: tuple.1) @@ -176,7 +192,7 @@ extension WalletConnectService { // works with pending requests public var pendingRequests: [WalletConnectSign.Request] { - Sign.instance.getPendingRequests() + Web3Wallet.instance.getPendingRequests().map { $0.request } } public var pendingRequestsUpdatedObservable: Observable { @@ -185,7 +201,7 @@ extension WalletConnectService { // works with pairings public var pairings: [WalletConnectPairing.Pairing] { - Pair.instance.getPairings() + Web3Wallet.instance.getPairings() } public var pairingUpdatedObservable: Observable { @@ -196,7 +212,7 @@ extension WalletConnectService { Single.create { observer in Task { [weak self] in do { - try await Pair.instance.disconnect(topic: topic) + try await Web3Wallet.instance.disconnectPairing(topic: topic) self?.updatePairings() observer(.success(())) } catch { @@ -227,7 +243,7 @@ extension WalletConnectService { // works with dApp public func validate(uri: String) throws -> WalletConnectUtils.WalletConnectURI { - guard let uri = WalletConnectUtils.WalletConnectURI(string: uri) else { + guard let uri = try? WalletConnectUtils.WalletConnectURI(uriString: uri) else { throw WalletConnectUriHandler.ConnectionError.wrongUri } return uri @@ -236,7 +252,7 @@ extension WalletConnectService { public func pair(uri: WalletConnectUtils.WalletConnectURI) async throws { Task.init { [weak self] in do { - try await Pair.instance.pair(uri: uri) + try await Web3Wallet.instance.pair(uri: uri) self?.updatePairings() } catch { // can't pair with dApp, duplicate pairing or can't parse uri @@ -245,7 +261,7 @@ extension WalletConnectService { } } - public func approve(proposal: WalletConnectSign.Session.Proposal, accounts: Set, methods: Set, events: Set) async throws { + public func approve(proposal: WalletConnectSign.Session.Proposal, accounts: [WalletConnectUtils.Account], methods: Set, events: Set) async throws { logger?.debug("[WALLET] Approve Session: \(proposal.id)") Task { [logger] in do { @@ -254,7 +270,7 @@ extension WalletConnectService { methods: methods, events: events ) - try await Sign.instance.approve(proposalId: proposal.id, namespaces: ["eip155": eip155]) + _ = try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: ["eip155": eip155]) } catch { logger?.error("WC v2 can't approve proposal, cause: \(error.localizedDescription)") throw error @@ -265,7 +281,7 @@ extension WalletConnectService { public func reject(proposal: WalletConnectSign.Session.Proposal) async throws { logger?.debug("[WALLET] Reject Session: \(proposal.id)") do { - try await Sign.instance.reject(proposalId: proposal.id, reason: .userRejected) + try await Web3Wallet.instance.rejectSession(proposalId: proposal.id, reason: .userRejected) } catch { logger?.error("WC v2 can't reject proposal, cause: \(error.localizedDescription)") throw error @@ -275,7 +291,7 @@ extension WalletConnectService { public func disconnect(topic: String, reason _: WalletConnectSign.Reason) { Task { [weak self, logger] in do { - try await Sign.instance.disconnect(topic: topic) + try await Web3Wallet.instance.disconnect(topic: topic) self?.updateSessions() } catch { logger?.error("WC v2 can't disconnect topic, cause: \(error.localizedDescription)") @@ -301,7 +317,7 @@ extension WalletConnectService { public func reject(request: WalletConnectSign.Request) { Task { [weak self] in do { - try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .error(.init(code: 5000, message: "Reject by User"))) + try await Web3Wallet.instance.respond(topic: request.topic, requestId: request.id, response: .error(.init(code: 5000, message: "Reject by User"))) self?.pendingRequestsUpdatedRelay.accept(()) } } @@ -365,3 +381,22 @@ extension WalletConnectSign.SocketConnectionStatus { } } } + +struct DefaultCryptoProvider: CryptoProvider { + + public func recoverPubKey(signature: EthereumSignature, message: Data) throws -> Data { + let signature = Data(signature.r + signature.s + [signature.v]) + let messageHash = keccak256(message) + var pubKey = HsCryptoKit.Crypto.ellipticPublicKey(signature: signature, of: messageHash, compressed: false) + pubKey?.remove(at: 0) + + return pubKey ?? Data() + } + + public func keccak256(_ data: Data) -> Data { + let digest = SHA3(variant: .keccak256) + let hash = digest.calculate(for: [UInt8](data)) + return Data(hash) + } + +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectSocketConnectionService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectSocketConnectionService.swift index 2e4dcd473f..03e2f15389 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectSocketConnectionService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/WalletConnect/WalletConnectSocketConnectionService.swift @@ -7,6 +7,8 @@ import RxSwift import WalletConnectRelay import WalletConnectSign import WalletConnectUtils +import Web3Wallet +import UIKit class WalletConnectSocketConnectionService { private static let retryInterval = 10 @@ -20,12 +22,11 @@ class WalletConnectSocketConnectionService { private let statusRelay = PublishRelay() private(set) var status = Status.disconnected { didSet { - logger?.debug("wc v2 change socket status: \(status)") statusRelay.accept(status) } } - weak var relayClient: RelayClient? { + var relayClient: NetworkingClient? { didSet { updateClient() }