diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 9d9c23300..d47aa4129 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -2,9 +2,9 @@ name: Swift on: push: - branches: [ main ] + branches: [ main, develop ] pull_request: - branches: [ main ] + branches: [ main, develop ] jobs: build: diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 6db21cbfb..dc0b3c745 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ 8460DCFC274F98A10081F94C /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DCFB274F98A10081F94C /* RequestViewController.swift */; }; 8460DD002750D6F50081F94C /* SessionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DCFF2750D6F50081F94C /* SessionDetailsViewController.swift */; }; 8460DD022750D7020081F94C /* SessionDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DD012750D7020081F94C /* SessionDetailsView.swift */; }; + 84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; + 84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -66,6 +68,8 @@ 8460DCFB274F98A10081F94C /* RequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = ""; }; 8460DCFF2750D6F50081F94C /* SessionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailsViewController.swift; sourceTree = ""; }; 8460DD012750D7020081F94C /* SessionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailsView.swift; sourceTree = ""; }; + 84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; + 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -92,6 +96,8 @@ isa = PBXGroup; children = ( 761C649B26FB7B7F004239D1 /* ResponderViewController.swift */, + 84F568C32795832A00D0A289 /* EthereumTransaction.swift */, + 84F568C1279582D200D0A289 /* Signer.swift */, 84494387278D9C1B00CC26BB /* UIAlertController.swift */, 76744CF426FDFB6B00B77ED9 /* ResponderView.swift */, 76744CF826FE4D7400B77ED9 /* ActiveSessionCell.swift */, @@ -309,10 +315,12 @@ 761C649E26FB7FD7004239D1 /* SessionViewController.swift in Sources */, 76744CF726FE4D5400B77ED9 /* ActiveSessionItem.swift in Sources */, 764E1D4226F8D3FC00A1FB15 /* SceneDelegate.swift in Sources */, + 84F568C2279582D200D0A289 /* Signer.swift in Sources */, 8460DD022750D7020081F94C /* SessionDetailsView.swift in Sources */, 7603D74D2703429A00DD27A2 /* ProposerView.swift in Sources */, 764E1D5A26F8DF1B00A1FB15 /* ScannerViewController.swift in Sources */, 84494388278D9C1B00CC26BB /* UIAlertController.swift in Sources */, + 84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -469,7 +477,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; INFOPLIST_FILE = ExampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -493,7 +501,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; INFOPLIST_FILE = ExampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/Example/ExampleApp/Info.plist b/Example/ExampleApp/Info.plist index a1328e4b8..1fa4cd501 100644 --- a/Example/ExampleApp/Info.plist +++ b/Example/ExampleApp/Info.plist @@ -20,12 +20,12 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS NSCameraUsageDescription Allow the app to scan for QR codes - ITSAppUsesNonExemptEncryption - UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Example/ExampleApp/Responder/EthereumTransaction.swift b/Example/ExampleApp/Responder/EthereumTransaction.swift new file mode 100644 index 000000000..a19bbaae6 --- /dev/null +++ b/Example/ExampleApp/Responder/EthereumTransaction.swift @@ -0,0 +1,17 @@ + +import Foundation +import Web3 + +extension EthereumTransaction { +var description: String { + return """ + from: \(String(describing: from!.hex(eip55: true))) + to: \(String(describing: to!.hex(eip55: true))), + value: \(String(describing: value!.hex())), + gasPrice: \(String(describing: gasPrice?.hex())), + gas: \(String(describing: gas?.hex())), + data: \(data.hex()), + nonce: \(String(describing: nonce?.hex())) + """ +} +} diff --git a/Example/ExampleApp/Responder/ResponderViewController.swift b/Example/ExampleApp/Responder/ResponderViewController.swift index 48c7d5463..c5049becd 100644 --- a/Example/ExampleApp/Responder/ResponderViewController.swift +++ b/Example/ExampleApp/Responder/ResponderViewController.swift @@ -16,14 +16,13 @@ final class ResponderViewController: UIViewController { metadata: metadata, projectId: "52af113ee0c1e1a20f4995730196c13e", isController: true, - relayHost: "relay.dev.walletconnect.com", + relayHost: "relay.dev.walletconnect.com", //use with dapp at https://canary.react-app.walletconnect.com/ clientName: "responder" ) }() - lazy var account = privateKey.address.hex(eip55: true) + lazy var account = Signer.privateKey.address.hex(eip55: true) var sessionItems: [ActiveSessionItem] = [] var currentProposal: Session.Proposal? - let privateKey: EthereumPrivateKey = try! EthereumPrivateKey(hexPrivateKey: "0xe56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540") private let responderView: ResponderView = { ResponderView() @@ -69,29 +68,32 @@ final class ResponderViewController: UIViewController { } private func showSessionDetailsViewController(_ session: Session) { - let sessionInfo = SessionInfo(name: session.peer.name ?? "", - descriptionText: session.peer.description ?? "", - dappURL: session.peer.description ?? "", - iconURL: session.peer.icons?.first ?? "", - chains: Array(session.permissions.blockchains), - methods: Array(session.permissions.methods)) - let vc = SessionDetailsViewController(sessionInfo) + let vc = SessionDetailsViewController(session, client) navigationController?.pushViewController(vc, animated: true) } private func showSessionRequest(_ sessionRequest: Request) { let requestVC = RequestViewController(sessionRequest) requestVC.onSign = { [unowned self] in - let result = signEth(request: sessionRequest) + let result = Signer.signEth(request: sessionRequest) let response = JSONRPCResponse(id: sessionRequest.id, result: result) client.respond(topic: sessionRequest.topic, response: .response(response)) + reloadSessionDetailsIfNeeded() } - requestVC.onReject = { [weak self] in - self?.client.respond(topic: sessionRequest.topic, response: .error(JSONRPCErrorResponse(id: sessionRequest.id, error: JSONRPCErrorResponse.Error(code: 0, message: "")))) + requestVC.onReject = { [unowned self] in + client.respond(topic: sessionRequest.topic, response: .error(JSONRPCErrorResponse(id: sessionRequest.id, error: JSONRPCErrorResponse.Error(code: 0, message: "")))) + reloadSessionDetailsIfNeeded() } + reloadSessionDetailsIfNeeded() present(requestVC, animated: true) } + func reloadSessionDetailsIfNeeded() { + if let sessionDetailsViewController = navigationController?.viewControllers.first(where: {$0 is SessionDetailsViewController}) as? SessionDetailsViewController { + sessionDetailsViewController.reloadTable() + } + } + private func pairClient(uri: String) { print("[RESPONDER] Pairing to: \(uri)") do { @@ -173,7 +175,7 @@ extension ResponderViewController: WalletConnectClientDelegate { dappURL: appMetadata.url ?? "", iconURL: appMetadata.icons?.first ?? "", chains: Array(sessionProposal.permissions.blockchains), - methods: Array(sessionProposal.permissions.methods)) + methods: Array(sessionProposal.permissions.methods), pendingRequests: []) currentProposal = sessionProposal DispatchQueue.main.async { // FIXME: Delegate being called from background thread self.showSessionProposal(info) @@ -227,57 +229,4 @@ extension ResponderViewController: WalletConnectClientDelegate { self.responderView.tableView.reloadData() } } - - func signEth(request: Request) -> AnyCodable { - let method = request.method - if method == "personal_sign" { - let params = try! request.params.get([String].self) - let messageToSign = params[0] - let signHash = signHash(messageToSign) - let (v, r, s) = try! self.privateKey.sign(hash: signHash) - let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) - return AnyCodable(result) - } else if method == "eth_signTypedData" { - let params = try! request.params.get([String].self) - print(params) - let messageToSign = params[1] - let signHash = signHash(messageToSign) - let (v, r, s) = try! self.privateKey.sign(hash: signHash) - let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) - return AnyCodable(result) - } else if method == "eth_sendTransaction" { - let params = try! request.params.get([EthereumTransaction].self) - var transaction = params[0] - transaction.gas = EthereumQuantity(quantity: BigUInt("1234")) - print(transaction.description) - let signedTx = try! transaction.sign(with: self.privateKey, chainId: 4) - let (r, s, v) = (signedTx.r, signedTx.s, signedTx.v) - let result = r.hex() + s.hex().dropFirst(2) + String(v.quantity, radix: 16) - return AnyCodable(result) - } - fatalError("not implemented") - } - - func signHash(_ message: String) -> Bytes { - let prefix = "\u{19}Ethereum Signed Message:\n" - let messageData = Data(hex: message) - let prefixData = (prefix + String(messageData.count)).data(using: .utf8)! - let prefixedMessageData = prefixData + messageData - let dataToHash: Bytes = .init(hex: prefixedMessageData.toHexString()) - return SHA3(variant: .keccak256).calculate(for: dataToHash) - } -} - -extension EthereumTransaction { - var description: String { - return """ - from: \(String(describing: from!.hex(eip55: true))) - to: \(String(describing: to!.hex(eip55: true))), - value: \(String(describing: value!.hex())), - gasPrice: \(String(describing: gasPrice?.hex())), - gas: \(String(describing: gas?.hex())), - data: \(data.hex()), - nonce: \(String(describing: nonce?.hex())) - """ - } } diff --git a/Example/ExampleApp/Responder/SessionProposal/SessionInfo.swift b/Example/ExampleApp/Responder/SessionProposal/SessionInfo.swift index 49e54f897..218d7834d 100644 --- a/Example/ExampleApp/Responder/SessionProposal/SessionInfo.swift +++ b/Example/ExampleApp/Responder/SessionProposal/SessionInfo.swift @@ -5,29 +5,5 @@ struct SessionInfo { let iconURL: String let chains: [String] let methods: [String] -} - -extension SessionInfo { - - static func mock() -> SessionInfo { - SessionInfo( - name: "Dapp Name", - descriptionText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris at eleifend est, vel porta enim. Praesent non placerat orci. Curabitur orci sem, molestie feugiat enim eu, tincidunt tincidunt est.", - dappURL: "decentralized.finance", - iconURL: "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png", - chains: ["Ethereum Kovan", "BSC Mainnet", "Fantom Opera"], - methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData"] - ) - } - - static func mockPancakeSwap() -> SessionInfo { - SessionInfo( - name: "🥞 PancakeSwap", - descriptionText: "Cheaper and faster than Uniswap? Discover PancakeSwap, the leading DEX on Binance Smart Chain (BSC) with the best farms in DeFi and a lottery for CAKE.", - dappURL: "pancakeswap.finance", - iconURL: "https://pancakeswap.finance/logo.png", - chains: ["Binance Smart Chain (BSC)"], - methods: ["personal_sign"] - ) - } + let pendingRequests: [String] } diff --git a/Example/ExampleApp/Responder/Signer.swift b/Example/ExampleApp/Responder/Signer.swift new file mode 100644 index 000000000..1e53bc75d --- /dev/null +++ b/Example/ExampleApp/Responder/Signer.swift @@ -0,0 +1,48 @@ +import Web3 +import Foundation +import WalletConnectUtils +import WalletConnect +import CryptoSwift + +class Signer { + static let privateKey: EthereumPrivateKey = try! EthereumPrivateKey(hexPrivateKey: "0xe56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540") + private init(){} + static func signEth(request: Request) -> AnyCodable { + let method = request.method + if method == "personal_sign" { + let params = try! request.params.get([String].self) + let messageToSign = params[0] + let signHash = signHash(messageToSign) + let (v, r, s) = try! self.privateKey.sign(hash: signHash) + let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) + return AnyCodable(result) + } else if method == "eth_signTypedData" { + let params = try! request.params.get([String].self) + print(params) + let messageToSign = params[1] + let signHash = signHash(messageToSign) + let (v, r, s) = try! self.privateKey.sign(hash: signHash) + let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) + return AnyCodable(result) + } else if method == "eth_sendTransaction" { + let params = try! request.params.get([EthereumTransaction].self) + var transaction = params[0] + transaction.gas = EthereumQuantity(quantity: BigUInt("1234")) + print(transaction.description) + let signedTx = try! transaction.sign(with: self.privateKey, chainId: 4) + let (r, s, v) = (signedTx.r, signedTx.s, signedTx.v) + let result = r.hex() + s.hex().dropFirst(2) + String(v.quantity, radix: 16) + return AnyCodable(result) + } + fatalError("not implemented") + } + + private static func signHash(_ message: String) -> Bytes { + let prefix = "\u{19}Ethereum Signed Message:\n" + let messageData = Data(hex: message) + let prefixData = (prefix + String(messageData.count)).data(using: .utf8)! + let prefixedMessageData = prefixData + messageData + let dataToHash: Bytes = .init(hex: prefixedMessageData.toHexString()) + return SHA3(variant: .keccak256).calculate(for: dataToHash) + } +} diff --git a/Example/ExampleApp/SceneDelegate.swift b/Example/ExampleApp/SceneDelegate.swift index de221ae10..3cdd41306 100644 --- a/Example/ExampleApp/SceneDelegate.swift +++ b/Example/ExampleApp/SceneDelegate.swift @@ -20,7 +20,7 @@ extension UITabBarController { let proposerController = UINavigationController(rootViewController: ProposerViewController()) proposerController.tabBarItem = UITabBarItem(title: "Dapp", image: UIImage(systemName: "appclip"), selectedImage: nil) let tabBarController = UITabBarController() - tabBarController.viewControllers = [responderController, proposerController] + tabBarController.viewControllers = [responderController] return tabBarController } } diff --git a/Example/ExampleApp/SessionDetails/SessionDetailsView.swift b/Example/ExampleApp/SessionDetails/SessionDetailsView.swift index f7565a8e4..10b7b7f6f 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailsView.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailsView.swift @@ -40,21 +40,17 @@ final class SessionDetailsView: UIView { return stackView }() - let chainsStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 10 - stackView.alignment = .leading - return stackView + + let pingButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Ping", for: .normal) + button.backgroundColor = .systemBlue + button.tintColor = .white + button.layer.cornerRadius = 8 + return button }() - let methodsStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 10 - stackView.alignment = .leading - return stackView - }() + let tableView = UITableView() override init(frame: CGRect) { super.init(frame: frame) @@ -62,11 +58,12 @@ final class SessionDetailsView: UIView { addSubview(iconView) addSubview(headerStackView) - addSubview(chainsStackView) - addSubview(methodsStackView) + addSubview(tableView) + headerStackView.addArrangedSubview(nameLabel) headerStackView.addArrangedSubview(urlLabel) headerStackView.addArrangedSubview(descriptionLabel) + addSubview(pingButton) subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false } @@ -80,13 +77,15 @@ final class SessionDetailsView: UIView { headerStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), headerStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32), - chainsStackView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 24), - chainsStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), - chainsStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32), - - methodsStackView.topAnchor.constraint(equalTo: chainsStackView.bottomAnchor, constant: 24), - methodsStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), - methodsStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32), + tableView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 0), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), + tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + + pingButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), + pingButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), + pingButton.heightAnchor.constraint(equalToConstant: 44), + pingButton.widthAnchor.constraint(equalToConstant: 64), ]) } @@ -101,26 +100,6 @@ final class SessionDetailsView: UIView { } } - func list(chains: [String]) { - let label = UILabel() - label.text = "Chains" - label.font = UIFont.systemFont(ofSize: 17.0, weight: .heavy) - chainsStackView.addArrangedSubview(label) - chains.forEach { - chainsStackView.addArrangedSubview(ListItem(text: $0)) - } - } - - func list(methods: [String]) { - let label = UILabel() - label.text = "Methods" - label.font = UIFont.systemFont(ofSize: 17.0, weight: .heavy) - methodsStackView.addArrangedSubview(label) - methods.forEach { - methodsStackView.addArrangedSubview(ListItem(text: $0)) - } - } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift index 061fa882c..688cc3c87 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift @@ -1,14 +1,25 @@ import UIKit +import WalletConnect +import WalletConnectUtils -final class SessionDetailsViewController: UIViewController { - +final class SessionDetailsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private let sessiondetailsView = { SessionDetailsView() }() - private let sessionInfo: SessionInfo - - init(_ sessionInfo: SessionInfo) { - self.sessionInfo = sessionInfo + private var sessionInfo: SessionInfo + private let client: WalletConnectClient + private let session: Session + init(_ session: Session, _ client: WalletConnectClient) { + let pendingRequests = client.getPendingRequests().map{$0.method} + self.sessionInfo = SessionInfo(name: session.peer.name ?? "", + descriptionText: session.peer.description ?? "", + dappURL: session.peer.description ?? "", + iconURL: session.peer.icons?.first ?? "", + chains: Array(session.permissions.blockchains), + methods: Array(session.permissions.methods), + pendingRequests: pendingRequests) + self.client = client + self.session = session super.init(nibName: nil, bundle: nil) } @@ -19,6 +30,10 @@ final class SessionDetailsViewController: UIViewController { override func viewDidLoad() { show(sessionInfo) super.viewDidLoad() + sessiondetailsView.pingButton.addTarget(self, action: #selector(ping), for: .touchUpInside) + sessiondetailsView.tableView.delegate = self + sessiondetailsView.tableView.dataSource = self + sessiondetailsView.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") } override func loadView() { @@ -30,7 +45,89 @@ final class SessionDetailsViewController: UIViewController { sessiondetailsView.descriptionLabel.text = sessionInfo.descriptionText sessiondetailsView.urlLabel.text = sessionInfo.dappURL sessiondetailsView.loadImage(at: sessionInfo.iconURL) - sessiondetailsView.list(chains: sessionInfo.chains) - sessiondetailsView.list(methods: sessionInfo.methods) + } + + @objc + private func ping() { + client.ping(topic: session.topic) { result in + switch result { + case .success(): + print("received ping response") + case .failure(let error): + print(error) + } + } + } + + //MARK: - Table View + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + return sessionInfo.chains.count + } else if section == 1 { + return sessionInfo.methods.count + } else { + return sessionInfo.pendingRequests.count + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + if indexPath.section == 0 { + cell.textLabel?.text = sessionInfo.chains[indexPath.row] + } else if indexPath.section == 1 { + cell.textLabel?.text = sessionInfo.methods[indexPath.row] + } else { + cell.textLabel?.text = sessionInfo.pendingRequests[indexPath.row] + } + return cell + } + + func numberOfSections(in tableView: UITableView) -> Int { + return 3 + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section == 0 { + return "Chains" + } else if section == 1 { + return "Methods" + } else { + return "Pending Requests" + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 2 { + let pendingRequests = client.getPendingRequests() + showSessionRequest(pendingRequests[indexPath.row]) + } + } + + private func showSessionRequest(_ sessionRequest: Request) { + let requestVC = RequestViewController(sessionRequest) + requestVC.onSign = { [unowned self] in + let result = Signer.signEth(request: sessionRequest) + let response = JSONRPCResponse(id: sessionRequest.id, result: result) + client.respond(topic: sessionRequest.topic, response: .response(response)) + reloadTable() + } + requestVC.onReject = { [unowned self] in + client.respond(topic: sessionRequest.topic, response: .error(JSONRPCErrorResponse(id: sessionRequest.id, error: JSONRPCErrorResponse.Error(code: 0, message: "")))) + reloadTable() + } + present(requestVC, animated: true) + } + + func reloadTable() { + let pendingRequests = client.getPendingRequests().map{$0.method} + self.sessionInfo = SessionInfo(name: session.peer.name ?? "", + descriptionText: session.peer.description ?? "", + dappURL: session.peer.description ?? "", + iconURL: session.peer.icons?.first ?? "", + chains: Array(session.permissions.blockchains), + methods: Array(session.permissions.methods), + pendingRequests: pendingRequests) + sessiondetailsView.tableView.reloadData() } } diff --git a/Sources/Relayer/WakuNetworkRelay.swift b/Sources/Relayer/WakuNetworkRelay.swift index 41fec1102..8e1eb99fc 100644 --- a/Sources/Relayer/WakuNetworkRelay.swift +++ b/Sources/Relayer/WakuNetworkRelay.swift @@ -36,8 +36,8 @@ public final class WakuNetworkRelay { uniqueIdentifier: String) { self.logger = logger self.dispatcher = dispatcher - let historyIdentifier = "\(uniqueIdentifier).relayer.subscription_json_rpc_record" - self.jsonRpcSubscriptionsHistory = JsonRpcHistory(logger: logger, keyValueStorage: keyValueStorage, identifier: historyIdentifier) + let historyIdentifier = "com.walletconnect.sdk.\(uniqueIdentifier).relayer.subscription_json_rpc_record" + self.jsonRpcSubscriptionsHistory = JsonRpcHistory(logger: logger, keyValueStore: KeyValueStore(defaults: keyValueStorage, identifier: historyIdentifier)) setUpBindings() } diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index 2f79fc063..534de8e24 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -171,7 +171,8 @@ final class SessionEngine { } let request = SessionType.PayloadParams.Request(method: params.method, params: params.params) let sessionPayloadParams = SessionType.PayloadParams(request: request, chainId: params.chainId) - relayer.request(.wcSessionPayload(sessionPayloadParams), onTopic: params.topic) { [weak self] result in + let sessionPayloadRequest = WCRequest(id: params.id, method: .sessionPayload, params: .sessionPayload(sessionPayloadParams)) + relayer.request(topic: params.topic, payload: sessionPayloadRequest) { [weak self] result in switch result { case .success(let response): completion(.success(response)) @@ -476,7 +477,6 @@ final class SessionEngine { permissions: Session.Permissions( blockchains: pendingSession.proposal.permissions.blockchain.chains, methods: pendingSession.proposal.permissions.jsonrpc.methods)) - onSessionApproved?(approvedSession) let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) relayer.respond(topic: topic, response: JsonRpcResponseTypes.response(response)) { [unowned self] error in @@ -484,6 +484,7 @@ final class SessionEngine { logger.error(error) } } + onSessionApproved?(approvedSession) } private func setupExpirationHandling() { diff --git a/Sources/WalletConnect/JsonRpcHistory/JsonRpcHistory.swift b/Sources/WalletConnect/JsonRpcHistory/JsonRpcHistory.swift index 1aa802bbd..9909dccd2 100644 --- a/Sources/WalletConnect/JsonRpcHistory/JsonRpcHistory.swift +++ b/Sources/WalletConnect/JsonRpcHistory/JsonRpcHistory.swift @@ -4,64 +4,61 @@ import WalletConnectUtils protocol JsonRpcHistoryRecording { func get(id: Int64) -> JsonRpcRecord? - func set(topic: String, request: WCRequest) throws + func set(topic: String, request: WCRequest, chainId: String?) throws func delete(topic: String) func resolve(response: JsonRpcResponseTypes) throws -> JsonRpcRecord func exist(id: Int64) -> Bool } - +//TODO -remove and use jsonrpc history only from utils class JsonRpcHistory: JsonRpcHistoryRecording { let storage: KeyValueStore let logger: ConsoleLogging - let identifier: String - init(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, uniqueIdentifier: String? = nil) { + init(logger: ConsoleLogging, keyValueStore: KeyValueStore) { self.logger = logger - self.storage = KeyValueStore(defaults: keyValueStorage) - self.identifier = "com.walletconnect.sdk.\(uniqueIdentifier ?? "")" + self.storage = keyValueStore } func get(id: Int64) -> JsonRpcRecord? { - try? storage.get(key: getKey(for: id)) + try? storage.get(key: "\(id)") } - func set(topic: String, request: WCRequest) throws { + func set(topic: String, request: WCRequest, chainId: String? = nil) throws { guard !exist(id: request.id) else { throw WalletConnectError.internal(.jsonRpcDuplicateDetected) } logger.debug("Setting JSON-RPC request history record - ID: \(request.id)") - let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: request.params), response: nil) - try storage.set(record, forKey: getKey(for: request.id)) + let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: request.params), response: nil, chainId: chainId) + try storage.set(record, forKey: "\(request.id)") } func delete(topic: String) { storage.getAll().forEach { record in if record.topic == topic { - storage.delete(forKey: getKey(for: record.id)) + storage.delete(forKey: "\(record.id)") } } } func resolve(response: JsonRpcResponseTypes) throws -> JsonRpcRecord { logger.debug("Resolving JSON-RPC response - ID: \(response.id)") - guard var record = try? storage.get(key: getKey(for: response.id)) else { + guard var record = try? storage.get(key: "\(response.id)") else { throw WalletConnectError.internal(.noJsonRpcRequestMatchingResponse) } if record.response != nil { throw WalletConnectError.internal(.jsonRpcDuplicateDetected) } else { record.response = response - try storage.set(record, forKey: getKey(for: record.id)) + try storage.set(record, forKey: "\(record.id)") return record } } func exist(id: Int64) -> Bool { - return (try? storage.get(key: getKey(for: id))) != nil + return (try? storage.get(key: "\(id)")) != nil } - private func getKey(for id: Int64) -> String { - let prefix = "\(identifier).wc_json_rpc_record." - return "\(prefix)\(id)" + public func getPending() -> [JsonRpcRecord] { + storage.getAll().filter{$0.response == nil} } } diff --git a/Sources/WalletConnect/JsonRpcHistory/JsonRpcRecord.swift b/Sources/WalletConnect/JsonRpcHistory/JsonRpcRecord.swift index 4bac1f787..f1461c5be 100644 --- a/Sources/WalletConnect/JsonRpcHistory/JsonRpcRecord.swift +++ b/Sources/WalletConnect/JsonRpcHistory/JsonRpcRecord.swift @@ -8,7 +8,8 @@ struct JsonRpcRecord: Codable { let topic: String let request: Request var response: JsonRpcResponseTypes? - + let chainId: String? + struct Request: Codable { let method: WCRequest.Method let params: WCRequest.Params diff --git a/Sources/WalletConnect/Relay/WalletConnectRelay.swift b/Sources/WalletConnect/Relay/WalletConnectRelay.swift index eec1180df..6e3c2c926 100644 --- a/Sources/WalletConnect/Relay/WalletConnectRelay.swift +++ b/Sources/WalletConnect/Relay/WalletConnectRelay.swift @@ -16,6 +16,7 @@ protocol WalletConnectRelaying: AnyObject { var transportConnectionPublisher: AnyPublisher {get} var wcRequestPublisher: AnyPublisher {get} func request(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>)->())?) + func request(topic: String, payload: WCRequest, completion: ((Result, JSONRPCErrorResponse>)->())?) func respond(topic: String, response: JsonRpcResponseTypes, completion: @escaping ((Error?)->())) func subscribe(topic: String) func unsubscribe(topic: String) @@ -70,7 +71,7 @@ class WalletConnectRelay: WalletConnectRelaying { func request(topic: String, payload: WCRequest, completion: ((Result, JSONRPCErrorResponse>)->())?) { do { - try jsonRpcHistory.set(topic: topic, request: payload) + try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) let message = try jsonRpcSerialiser.serialise(topic: topic, encodable: payload) networkRelayer.publish(topic: topic, payload: message) { [weak self] error in guard let self = self else {return} @@ -157,7 +158,7 @@ class WalletConnectRelay: WalletConnectRelaying { private func handleWCRequest(topic: String, request: WCRequest) { do { - try jsonRpcHistory.set(topic: topic, request: request) + try jsonRpcHistory.set(topic: topic, request: request, chainId: getChainId(request)) let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: request) wcRequestPublisherSubject.send(payload) } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { @@ -198,4 +199,9 @@ class WalletConnectRelay: WalletConnectRelaying { logger.info("Info: \(error.localizedDescription)") } } + + func getChainId(_ request: WCRequest) -> String? { + guard case let .sessionPayload(payload) = request.params else {return nil} + return payload.chainId + } } diff --git a/Sources/WalletConnect/Request.swift b/Sources/WalletConnect/Request.swift index cffcdd128..e55b13497 100644 --- a/Sources/WalletConnect/Request.swift +++ b/Sources/WalletConnect/Request.swift @@ -7,4 +7,24 @@ public struct Request: Codable, Equatable { public let method: String public let params: AnyCodable public let chainId: String? + + internal init(id: Int64, topic: String, method: String, params: AnyCodable, chainId: String?) { + self.id = id + self.topic = topic + self.method = method + self.params = params + self.chainId = chainId + } + + public init(topic: String, method: String, params: AnyCodable, chainId: String?) { + self.id = Self.generateId() + self.topic = topic + self.method = method + self.params = params + self.chainId = chainId + } + + public static func generateId() -> Int64 { + return Int64(Date().timeIntervalSince1970 * 1000)*1000 + Int64.random(in: 0..<1000) + } } diff --git a/Sources/WalletConnect/Storage/SequenceStore.swift b/Sources/WalletConnect/Storage/SequenceStore.swift index c8eeec60c..d4ff6fc39 100644 --- a/Sources/WalletConnect/Storage/SequenceStore.swift +++ b/Sources/WalletConnect/Storage/SequenceStore.swift @@ -18,10 +18,10 @@ final class SequenceStore where T: ExpirableSequence { private let dateInitializer: () -> Date private let identifier: String - init(storage: KeyValueStorage, uniqueIdentifier: String? = nil, dateInitializer: @escaping () -> Date = Date.init) { + init(storage: KeyValueStorage, identifier: String, dateInitializer: @escaping () -> Date = Date.init) { self.storage = storage self.dateInitializer = dateInitializer - self.identifier = "com.walletconnect.sdk.\(uniqueIdentifier ?? "")" + self.identifier = identifier } func hasSequence(forTopic topic: String) -> Bool { diff --git a/Sources/WalletConnect/StorageDomainIdentifiers.swift b/Sources/WalletConnect/StorageDomainIdentifiers.swift new file mode 100644 index 000000000..fa2bd7ecc --- /dev/null +++ b/Sources/WalletConnect/StorageDomainIdentifiers.swift @@ -0,0 +1,14 @@ + +import Foundation + +enum StorageDomainIdentifiers { + static func jsonRpcHistory(clientName: String) -> String { + return "com.walletconnect.sdk.\(clientName).wc_jsonRpcHistoryRecord" + } + static func pairings(clientName: String) -> String { + return "com.walletconnect.sdk.\(clientName).pairingSequences" + } + static func sessions(clientName: String) -> String { + return "com.walletconnect.sdk.\(clientName).sessionSequences" + } +} diff --git a/Sources/WalletConnect/Types/WCRequest.swift b/Sources/WalletConnect/Types/WCRequest.swift index 50c7f9278..b4b6c20cd 100644 --- a/Sources/WalletConnect/Types/WCRequest.swift +++ b/Sources/WalletConnect/Types/WCRequest.swift @@ -12,8 +12,8 @@ struct WCRequest: Codable { case method case params } - - internal init(method: Method, params: Params, id: Int64 = generateId(), jsonrpc: String = "2.0") { + + internal init(id: Int64 = generateId(), jsonrpc: String = "2.0", method: Method, params: Params) { self.id = id self.jsonrpc = jsonrpc self.method = method diff --git a/Sources/WalletConnect/WalletConnectClient.swift b/Sources/WalletConnect/WalletConnectClient.swift index 5bfa920ef..9038789ad 100644 --- a/Sources/WalletConnect/WalletConnectClient.swift +++ b/Sources/WalletConnect/WalletConnectClient.swift @@ -30,7 +30,8 @@ public final class WalletConnectClient { private let crypto: Crypto private let secureStorage: SecureStorage private let pairingQueue = DispatchQueue(label: "com.walletconnect.sdk.client.pairing", qos: .userInitiated) - + private let history: JsonRpcHistory + // MARK: - Initializers /// Initializes and returns newly created WalletConnect Client Instance. Establishes a network connection with the relay @@ -45,10 +46,10 @@ public final class WalletConnectClient { /// /// WalletConnect Client is not a singleton but once you create an instance, you should not deinitialise it. Usually only one instance of a client is required in the application. public convenience init(metadata: AppMetadata, projectId: String, isController: Bool, relayHost: String, keyValueStorage: KeyValueStorage = UserDefaults.standard, clientName: String? = nil) { - self.init(metadata: metadata, projectId: projectId, isController: isController, relayHost: relayHost, logger: ConsoleLogger(loggingLevel: .off), keychain: KeychainStorage(uniqueIdentifier: clientName), keyValueStore: keyValueStorage, clientName: clientName) + self.init(metadata: metadata, projectId: projectId, isController: isController, relayHost: relayHost, logger: ConsoleLogger(loggingLevel: .off), keychain: KeychainStorage(uniqueIdentifier: clientName), keyValueStorage: keyValueStorage, clientName: clientName) } - init(metadata: AppMetadata, projectId: String, isController: Bool, relayHost: String, logger: ConsoleLogging, keychain: KeychainStorage, keyValueStore: KeyValueStorage, clientName: String? = nil) { + init(metadata: AppMetadata, projectId: String, isController: Bool, relayHost: String, logger: ConsoleLogging, keychain: KeychainStorage, keyValueStorage: KeyValueStorage, clientName: String? = nil) { self.metadata = metadata self.isController = isController self.logger = logger @@ -56,12 +57,14 @@ public final class WalletConnectClient { self.crypto = Crypto(keychain: keychain) self.secureStorage = SecureStorage(keychain: keychain) let relayUrl = WakuNetworkRelay.makeRelayUrl(host: relayHost, projectId: projectId) - self.wakuRelay = WakuNetworkRelay(logger: logger, url: relayUrl, keyValueStorage: keyValueStore, uniqueIdentifier: clientName ?? "") + self.wakuRelay = WakuNetworkRelay(logger: logger, url: relayUrl, keyValueStorage: keyValueStorage, uniqueIdentifier: clientName ?? "") let serialiser = JSONRPCSerialiser(crypto: crypto) - self.relay = WalletConnectRelay(networkRelayer: wakuRelay, jsonRpcSerialiser: serialiser, logger: logger, jsonRpcHistory: JsonRpcHistory(logger: logger, keyValueStorage: keyValueStore, uniqueIdentifier: clientName)) - let pairingSequencesStore = PairingStorage(storage: SequenceStore(storage: keyValueStore, uniqueIdentifier: clientName)) - let sessionSequencesStore = SessionStorage(storage: SequenceStore(storage: keyValueStore, uniqueIdentifier: clientName)) + self.history = JsonRpcHistory(logger: logger, keyValueStore: KeyValueStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory(clientName: clientName ?? "_"))) + self.relay = WalletConnectRelay(networkRelayer: wakuRelay, jsonRpcSerialiser: serialiser, logger: logger, jsonRpcHistory: history) + let pairingSequencesStore = PairingStorage(storage: SequenceStore(storage: keyValueStorage, identifier: StorageDomainIdentifiers.pairings(clientName: clientName ?? "_"))) + let sessionSequencesStore = SessionStorage(storage: SequenceStore(storage: keyValueStorage, identifier: StorageDomainIdentifiers.sessions(clientName: clientName ?? "_"))) self.pairingEngine = PairingEngine(relay: relay, crypto: crypto, subscriber: WCSubscriber(relay: relay, logger: logger), sequencesStore: pairingSequencesStore, isController: isController, metadata: metadata, logger: logger) + self.sessionEngine = SessionEngine(relay: relay, crypto: crypto, subscriber: WCSubscriber(relay: relay, logger: logger), sequencesStore: sessionSequencesStore, isController: isController, metadata: metadata, logger: logger) setUpEnginesCallbacks() subscribeNotificationCenter() @@ -221,7 +224,16 @@ public final class WalletConnectClient { pairingEngine.getSettledPairings() } - //MARK: - Private + public func getPendingRequests() -> [Request] { + history.getPending() + .filter{$0.request.method == .sessionPayload} + .compactMap { + guard case let .sessionPayload(payloadRequest) = $0.request.params else {return nil} + return Request(id: $0.id, topic: $0.topic, method: payloadRequest.request.method, params: payloadRequest.request.params, chainId: payloadRequest.chainId) + } + } + + // MARK: - Private private func setUpEnginesCallbacks() { pairingEngine.onSessionProposal = { [unowned self] proposal in diff --git a/Sources/WalletConnectUtils/JsonRpcHistory.swift b/Sources/WalletConnectUtils/JsonRpcHistory.swift index aacc2c291..bca7270a0 100644 --- a/Sources/WalletConnectUtils/JsonRpcHistory.swift +++ b/Sources/WalletConnectUtils/JsonRpcHistory.swift @@ -4,53 +4,56 @@ import Foundation public class JsonRpcHistory where T: Codable&Equatable { enum RecordingError: Error { case jsonRpcDuplicateDetected + case noJsonRpcRequestMatchingResponse } private let storage: KeyValueStore private let logger: ConsoleLogging - private let identifier: String - - public init(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, identifier: String) { + + public init(logger: ConsoleLogging, keyValueStore: KeyValueStore) { self.logger = logger - self.storage = KeyValueStore(defaults: keyValueStorage) - self.identifier = identifier + self.storage = keyValueStore } public func get(id: Int64) -> JsonRpcRecord? { - try? storage.get(key: getKey(for: id)) + try? storage.get(key: "\(id)") } - public func set(topic: String, request: JSONRPCRequest) throws { + public func set(topic: String, request: JSONRPCRequest, chainId: String? = nil) throws { guard !exist(id: request.id) else { throw RecordingError.jsonRpcDuplicateDetected } logger.debug("Setting JSON-RPC request history record") - let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: AnyCodable(request.params)), response: nil) - try storage.set(record, forKey: getKey(for: request.id)) + let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: AnyCodable(request.params)), response: nil, chainId: chainId) + try storage.set(record, forKey: "\(request.id)") } public func delete(topic: String) { storage.getAll().forEach { record in if record.topic == topic { - storage.delete(forKey: getKey(for: record.id)) + storage.delete(forKey: "\(record.id)") } } } - public func resolve(response: JsonRpcResponseTypes) throws { - guard var record = try? storage.get(key: getKey(for: response.id)) else { return } + public func resolve(response: JsonRpcResponseTypes) throws -> JsonRpcRecord { + logger.debug("Resolving JSON-RPC response - ID: \(response.id)") + guard var record = try? storage.get(key: "\(response.id)") else { + throw RecordingError.noJsonRpcRequestMatchingResponse + } if record.response != nil { throw RecordingError.jsonRpcDuplicateDetected } else { record.response = response - try storage.set(record, forKey: getKey(for: record.id)) + try storage.set(record, forKey: "\(record.id)") + return record } } public func exist(id: Int64) -> Bool { - return (try? storage.get(key: getKey(for: id))) != nil + return (try? storage.get(key: "\(id)")) != nil } - private func getKey(for id: Int64) -> String { - return "com.walletconnect.sdk.\(identifier).\(id)" + public func getPending() -> [JsonRpcRecord] { + storage.getAll().filter{$0.response == nil} } } diff --git a/Sources/WalletConnectUtils/JsonRpcRecord.swift b/Sources/WalletConnectUtils/JsonRpcRecord.swift index 12ec84905..225cca1ac 100644 --- a/Sources/WalletConnectUtils/JsonRpcRecord.swift +++ b/Sources/WalletConnectUtils/JsonRpcRecord.swift @@ -1,16 +1,16 @@ import Foundation -import WalletConnectUtils public struct JsonRpcRecord: Codable { - let id: Int64 - let topic: String - let request: Request - var response: JsonRpcResponseTypes? + public let id: Int64 + public let topic: String + public let request: Request + public var response: JsonRpcResponseTypes? + public let chainId: String? - struct Request: Codable { - let method: String - let params: AnyCodable + public struct Request: Codable { + public let method: String + public let params: AnyCodable } } diff --git a/Sources/WalletConnectUtils/KeyValueStore.swift b/Sources/WalletConnectUtils/KeyValueStore.swift index 71a48e392..cccbd1da5 100644 --- a/Sources/WalletConnectUtils/KeyValueStore.swift +++ b/Sources/WalletConnectUtils/KeyValueStore.swift @@ -3,24 +3,27 @@ import Foundation public final class KeyValueStore where T: Codable { private let defaults: KeyValueStorage + private let prefix: String - public init(defaults: KeyValueStorage) { + public init(defaults: KeyValueStorage, identifier: String) { self.defaults = defaults + self.prefix = identifier } public func set(_ item: T, forKey key: String) throws { let encoded = try JSONEncoder().encode(item) - defaults.set(encoded, forKey: key) + defaults.set(encoded, forKey: getContextPrefixedKey(for: key)) } public func get(key: String) throws -> T? { - guard let data = defaults.object(forKey: key) as? Data else { return nil } + guard let data = defaults.object(forKey: getContextPrefixedKey(for: key)) as? Data else { return nil } let item = try JSONDecoder().decode(T.self, from: data) return item } public func getAll() -> [T] { return defaults.dictionaryRepresentation().compactMap { + guard $0.key.hasPrefix(prefix) else {return nil} if let data = $0.value as? Data, let item = try? JSONDecoder().decode(T.self, from: data) { return item @@ -30,6 +33,10 @@ public final class KeyValueStore where T: Codable { } public func delete(forKey key: String) { - defaults.removeObject(forKey: key) + defaults.removeObject(forKey: getContextPrefixedKey(for: key)) + } + + private func getContextPrefixedKey(for key: String) -> String { + return "\(prefix).\(key)" } } diff --git a/Tests/IntegrationTests/ClientTest.swift b/Tests/IntegrationTests/ClientTest.swift index 7a6cb0c7f..cb56eff3c 100644 --- a/Tests/IntegrationTests/ClientTest.swift +++ b/Tests/IntegrationTests/ClientTest.swift @@ -34,7 +34,7 @@ final class ClientTests: XCTestCase { relayHost: relayHost, logger: logger, keychain: KeychainStorage(keychainService: KeychainServiceFake()), - keyValueStore: RuntimeKeyValueStorage()) + keyValueStorage: RuntimeKeyValueStorage()) return ClientDelegate(client: client) } @@ -78,37 +78,34 @@ final class ClientTests: XCTestCase { waitForExpectations(timeout: defaultTimeout, handler: nil) } - // FIXME: Broken test!! -// func testNewSessionOnExistingPairing() { -// let proposerSettlesSessionExpectation = expectation(description: "Proposer settles session") -// proposerSettlesSessionExpectation.expectedFulfillmentCount = 2 -// let responderSettlesSessionExpectation = expectation(description: "Responder settles session") -// responderSettlesSessionExpectation.expectedFulfillmentCount = 2 -// var pairingTopic: String! -// var initiatedSecondSession = false -// let permissions = SessionType.Permissions(blockchain: SessionType.Blockchain(chains: []), jsonrpc: SessionType.JSONRPC(methods: [])) -// let connectParams = ConnectParams(permissions: permissions) -// let uri = try! proposer.client.connect(params: connectParams)! -// try! responder.client.pair(uri: uri) -// proposer.onPairingSettled = { pairing in -// pairingTopic = pairing.topic -// } -// responder.onSessionProposal = { [unowned self] proposal in -// self.responder.client.approve(proposal: proposal, accounts: []){_ in} -// } -// responder.onSessionSettled = { sessionSettled in -// responderSettlesSessionExpectation.fulfill() -// } -// proposer.onSessionSettled = { [unowned self] sessionSettled in -// proposerSettlesSessionExpectation.fulfill() -// if !initiatedSecondSession { -// let params = ConnectParams(permissions: SessionType.Permissions(blockchain: SessionType.Blockchain(chains: []), jsonrpc: SessionType.JSONRPC(methods: [])), topic: pairingTopic) -// let _ = try! proposer.client.connect(params: params) -// initiatedSecondSession = true -// } -// } -// waitForExpectations(timeout: defaultTimeout, handler: nil) -// } + func testNewSessionOnExistingPairing() { + let proposerSettlesSessionExpectation = expectation(description: "Proposer settles session") + proposerSettlesSessionExpectation.expectedFulfillmentCount = 2 + let responderSettlesSessionExpectation = expectation(description: "Responder settles session") + responderSettlesSessionExpectation.expectedFulfillmentCount = 2 + var pairingTopic: String! + var initiatedSecondSession = false + let permissions = Session.Permissions.stub() + let uri = try! proposer.client.connect(sessionPermissions: permissions, topic: nil)! + try! responder.client.pair(uri: uri) + proposer.onPairingSettled = { pairing in + pairingTopic = pairing.topic + } + responder.onSessionProposal = { [unowned self] proposal in + responder.client.approve(proposal: proposal, accounts: []) + } + responder.onSessionSettled = { sessionSettled in + responderSettlesSessionExpectation.fulfill() + } + proposer.onSessionSettled = { [unowned self] sessionSettled in + proposerSettlesSessionExpectation.fulfill() + if !initiatedSecondSession { + let _ = try! proposer.client.connect(sessionPermissions: permissions, topic: pairingTopic) + initiatedSecondSession = true + } + } + waitForExpectations(timeout: defaultTimeout, handler: nil) + } func testResponderRejectsSession() { let sessionRejectExpectation = expectation(description: "Proposer is notified on session rejection") diff --git a/Tests/WalletConnectTests/JsonRpcHistoryTests.swift b/Tests/WalletConnectTests/JsonRpcHistoryTests.swift index 0a5798077..c807c1b46 100644 --- a/Tests/WalletConnectTests/JsonRpcHistoryTests.swift +++ b/Tests/WalletConnectTests/JsonRpcHistoryTests.swift @@ -10,7 +10,7 @@ final class JsonRpcHistoryTests: XCTestCase { var sut: WalletConnect.JsonRpcHistory! override func setUp() { - sut = JsonRpcHistory(logger: ConsoleLoggerMock(), keyValueStorage: RuntimeKeyValueStorage()) + sut = JsonRpcHistory(logger: ConsoleLoggerMock(), keyValueStore: KeyValueStore(defaults: RuntimeKeyValueStorage(), identifier: "")) } override func tearDown() { @@ -18,54 +18,77 @@ final class JsonRpcHistoryTests: XCTestCase { } func testSetRecord() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() XCTAssertFalse(sut.exist(id: recordinput.request.id)) try! sut.set(topic: recordinput.topic, request: recordinput.request) XCTAssertTrue(sut.exist(id: recordinput.request.id)) } func testGetRecord() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() XCTAssertNil(sut.get(id: recordinput.request.id)) try! sut.set(topic: recordinput.topic, request: recordinput.request) XCTAssertNotNil(sut.get(id: recordinput.request.id)) } func testResolve() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() try! sut.set(topic: recordinput.topic, request: recordinput.request) XCTAssertNil(sut.get(id: recordinput.request.id)?.response) let jsonRpcResponse = JSONRPCResponse(id: recordinput.request.id, result: AnyCodable("")) let response = JsonRpcResponseTypes.response(jsonRpcResponse) - try! sut.resolve(response: response) + _ = try! sut.resolve(response: response) XCTAssertNotNil(sut.get(id: jsonRpcResponse.id)?.response) } func testThrowsOnResolveDuplicate() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() try! sut.set(topic: recordinput.topic, request: recordinput.request) let jsonRpcResponse = JSONRPCResponse(id: recordinput.request.id, result: AnyCodable("")) let response = JsonRpcResponseTypes.response(jsonRpcResponse) - try! sut.resolve(response: response) + _ = try! sut.resolve(response: response) XCTAssertThrowsError(try sut.resolve(response: response)) } func testThrowsOnSetDuplicate() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() try! sut.set(topic: recordinput.topic, request: recordinput.request) XCTAssertThrowsError(try sut.set(topic: recordinput.topic, request: recordinput.request)) } func testDelete() { - let recordinput = testJsonRpcRecordInput + let recordinput = getTestJsonRpcRecordInput() try! sut.set(topic: recordinput.topic, request: recordinput.request) XCTAssertNotNil(sut.get(id: recordinput.request.id)) sut.delete(topic: testTopic) XCTAssertNil(sut.get(id: recordinput.request.id)) } + + func testGetPending() { + let recordinput1 = getTestJsonRpcRecordInput(id: 1) + let recordinput2 = getTestJsonRpcRecordInput(id: 2) + try! sut.set(topic: recordinput1.topic, request: recordinput1.request) + try! sut.set(topic: recordinput2.topic, request: recordinput2.request) + XCTAssertEqual(sut.getPending().count, 2) + let jsonRpcResponse = JSONRPCResponse(id: recordinput1.request.id, result: AnyCodable("")) + let response = JsonRpcResponseTypes.response(jsonRpcResponse) + _ = try! sut.resolve(response: response) + XCTAssertEqual(sut.getPending().count, 1) + } } private let testTopic = "test_topic" -private var testJsonRpcRecordInput: (topic: String, request: WCRequest) { - return (topic: testTopic, request: SerialiserTestData.pairingApproveJSONRPCRequest) +private func getTestJsonRpcRecordInput(id: Int64 = 0) -> (topic: String, request: WCRequest) { + let request = WCRequest(id: id, + jsonrpc: "2.0", + method: WCRequest.Method.pairingApprove, + params: WCRequest.Params.pairingApprove( + PairingType.ApprovalParams(relay: RelayProtocolOptions(protocol: "waku", + params: nil), responder: PairingParticipant(publicKey: "be9225978b6287a02d259ee0d9d1bcb683082d8386b7fb14b58ac95b93b2ef43"), + expiry: 1632742217, + state: PairingState(metadata: AppMetadata(name: "iOS", + description: nil, + url: nil, + icons: nil))))) + return (topic: testTopic, request: request) } diff --git a/Tests/WalletConnectTests/SequenceStoreTests.swift b/Tests/WalletConnectTests/SequenceStoreTests.swift index 996e5e7ed..e37a2cf4e 100644 --- a/Tests/WalletConnectTests/SequenceStoreTests.swift +++ b/Tests/WalletConnectTests/SequenceStoreTests.swift @@ -21,7 +21,7 @@ final class SequenceStoreTests: XCTestCase { override func setUp() { timeTraveler = TimeTraveler() storageFake = RuntimeKeyValueStorage() - sut = SequenceStore(storage: storageFake, dateInitializer: timeTraveler.generateDate) + sut = SequenceStore(storage: storageFake, identifier: "", dateInitializer: timeTraveler.generateDate) sut.onSequenceExpiration = { _, _ in XCTFail("Unexpected expiration call") } diff --git a/Tests/WalletConnectTests/TestsData/SerialiserTestData.swift b/Tests/WalletConnectTests/TestsData/SerialiserTestData.swift index 58b8eaff8..f2f4d91e1 100644 --- a/Tests/WalletConnectTests/TestsData/SerialiserTestData.swift +++ b/Tests/WalletConnectTests/TestsData/SerialiserTestData.swift @@ -12,6 +12,8 @@ enum SerialiserTestData { Data(hex: "14aa7f6034dd0213be5901b472f461769855ac1e2f6bec6a8ed1157a9da3b2df08802cbd6e0d030d86ff99011040cfc831eec3636c1d46bfc22cbe055560fea3") static let serialisedMessage = "f0d00d4274a7e9711e4e0f21820b887745c59ad0c053925072f4503a39fe579ca8b7b8fa6bf0c7297e6db8f6585ee77ffc6d3106fa827043279f9db08cd2e29a988c7272fa3cfdb739163bb9606822c714aa7f6034dd0213be5901b472f461769855ac1e2f6bec6a8ed1157a9da3b2df08802cbd6e0d030d86ff99011040cfc831eec3636c1d46bfc22cbe055560fea3" static let pairingApproveJSONRPCRequest = WCRequest( + id: 0, + jsonrpc: "2.0", method: WCRequest.Method.pairingApprove, params: WCRequest.Params.pairingApprove( PairingType.ApprovalParams( @@ -24,9 +26,7 @@ enum SerialiserTestData { name: "iOS", description: nil, url: nil, - icons: nil)))), - id: 0, - jsonrpc: "2.0" + icons: nil)))) ) static let pairingApproveJSON = """ diff --git a/Tests/WalletConnectTests/WCRelayTests.swift b/Tests/WalletConnectTests/WCRelayTests.swift index 4131d0326..37ead33db 100644 --- a/Tests/WalletConnectTests/WCRelayTests.swift +++ b/Tests/WalletConnectTests/WCRelayTests.swift @@ -18,7 +18,7 @@ class WalletConnectRelayTests: XCTestCase { let logger = ConsoleLoggerMock() serialiser = MockedJSONRPCSerialiser() networkRelayer = MockedNetworkRelayer() - wcRelay = WalletConnectRelay(networkRelayer: networkRelayer, jsonRpcSerialiser: serialiser, logger: logger, jsonRpcHistory: JsonRpcHistory(logger: logger, keyValueStorage: RuntimeKeyValueStorage())) + wcRelay = WalletConnectRelay(networkRelayer: networkRelayer, jsonRpcSerialiser: serialiser, logger: logger, jsonRpcHistory: JsonRpcHistory(logger: logger, keyValueStore: KeyValueStore(defaults: RuntimeKeyValueStorage(), identifier: ""))) } override func tearDown() { @@ -87,7 +87,7 @@ extension WalletConnectRelayTests { let wcRequestId: Int64 = 123456 let sessionPayloadParams = SessionType.PayloadParams(request: SessionType.PayloadParams.Request(method: "method", params: AnyCodable("params")), chainId: "") let params = WCRequest.Params.sessionPayload(sessionPayloadParams) - let wcRequest = WCRequest(method: WCRequest.Method.sessionPayload, params: params, id: wcRequestId) + let wcRequest = WCRequest(id: wcRequestId, method: WCRequest.Method.sessionPayload, params: params) return wcRequest } }