From 1e60517a563def2a5741304336509233af37218d Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 2 Nov 2022 12:53:45 +0600 Subject: [PATCH 01/15] Solana sample app --- .../Contents.json | 21 +++++++++++++++ .../Solana_logo.png | Bin 0 -> 28416 bytes .../AccountRequestViewController.swift | 5 ++-- .../Sign/Connect/ConnectViewController.swift | 25 +++++++++++++++--- .../SelectChainViewController.swift | 25 +++++++++++++++--- .../Wallet/WalletViewController.swift | 10 ++++--- 6 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json create mode 100644 Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json new file mode 100644 index 000000000..97219ba07 --- /dev/null +++ b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Solana_logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..26aec35be0f047baaac46df6bd3206f4c12bf6d9 GIT binary patch literal 28416 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 literal 0 HcmV?d00001 diff --git a/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift b/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift index bd9711469..fa2d7b4d3 100644 --- a/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift +++ b/Example/DApp/Sign/AccountRequest/AccountRequestViewController.swift @@ -7,7 +7,7 @@ class AccountRequestViewController: UIViewController, UITableViewDelegate, UITab private let session: Session private let chainId: String private let account: String - private let methods = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"] + private let methods: [String] private let accountRequestView = { AccountRequestView() }() @@ -16,6 +16,7 @@ class AccountRequestViewController: UIViewController, UITableViewDelegate, UITab self.session = session self.chainId = accountDetails.chain self.account = accountDetails.account + self.methods = accountDetails.methods super.init(nibName: nil, bundle: nil) } @@ -37,7 +38,7 @@ class AccountRequestViewController: UIViewController, UITableViewDelegate, UITab } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - methods.count + return methods.count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { diff --git a/Example/DApp/Sign/Connect/ConnectViewController.swift b/Example/DApp/Sign/Connect/ConnectViewController.swift index 15e50a9fd..0d4932cc7 100644 --- a/Example/DApp/Sign/Connect/ConnectViewController.swift +++ b/Example/DApp/Sign/Connect/ConnectViewController.swift @@ -81,9 +81,28 @@ class ConnectViewController: UIViewController, UITableViewDataSource, UITableVie func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let pairingTopic = activePairings[indexPath.row].topic - let blockchains: Set = [Blockchain("eip155:1")!, Blockchain("eip155:137")!] - let methods: Set = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"] - let namespaces: [String: ProposalNamespace] = ["eip155": ProposalNamespace(chains: blockchains, methods: methods, events: [], extensions: nil)] + let namespaces: [String: ProposalNamespace] = [ + "eip155": ProposalNamespace( + chains: [ + Blockchain("eip155:1")!, + Blockchain("eip155:137")! + ], + methods: [ + "eth_sendTransaction", + "personal_sign", + "eth_signTypedData" + ], events: [], extensions: nil + ), + "solana": ProposalNamespace( + chains: [ + Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, + ], + methods: [ + "solana_signMessage", + "solana_signTransaction", + ], events: [], extensions: nil + ) + ] Task { _ = try await Sign.instance.connect(requiredNamespaces: namespaces, topic: pairingTopic) connectWithExampleWallet() diff --git a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift index 7abd2b7d4..a00b992ba 100644 --- a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift +++ b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift @@ -31,9 +31,28 @@ class SelectChainViewController: UIViewController, UITableViewDataSource { @objc private func connect() { print("[PROPOSER] Connecting to a pairing...") - let methods: Set = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"] - let blockchains: Set = [Blockchain("eip155:1")!, Blockchain("eip155:137")!] - let namespaces: [String: ProposalNamespace] = ["eip155": ProposalNamespace(chains: blockchains, methods: methods, events: [], extensions: nil)] + let namespaces: [String: ProposalNamespace] = [ + "eip155": ProposalNamespace( + chains: [ + Blockchain("eip155:1")!, + Blockchain("eip155:137")! + ], + methods: [ + "eth_sendTransaction", + "personal_sign", + "eth_signTypedData" + ], events: [], extensions: nil + ), + "solana": ProposalNamespace( + chains: [ + Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, + ], + methods: [ + "solana_signMessage", + "solana_signTransaction", + ], events: [], extensions: nil + ) + ] Task { let uri = try await Pair.instance.create() try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic) diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index a888df7bd..6ebc4f14b 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -8,7 +8,11 @@ import CryptoSwift import Combine final class WalletViewController: UIViewController { - lazy var account = Signer.privateKey.address.hex(eip55: true) + lazy var accounts = [ + "eip155": Signer.privateKey.address.hex(eip55: true), + "solana": "BvQtshZCDdzXeuC6Hrp2tBjJThUf3LN2Qeup3avrStTX" + ] + var sessionItems: [ActiveSessionItem] = [] var currentProposal: Session.Proposal? private var publishers = [AnyCancellable]() @@ -215,10 +219,10 @@ extension WalletViewController: ProposalViewControllerDelegate { proposal.requiredNamespaces.forEach { let caip2Namespace = $0.key let proposalNamespace = $0.value - let accounts = Set(proposalNamespace.chains.compactMap { Account($0.absoluteString + ":\(account)") }) + let accounts = Set(proposalNamespace.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) let extensions: [SessionNamespace.Extension]? = proposalNamespace.extensions?.map { element in - let accounts = Set(element.chains.compactMap { Account($0.absoluteString + ":\(account)") }) + let accounts = Set(element.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) return SessionNamespace.Extension(accounts: accounts, methods: element.methods, events: element.events) } let sessionNamespace = SessionNamespace(accounts: accounts, methods: proposalNamespace.methods, events: proposalNamespace.events, extensions: extensions) From 6a2538f96b7d2e225c20ea6dbb7c919aaabbf248 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 11 Nov 2022 18:36:48 +0600 Subject: [PATCH 02/15] savepoint --- Example/ExampleApp.xcodeproj/project.pbxproj | 35 +++++++++++- .../xcshareddata/swiftpm/Package.resolved | 30 ++++++++-- .../Request/RequestViewController.swift | 11 +--- Example/ExampleApp/Shared/Signer.swift | 43 --------------- .../Shared/Signer/EthereumSigner.swift | 50 +++++++++++++++++ Example/ExampleApp/Shared/Signer/Signer.swift | 26 +++++++++ .../Shared/Signer/SolanaSigner.swift | 55 +++++++++++++++++++ .../Wallet/WalletViewController.swift | 4 +- 8 files changed, 192 insertions(+), 62 deletions(-) delete mode 100644 Example/ExampleApp/Shared/Signer.swift create mode 100644 Example/ExampleApp/Shared/Signer/EthereumSigner.swift create mode 100644 Example/ExampleApp/Shared/Signer/Signer.swift create mode 100644 Example/ExampleApp/Shared/Signer/SolanaSigner.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index ffb400057..79b46d93d 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0D828E436A3001BACF9 /* InputConfig.swift */; }; A51AC0DD28E43727001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0DB28E436E6001BACF9 /* InputConfig.swift */; }; A51AC0DF28E4379F001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0DE28E4379F001BACF9 /* InputConfig.swift */; }; + A5434023291E6A270068F706 /* SolanaSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A5434022291E6A270068F706 /* SolanaSwift */; }; A55CAAB028B92AFF00844382 /* ScanModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55CAAAB28B92AFF00844382 /* ScanModule.swift */; }; A55CAAB128B92AFF00844382 /* ScanPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55CAAAC28B92AFF00844382 /* ScanPresenter.swift */; }; A55CAAB228B92AFF00844382 /* ScanRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55CAAAD28B92AFF00844382 /* ScanRouter.swift */; }; @@ -91,6 +92,8 @@ A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA362873D8EE00AA7720 /* UIColor.swift */; }; A578FA392873FCE000AA7720 /* ChatScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA382873FCE000AA7720 /* ChatScrollView.swift */; }; A578FA3D2874002400AA7720 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA3C2874002400AA7720 /* View.swift */; }; + A57E71A6291CF76400325797 /* EthereumSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57E71A5291CF76400325797 /* EthereumSigner.swift */; }; + A57E71A8291CF8A500325797 /* SolanaSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57E71A7291CF8A500325797 /* SolanaSigner.swift */; }; A58E7CEB28729F550082D443 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58E7CEA28729F550082D443 /* AppDelegate.swift */; }; A58E7CED28729F550082D443 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58E7CEC28729F550082D443 /* SceneDelegate.swift */; }; A58E7CF428729F550082D443 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A58E7CF328729F550082D443 /* Assets.xcassets */; }; @@ -282,6 +285,8 @@ A578FA362873D8EE00AA7720 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; A578FA382873FCE000AA7720 /* ChatScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollView.swift; sourceTree = ""; }; A578FA3C2874002400AA7720 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; + A57E71A5291CF76400325797 /* EthereumSigner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumSigner.swift; sourceTree = ""; }; + A57E71A7291CF8A500325797 /* SolanaSigner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolanaSigner.swift; sourceTree = ""; }; A58E7CE828729F550082D443 /* Showcase.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Showcase.app; sourceTree = BUILT_PRODUCTS_DIR; }; A58E7CEA28729F550082D443 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A58E7CEC28729F550082D443 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -370,6 +375,7 @@ buildActionMask = 2147483647; files = ( A5AE354728A1A2AC0059AE8A /* Web3 in Frameworks */, + A5434023291E6A270068F706 /* SolanaSwift in Frameworks */, 764E1D5826F8DBAB00A1FB15 /* WalletConnect in Frameworks */, A5D85226286333D500DAF5C3 /* Starscream in Frameworks */, A5C4DD8728A2DE88006A626D /* WalletConnectRouter in Frameworks */, @@ -434,10 +440,10 @@ 761248182819FA8B00CB6D48 /* Shared */ = { isa = PBXGroup; children = ( + A57E71A4291CF73300325797 /* Signer */, 761C64A526FCB0AA004239D1 /* SessionInfo.swift */, 84F568C32795832A00D0A289 /* EthereumTransaction.swift */, A5A4FC5D283D23CA00BBEC1E /* Array.swift */, - 84F568C1279582D200D0A289 /* Signer.swift */, 765056262821989600F9AE79 /* Color+Extension.swift */, 84494387278D9C1B00CC26BB /* UIAlertController.swift */, 76235E882820198B004ED0AA /* UIKit+Previews.swift */, @@ -755,6 +761,16 @@ path = SwiftUI; sourceTree = ""; }; + A57E71A4291CF73300325797 /* Signer */ = { + isa = PBXGroup; + children = ( + 84F568C1279582D200D0A289 /* Signer.swift */, + A57E71A5291CF76400325797 /* EthereumSigner.swift */, + A57E71A7291CF8A500325797 /* SolanaSigner.swift */, + ); + path = Signer; + sourceTree = ""; + }; A58E7CE928729F550082D443 /* Showcase */ = { isa = PBXGroup; children = ( @@ -1132,6 +1148,7 @@ A5D85225286333D500DAF5C3 /* Starscream */, A5AE354628A1A2AC0059AE8A /* Web3 */, A5C4DD8628A2DE88006A626D /* WalletConnectRouter */, + A5434022291E6A270068F706 /* SolanaSwift */, ); productName = ExampleApp; productReference = 764E1D3C26F8D3FC00A1FB15 /* WalletConnect Wallet.app */; @@ -1263,6 +1280,7 @@ packageReferences = ( A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */, A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */, + A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */, ); productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */; projectDirPath = ""; @@ -1339,9 +1357,11 @@ 76235E8B28201C9C004ED0AA /* Utilities.swift in Sources */, 76744CF726FE4D5400B77ED9 /* ActiveSessionItem.swift in Sources */, A5A4FC5A283CC08600BBEC1E /* SessionNamespaceViewModel.swift in Sources */, + A57E71A8291CF8A500325797 /* SolanaSigner.swift in Sources */, 764E1D4226F8D3FC00A1FB15 /* SceneDelegate.swift in Sources */, 84F568C2279582D200D0A289 /* Signer.swift in Sources */, A5A4FC56283CBB7800BBEC1E /* SessionDetailView.swift in Sources */, + A57E71A6291CF76400325797 /* EthereumSigner.swift in Sources */, 7600223B2819FC0B0011DD38 /* ProposalView.swift in Sources */, 761248172819F9E600CB6D48 /* WalletView.swift in Sources */, A5A4FC58283CBB9F00BBEC1E /* SessionDetailViewModel.swift in Sources */, @@ -1983,6 +2003,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/flypaper0/solana-swift"; + requirement = { + branch = "flypaper0-patch-1"; + kind = branch; + }; + }; A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/WalletConnect/Web3.swift"; @@ -2018,6 +2046,11 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; }; + A5434022291E6A270068F706 /* SolanaSwift */ = { + isa = XCSwiftPackageProductDependency; + package = A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */; + productName = SolanaSwift; + }; A5629AE92877F2D600094373 /* WalletConnectChat */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectChat; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0fbf7253f..01b4fca24 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -37,22 +37,40 @@ "version": "0.1.7" } }, + { + "package": "SolanaSwift", + "repositoryURL": "https://github.com/flypaper0/solana-swift", + "state": { + "branch": "flypaper0-patch-1", + "revision": "697b6232be5dcd33d4b5b95fe21721cab5be1050", + "version": null + } + }, { "package": "Starscream", "repositoryURL": "https://github.com/daltoniam/Starscream", "state": { "branch": null, - "revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d", - "version": "3.1.1" + "revision": "a063fda2b8145a231953c20e7a646be254365396", + "version": "3.1.2" } }, { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "package": "Task_retrying", + "repositoryURL": "https://github.com/bigearsenal/task-retrying-swift.git", "state": { "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" + "revision": "645eaaf207a6f39ab4b469558d916ae23df199b5", + "version": "1.0.3" + } + }, + { + "package": "TweetNacl", + "repositoryURL": "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git", + "state": { + "branch": null, + "revision": "f8fd111642bf2336b11ef9ea828510693106e954", + "version": "1.1.0" } }, { diff --git a/Example/ExampleApp/Request/RequestViewController.swift b/Example/ExampleApp/Request/RequestViewController.swift index f05ab4b0a..05f240675 100644 --- a/Example/ExampleApp/Request/RequestViewController.swift +++ b/Example/ExampleApp/Request/RequestViewController.swift @@ -43,15 +43,6 @@ class RequestViewController: UIViewController { } private func getParamsDescription() -> String { - let method = sessionRequest.method - if method == "personal_sign" { - return try! sessionRequest.params.get([String].self).description - } else if method == "eth_signTypedData" { - return try! sessionRequest.params.get([String].self).description - } else if method == "eth_sendTransaction" { - let params = try! sessionRequest.params.get([EthereumTransaction].self) - return params[0].description - } - fatalError("not implemented") + return String(describing: sessionRequest.params.value) } } diff --git a/Example/ExampleApp/Shared/Signer.swift b/Example/ExampleApp/Shared/Signer.swift deleted file mode 100644 index d1f2a9fc6..000000000 --- a/Example/ExampleApp/Shared/Signer.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Web3 -import Foundation -import WalletConnectUtils -import WalletConnectSign - -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 dataToHash = dataToHash(messageToSign) - let (v, r, s) = try! self.privateKey.sign(message: .init(hex: dataToHash.toHexString())) - let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) - return AnyCodable(result) - } else if method == "eth_signTypedData" { - // TODO - let result = "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" - 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 dataToHash(_ 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 dataToHash - } -} diff --git a/Example/ExampleApp/Shared/Signer/EthereumSigner.swift b/Example/ExampleApp/Shared/Signer/EthereumSigner.swift new file mode 100644 index 000000000..6f1178872 --- /dev/null +++ b/Example/ExampleApp/Shared/Signer/EthereumSigner.swift @@ -0,0 +1,50 @@ +import Foundation +import Commons +import Web3 + +struct EthereumSigner { + + private init() {} + + static var address: String { + return privateKey.address.hex(eip55: true) + } + + private static let privateKey: EthereumPrivateKey = { + return try! EthereumPrivateKey(hexPrivateKey: "0xe56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540") + }() + + static func personalSign(_ params: AnyCodable) -> AnyCodable { + let params = try! params.get([String].self) + let messageToSign = params[0] + let dataToHash = dataToHash(messageToSign) + let (v, r, s) = try! privateKey.sign(message: .init(hex: dataToHash.toHexString())) + let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) + return AnyCodable(result) + } + + static func signTypedData(_ params: AnyCodable) -> AnyCodable { + let result = "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" + return AnyCodable(result) + } + + static func sendTransaction(_ params: AnyCodable) -> AnyCodable { + let params = try! 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) + } + + private static func dataToHash(_ 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 dataToHash + } +} diff --git a/Example/ExampleApp/Shared/Signer/Signer.swift b/Example/ExampleApp/Shared/Signer/Signer.swift new file mode 100644 index 000000000..7576a350b --- /dev/null +++ b/Example/ExampleApp/Shared/Signer/Signer.swift @@ -0,0 +1,26 @@ +import Foundation +import Commons +import WalletConnectSign + +class Signer { + + private init() {} + + static func signEth(request: Request) -> AnyCodable { + switch request.method { + case "personal_sign": + return EthereumSigner.personalSign(request.params) + + case "eth_signTypedData": + return EthereumSigner.signTypedData(request.params) + + case "eth_sendTransaction": + return EthereumSigner.sendTransaction(request.params) + + case "solana_signTransaction": + return SolanaSigner.signTransaction(request.params) + default: + fatalError("not implemented") + } + } +} diff --git a/Example/ExampleApp/Shared/Signer/SolanaSigner.swift b/Example/ExampleApp/Shared/Signer/SolanaSigner.swift new file mode 100644 index 000000000..db070b16b --- /dev/null +++ b/Example/ExampleApp/Shared/Signer/SolanaSigner.swift @@ -0,0 +1,55 @@ +import Foundation +import Commons +import SolanaSwift +import TweetNacl + +struct SolanaSigner { + + static var address: String { + return account.publicKey.base58EncodedString + } + + private static let account: Account = { +// let phrase = "cargo morning orient cannon ship code journey walnut cycle cupboard width high" + let key = "4eN1YZm598FtdigriE5int7Gf5dxs58rzVh3ftRwxjkYXxkiDiweuvkop2Kr5Td174DcbVdDxzjWqQ96uir3NYka" + return try! Account(secretKey: Data(Base58.decode(key))) + }() + + private init() {} + + static func signTransaction(_ params: AnyCodable) -> AnyCodable { + let transaction = try! params.get(SolSignTransaction.self) + print("Transaction:::\n\(String(describing: params.value))") + + let message = try! transaction.transaction.compileMessage() + let serializedMessage = try! message.serialize() + + let signature = try! NaclSign.signDetached( + message: serializedMessage, + secretKey: account.secretKey + ) + return AnyCodable(["signature": Base58.encode(signature)]) + } +} + +fileprivate struct SolSignTransaction: Codable { + let instructions: [TransactionInstruction] + let recentBlockhash: String + let feePayer: PublicKey + + var transaction: Transaction { + return Transaction( + instructions: instructions, + recentBlockhash: recentBlockhash, + feePayer: feePayer + ) + } +} + +fileprivate struct Signature: Codable { + struct Sig: Codable { + let data: [UInt8] + } + let signature: Sig? + let publicKey: PublicKey +} diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index 6ebc4f14b..418593775 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -9,8 +9,8 @@ import Combine final class WalletViewController: UIViewController { lazy var accounts = [ - "eip155": Signer.privateKey.address.hex(eip55: true), - "solana": "BvQtshZCDdzXeuC6Hrp2tBjJThUf3LN2Qeup3avrStTX" + "eip155": EthereumSigner.address, + "solana": SolanaSigner.address ] var sessionItems: [ActiveSessionItem] = [] From c825c890c22569136b822789dfb7740e68535f22 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 11 Nov 2022 19:40:52 +0600 Subject: [PATCH 03/15] CI refactor --- .github/actions/ci/action.yml | 8 ++++---- .github/workflows/ci.yml | 8 +++++--- .../Contents.json | 2 +- .../Solana_logo.png | Bin 28416 -> 0 bytes .../solana (1).png | Bin 0 -> 762 bytes .../SelectChain/SelectChainViewController.swift | 7 ++++++- Example/ExampleApp.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../SessionDetailViewController.swift | 2 +- Example/ExampleApp/Shared/Signer/Signer.swift | 2 +- .../ExampleApp/Shared/Signer/SolanaSigner.swift | 12 ------------ .../Wallet/WalletViewController.swift | 2 +- 12 files changed, 22 insertions(+), 27 deletions(-) delete mode 100644 Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png create mode 100644 Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/solana (1).png diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 0e21edcae..e3f1b5e5f 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -52,8 +52,8 @@ runs: -project Example/ExampleApp.xcodeproj \ -scheme Wallet \ -clonedSourcePackagesDirPath SourcePackagesCache \ - -derivedDataPath DerivedDataCache \ - -sdk iphonesimulator" + -destination 'platform=iOS Simulator,name=iPhone 13' \ + -derivedDataPath DerivedDataCache" # DApp build - name: Build Example Dapp @@ -63,8 +63,8 @@ runs: -project Example/ExampleApp.xcodeproj \ -scheme DApp \ -clonedSourcePackagesDirPath SourcePackagesCache \ - -derivedDataPath DerivedDataCache \ - -sdk iphonesimulator" + -destination 'platform=iOS Simulator,name=iPhone 13' \ + -derivedDataPath DerivedDataCache" # UI tests - name: UI Tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf2b26eae..8ff4a9a95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ concurrency: jobs: build: - runs-on: macos-latest + runs-on: macos-12 strategy: matrix: test-type: [unit-tests, integration-tests, build-example-wallet, build-example-dapp] @@ -27,6 +27,8 @@ jobs: - name: Setup Xcode Version uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '13.4.1' - uses: actions/cache@v2 with: @@ -41,8 +43,8 @@ jobs: - name: Resolve Dependencies shell: bash run: " - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache; \ - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache" + xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache -destination 'platform=iOS Simulator,name=iPhone 13'; \ + xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache -destination 'platform=iOS Simulator,name=iPhone 13'" - uses: ./.github/actions/ci with: diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json index 97219ba07..e40d0ae29 100644 --- a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json +++ b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Solana_logo.png", + "filename" : "solana (1).png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Solana_logo.png deleted file mode 100644 index 26aec35be0f047baaac46df6bd3206f4c12bf6d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28416 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 diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/solana (1).png b/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/solana (1).png new file mode 100644 index 0000000000000000000000000000000000000000..1e7c649f810253dc9ef5671d00ecc1cf531daefe GIT binary patch literal 762 zcmV0_ zp;Z=Gt%jhXtvKPcfq)touprR{jg=2W)PKp9L{5{EA%=_PW?tAy$bKaB` zLo2$_iNEkGe!xt#g{3CnHsLKij_>gc=HW4{z>oL<dg0G1Z4~-;!dng$!=o| zN3jKa@lA?riZ@U}FFwQ<*okgDgaufQx6{stu@0v(ScH58{Wy;e1va!1=dc;0jgs`> zI9|a79w|Ux#S(m#lAKChH7MZ(o=-dfKje9m^H`6QiF3A#HsfV<;6Mt5Hc_(q4TvAb zLA;+hcH*~mqS@~HG-W=PEiB_RY;8dP6wYFCHa8*eZj=yE3)-<3A7_E@icY#MT5&Es zivj$e%^krrcmkysEEAW~iI0RzrCr?-%B?plY2>(z?f4Vlw&F?Lz;pO13;YAG;T`-_ z=L7?o#!xmlm3Ds>?d23OlZ`4Su`lhW9!9YTec9Y}8uxIo)xS0{g{Al|3m(G5*w!FP z1&gxZUHDm8%Js5v#cq7NMgrfI1Nbomyh?>KTxQ5q7N7mq^5Zk0$;S|LpA-Z0*f}O=0 zVRrraQtibRA+HE$JuZYcCkDmQ`o&55>IG(~mh>fIIj=TbFepy2UbN}Bcs5rG(`&;j zyeUetUtH?MT;%oM8Am!t?v!W27%FI~qi1^%|&2g-bt(ip_y3IG5A07*qoM6N<$g4>T private func showSessionRequest(_ request: Request) { let viewController = RequestViewController(request) viewController.onSign = { [unowned self] in - let result = Signer.signEth(request: request) + let result = Signer.sign(request: request) respondOnSign(request: request, response: result) reload() } diff --git a/Example/ExampleApp/Shared/Signer/Signer.swift b/Example/ExampleApp/Shared/Signer/Signer.swift index 7576a350b..dde8e101f 100644 --- a/Example/ExampleApp/Shared/Signer/Signer.swift +++ b/Example/ExampleApp/Shared/Signer/Signer.swift @@ -6,7 +6,7 @@ class Signer { private init() {} - static func signEth(request: Request) -> AnyCodable { + static func sign(request: Request) -> AnyCodable { switch request.method { case "personal_sign": return EthereumSigner.personalSign(request.params) diff --git a/Example/ExampleApp/Shared/Signer/SolanaSigner.swift b/Example/ExampleApp/Shared/Signer/SolanaSigner.swift index db070b16b..c65ec83f1 100644 --- a/Example/ExampleApp/Shared/Signer/SolanaSigner.swift +++ b/Example/ExampleApp/Shared/Signer/SolanaSigner.swift @@ -10,7 +10,6 @@ struct SolanaSigner { } private static let account: Account = { -// let phrase = "cargo morning orient cannon ship code journey walnut cycle cupboard width high" let key = "4eN1YZm598FtdigriE5int7Gf5dxs58rzVh3ftRwxjkYXxkiDiweuvkop2Kr5Td174DcbVdDxzjWqQ96uir3NYka" return try! Account(secretKey: Data(Base58.decode(key))) }() @@ -19,11 +18,8 @@ struct SolanaSigner { static func signTransaction(_ params: AnyCodable) -> AnyCodable { let transaction = try! params.get(SolSignTransaction.self) - print("Transaction:::\n\(String(describing: params.value))") - let message = try! transaction.transaction.compileMessage() let serializedMessage = try! message.serialize() - let signature = try! NaclSign.signDetached( message: serializedMessage, secretKey: account.secretKey @@ -45,11 +41,3 @@ fileprivate struct SolSignTransaction: Codable { ) } } - -fileprivate struct Signature: Codable { - struct Sig: Codable { - let data: [UInt8] - } - let signature: Sig? - let publicKey: PublicKey -} diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index 418593775..034998740 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -76,7 +76,7 @@ final class WalletViewController: UIViewController { private func showSessionRequest(_ request: Request) { let requestVC = RequestViewController(request) requestVC.onSign = { [unowned self] in - let result = Signer.signEth(request: request) + let result = Signer.sign(request: request) respondOnSign(request: request, response: result) reloadSessionDetailsIfNeeded() } From 494f8cd4808bb4016f2735f18ae9bd4ac0ec53ab Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 16 Nov 2022 08:37:38 +0100 Subject: [PATCH 04/15] savepoint --- .../Engine/Common/ApproveEngine.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 438b7f29f..6cf18b302 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -56,6 +56,7 @@ final class ApproveEngine { } func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { + print("approving: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.wrongRequestParams } @@ -87,12 +88,14 @@ final class ApproveEngine { let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) let response = RPCResponse(id: payload.id, result: result) + Task { try await networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod()) + } try pairing.updateExpiry() pairingStore.setPairing(pairing) - try await settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces) + try settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces) } func reject(proposerPubKey: String, reason: SignReasonCode) async throws { @@ -104,7 +107,7 @@ final class ApproveEngine { // TODO: Delete pairing if inactive } - func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace]) async throws { + func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace]) throws { guard let agreementKeys = kms.getAgreementSecret(for: topic) else { throw Errors.agreementMissingOrInvalid } @@ -138,12 +141,18 @@ final class ApproveEngine { logger.debug("Sending session settle request") - try await networkingInteractor.subscribe(topic: topic) + Task { + try await networkingInteractor.subscribe(topic: topic) + print("subscription end: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") + } sessionStore.setSession(session) let protocolMethod = SessionSettleProtocolMethod() let request = RPCRequest(method: protocolMethod.method, params: settleParams) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + Task { + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + print("settle sent: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") + } onSessionSettle?(session.publicRepresentation()) } } From 3e565aad11a60f684490ed3d4af1773dfbfbe43b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 16 Nov 2022 08:39:37 +0100 Subject: [PATCH 05/15] remove print statements --- Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 6cf18b302..074c7ac40 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -56,7 +56,6 @@ final class ApproveEngine { } func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { - print("approving: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.wrongRequestParams } @@ -143,7 +142,6 @@ final class ApproveEngine { Task { try await networkingInteractor.subscribe(topic: topic) - print("subscription end: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") } sessionStore.setSession(session) @@ -151,7 +149,6 @@ final class ApproveEngine { let request = RPCRequest(method: protocolMethod.method, params: settleParams) Task { try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - print("settle sent: \(Int64((Date().timeIntervalSince1970 * 1000.0).rounded()))") } onSessionSettle?(session.publicRepresentation()) } From 597061ef3f82a01e8a9d922aa4a832b5ca259252 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 16 Nov 2022 08:59:48 +0100 Subject: [PATCH 06/15] update concurrency --- .../Engine/Common/ApproveEngine.swift | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 074c7ac40..8ac916277 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -87,14 +87,15 @@ final class ApproveEngine { let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) let response = RPCResponse(id: payload.id, result: result) - Task { - try await networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod()) - } + + async let proposeResponse: () = networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod()) + + async let settleRequest: () = settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces) + + let _ = try await [proposeResponse, settleRequest] try pairing.updateExpiry() pairingStore.setPairing(pairing) - - try settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces) } func reject(proposerPubKey: String, reason: SignReasonCode) async throws { @@ -106,7 +107,7 @@ final class ApproveEngine { // TODO: Delete pairing if inactive } - func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace]) throws { + func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace]) async throws { guard let agreementKeys = kms.getAgreementSecret(for: topic) else { throw Errors.agreementMissingOrInvalid } @@ -140,16 +141,16 @@ final class ApproveEngine { logger.debug("Sending session settle request") - Task { - try await networkingInteractor.subscribe(topic: topic) - } sessionStore.setSession(session) let protocolMethod = SessionSettleProtocolMethod() let request = RPCRequest(method: protocolMethod.method, params: settleParams) - Task { - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - } + + async let subscription: () = networkingInteractor.subscribe(topic: topic) + async let settleRequest: () = networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + + let _ = try await [settleRequest, subscription] + onSessionSettle?(session.publicRepresentation()) } } From 21924019de7ae79c4a88b21e3f0d1de9c61ef008 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 17 Nov 2022 12:34:35 +0100 Subject: [PATCH 07/15] remove force unwrap from request --- Sources/WalletConnectNetworking/NetworkingInteractor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 2beaca2db..4aa117c12 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -95,7 +95,7 @@ public class NetworkingInteractor: NetworkInteracting { public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { try rpcHistory.set(request, forTopic: topic, emmitedBy: .local) - let message = try! serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) + let message = try serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.requestConfig.tag, prompt: protocolMethod.requestConfig.prompt, ttl: protocolMethod.requestConfig.ttl) } From d3cb5ce96327e2a64f25724b3bf4fd5008358873 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 21 Nov 2022 13:12:05 +0100 Subject: [PATCH 08/15] savepoint --- Sources/JSONRPC/RPCResponse.swift | 2 +- Sources/WalletConnectSign/Engine/Common/SessionEngine.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JSONRPC/RPCResponse.swift b/Sources/JSONRPC/RPCResponse.swift index b6fddfb80..09b0a099c 100644 --- a/Sources/JSONRPC/RPCResponse.swift +++ b/Sources/JSONRPC/RPCResponse.swift @@ -19,7 +19,7 @@ public struct RPCResponse: Equatable { public let outcome: RPCResult - internal init(id: RPCID?, outcome: RPCResult) { + public init(id: RPCID?, outcome: RPCResult) { self.jsonrpc = "2.0" self.id = id self.outcome = outcome diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index b921bb6b4..13797ea27 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -63,7 +63,7 @@ final class SessionEngine { guard sessionStore.hasSession(forTopic: topic) else { throw Errors.sessionNotFound(topic: topic) } - let response = RPCResponse(id: requestId, result: response) + let response = RPCResponse(id: requestId, outcome: response) try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: SessionRequestProtocolMethod()) } From a1f131994f08b40861581b0382efe1c3edd679e3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 22 Nov 2022 10:01:51 +0100 Subject: [PATCH 09/15] fix session response issue --- .../xcshareddata/swiftpm/Package.resolved | 13 +++++++++++-- .../Engine/Common/SessionEngine.swift | 15 +++++++++++++-- Tests/JSONRPCTests/RPCResponseTests.swift | 6 ++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8893d902d..36e0ec5e5 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,17 @@ "repositoryURL": "https://github.com/daltoniam/Starscream", "state": { "branch": null, - "revision": "a063fda2b8145a231953c20e7a646be254365396", - "version": "3.1.2" + "revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d", + "version": "3.1.1" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" } }, { diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 13797ea27..52c5fcb9f 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -121,12 +121,23 @@ private extension SessionEngine { func setupResponseSubscriptions() { networkingInteractor.responseSubscription(on: SessionRequestProtocolMethod()) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in onSessionResponse?(Response( id: payload.id, topic: payload.topic, chainId: payload.request.chainId.absoluteString, - result: payload.response + result: .response(payload.response) + )) + } + .store(in: &publishers) + + networkingInteractor.responseErrorSubscription(on: SessionRequestProtocolMethod()) + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + onSessionResponse?(Response( + id: payload.id, + topic: payload.topic, + chainId: payload.request.chainId.absoluteString, + result: .error(payload.error) )) } .store(in: &publishers) diff --git a/Tests/JSONRPCTests/RPCResponseTests.swift b/Tests/JSONRPCTests/RPCResponseTests.swift index d067761b3..e98c0cf9a 100644 --- a/Tests/JSONRPCTests/RPCResponseTests.swift +++ b/Tests/JSONRPCTests/RPCResponseTests.swift @@ -10,7 +10,8 @@ private func makeResultResponses() -> [RPCResponse] { RPCResponse(id: Int64.random(), result: String.random()), RPCResponse(id: Int64.random(), result: (1...10).map { String($0) }), RPCResponse(id: Int64.random(), result: EmptyCodable()), - RPCResponse(id: String.random(), result: Int.random()) + RPCResponse(id: String.random(), result: Int.random()), + RPCResponse(id: RPCID(String.random()), outcome: .response(AnyCodable(Int.random()))) ] } @@ -19,7 +20,8 @@ private func makeErrorResponses() -> [RPCResponse] { RPCResponse(id: Int64.random(), error: JSONRPCError.stub()), RPCResponse(id: String.random(), error: JSONRPCError.stub(data: AnyCodable(Int.random()))), RPCResponse(id: Int64.random(), errorCode: Int.random(), message: String.random(), associatedData: AnyCodable(String.random())), - RPCResponse(id: String.random(), errorCode: Int.random(), message: String.random(), associatedData: nil) + RPCResponse(id: String.random(), errorCode: Int.random(), message: String.random(), associatedData: nil), + RPCResponse(id: RPCID(String.random()), outcome: .error(JSONRPCError.stub())) ] } From 4d9be4f582d46a506fe43e7dd783ebd11cb7cd2d Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 22 Nov 2022 15:18:36 +0500 Subject: [PATCH 10/15] AutomaticSocketConnectionHandler codestyle --- .../AppStateObserving.swift | 2 +- .../NetworkMonitoring.swift | 2 +- .../AutomaticSocketConnectionHandler.swift | 46 ++++++++++++------- .../ManualSocketConnectionHandler.swift | 5 ++ .../SocketConnectionHandler.swift | 1 + ...utomaticSocketConnectionHandlerTests.swift | 2 +- 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/Sources/WalletConnectRelay/AppStateObserving.swift b/Sources/WalletConnectRelay/AppStateObserving.swift index bc708c74b..e166ba8b8 100644 --- a/Sources/WalletConnectRelay/AppStateObserving.swift +++ b/Sources/WalletConnectRelay/AppStateObserving.swift @@ -3,7 +3,7 @@ import Foundation import UIKit #endif -protocol AppStateObserving { +protocol AppStateObserving: AnyObject { var onWillEnterForeground: (() -> Void)? {get set} var onWillEnterBackground: (() -> Void)? {get set} } diff --git a/Sources/WalletConnectRelay/NetworkMonitoring.swift b/Sources/WalletConnectRelay/NetworkMonitoring.swift index 66fe0a88c..c4200171f 100644 --- a/Sources/WalletConnectRelay/NetworkMonitoring.swift +++ b/Sources/WalletConnectRelay/NetworkMonitoring.swift @@ -1,7 +1,7 @@ import Foundation import Network -protocol NetworkMonitoring { +protocol NetworkMonitoring: AnyObject { var onSatisfied: (() -> Void)? {get set} var onUnsatisfied: (() -> Void)? {get set} func startMonitoring() diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 273b3a175..169633d98 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -4,26 +4,31 @@ import UIKit import Foundation import Combine -class AutomaticSocketConnectionHandler: SocketConnectionHandler { - enum Error: Swift.Error { - case manualSocketConnectionForbidden - case manualSocketDisconnectionForbidden +class AutomaticSocketConnectionHandler { + + enum Errors: Error { + case manualSocketConnectionForbidden, manualSocketDisconnectionForbidden } - private var appStateObserver: AppStateObserving + let socket: WebSocketConnecting - private var networkMonitor: NetworkMonitoring + + private let appStateObserver: AppStateObserving + private let networkMonitor: NetworkMonitoring private let backgroundTaskRegistrar: BackgroundTaskRegistering private var publishers = Set() - init(networkMonitor: NetworkMonitoring = NetworkMonitor(), - socket: WebSocketConnecting, - appStateObserver: AppStateObserving = AppStateObserver(), - backgroundTaskRegistrar: BackgroundTaskRegistering = BackgroundTaskRegistrar()) { + init( + socket: WebSocketConnecting, + networkMonitor: NetworkMonitoring = NetworkMonitor(), + appStateObserver: AppStateObserving = AppStateObserver(), + backgroundTaskRegistrar: BackgroundTaskRegistering = BackgroundTaskRegistrar() + ) { self.appStateObserver = appStateObserver self.socket = socket self.networkMonitor = networkMonitor self.backgroundTaskRegistrar = backgroundTaskRegistrar + setUpStateObserving() setUpNetworkMonitoring() @@ -59,17 +64,26 @@ class AutomaticSocketConnectionHandler: SocketConnectionHandler { socket.disconnect() } + func handleNetworkSatisfied() { + if !socket.isConnected { + socket.connect() + } + } +} + +// MARK: - SocketConnectionHandler + +extension AutomaticSocketConnectionHandler: SocketConnectionHandler { + func handleConnect() throws { - throw Error.manualSocketConnectionForbidden + throw Errors.manualSocketConnectionForbidden } func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws { - throw Error.manualSocketDisconnectionForbidden + throw Errors.manualSocketDisconnectionForbidden } - func handleNetworkSatisfied() { - if !socket.isConnected { - socket.connect() - } + func handleDisconnection() { + } } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift index 25f293d05..bed340e44 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift @@ -14,4 +14,9 @@ class ManualSocketConnectionHandler: SocketConnectionHandler { func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws { socket.disconnect() } + + func handleDisconnection() { + // No operation + // ManualSocketConnectionHandler does not support reconnection logic + } } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift index 0b5e0673b..035d1d1df 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift @@ -3,4 +3,5 @@ import Foundation protocol SocketConnectionHandler { func handleConnect() throws func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws + func handleDisconnection() } diff --git a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift index d37cec47f..d3016701f 100644 --- a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift +++ b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift @@ -14,8 +14,8 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { appStateObserver = AppStateObserverMock() backgroundTaskRegistrar = BackgroundTaskRegistrarMock() sut = AutomaticSocketConnectionHandler( - networkMonitor: networkMonitor, socket: webSocketSession, + networkMonitor: networkMonitor, appStateObserver: appStateObserver, backgroundTaskRegistrar: backgroundTaskRegistrar) } From 88be00f2071f66a2f2a37dcab6ae34a90826038b Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 22 Nov 2022 15:22:42 +0500 Subject: [PATCH 11/15] Disconnection Handler --- Sources/WalletConnectRelay/Dispatching.swift | 1 + .../AutomaticSocketConnectionHandler.swift | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index fcf9b588f..10dff9898 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -103,6 +103,7 @@ final class Dispatcher: NSObject, Dispatching { } socket.onDisconnect = { [unowned self] _ in self.socketConnectionStatusPublisherSubject.send(.disconnected) + self.socketConnectionHandler.handleDisconnection() } } } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 169633d98..0d18c1620 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -41,30 +41,28 @@ class AutomaticSocketConnectionHandler { } appStateObserver.onWillEnterForeground = { [unowned self] in - if !socket.isConnected { - socket.connect() - } + reconnect() } } private func setUpNetworkMonitoring() { networkMonitor.onSatisfied = { [weak self] in - self?.handleNetworkSatisfied() + self?.reconnect() } networkMonitor.startMonitoring() } - func registerBackgroundTask() { + private func registerBackgroundTask() { backgroundTaskRegistrar.register(name: "Finish Network Tasks") { [unowned self] in endBackgroundTask() } } - func endBackgroundTask() { + private func endBackgroundTask() { socket.disconnect() } - func handleNetworkSatisfied() { + private func reconnect() { if !socket.isConnected { socket.connect() } @@ -84,6 +82,6 @@ extension AutomaticSocketConnectionHandler: SocketConnectionHandler { } func handleDisconnection() { - + reconnect() } } From 0434e31e019190b8c6047475c86a618046a5e4fc Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 22 Nov 2022 16:02:42 +0500 Subject: [PATCH 12/15] Handle disconnect on foreground only --- .../AppStateObserving.swift | 21 +++++++++++++- .../AutomaticSocketConnectionHandler.swift | 7 +++-- ...utomaticSocketConnectionHandlerTests.swift | 29 +++++++++++++++---- .../Mocks/AppStateObserverMock.swift | 1 + 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/Sources/WalletConnectRelay/AppStateObserving.swift b/Sources/WalletConnectRelay/AppStateObserving.swift index e166ba8b8..3db4ff629 100644 --- a/Sources/WalletConnectRelay/AppStateObserving.swift +++ b/Sources/WalletConnectRelay/AppStateObserving.swift @@ -1,14 +1,23 @@ import Foundation -#if os(iOS) + +#if canImport(UIKit) import UIKit +#elseif canImport(AppKit) +import AppKit #endif +enum ApplicationState { + case background, foreground +} + protocol AppStateObserving: AnyObject { + var currentState: ApplicationState { get } var onWillEnterForeground: (() -> Void)? {get set} var onWillEnterBackground: (() -> Void)? {get set} } class AppStateObserver: AppStateObserving { + @objc var onWillEnterForeground: (() -> Void)? @objc var onWillEnterBackground: (() -> Void)? @@ -17,6 +26,16 @@ class AppStateObserver: AppStateObserving { subscribeNotificationCenter() } + var currentState: ApplicationState { +#if canImport(UIKit) + let isActive = UIApplication.shared.applicationState == .active + return isActive ? .foreground : .background +#elseif canImport(AppKit) + let isActive = NSApplication.shared.isActive + return isActive ? .foreground : .background +#endif + } + private func subscribeNotificationCenter() { #if os(iOS) NotificationCenter.default.addObserver( diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 0d18c1620..30c46573b 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -10,8 +10,7 @@ class AutomaticSocketConnectionHandler { case manualSocketConnectionForbidden, manualSocketDisconnectionForbidden } - let socket: WebSocketConnecting - + private let socket: WebSocketConnecting private let appStateObserver: AppStateObserving private let networkMonitor: NetworkMonitoring private let backgroundTaskRegistrar: BackgroundTaskRegistering @@ -82,6 +81,8 @@ extension AutomaticSocketConnectionHandler: SocketConnectionHandler { } func handleDisconnection() { - reconnect() + if appStateObserver.currentState == .foreground { + reconnect() + } } } diff --git a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift index d3016701f..1207b4335 100644 --- a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift +++ b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift @@ -6,8 +6,9 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { var sut: AutomaticSocketConnectionHandler! var webSocketSession: WebSocketMock! var networkMonitor: NetworkMonitoringMock! - var appStateObserver: AppStateObserving! + var appStateObserver: AppStateObserverMock! var backgroundTaskRegistrar: BackgroundTaskRegistrarMock! + override func setUp() { webSocketSession = WebSocketMock() networkMonitor = NetworkMonitoringMock() @@ -22,9 +23,9 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { func testConnectsOnConnectionSatisfied() { webSocketSession.disconnect() - XCTAssertFalse(sut.socket.isConnected) + XCTAssertFalse(webSocketSession.isConnected) networkMonitor.onSatisfied?() - XCTAssertTrue(sut.socket.isConnected) + XCTAssertTrue(webSocketSession.isConnected) } func testHandleConnectThrows() { @@ -38,7 +39,7 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { func testReconnectsOnEnterForeground() { webSocketSession.disconnect() appStateObserver.onWillEnterForeground?() - XCTAssertTrue(sut.socket.isConnected) + XCTAssertTrue(webSocketSession.isConnected) } func testRegisterTaskOnEnterBackground() { @@ -49,8 +50,24 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { func testDisconnectOnEndBackgroundTask() { appStateObserver.onWillEnterBackground?() - XCTAssertTrue(sut.socket.isConnected) + XCTAssertTrue(webSocketSession.isConnected) backgroundTaskRegistrar.completion!() - XCTAssertFalse(sut.socket.isConnected) + XCTAssertFalse(webSocketSession.isConnected) + } + + func testReconnectOnDisconnectForeground() { + appStateObserver.currentState = .foreground + XCTAssertTrue(webSocketSession.isConnected) + webSocketSession.disconnect() + sut.handleDisconnection() + XCTAssertTrue(webSocketSession.isConnected) + } + + func testReconnectOnDisconnectBackground() { + appStateObserver.currentState = .background + XCTAssertTrue(webSocketSession.isConnected) + webSocketSession.disconnect() + sut.handleDisconnection() + XCTAssertFalse(webSocketSession.isConnected) } } diff --git a/Tests/RelayerTests/Mocks/AppStateObserverMock.swift b/Tests/RelayerTests/Mocks/AppStateObserverMock.swift index 5eb39c927..0da2d08d8 100644 --- a/Tests/RelayerTests/Mocks/AppStateObserverMock.swift +++ b/Tests/RelayerTests/Mocks/AppStateObserverMock.swift @@ -2,6 +2,7 @@ import Foundation @testable import WalletConnectRelay class AppStateObserverMock: AppStateObserving { + var currentState: ApplicationState = .foreground var onWillEnterForeground: (() -> Void)? var onWillEnterBackground: (() -> Void)? } From ef4862f8e04b846840f69aa7ffe01d606deab567 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 22 Nov 2022 13:08:58 +0100 Subject: [PATCH 13/15] upgrade lib version --- .../xcshareddata/swiftpm/Package.resolved | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 36e0ec5e5..8893d902d 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,17 +51,8 @@ "repositoryURL": "https://github.com/daltoniam/Starscream", "state": { "branch": null, - "revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d", - "version": "3.1.1" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" + "revision": "a063fda2b8145a231953c20e7a646be254365396", + "version": "3.1.2" } }, { From 76329af329abda4d8aad8ae9b1940489da6d7862 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 22 Nov 2022 20:40:44 +0500 Subject: [PATCH 14/15] Renamed to reconnectIfNeeded --- .../AutomaticSocketConnectionHandler.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 30c46573b..5e95f79b8 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -40,13 +40,13 @@ class AutomaticSocketConnectionHandler { } appStateObserver.onWillEnterForeground = { [unowned self] in - reconnect() + reconnectIfNeeded() } } private func setUpNetworkMonitoring() { networkMonitor.onSatisfied = { [weak self] in - self?.reconnect() + self?.reconnectIfNeeded() } networkMonitor.startMonitoring() } @@ -61,7 +61,7 @@ class AutomaticSocketConnectionHandler { socket.disconnect() } - private func reconnect() { + private func reconnectIfNeeded() { if !socket.isConnected { socket.connect() } @@ -82,7 +82,7 @@ extension AutomaticSocketConnectionHandler: SocketConnectionHandler { func handleDisconnection() { if appStateObserver.currentState == .foreground { - reconnect() + reconnectIfNeeded() } } } From 4c0489c20195d9da93cd5504bb544aed05034e21 Mon Sep 17 00:00:00 2001 From: llbartekll Date: Fri, 25 Nov 2022 10:57:12 +0000 Subject: [PATCH 15/15] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 1f6b5d28d..4a5b368a0 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.0.5"} +{"version": "1.0.6"}