diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml
index 7e1880db3..44f9f0988 100644
--- a/.github/actions/ci/action.yml
+++ b/.github/actions/ci/action.yml
@@ -8,6 +8,9 @@ inputs:
description: 'The endpoint of the relay e.g. relay.walletconnect.com'
required: false
default: 'relay.walletconnect.com'
+ project-id:
+ description: 'WalletConnect project id'
+ required: true
runs:
using: "composite"
@@ -29,12 +32,14 @@ runs:
shell: bash
env:
RELAY_ENDPOINT: ${{ inputs.relay-endpoint }}
+ PROJECT_ID: ${{ inputs.project-id }}
run: "xcodebuild \
-project Example/ExampleApp.xcodeproj \
-scheme IntegrationTests \
-clonedSourcePackagesDirPath SourcePackagesCache \
-destination 'platform=iOS Simulator,name=iPhone 13' \
RELAY_HOST='$RELAY_ENDPOINT' \
+ PROJECT_ID='$PROJECT_ID' \
test"
# Wallet build
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bcbcf09cf..9b697507a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,12 +28,6 @@ jobs:
- name: Setup Xcode Version
uses: maxim-lobanov/setup-xcode@v1
- - 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"
-
- uses: actions/cache@v2
with:
path: |
@@ -43,9 +37,16 @@ jobs:
restore-keys: |
${{ runner.os }}-spm-
+ - 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"
+
- uses: ./.github/actions/ci
with:
type: ${{ matrix.test-type }}
+ project-id: ${{ secrets.PROJECT_ID }}
test-ui:
if: github.ref == 'refs/heads/main'
diff --git a/.gitignore b/.gitignore
index 36f794f62..dad29299f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@ Package.resolved
*/fastlane/test_output
*/fastlane/README.md
+# Configuration
+Configuration.xcconfig
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
index 75284ea37..ff9119893 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
@@ -272,6 +272,16 @@
ReferencedContainer = "container:">
+
+
+
+
()
@Published var state: SigningState = .none
- @Published var uri: String?
+ @Published var uriString: String?
var qrImage: UIImage? {
- return uri.map { QRCodeGenerator.generateQRCode(from: $0) }
+ return uriString.map { QRCodeGenerator.generateQRCode(from: $0) }
}
init() {
@@ -26,12 +27,14 @@ final class AuthViewModel: ObservableObject {
@MainActor
func setupInitialState() async throws {
state = .none
- uri = nil
- uri = try await Auth.instance.request(.stub()).absoluteString
+ uriString = nil
+ let uri = try! await Pair.instance.create()
+ uriString = uri.absoluteString
+ try await Auth.instance.request(.stub(), topic: uri.topic)
}
func copyDidPressed() {
- UIPasteboard.general.string = uri
+ UIPasteboard.general.string = uriString
}
func walletDidPressed() {
@@ -39,7 +42,7 @@ final class AuthViewModel: ObservableObject {
}
func deeplinkPressed() {
- guard let uri = uri else { return }
+ guard let uri = uriString else { return }
UIApplication.shared.open(URL(string: "showcase://wc?uri=\(uri)")!)
}
}
diff --git a/Example/DApp/Common/InputConfig.swift b/Example/DApp/Common/InputConfig.swift
new file mode 100644
index 000000000..53931721a
--- /dev/null
+++ b/Example/DApp/Common/InputConfig.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+struct InputConfig {
+
+ static var projectId: String {
+ return config(for: "PROJECT_ID")!
+ }
+
+ private static func config(for key: String) -> String? {
+ return Bundle.main.object(forInfoDictionaryKey: key) as? String
+ }
+}
diff --git a/Example/DApp/Info.plist b/Example/DApp/Info.plist
index 7c5f0be1e..07ba0e705 100644
--- a/Example/DApp/Info.plist
+++ b/Example/DApp/Info.plist
@@ -2,6 +2,8 @@
+ PROJECT_ID
+ $(PROJECT_ID)
ITSAppUsesNonExemptEncryption
UIApplicationSceneManifest
diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift
index d130770b8..afeca0dce 100644
--- a/Example/DApp/SceneDelegate.swift
+++ b/Example/DApp/SceneDelegate.swift
@@ -1,6 +1,7 @@
import UIKit
import Starscream
import WalletConnectRelay
+import WalletConnectNetworking
extension WebSocket: WebSocketConnecting { }
@@ -18,7 +19,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
private let authCoordinator = AuthCoordinator()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
- Relay.configure(projectId: "3ca2919724fbfa5456a25194e369a8b4", socketFactory: SocketFactory())
+ Networking.configure(projectId: InputConfig.projectId, socketFactory: SocketFactory())
setupWindow(scene: scene)
}
diff --git a/Example/DApp/Sign/Connect/ConnectViewController.swift b/Example/DApp/Sign/Connect/ConnectViewController.swift
index adebac5c3..15e50a9fd 100644
--- a/Example/DApp/Sign/Connect/ConnectViewController.swift
+++ b/Example/DApp/Sign/Connect/ConnectViewController.swift
@@ -1,10 +1,11 @@
import Foundation
import UIKit
import WalletConnectSign
+import WalletConnectPairing
class ConnectViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let uri: WalletConnectURI
- let activePairings: [Pairing] = Sign.instance.getPairings()
+ let activePairings: [Pairing] = Pair.instance.getPairings()
let segmentedControl = UISegmentedControl(items: ["Pairings", "New Pairing"])
init(uri: WalletConnectURI) {
diff --git a/Example/DApp/Sign/ResponseViewController.swift b/Example/DApp/Sign/ResponseViewController.swift
index 4f8d1c0b3..eec81065f 100644
--- a/Example/DApp/Sign/ResponseViewController.swift
+++ b/Example/DApp/Sign/ResponseViewController.swift
@@ -23,14 +23,14 @@ class ResponseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
- let record = Sign.instance.getSessionRequestRecord(id: response.result.id)!
+ let record = Sign.instance.getSessionRequestRecord(id: response.id)!
switch response.result {
case .response(let response):
- responseView.nameLabel.text = "Received Response\n\(record.request.method)"
- responseView.descriptionLabel.text = try! response.result.get(String.self).description
+ responseView.nameLabel.text = "Received Response\n\(record.method)"
+ responseView.descriptionLabel.text = try! response.get(String.self).description
case .error(let error):
- responseView.nameLabel.text = "Received Error\n\(record.request.method)"
- responseView.descriptionLabel.text = error.error.message
+ responseView.nameLabel.text = "Received Error\n\(record.method)"
+ responseView.descriptionLabel.text = error.message
}
responseView.dismissButton.addTarget(self, action: #selector(dismissSelf), for: .touchUpInside)
}
diff --git a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift
index 74210f740..7abd2b7d4 100644
--- a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift
+++ b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift
@@ -1,5 +1,6 @@
import Foundation
import WalletConnectSign
+import WalletConnectPairing
import UIKit
import Combine
@@ -15,16 +16,12 @@ class SelectChainViewController: UIViewController, UITableViewDataSource {
private var publishers = [AnyCancellable]()
let chains = [Chain(name: "Ethereum", id: "eip155:1"), Chain(name: "Polygon", id: "eip155:137")]
- var onSessionSettled: ((Session) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Available Chains"
selectChainView.tableView.dataSource = self
selectChainView.connectButton.addTarget(self, action: #selector(connect), for: .touchUpInside)
selectChainView.openWallet.addTarget(self, action: #selector(openWallet), for: .touchUpInside)
- Sign.instance.sessionSettlePublisher.sink {[unowned self] session in
- onSessionSettled?(session)
- }.store(in: &publishers)
}
override func loadView() {
@@ -38,8 +35,9 @@ class SelectChainViewController: UIViewController, UITableViewDataSource {
let blockchains: Set = [Blockchain("eip155:1")!, Blockchain("eip155:137")!]
let namespaces: [String: ProposalNamespace] = ["eip155": ProposalNamespace(chains: blockchains, methods: methods, events: [], extensions: nil)]
Task {
- let uri = try await Sign.instance.connect(requiredNamespaces: namespaces)
- showConnectScreen(uri: uri!)
+ let uri = try await Pair.instance.create()
+ try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic)
+ showConnectScreen(uri: uri)
}
}
diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift
index 1373e4c30..51e70078e 100644
--- a/Example/DApp/Sign/SignCoordinator.swift
+++ b/Example/DApp/Sign/SignCoordinator.swift
@@ -2,6 +2,7 @@ import UIKit
import Combine
import WalletConnectSign
import WalletConnectRelay
+import WalletConnectPairing
final class SignCoordinator {
@@ -25,7 +26,7 @@ final class SignCoordinator {
url: "wallet.connect",
icons: ["https://avatars.githubusercontent.com/u/37784886"])
- Sign.configure(metadata: metadata)
+ Pair.configure(metadata: metadata)
#if DEBUG
if CommandLine.arguments.contains("-cleanInstall") {
try? Sign.instance.cleanup()
@@ -44,6 +45,12 @@ final class SignCoordinator {
presentResponse(for: response)
}.store(in: &publishers)
+ Sign.instance.sessionSettlePublisher
+ .receive(on: DispatchQueue.main)
+ .sink { [unowned self] session in
+ showAccountsScreen(session)
+ }.store(in: &publishers)
+
if let session = Sign.instance.getSessions().first {
showAccountsScreen(session)
} else {
@@ -53,9 +60,6 @@ final class SignCoordinator {
private func showSelectChainScreen() {
let controller = SelectChainViewController()
- controller.onSessionSettled = { [unowned self] session in
- showAccountsScreen(session)
- }
navigationController.viewControllers = [controller]
}
@@ -64,6 +68,7 @@ final class SignCoordinator {
controller.onDisconnect = { [unowned self] in
showSelectChainScreen()
}
+ navigationController.presentedViewController?.dismiss(animated: false)
navigationController.viewControllers = [controller]
}
diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj
index e23637099..f0ef7ebf7 100644
--- a/Example/ExampleApp.xcodeproj/project.pbxproj
+++ b/Example/ExampleApp.xcodeproj/project.pbxproj
@@ -30,6 +30,7 @@
8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 8448F1D327E4726F0000B866 /* WalletConnect */; };
84494388278D9C1B00CC26BB /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84494387278D9C1B00CC26BB /* UIAlertController.swift */; };
8460DCFC274F98A10081F94C /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DCFB274F98A10081F94C /* RequestViewController.swift */; };
+ 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 847CF3AE28E3141700F1D760 /* WalletConnectPush */; settings = {ATTRIBUTES = (Required, ); }; };
84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; };
84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; };
84CE642127981DED00142511 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE642027981DED00142511 /* SceneDelegate.swift */; };
@@ -44,14 +45,18 @@
84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644D279ED2FF00142511 /* SelectChainView.swift */; };
84CE6452279ED42B00142511 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6451279ED42B00142511 /* ConnectView.swift */; };
84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; };
+ 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; };
84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; };
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; };
84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; };
84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; };
84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; };
- A501AC2728C8E59800CEAA42 /* URLConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A501AC2628C8E59800CEAA42 /* URLConfig.swift */; };
A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; };
A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; };
+ A518B31428E33A6500A2CE93 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518B31328E33A6500A2CE93 /* InputConfig.swift */; };
+ 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 */; };
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 */; };
@@ -235,13 +240,17 @@
84CE6451279ED42B00142511 /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; };
84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = ""; };
84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = ""; };
+ 84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; };
84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.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 = ""; };
84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; };
- A501AC2628C8E59800CEAA42 /* URLConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLConfig.swift; sourceTree = ""; };
A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; };
A50F3945288005B200064555 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; };
+ A518B31328E33A6500A2CE93 /* InputConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; };
+ A51AC0D828E436A3001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; };
+ A51AC0DB28E436E6001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; };
+ A51AC0DE28E4379F001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; };
A55CAAAB28B92AFF00844382 /* ScanModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanModule.swift; sourceTree = ""; };
A55CAAAC28B92AFF00844382 /* ScanPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanPresenter.swift; sourceTree = ""; };
A55CAAAD28B92AFF00844382 /* ScanRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanRouter.swift; sourceTree = ""; };
@@ -354,6 +363,7 @@
A5E22D212840C8D300E36487 /* WalletEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEngine.swift; sourceTree = ""; };
A5E22D232840C8DB00E36487 /* SafariEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariEngine.swift; sourceTree = ""; };
A5E22D2B2840EAC300E36487 /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = ""; };
+ A5F48A0528E43D3F0034CBFB /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = ../Configuration.xcconfig; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -402,6 +412,7 @@
files = (
A5E03DFF2864662500888481 /* WalletConnect in Frameworks */,
A5E03DF52864651200888481 /* Starscream in Frameworks */,
+ 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */,
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */,
A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */,
);
@@ -450,6 +461,7 @@
764E1D3326F8D3FC00A1FB15 = {
isa = PBXGroup;
children = (
+ A5F48A0528E43D3F0034CBFB /* Configuration.xcconfig */,
84CE6453279FFE1100142511 /* Wallet.entitlements */,
764E1D3E26F8D3FC00A1FB15 /* ExampleApp */,
84CE641D27981DED00142511 /* DApp */,
@@ -479,6 +491,7 @@
children = (
764E1D3F26F8D3FC00A1FB15 /* AppDelegate.swift */,
764E1D4126F8D3FC00A1FB15 /* SceneDelegate.swift */,
+ A51AC0DA28E436DE001BACF9 /* Common */,
761248152819F9A800CB6D48 /* Wallet */,
761C64A426FCB08B004239D1 /* SessionProposal */,
8460DCFE2750D6DF0081F94C /* SessionDetails */,
@@ -581,6 +594,14 @@
path = Connect;
sourceTree = "";
};
+ 84CEC64728D8A98900D081A8 /* Pairing */ = {
+ isa = PBXGroup;
+ children = (
+ 84CEC64528D89D6B00D081A8 /* PairingTests.swift */,
+ );
+ path = Pairing;
+ sourceTree = "";
+ };
84D2A66728A4F5260088AE09 /* Auth */ = {
isa = PBXGroup;
children = (
@@ -597,6 +618,14 @@
path = Types;
sourceTree = "";
};
+ A51AC0DA28E436DE001BACF9 /* Common */ = {
+ isa = PBXGroup;
+ children = (
+ A51AC0DB28E436E6001BACF9 /* InputConfig.swift */,
+ );
+ path = Common;
+ sourceTree = "";
+ };
A55CAAAA28B92AF200844382 /* Scan */ = {
isa = PBXGroup;
children = (
@@ -752,6 +781,7 @@
A58E7CFD2872A0F80082D443 /* Common */ = {
isa = PBXGroup;
children = (
+ A51AC0DE28E4379F001BACF9 /* InputConfig.swift */,
A50F3944288005A700064555 /* Types */,
A5C2021F287EA5AF007E3188 /* Components */,
A578FA332873049400AA7720 /* Style */,
@@ -969,6 +999,7 @@
A5BB7FAB28B6AA7100707FC6 /* Common */ = {
isa = PBXGroup;
children = (
+ A51AC0D828E436A3001BACF9 /* InputConfig.swift */,
A5BB7FAC28B6AA7D00707FC6 /* QRCodeGenerator.swift */,
);
path = Common;
@@ -1019,6 +1050,7 @@
A5E03DEE286464DB00888481 /* IntegrationTests */ = {
isa = PBXGroup;
children = (
+ 84CEC64728D8A98900D081A8 /* Pairing */,
A5E03E0B28646AA500888481 /* Relay */,
A5E03E0A28646A8A00888481 /* Stubs */,
A5E03E0928646A8100888481 /* Sign */,
@@ -1049,11 +1081,11 @@
A5E03E0A28646A8A00888481 /* Stubs */ = {
isa = PBXGroup;
children = (
+ A518B31328E33A6500A2CE93 /* InputConfig.swift */,
A5E03E1028646F8000888481 /* KeychainStorageMock.swift */,
A5E03E0E28646D8A00888481 /* WebSocketFactory.swift */,
A5E03DFC286465D100888481 /* Stubs.swift */,
84FE684528ACDB4700C893FF /* RequestParams.swift */,
- A501AC2628C8E59800CEAA42 /* URLConfig.swift */,
);
path = Stubs;
sourceTree = "";
@@ -1190,6 +1222,7 @@
A5E03DFE2864662500888481 /* WalletConnect */,
A5E03E00286466EA00888481 /* WalletConnectChat */,
84DDB4EC28ABB663003D66ED /* WalletConnectAuth */,
+ 847CF3AE28E3141700F1D760 /* WalletConnectPush */,
);
productName = IntegrationTests;
productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */;
@@ -1296,6 +1329,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ A51AC0DD28E43727001BACF9 /* InputConfig.swift in Sources */,
76235E892820198B004ED0AA /* UIKit+Previews.swift in Sources */,
76B149F02821C03B00F05F91 /* Proposal.swift in Sources */,
765056272821989600F9AE79 /* Color+Extension.swift in Sources */,
@@ -1333,6 +1367,7 @@
84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */,
84CE641F27981DED00142511 /* AppDelegate.swift in Sources */,
A5BB7FAD28B6AA7D00707FC6 /* QRCodeGenerator.swift in Sources */,
+ A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */,
A5BB7FA928B6A5FD00707FC6 /* AuthViewModel.swift in Sources */,
84CE6452279ED42B00142511 /* ConnectView.swift in Sources */,
84CE6448279AE68600142511 /* AccountRequestViewController.swift in Sources */,
@@ -1352,6 +1387,7 @@
files = (
A58E7D3B2872D55F0082D443 /* ChatInteractor.swift in Sources */,
A58E7D1F2872A57B0082D443 /* ApplicationConfigurator.swift in Sources */,
+ A51AC0DF28E4379F001BACF9 /* InputConfig.swift in Sources */,
A58E7D452872EE570082D443 /* ContentMessageView.swift in Sources */,
A5C20223287EA7E2007E3188 /* BrandButton.swift in Sources */,
A5629ADF2876CC6E00094373 /* InviteListPresenter.swift in Sources */,
@@ -1455,14 +1491,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */,
767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */,
+ A518B31428E33A6500A2CE93 /* InputConfig.swift in Sources */,
A5E03E03286466F400888481 /* ChatTests.swift in Sources */,
84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */,
84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */,
7694A5262874296A0001257E /* RegistryTests.swift in Sources */,
A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */,
A5E03E0D28646AD200888481 /* RelayClientEndToEndTests.swift in Sources */,
- A501AC2728C8E59800CEAA42 /* URLConfig.swift in Sources */,
A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */,
A5E03E0F28646D8A00888481 /* WebSocketFactory.swift in Sources */,
84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */,
@@ -1516,6 +1553,7 @@
/* Begin XCBuildConfiguration section */
764E1D4E26F8D3FE00A1FB15 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = A5F48A0528E43D3F0034CBFB /* Configuration.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -1577,6 +1615,7 @@
};
764E1D4F26F8D3FE00A1FB15 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = A5F48A0528E43D3F0034CBFB /* Configuration.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -1864,7 +1903,6 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- RELAY_HOST = relay.walletconnect.com;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1883,7 +1921,6 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- RELAY_HOST = relay.walletconnect.com;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1952,10 +1989,10 @@
/* Begin XCRemoteSwiftPackageReference section */
A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */ = {
isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/flypaper0/Web3.swift";
+ repositoryURL = "https://github.com/WalletConnect/Web3.swift";
requirement = {
- branch = "feature/eip-155";
- kind = branch;
+ kind = exactVersion;
+ version = 1.0.0;
};
};
A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */ = {
@@ -1977,6 +2014,10 @@
isa = XCSwiftPackageProductDependency;
productName = WalletConnect;
};
+ 847CF3AE28E3141700F1D760 /* WalletConnectPush */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = WalletConnectPush;
+ };
84DDB4EC28ABB663003D66ED /* WalletConnectAuth */ = {
isa = XCSwiftPackageProductDependency;
productName = WalletConnectAuth;
diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 1b5f80e67..01652b58f 100644
--- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git",
"state": {
"branch": null,
- "revision": "039f56c5d7960f277087a0be51f5eb04ed0ec073",
- "version": "1.5.1"
+ "revision": "19b3c3ceed117c5cc883517c4e658548315ba70b",
+ "version": "1.6.0"
}
},
{
@@ -24,8 +24,8 @@
"repositoryURL": "https://github.com/mxcl/PromiseKit.git",
"state": {
"branch": null,
- "revision": "ed3192004c0b00b4b3d7baa9578ee655c66faae3",
- "version": "6.18.0"
+ "revision": "43772616c46a44a9977e41924ae01d0e55f2f9ca",
+ "version": "6.18.1"
}
},
{
@@ -57,11 +57,11 @@
},
{
"package": "Web3",
- "repositoryURL": "https://github.com/flypaper0/Web3.swift",
+ "repositoryURL": "https://github.com/WalletConnect/Web3.swift",
"state": {
- "branch": "feature/eip-155",
- "revision": "92a43a8c279b9df25fe23dd6f8311e6fb0ea06ed",
- "version": null
+ "branch": null,
+ "revision": "bdaaed96eee3a9bf7f341165f89a94288961d14c",
+ "version": "1.0.0"
}
}
]
diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme
index 54745ba8d..6c42dec52 100644
--- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme
+++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme
@@ -26,6 +26,11 @@
value = "$(RELAY_HOST)"
isEnabled = "YES">
+
+
String? {
+ return Bundle.main.object(forInfoDictionaryKey: key) as? String
+ }
+}
diff --git a/Example/ExampleApp/Info.plist b/Example/ExampleApp/Info.plist
index fd00d23da..4d76b146a 100644
--- a/Example/ExampleApp/Info.plist
+++ b/Example/ExampleApp/Info.plist
@@ -2,6 +2,8 @@
+ PROJECT_ID
+ $(PROJECT_ID)
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
diff --git a/Example/ExampleApp/SceneDelegate.swift b/Example/ExampleApp/SceneDelegate.swift
index ba57b9ff4..0037fb05b 100644
--- a/Example/ExampleApp/SceneDelegate.swift
+++ b/Example/ExampleApp/SceneDelegate.swift
@@ -1,6 +1,10 @@
import UIKit
+import Foundation
+import Combine
import WalletConnectSign
+import WalletConnectNetworking
import WalletConnectRelay
+import WalletConnectPairing
import Starscream
extension WebSocket: WebSocketConnecting { }
@@ -23,8 +27,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
url: "example.wallet",
icons: ["https://avatars.githubusercontent.com/u/37784886"])
- Relay.configure(projectId: "3ca2919724fbfa5456a25194e369a8b4", socketFactory: SocketFactory())
- Sign.configure(metadata: metadata)
+ Networking.configure(projectId: InputConfig.projectId, socketFactory: SocketFactory())
+ Pair.configure(metadata: metadata)
#if DEBUG
if CommandLine.arguments.contains("-cleanInstall") {
try? Sign.instance.cleanup()
@@ -35,24 +39,25 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window = UIWindow(windowScene: windowScene)
window?.rootViewController = UITabBarController.createExampleApp()
window?.makeKeyAndVisible()
+
+ if let userActivity = connectionOptions.userActivities.first {
+ handle(userActivity: userActivity)
+ }
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
- guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
- let incomingURL = userActivity.webpageURL else {
- return
- }
- let wcUri = incomingURL.absoluteString.deletingPrefix("https://walletconnect.com/wc?uri=")
- let vc = ((window!.rootViewController as! UINavigationController).viewControllers[0] as! WalletViewController)
- Task(priority: .high) {try? await Sign.instance.pair(uri: WalletConnectURI(string: wcUri)!)}
- vc.onClientConnected = {
- Task(priority: .high) {
- do {
- try await Sign.instance.pair(uri: WalletConnectURI(string: wcUri)!)
- } catch {
- print(error)
- }
- }
+ handle(userActivity: userActivity)
+ }
+
+ private func handle(userActivity: NSUserActivity) {
+ guard
+ let url = userActivity.webpageURL,
+ userActivity.activityType == NSUserActivityTypeBrowsingWeb
+ else { return }
+
+ let wcUri = url.absoluteString.deletingPrefix("https://walletconnect.com/wc?uri=")
+ Task(priority: .high) {
+ try! await Pair.instance.pair(uri: WalletConnectURI(string: wcUri)!)
}
}
}
diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift
index 4d7d6b39e..f1b2a05bc 100644
--- a/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift
+++ b/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift
@@ -24,8 +24,7 @@ final class SessionDetailViewController: UIHostingController
let viewController = RequestViewController(request)
viewController.onSign = { [unowned self] in
let result = Signer.signEth(request: request)
- let response = JSONRPCResponse(id: request.id, result: result)
- respondOnSign(request: request, response: response)
+ respondOnSign(request: request, response: result)
reload()
}
viewController.onReject = { [unowned self] in
@@ -35,11 +34,11 @@ final class SessionDetailViewController: UIHostingController
present(viewController, animated: true)
}
- private func respondOnSign(request: Request, response: JSONRPCResponse) {
+ private func respondOnSign(request: Request, response: AnyCodable) {
print("[WALLET] Respond on Sign")
Task {
do {
- try await Sign.instance.respond(topic: request.topic, response: .response(response))
+ try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .response(response))
} catch {
print("[DAPP] Respond Error: \(error.localizedDescription)")
}
@@ -52,10 +51,8 @@ final class SessionDetailViewController: UIHostingController
do {
try await Sign.instance.respond(
topic: request.topic,
- response: .error(JSONRPCErrorResponse(
- id: request.id,
- error: JSONRPCErrorResponse.Error(code: 0, message: ""))
- )
+ requestId: request.id,
+ response: .error(.init(code: 0, message: ""))
)
} catch {
print("[DAPP] Respond Error: \(error.localizedDescription)")
diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift
index f29dea255..d43a5ce3d 100644
--- a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift
+++ b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift
@@ -7,6 +7,8 @@ final class SessionDetailViewModel: ObservableObject {
private let session: Session
private let client: SignClient
+ private var publishers = Set()
+
enum Fields {
case accounts
case methods
@@ -22,6 +24,8 @@ final class SessionDetailViewModel: ObservableObject {
self.session = session
self.client = client
self.namespaces = session.namespaces
+
+ setupSubscriptions()
}
var peerName: String {
@@ -81,13 +85,8 @@ final class SessionDetailViewModel: ObservableObject {
}
func ping() {
- client.ping(topic: session.topic) { result in
- switch result {
- case .success:
- self.pingSuccess = true
- case .failure:
- self.pingFailed = true
- }
+ Task(priority: .userInitiated) {
+ try await client.ping(topic: session.topic)
}
}
@@ -98,6 +97,15 @@ final class SessionDetailViewModel: ObservableObject {
private extension SessionDetailViewModel {
+ func setupSubscriptions() {
+ client.pingResponsePublisher
+ .receive(on: DispatchQueue.main)
+ .sink { _ in
+ self.pingSuccess = true
+ }
+ .store(in: &publishers)
+ }
+
func addTestAccount(for chain: String) {
guard let viewModel = namespace(for: chain) else { return }
diff --git a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift
index f53ecbdef..bb6d21be6 100644
--- a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift
+++ b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift
@@ -49,11 +49,11 @@ final class SessionDetailsViewController: UIViewController, UITableViewDelegate,
@objc
private func ping() {
- Sign.instance.ping(topic: session.topic) { result in
- switch result {
- case .success:
+ Task(priority: .userInitiated) { @MainActor in
+ do {
+ try await Sign.instance.ping(topic: session.topic)
print("received ping response")
- case .failure(let error):
+ } catch {
print(error)
}
}
diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift
index 9d574ea34..a888df7bd 100644
--- a/Example/ExampleApp/Wallet/WalletViewController.swift
+++ b/Example/ExampleApp/Wallet/WalletViewController.swift
@@ -1,6 +1,7 @@
import UIKit
import WalletConnectSign
import WalletConnectUtils
+import WalletConnectPairing
import WalletConnectRouter
import Web3
import CryptoSwift
@@ -10,9 +11,10 @@ final class WalletViewController: UIViewController {
lazy var account = Signer.privateKey.address.hex(eip55: true)
var sessionItems: [ActiveSessionItem] = []
var currentProposal: Session.Proposal?
- var onClientConnected: (() -> Void)?
private var publishers = [AnyCancellable]()
+ var onClientConnected: (() -> Void)?
+
private let walletView: WalletView = {
WalletView()
}()
@@ -71,8 +73,7 @@ final class WalletViewController: UIViewController {
let requestVC = RequestViewController(request)
requestVC.onSign = { [unowned self] in
let result = Signer.signEth(request: request)
- let response = JSONRPCResponse(id: request.id, result: result)
- respondOnSign(request: request, response: response)
+ respondOnSign(request: request, response: result)
reloadSessionDetailsIfNeeded()
}
requestVC.onReject = { [unowned self] in
@@ -90,11 +91,11 @@ final class WalletViewController: UIViewController {
}
@MainActor
- private func respondOnSign(request: Request, response: JSONRPCResponse) {
+ private func respondOnSign(request: Request, response: AnyCodable) {
print("[WALLET] Respond on Sign")
Task {
do {
- try await Sign.instance.respond(topic: request.topic, response: .response(response))
+ try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .response(response))
} catch {
print("[DAPP] Respond Error: \(error.localizedDescription)")
}
@@ -108,10 +109,8 @@ final class WalletViewController: UIViewController {
do {
try await Sign.instance.respond(
topic: request.topic,
- response: .error(JSONRPCErrorResponse(
- id: request.id,
- error: JSONRPCErrorResponse.Error(code: 0, message: ""))
- )
+ requestId: request.id,
+ response: .error(.init(code: 0, message: ""))
)
} catch {
print("[DAPP] Respond Error: \(error.localizedDescription)")
@@ -124,7 +123,7 @@ final class WalletViewController: UIViewController {
print("[WALLET] Pairing to: \(uri)")
Task {
do {
- try await Sign.instance.pair(uri: uri)
+ try await Pair.instance.pair(uri: uri)
} catch {
print("[DAPP] Pairing connect error: \(error)")
}
@@ -241,8 +240,8 @@ extension WalletViewController {
.receive(on: DispatchQueue.main)
.sink { [weak self] status in
if status == .connected {
- self?.onClientConnected?()
print("Client connected")
+ self?.onClientConnected?()
}
}.store(in: &publishers)
diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift
index 6abe13983..ea165a98f 100644
--- a/Example/IntegrationTests/Auth/AuthTests.swift
+++ b/Example/IntegrationTests/Auth/AuthTests.swift
@@ -5,131 +5,196 @@ import WalletConnectUtils
import WalletConnectRelay
import Combine
@testable import Auth
+import WalletConnectPairing
+import WalletConnectNetworking
final class AuthTests: XCTestCase {
- var app: AuthClient!
- var wallet: AuthClient!
+ var appPairingClient: PairingClient!
+ var walletPairingClient: PairingClient!
+
+ var appAuthClient: AuthClient!
+ var walletAuthClient: AuthClient!
let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f")
+ let eip1271Signature = "0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c"
private var publishers = [AnyCancellable]()
override func setUp() {
- app = makeClient(prefix: "👻 App")
- let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")!
- wallet = makeClient(prefix: "🤑 Wallet", account: walletAccount)
-
- let expectation = expectation(description: "Wait Clients Connected")
- expectation.expectedFulfillmentCount = 2
-
- app.socketConnectionStatusPublisher.sink { status in
- if status == .connected {
- expectation.fulfill()
- }
- }.store(in: &publishers)
-
- wallet.socketConnectionStatusPublisher.sink { status in
- if status == .connected {
- expectation.fulfill()
- }
- }.store(in: &publishers)
+ setupClients()
+ }
- wait(for: [expectation], timeout: 5)
+ private func setupClients(address: String = "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf", iatProvider: IATProvider = DefaultIATProvider()) {
+ let walletAccount = Account(chainIdentifier: "eip155:1", address: address)!
+ (appPairingClient, appAuthClient) = makeClients(prefix: "🤖 App", iatProvider: iatProvider)
+ (walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", account: walletAccount, iatProvider: iatProvider)
}
- func makeClient(prefix: String, account: Account? = nil) -> AuthClient {
+ func makeClients(prefix: String, account: Account? = nil, iatProvider: IATProvider) -> (PairingClient, AuthClient) {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
- let projectId = "3ca2919724fbfa5456a25194e369a8b4"
let keychain = KeychainStorageMock()
- let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
+ let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: logger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage)
- return AuthClientFactory.create(
+ let pairingClient = PairingClientFactory.create(
+ logger: logger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient)
+
+ let authClient = AuthClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
account: account,
+ projectId: InputConfig.projectId,
logger: logger,
- keyValueStorage: RuntimeKeyValueStorage(),
+ keyValueStorage: keyValueStorage,
keychainStorage: keychain,
- relayClient: relayClient)
+ networkingClient: networkingClient,
+ pairingRegisterer: pairingClient,
+ iatProvider: iatProvider)
+
+ return (pairingClient, authClient)
}
func testRequest() async {
let requestExpectation = expectation(description: "request delivered to wallet")
- let uri = try! await app.request(RequestParams.stub())
- try! await wallet.pair(uri: uri)
- wallet.authRequestPublisher.sink { _ in
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { _ in
requestExpectation.fulfill()
}.store(in: &publishers)
- wait(for: [requestExpectation], timeout: 2)
+ wait(for: [requestExpectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testEIP191RespondSuccess() async {
+ let responseExpectation = expectation(description: "successful response delivered")
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { [unowned self] request in
+ Task(priority: .high) {
+ let signer = MessageSignerFactory.create(projectId: InputConfig.projectId)
+ let signature = try! signer.sign(message: request.message, privateKey: prvKey, type: .eip191)
+ try! await walletAuthClient.respond(requestId: request.id, signature: signature)
+ }
+ }
+ .store(in: &publishers)
+ appAuthClient.authResponsePublisher.sink { (_, result) in
+ guard case .success = result else { XCTFail(); return }
+ responseExpectation.fulfill()
+ }
+ .store(in: &publishers)
+ wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
- func testRespondSuccess() async {
+ func testEIP1271RespondSuccess() async {
+ setupClients(address: "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71", iatProvider: IATProviderMock())
+
let responseExpectation = expectation(description: "successful response delivered")
- let uri = try! await app.request(RequestParams.stub())
- try! await wallet.pair(uri: uri)
- wallet.authRequestPublisher.sink { [unowned self] request in
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams(
+ domain: "localhost",
+ chainId: "eip155:1",
+ nonce: "1665443015700",
+ aud: "http://localhost:3000/",
+ nbf: nil,
+ exp: "2022-10-11T23:03:35.700Z",
+ statement: nil,
+ requestId: nil,
+ resources: nil
+ ), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
- let signature = try! MessageSigner().sign(message: request.message, privateKey: prvKey)
- try! await wallet.respond(requestId: request.id, signature: signature)
+ let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
+ try! await walletAuthClient.respond(requestId: request.id, signature: signature)
}
}
.store(in: &publishers)
- app.authResponsePublisher.sink { (_, result) in
+ appAuthClient.authResponsePublisher.sink { (_, result) in
guard case .success = result else { XCTFail(); return }
responseExpectation.fulfill()
}
.store(in: &publishers)
- wait(for: [responseExpectation], timeout: 5)
+ wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testEIP191RespondError() async {
+ let responseExpectation = expectation(description: "error response delivered")
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { [unowned self] request in
+ Task(priority: .high) {
+ let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
+ try! await walletAuthClient.respond(requestId: request.id, signature: signature)
+ }
+ }
+ .store(in: &publishers)
+ appAuthClient.authResponsePublisher.sink { (_, result) in
+ guard case let .failure(error) = result, error == .signatureVerificationFailed else { XCTFail(); return }
+ responseExpectation.fulfill()
+ }
+ .store(in: &publishers)
+ wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
func testUserRespondError() async {
let responseExpectation = expectation(description: "error response delivered")
- let uri = try! await app.request(RequestParams.stub())
- try! await wallet.pair(uri: uri)
- wallet.authRequestPublisher.sink { [unowned self] request in
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
- try! await wallet.reject(requestId: request.id)
+ try! await walletAuthClient.reject(requestId: request.id)
}
}
.store(in: &publishers)
- app.authResponsePublisher.sink { (_, result) in
+ appAuthClient.authResponsePublisher.sink { (_, result) in
guard case .failure(let error) = result else { XCTFail(); return }
XCTAssertEqual(error, .userRejeted)
responseExpectation.fulfill()
}
.store(in: &publishers)
- wait(for: [responseExpectation], timeout: 5)
+ wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
func testRespondSignatureVerificationFailed() async {
let responseExpectation = expectation(description: "invalid signature response delivered")
- let uri = try! await app.request(RequestParams.stub())
- try! await wallet.pair(uri: uri)
- wallet.authRequestPublisher.sink { [unowned self] request in
+ let uri = try! await appPairingClient.create()
+ try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic)
+
+ try! await walletPairingClient.pair(uri: uri)
+ walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
- let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature)
- try! await wallet.respond(requestId: request.id, signature: cacaoSignature)
+ let cacaoSignature = CacaoSignature(t: .eip191, s: invalidSignature)
+ try! await walletAuthClient.respond(requestId: request.id, signature: cacaoSignature)
}
}
.store(in: &publishers)
- app.authResponsePublisher.sink { (_, result) in
+ appAuthClient.authResponsePublisher.sink { (_, result) in
guard case .failure(let error) = result else { XCTFail(); return }
XCTAssertEqual(error, .signatureVerificationFailed)
responseExpectation.fulfill()
}
.store(in: &publishers)
- wait(for: [responseExpectation], timeout: 2)
+ wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout)
}
+}
- func testPing() async {
- let pingExpectation = expectation(description: "expects ping response")
- let uri = try! await app.request(RequestParams.stub())
- try! await wallet.pair(uri: uri)
- try! await wallet.ping(topic: uri.topic)
- wallet.pingResponsePublisher
- .sink { topic in
- XCTAssertEqual(topic, uri.topic)
- pingExpectation.fulfill()
- }
- .store(in: &publishers)
- wait(for: [pingExpectation], timeout: 5)
+private struct IATProviderMock: IATProvider {
+ var iat: String {
+ return "2022-10-10T23:03:35.700Z"
}
}
diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift
index 191ea398f..177bda36f 100644
--- a/Example/IntegrationTests/Chat/ChatTests.swift
+++ b/Example/IntegrationTests/Chat/ChatTests.swift
@@ -16,34 +16,12 @@ final class ChatTests: XCTestCase {
registry = KeyValueRegistry()
invitee = makeClient(prefix: "🦖 Registered")
inviter = makeClient(prefix: "🍄 Inviter")
-
- waitClientsConnected()
- }
-
- private func waitClientsConnected() {
- let expectation = expectation(description: "Wait Clients Connected")
- expectation.expectedFulfillmentCount = 2
-
- invitee.socketConnectionStatusPublisher.sink { status in
- if status == .connected {
- expectation.fulfill()
- }
- }.store(in: &publishers)
-
- inviter.socketConnectionStatusPublisher.sink { status in
- if status == .connected {
- expectation.fulfill()
- }
- }.store(in: &publishers)
-
- wait(for: [expectation], timeout: 5)
}
func makeClient(prefix: String) -> ChatClient {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
- let projectId = "3ca2919724fbfa5456a25194e369a8b4"
let keychain = KeychainStorageMock()
- let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
+ let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
return ChatClientFactory.create(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage())
}
@@ -56,7 +34,7 @@ final class ChatTests: XCTestCase {
invitee.invitePublisher.sink { _ in
inviteExpectation.fulfill()
}.store(in: &publishers)
- wait(for: [inviteExpectation], timeout: 4)
+ wait(for: [inviteExpectation], timeout: InputConfig.defaultTimeout)
}
func testAcceptAndCreateNewThread() {
@@ -83,7 +61,7 @@ final class ChatTests: XCTestCase {
newThreadInviterExpectation.fulfill()
}.store(in: &publishers)
- wait(for: [newThreadinviteeExpectation, newThreadInviterExpectation], timeout: 10)
+ wait(for: [newThreadinviteeExpectation, newThreadInviterExpectation], timeout: InputConfig.defaultTimeout)
}
func testMessage() {
@@ -118,6 +96,6 @@ final class ChatTests: XCTestCase {
messageExpectation.fulfill()
}.store(in: &publishers)
- wait(for: [messageExpectation], timeout: 10)
+ wait(for: [messageExpectation], timeout: InputConfig.defaultTimeout)
}
}
diff --git a/Example/IntegrationTests/Chat/RegistryTests.swift b/Example/IntegrationTests/Chat/RegistryTests.swift
index 7a6fb6a1c..eb98d22f2 100644
--- a/Example/IntegrationTests/Chat/RegistryTests.swift
+++ b/Example/IntegrationTests/Chat/RegistryTests.swift
@@ -1,5 +1,5 @@
import XCTest
-import WalletConnectRelay
+import WalletConnectNetworking
import WalletConnectKMS
import WalletConnectUtils
@testable import Chat
@@ -7,7 +7,7 @@ import WalletConnectUtils
final class RegistryTests: XCTestCase {
func testRegistry() async throws {
- let client = HTTPClient(host: "keys.walletconnect.com")
+ let client = HTTPNetworkClient(host: "keys.walletconnect.com")
let registry = KeyserverRegistryProvider(client: client)
let account = Account("eip155:1:" + Data.randomBytes(count: 16).toHexString())!
let pubKey = SigningPrivateKey().publicKey.hexRepresentation
diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift
new file mode 100644
index 000000000..dce08c134
--- /dev/null
+++ b/Example/IntegrationTests/Pairing/PairingTests.swift
@@ -0,0 +1,149 @@
+import Foundation
+import XCTest
+import WalletConnectUtils
+@testable import WalletConnectKMS
+import WalletConnectRelay
+import Combine
+import WalletConnectNetworking
+import WalletConnectPush
+@testable import WalletConnectPairing
+
+final class PairingTests: XCTestCase {
+
+ var appPairingClient: PairingClient!
+ var walletPairingClient: PairingClient!
+
+ var appPushClient: PushClient!
+ var walletPushClient: PushClient!
+
+ var pairingStorage: PairingStorage!
+
+ private var publishers = [AnyCancellable]()
+
+ func makeClients(prefix: String) -> (PairingClient, PushClient) {
+ let keychain = KeychainStorageMock()
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug)
+ let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug)
+ let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug)
+ let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug)
+
+ let relayClient = RelayClient(
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
+ keyValueStorage: RuntimeKeyValueStorage(),
+ keychainStorage: keychain,
+ socketFactory: SocketFactory(),
+ logger: relayLogger)
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: networkingLogger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage)
+
+ let pairingClient = PairingClientFactory.create(
+ logger: pairingLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient)
+
+ let pushClient = PushClientFactory.create(
+ logger: pushLogger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient,
+ pairingClient: pairingClient)
+
+ return (pairingClient, pushClient)
+ }
+
+ func makePairingClient(prefix: String) -> PairingClient {
+ let keychain = KeychainStorageMock()
+ let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let relayClient = RelayClient(
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
+ keychainStorage: keychain,
+ socketFactory: SocketFactory(),
+ logger: logger)
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: logger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage)
+
+ let pairingClient = PairingClientFactory.create(
+ logger: logger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient)
+
+ return pairingClient
+ }
+
+ func testProposePushOnPairing() async {
+ let expectation = expectation(description: "propose push on pairing")
+
+ (appPairingClient, appPushClient) = makeClients(prefix: "🤖 App")
+ (walletPairingClient, walletPushClient) = makeClients(prefix: "🐶 Wallet")
+
+ walletPushClient.proposalPublisher.sink { _ in
+ expectation.fulfill()
+ }.store(in: &publishers)
+
+ let uri = try! await appPairingClient.create()
+
+ try! await walletPairingClient.pair(uri: uri)
+
+ try! await appPushClient.propose(topic: uri.topic)
+
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testPing() async {
+ let expectation = expectation(description: "expects ping response")
+
+ (appPairingClient, appPushClient) = makeClients(prefix: "🤖 App")
+ (walletPairingClient, walletPushClient) = makeClients(prefix: "🐶 Wallet")
+
+ let uri = try! await appPairingClient.create()
+ try! await walletPairingClient.pair(uri: uri)
+ try! await walletPairingClient.ping(topic: uri.topic)
+ walletPairingClient.pingResponsePublisher
+ .sink { topic in
+ XCTAssertEqual(topic, uri.topic)
+ expectation.fulfill()
+ }.store(in: &publishers)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+ }
+
+ func testResponseErrorForMethodUnregistered() async {
+ (appPairingClient, appPushClient) = makeClients(prefix: "🤖 App")
+ walletPairingClient = makePairingClient(prefix: "🐶 Wallet")
+
+ let expectation = expectation(description: "wallet responds unsupported method for unregistered method")
+
+ appPushClient.responsePublisher.sink { (_, response) in
+ XCTAssertEqual(response, .failure(WalletConnectPairing.PairError(code: 10001)!))
+ expectation.fulfill()
+ }.store(in: &publishers)
+
+ let uri = try! await appPairingClient.create()
+
+ try! await walletPairingClient.pair(uri: uri)
+
+ try! await appPushClient.propose(topic: uri.topic)
+
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
+
+ }
+
+ func testDisconnect() {
+ // TODO
+ }
+}
diff --git a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
index d456d69dc..60ab35252 100644
--- a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
+++ b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
@@ -7,24 +7,22 @@ import Starscream
final class RelayClientEndToEndTests: XCTestCase {
- let defaultTimeout: TimeInterval = 10
-
- let projectId = "3ca2919724fbfa5456a25194e369a8b4"
private var publishers = Set()
func makeRelayClient() -> RelayClient {
- let clientIdStorage = ClientIdStorage(keychain: KeychainStorageMock())
+ let didKeyFactory = ED25519DIDKeyFactory()
+ let clientIdStorage = ClientIdStorage(keychain: KeychainStorageMock(), didKeyFactory: didKeyFactory)
let socketAuthenticator = SocketAuthenticator(
clientIdStorage: clientIdStorage,
- didKeyFactory: ED25519DIDKeyFactory(),
- relayHost: URLConfig.relayHost
+ didKeyFactory: didKeyFactory,
+ relayHost: InputConfig.relayHost
)
let urlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator)
- let socket = WebSocket(url: urlFactory.create(host: URLConfig.relayHost, projectId: projectId))
+ let socket = WebSocket(url: urlFactory.create(host: InputConfig.relayHost, projectId: InputConfig.projectId))
let logger = ConsoleLogger()
let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: ManualSocketConnectionHandler(socket: socket), logger: logger)
- return RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage())
+ return RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage(), clientIdStorage: clientIdStorage)
}
func testSubscribe() {
@@ -42,7 +40,7 @@ final class RelayClientEndToEndTests: XCTestCase {
}
}.store(in: &publishers)
- wait(for: [subscribeExpectation], timeout: defaultTimeout)
+ wait(for: [subscribeExpectation], timeout: InputConfig.defaultTimeout)
}
func testEndToEndPayload() {
@@ -77,7 +75,7 @@ final class RelayClientEndToEndTests: XCTestCase {
}.store(in: &publishers)
relayA.socketConnectionStatusPublisher.sink { _ in
- relayA.publish(topic: randomTopic, payload: payloadA, tag: 0, onNetworkAcknowledge: { error in
+ relayA.publish(topic: randomTopic, payload: payloadA, tag: 0, prompt: false, ttl: 60, onNetworkAcknowledge: { error in
XCTAssertNil(error)
})
relayA.subscribe(topic: randomTopic) { error in
@@ -85,7 +83,7 @@ final class RelayClientEndToEndTests: XCTestCase {
}
}.store(in: &publishers)
relayB.socketConnectionStatusPublisher.sink { _ in
- relayB.publish(topic: randomTopic, payload: payloadB, tag: 0, onNetworkAcknowledge: { error in
+ relayB.publish(topic: randomTopic, payload: payloadB, tag: 0, prompt: false, ttl: 60, onNetworkAcknowledge: { error in
XCTAssertNil(error)
})
relayB.subscribe(topic: randomTopic) { error in
@@ -93,7 +91,7 @@ final class RelayClientEndToEndTests: XCTestCase {
}
}.store(in: &publishers)
- wait(for: [expectationA, expectationB], timeout: defaultTimeout)
+ wait(for: [expectationA, expectationB], timeout: InputConfig.defaultTimeout)
XCTAssertEqual(subscriptionATopic, randomTopic)
XCTAssertEqual(subscriptionBTopic, randomTopic)
diff --git a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift
index 71ac99ab3..e299d0316 100644
--- a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift
+++ b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift
@@ -14,6 +14,7 @@ class ClientDelegate {
var onSessionDelete: (() -> Void)?
var onSessionUpdateNamespaces: ((String, [String: SessionNamespace]) -> Void)?
var onSessionExtend: ((String, Date) -> Void)?
+ var onPing: ((String) -> Void)?
var onEventReceived: ((Session.Event, String) -> Void)?
private var publishers = Set()
@@ -63,5 +64,9 @@ class ClientDelegate {
client.sessionExtendPublisher.sink { (topic, date) in
self.onSessionExtend?(topic, date)
}.store(in: &publishers)
+
+ client.pingResponsePublisher.sink { topic in
+ self.onPing?(topic)
+ }.store(in: &publishers)
}
}
diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift
index e00529b0b..acbfbb728 100644
--- a/Example/IntegrationTests/Sign/SignClientTests.swift
+++ b/Example/IntegrationTests/Sign/SignClientTests.swift
@@ -1,59 +1,56 @@
import XCTest
import WalletConnectUtils
+import JSONRPC
@testable import WalletConnectKMS
@testable import WalletConnectSign
@testable import WalletConnectRelay
+import WalletConnectPairing
+import WalletConnectNetworking
final class SignClientTests: XCTestCase {
-
- let defaultTimeout: TimeInterval = 8
-
var dapp: ClientDelegate!
var wallet: ClientDelegate!
- static private func makeClientDelegate(
- name: String,
- projectId: String = "3ca2919724fbfa5456a25194e369a8b4"
- ) -> ClientDelegate {
+ static private func makeClientDelegate(name: String) -> ClientDelegate {
let logger = ConsoleLogger(suffix: name, loggingLevel: .debug)
let keychain = KeychainStorageMock()
let relayClient = RelayClient(
- relayHost: URLConfig.relayHost,
- projectId: projectId,
+ relayHost: InputConfig.relayHost,
+ projectId: InputConfig.projectId,
keyValueStorage: RuntimeKeyValueStorage(),
keychainStorage: keychain,
socketFactory: SocketFactory(),
socketConnectionType: .automatic,
logger: logger
)
+ let keyValueStorage = RuntimeKeyValueStorage()
+
+ let networkingClient = NetworkingClientFactory.create(
+ relayClient: relayClient,
+ logger: logger,
+ keychainStorage: keychain,
+ keyValueStorage: keyValueStorage
+ )
+ let pairingClient = PairingClientFactory.create(
+ logger: logger,
+ keyValueStorage: keyValueStorage,
+ keychainStorage: keychain,
+ networkingClient: networkingClient
+ )
let client = SignClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
logger: logger,
- keyValueStorage: RuntimeKeyValueStorage(),
+ keyValueStorage: keyValueStorage,
keychainStorage: keychain,
- relayClient: relayClient
+ pairingClient: pairingClient,
+ networkingClient: networkingClient
)
return ClientDelegate(client: client)
}
- private func listenForConnection() async {
- let group = DispatchGroup()
- group.enter()
- dapp.onConnected = {
- group.leave()
- }
- group.enter()
- wallet.onConnected = {
- group.leave()
- }
- group.wait()
- return
- }
-
override func setUp() async throws {
dapp = Self.makeClientDelegate(name: "🍏P")
wallet = Self.makeClientDelegate(name: "🍎R")
- await listenForConnection()
}
override func tearDown() {
@@ -85,7 +82,7 @@ final class SignClientTests: XCTestCase {
let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try await wallet.client.pair(uri: uri!)
- wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: defaultTimeout)
+ wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout)
}
func testSessionReject() async throws {
@@ -109,7 +106,7 @@ final class SignClientTests: XCTestCase {
XCTAssertEqual(store.rejectedProposal, proposal)
sessionRejectExpectation.fulfill() // TODO: Assert reason code
}
- wait(for: [sessionRejectExpectation], timeout: defaultTimeout)
+ wait(for: [sessionRejectExpectation], timeout: InputConfig.defaultTimeout)
}
func testSessionDelete() async throws {
@@ -133,21 +130,37 @@ final class SignClientTests: XCTestCase {
let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try await wallet.client.pair(uri: uri!)
- wait(for: [sessionDeleteExpectation], timeout: defaultTimeout)
+ wait(for: [sessionDeleteExpectation], timeout: InputConfig.defaultTimeout)
}
- func testNewPairingPing() async throws {
- let pongResponseExpectation = expectation(description: "Ping sender receives a pong response")
+ func testSessionPing() async throws {
+ let expectation = expectation(description: "Proposer receives ping response")
- let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired())!
- try await wallet.client.pair(uri: uri)
+ let requiredNamespaces = ProposalNamespace.stubRequired()
+ let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
+
+ wallet.onSessionProposal = { proposal in
+ Task(priority: .high) {
+ try! await self.wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces)
+ }
+ }
+
+ dapp.onSessionSettled = { sessionSettled in
+ Task(priority: .high) {
+ try! await self.dapp.client.ping(topic: sessionSettled.topic)
+ }
+ }
- let pairing = wallet.client.getPairings().first!
- wallet.client.ping(topic: pairing.topic) { result in
- if case .failure = result { XCTFail() }
- pongResponseExpectation.fulfill()
+ dapp.onPing = { topic in
+ let session = self.wallet.client.getSessions().first!
+ XCTAssertEqual(topic, session.topic)
+ expectation.fulfill()
}
- wait(for: [pongResponseExpectation], timeout: defaultTimeout)
+
+ let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)!
+ try await wallet.client.pair(uri: uri)
+
+ wait(for: [expectation], timeout: .infinity)
}
func testSessionRequest() async throws {
@@ -171,7 +184,7 @@ final class SignClientTests: XCTestCase {
}
dapp.onSessionSettled = { [unowned self] settledSession in
Task(priority: .high) {
- let request = Request(id: 0, topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
+ let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
try await dapp.client.request(params: request)
}
}
@@ -181,14 +194,13 @@ final class SignClientTests: XCTestCase {
XCTAssertEqual(sessionRequest.method, requestMethod)
requestExpectation.fulfill()
Task(priority: .high) {
- let jsonrpcResponse = JSONRPCResponse(id: sessionRequest.id, result: AnyCodable(responseParams))
- try await wallet.client.respond(topic: sessionRequest.topic, response: .response(jsonrpcResponse))
+ try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(AnyCodable(responseParams)))
}
}
dapp.onSessionResponse = { response in
switch response.result {
case .response(let response):
- XCTAssertEqual(try! response.result.get(String.self), responseParams)
+ XCTAssertEqual(try! response.get(String.self), responseParams)
case .error:
XCTFail()
}
@@ -197,7 +209,7 @@ final class SignClientTests: XCTestCase {
let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try await wallet.client.pair(uri: uri!)
- wait(for: [requestExpectation, responseExpectation], timeout: defaultTimeout)
+ wait(for: [requestExpectation, responseExpectation], timeout: InputConfig.defaultTimeout)
}
func testSessionRequestFailureResponse() async throws {
@@ -207,7 +219,8 @@ final class SignClientTests: XCTestCase {
let requestMethod = "eth_sendTransaction"
let requestParams = [EthSendTransaction.stub()]
- let error = JSONRPCErrorResponse.Error(code: 0, message: "error")
+ let error = JSONRPCError(code: 0, message: "error")
+
let chain = Blockchain("eip155:1")!
wallet.onSessionProposal = { [unowned self] proposal in
@@ -217,32 +230,30 @@ final class SignClientTests: XCTestCase {
}
dapp.onSessionSettled = { [unowned self] settledSession in
Task(priority: .high) {
- let request = Request(id: 0, topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
+ let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
try await dapp.client.request(params: request)
}
}
wallet.onSessionRequest = { [unowned self] sessionRequest in
Task(priority: .high) {
- let response = JSONRPCErrorResponse(id: sessionRequest.id, error: error)
- try await wallet.client.respond(topic: sessionRequest.topic, response: .error(response))
+ try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .error(error))
}
}
dapp.onSessionResponse = { response in
switch response.result {
case .response:
XCTFail()
- case .error(let errorResponse):
- XCTAssertEqual(error, errorResponse.error)
+ case .error(let receivedError):
+ XCTAssertEqual(error, receivedError)
}
expectation.fulfill()
}
let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
-
func testNewSessionOnExistingPairing() async {
let dappSettlementExpectation = expectation(description: "Dapp settles session")
dappSettlementExpectation.expectedFulfillmentCount = 2
@@ -266,7 +277,7 @@ final class SignClientTests: XCTestCase {
let pairingTopic = dapp.client.getPairings().first!.topic
if !initiatedSecondSession {
Task(priority: .high) {
- let _ = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic)
+ _ = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces, topic: pairingTopic)
}
initiatedSecondSession = true
}
@@ -277,31 +288,9 @@ final class SignClientTests: XCTestCase {
let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try! await wallet.client.pair(uri: uri!)
- wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: defaultTimeout)
-
+ wait(for: [dappSettlementExpectation, walletSettlementExpectation], timeout: InputConfig.defaultTimeout)
}
- func testSessionPing() async {
- let expectation = expectation(description: "Dapp receives ping response")
- let requiredNamespaces = ProposalNamespace.stubRequired()
- let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
-
- wallet.onSessionProposal = { [unowned self] proposal in
- Task(priority: .high) {
- try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces)
- }
- }
- dapp.onSessionSettled = { [unowned self] settledSession in
- dapp.client.ping(topic: settledSession.topic) {_ in
- expectation.fulfill()
- }
- }
- let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
- try! await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
- }
-
-
func testSuccessfulSessionUpdateNamespaces() async {
let expectation = expectation(description: "Dapp updates namespaces")
let requiredNamespaces = ProposalNamespace.stubRequired()
@@ -322,10 +311,9 @@ final class SignClientTests: XCTestCase {
}
let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try! await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
-
func testSuccessfulSessionExtend() async {
let expectation = expectation(description: "Dapp extends session")
@@ -351,7 +339,7 @@ final class SignClientTests: XCTestCase {
let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try! await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
func testSessionEventSucceeds() async {
@@ -381,7 +369,7 @@ final class SignClientTests: XCTestCase {
let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try! await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
func testSessionEventFails() async {
@@ -408,6 +396,6 @@ final class SignClientTests: XCTestCase {
let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces)
try! await wallet.client.pair(uri: uri!)
- wait(for: [expectation], timeout: defaultTimeout)
+ wait(for: [expectation], timeout: InputConfig.defaultTimeout)
}
}
diff --git a/Example/IntegrationTests/Stubs/InputConfig.swift b/Example/IntegrationTests/Stubs/InputConfig.swift
new file mode 100644
index 000000000..0e0c2efb7
--- /dev/null
+++ b/Example/IntegrationTests/Stubs/InputConfig.swift
@@ -0,0 +1,20 @@
+import Foundation
+
+struct InputConfig {
+
+ static var relayHost: String {
+ return config(for: "RELAY_HOST")!
+ }
+
+ static var projectId: String {
+ return config(for: "PROJECT_ID")!
+ }
+
+ static var defaultTimeout: TimeInterval {
+ return 30
+ }
+
+ private static func config(for key: String) -> String? {
+ return ProcessInfo.processInfo.environment[key]
+ }
+}
diff --git a/Example/IntegrationTests/Stubs/URLConfig.swift b/Example/IntegrationTests/Stubs/URLConfig.swift
deleted file mode 100644
index 8fed455dc..000000000
--- a/Example/IntegrationTests/Stubs/URLConfig.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-import Foundation
-
-struct URLConfig {
-
- static var relayHost: String {
- return ProcessInfo.processInfo.environment["RELAY_HOST"]!
- }
-}
diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
index 8aa2f8457..a8936517a 100644
--- a/Example/Showcase/Classes/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
+++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift
@@ -1,19 +1,20 @@
-import WalletConnectRelay
+import WalletConnectNetworking
import WalletConnectPairing
import Auth
struct ThirdPartyConfigurator: Configurator {
func configure() {
- Relay.configure(projectId: "relay.walletconnect.com", socketFactory: SocketFactory())
-
- Auth.configure(
+ Networking.configure(projectId: InputConfig.projectId, socketFactory: SocketFactory())
+ Pair.configure(
metadata: AppMetadata(
name: "Showcase App",
description: "Showcase description",
url: "example.wallet",
icons: ["https://avatars.githubusercontent.com/u/37784886"]
- ),
+ ))
+
+ Auth.configure(
account: Account("eip155:1:0xe5EeF1368781911d265fDB6946613dA61915a501")!
)
}
diff --git a/Example/Showcase/Classes/ApplicationLayer/SceneDelegate.swift b/Example/Showcase/Classes/ApplicationLayer/SceneDelegate.swift
index 5956c494e..7681f528a 100644
--- a/Example/Showcase/Classes/ApplicationLayer/SceneDelegate.swift
+++ b/Example/Showcase/Classes/ApplicationLayer/SceneDelegate.swift
@@ -1,5 +1,6 @@
import UIKit
import Auth
+import WalletConnectPairing
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@@ -30,7 +31,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let uri = context.url.absoluteString.replacingOccurrences(of: "showcase://wc?uri=", with: "")
Task {
- try await Auth.instance.pair(uri: WalletConnectURI(string: uri)!)
+ try await Pair.instance.pair(uri: WalletConnectURI(string: uri)!)
}
}
}
diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
index b47b23b17..8b4e0c597 100644
--- a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
+++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
@@ -1,14 +1,15 @@
import Foundation
import Chat
-import WalletConnectKMS
+import WalletConnectNetworking
import WalletConnectRelay
+import WalletConnectKMS
import WalletConnectUtils
class ChatFactory {
static func create() -> ChatClient {
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.showcase")
- let client = HTTPClient(host: "keys.walletconnect.com")
+ let client = HTTPNetworkClient(host: "keys.walletconnect.com")
let registry = KeyserverRegistryProvider(client: client)
return ChatClientFactory.create(
registry: registry,
diff --git a/Example/Showcase/Classes/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift
index ed3702e8f..c0c3a8bd3 100644
--- a/Example/Showcase/Classes/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift
+++ b/Example/Showcase/Classes/PresentationLayer/Wallet/AuthRequest/AuthRequestInteractor.swift
@@ -6,8 +6,8 @@ final class AuthRequestInteractor {
func approve(request: AuthRequest) async throws {
let privateKey = Data(hex: "e56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540")
- let signer = MessageSigner()
- let signature = try signer.sign(message: request.message, privateKey: privateKey)
+ let signer = MessageSignerFactory.create()
+ let signature = try signer.sign(message: request.message, privateKey: privateKey, type: .eip191)
try await Auth.instance.respond(requestId: request.id, signature: signature)
}
diff --git a/Example/Showcase/Classes/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
index 051ba266e..7379f18c5 100644
--- a/Example/Showcase/Classes/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
+++ b/Example/Showcase/Classes/PresentationLayer/Wallet/Wallet/WalletInteractor.swift
@@ -1,10 +1,11 @@
import Combine
import Auth
+import WalletConnectPairing
final class WalletInteractor {
func pair(uri: WalletConnectURI) async throws {
- try await Auth.instance.pair(uri: uri)
+ try await Pair.instance.pair(uri: uri)
}
var requestPublisher: AnyPublisher {
diff --git a/Example/Showcase/Common/InputConfig.swift b/Example/Showcase/Common/InputConfig.swift
new file mode 100644
index 000000000..53931721a
--- /dev/null
+++ b/Example/Showcase/Common/InputConfig.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+struct InputConfig {
+
+ static var projectId: String {
+ return config(for: "PROJECT_ID")!
+ }
+
+ private static func config(for key: String) -> String? {
+ return Bundle.main.object(forInfoDictionaryKey: key) as? String
+ }
+}
diff --git a/Example/Showcase/Other/Info.plist b/Example/Showcase/Other/Info.plist
index 95d2824b0..2b842100f 100644
--- a/Example/Showcase/Other/Info.plist
+++ b/Example/Showcase/Other/Info.plist
@@ -2,6 +2,8 @@
+ PROJECT_ID
+ $(PROJECT_ID)
CFBundleIconName
AppIcon
CFBundleURLTypes
diff --git a/Example/UITests/Engine/WalletEngine.swift b/Example/UITests/Engine/WalletEngine.swift
index 536e45443..2255d87b8 100644
--- a/Example/UITests/Engine/WalletEngine.swift
+++ b/Example/UITests/Engine/WalletEngine.swift
@@ -45,7 +45,15 @@ struct WalletEngine {
instance.buttons["Ping"]
}
+ var okButton: XCUIElement {
+ instance.buttons["OK"]
+ }
+
var pingAlert: XCUIElement {
instance.alerts.element.staticTexts["Received ping response"]
}
+
+ func swipeDismiss() {
+ instance.swipeDown(velocity: .fast)
+ }
}
diff --git a/Example/UITests/Regression/RegressionTests.swift b/Example/UITests/Regression/RegressionTests.swift
index 716ba8702..6b5047e9c 100644
--- a/Example/UITests/Regression/RegressionTests.swift
+++ b/Example/UITests/Regression/RegressionTests.swift
@@ -4,13 +4,10 @@ class PairingTests: XCTestCase {
private let engine: Engine = Engine()
- private static var cleanLaunch: Bool = true
-
- override func setUp() {
- engine.routing.launch(app: .dapp, clean: PairingTests.cleanLaunch)
- engine.routing.launch(app: .wallet, clean: PairingTests.cleanLaunch)
-
- PairingTests.cleanLaunch = false
+ override class func setUp() {
+ let engine: Engine = Engine()
+ engine.routing.launch(app: .dapp, clean: true)
+ engine.routing.launch(app: .wallet, clean: true)
}
/// Check pairing proposal approval via QR code or uri
@@ -45,6 +42,9 @@ class PairingTests: XCTestCase {
engine.wallet.pingButton.waitTap()
XCTAssertTrue(engine.wallet.pingAlert.waitExists())
+
+ engine.wallet.okButton.waitTap()
+ engine.wallet.swipeDismiss()
}
/// Approve session on existing pairing
diff --git a/Package.swift b/Package.swift
index 7640db6ad..bc8515e34 100644
--- a/Package.swift
+++ b/Package.swift
@@ -19,6 +19,12 @@ let package = Package(
.library(
name: "WalletConnectAuth",
targets: ["Auth"]),
+ .library(
+ name: "WalletConnectPairing",
+ targets: ["WalletConnectPairing"]),
+ .library(
+ name: "WalletConnectPush",
+ targets: ["WalletConnectPush"]),
.library(
name: "WalletConnectRouter",
targets: ["WalletConnectRouter"]),
@@ -27,12 +33,12 @@ let package = Package(
targets: ["WalletConnectNetworking"])
],
dependencies: [
- .package(url: "https://github.com/flypaper0/Web3.swift", .branch("feature/eip-155"))
+ .package(url: "https://github.com/WalletConnect/Web3.swift", .exact("1.0.0"))
],
targets: [
.target(
name: "WalletConnectSign",
- dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS", "WalletConnectPairing"],
+ dependencies: ["WalletConnectNetworking", "WalletConnectPairing"],
path: "Sources/WalletConnectSign"),
.target(
name: "Chat",
@@ -42,6 +48,10 @@ let package = Package(
name: "Auth",
dependencies: ["WalletConnectPairing", "WalletConnectNetworking", .product(name: "Web3", package: "Web3.swift")],
path: "Sources/Auth"),
+ .target(
+ name: "WalletConnectPush",
+ dependencies: ["WalletConnectPairing", "WalletConnectNetworking"],
+ path: "Sources/WalletConnectPush"),
.target(
name: "WalletConnectRelay",
dependencies: ["WalletConnectUtils", "WalletConnectKMS"],
@@ -52,7 +62,7 @@ let package = Package(
path: "Sources/WalletConnectKMS"),
.target(
name: "WalletConnectPairing",
- dependencies: ["WalletConnectUtils", "WalletConnectNetworking", "JSONRPC"]),
+ dependencies: ["WalletConnectNetworking"]),
.target(
name: "WalletConnectUtils",
dependencies: ["Commons", "JSONRPC"]),
@@ -71,6 +81,9 @@ let package = Package(
.testTarget(
name: "WalletConnectSignTests",
dependencies: ["WalletConnectSign", "TestingUtils"]),
+ .testTarget(
+ name: "WalletConnectPairingTests",
+ dependencies: ["WalletConnectPairing", "TestingUtils"]),
.testTarget(
name: "ChatTests",
dependencies: ["Chat", "WalletConnectUtils", "TestingUtils"]),
diff --git a/README.md b/README.md
index d723213a6..32b7736dd 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,12 @@ dependencies: [
.package(url: "https://github.com/WalletConnect/WalletConnectSwiftV2", .branch("main")),
],
```
+## Setting Project ID
+Follow instructions from *Configuration.xcconfig* and configure PROJECT_ID with your ID from WalletConnect Dashboard
+```
+// Uncomment next line and paste your project id. Get this on: https://cloud.walletconnect.com/sign-in
+// PROJECT_ID = YOUR_PROJECT_ID
+```
## Example App
open `Example/ExampleApp.xcodeproj`
diff --git a/Sources/Auth/Auth.swift b/Sources/Auth/Auth.swift
index b209104bd..8687e05d9 100644
--- a/Sources/Auth/Auth.swift
+++ b/Sources/Auth/Auth.swift
@@ -1,5 +1,6 @@
import Foundation
-import WalletConnectRelay
+import WalletConnectNetworking
+import WalletConnectPairing
import Combine
/// Auth instatnce wrapper
@@ -17,26 +18,23 @@ public class Auth {
/// Auth client instance
public static var instance: AuthClient = {
- guard let config = Auth.config else {
- fatalError("Error - you must call Auth.configure(_:) before accessing the shared instance.")
- }
return AuthClientFactory.create(
- metadata: config.metadata,
- account: config.account,
- relayClient: Relay.instance)
+ metadata: Pair.metadata,
+ account: config?.account,
+ projectId: Networking.projectId,
+ networkingClient: Networking.interactor,
+ pairingRegisterer: Pair.registerer
+ )
}()
private static var config: Config?
private init() { }
- /// Auth instance config method
+ /// Auth instance wallet config method
/// - Parameters:
- /// - metadata: App metadata
- /// - account: account that wallet will be authenticating with. Should be nil for non wallet clients.
- static public func configure(metadata: AppMetadata, account: Account?) {
- Auth.config = Auth.Config(
- metadata: metadata,
- account: account)
+ /// - account: account that wallet will be authenticating with.
+ static public func configure(account: Account) {
+ Auth.config = Auth.Config(account: account)
}
}
diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift
index c75a2895e..f4080be27 100644
--- a/Sources/Auth/AuthClient.swift
+++ b/Sources/Auth/AuthClient.swift
@@ -11,9 +11,7 @@ import WalletConnectRelay
/// Access via `Auth.instance`
public class AuthClient {
enum Errors: Error {
- case pairingUriWrongApiParam
case unknownWalletAddress
- case noPairingMatchingTopic
}
// MARK: - Public Properties
@@ -34,10 +32,6 @@ public class AuthClient {
authResponsePublisherSubject.eraseToAnyPublisher()
}
- public var pingResponsePublisher: AnyPublisher<(String), Never> {
- pingResponsePublisherSubject.eraseToAnyPublisher()
- }
-
/// Publisher that sends web socket connection status
public let socketConnectionStatusPublisher: AnyPublisher
@@ -46,90 +40,45 @@ public class AuthClient {
// MARK: - Private Properties
+ private let pairingRegisterer: PairingRegisterer
+
private var authResponsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>()
private var authRequestPublisherSubject = PassthroughSubject()
- private var pingResponsePublisherSubject = PassthroughSubject()
- private let appPairService: AppPairService
private let appRequestService: AppRequestService
- private let deletePairingService: DeletePairingService
private let appRespondSubscriber: AppRespondSubscriber
- private let walletPairService: WalletPairService
private let walletRequestSubscriber: WalletRequestSubscriber
private let walletRespondService: WalletRespondService
- private let cleanupService: CleanupService
- private let pairingStorage: WCPairingStorage
private let pendingRequestsProvider: PendingRequestsProvider
- private let pingService: PairingPingService
- private let pairingsProvider: PairingsProvider
private var account: Account?
- init(appPairService: AppPairService,
- appRequestService: AppRequestService,
+ init(appRequestService: AppRequestService,
appRespondSubscriber: AppRespondSubscriber,
- walletPairService: WalletPairService,
walletRequestSubscriber: WalletRequestSubscriber,
walletRespondService: WalletRespondService,
- deletePairingService: DeletePairingService,
account: Account?,
pendingRequestsProvider: PendingRequestsProvider,
- cleanupService: CleanupService,
logger: ConsoleLogging,
- pairingStorage: WCPairingStorage,
socketConnectionStatusPublisher: AnyPublisher,
- pingService: PairingPingService,
- pairingsProvider: PairingsProvider
+ pairingRegisterer: PairingRegisterer
) {
- self.appPairService = appPairService
self.appRequestService = appRequestService
- self.walletPairService = walletPairService
self.walletRequestSubscriber = walletRequestSubscriber
self.walletRespondService = walletRespondService
self.appRespondSubscriber = appRespondSubscriber
self.account = account
self.pendingRequestsProvider = pendingRequestsProvider
- self.cleanupService = cleanupService
self.logger = logger
- self.pairingStorage = pairingStorage
self.socketConnectionStatusPublisher = socketConnectionStatusPublisher
- self.deletePairingService = deletePairingService
- self.pingService = pingService
- self.pairingsProvider = pairingsProvider
+ self.pairingRegisterer = pairingRegisterer
setUpPublishers()
}
- /// For wallet to establish a pairing and receive an authentication request
- /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future authentication request.
- /// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking.
- ///
- /// Throws Error:
- /// - When URI is invalid format or missing params
- /// - When topic is already in use
- public func pair(uri: WalletConnectURI) async throws {
- guard uri.api == .auth else {
- throw Errors.pairingUriWrongApiParam
- }
- try await walletPairService.pair(uri)
- }
-
- /// For a dapp to send an authentication request to a wallet
- /// - Parameter params: Set of parameters required to request authentication
- ///
- /// - Returns: Pairing URI that should be shared with wallet out of bound. Common way is to present it as a QR code.
- public func request(_ params: RequestParams) async throws -> WalletConnectURI {
- logger.debug("Requesting Authentication")
- let uri = try await appPairService.create()
- try await appRequestService.request(params: params, topic: uri.topic)
- return uri
- }
-
/// For a dapp to send an authentication request to a wallet
/// - Parameter params: Set of parameters required to request authentication
/// - Parameter topic: Pairing topic that wallet already subscribes for
public func request(_ params: RequestParams, topic: String) async throws {
logger.debug("Requesting Authentication on existing pairing")
- guard pairingStorage.hasPairing(forTopic: topic) else {
- throw Errors.noPairingMatchingTopic
- }
+ try pairingRegisterer.validatePairingExistance(topic)
try await appRequestService.request(params: params, topic: topic)
}
@@ -148,18 +97,6 @@ public class AuthClient {
try await walletRespondService.respondError(requestId: requestId)
}
- public func disconnect(topic: String) async throws {
- try await deletePairingService.delete(topic: topic)
- }
-
- public func ping(topic: String) async throws {
- try await pingService.ping(topic: topic)
- }
-
- public func getPairings() -> [Pairing] {
- pairingsProvider.getPairings()
- }
-
/// Query pending authentication requests
/// - Returns: Pending authentication requests
public func getPendingRequests() throws -> [AuthRequest] {
@@ -167,15 +104,6 @@ public class AuthClient {
return try pendingRequestsProvider.getPendingRequests(account: account)
}
-#if DEBUG
- /// Delete all stored data such as: pairings, keys
- ///
- /// - Note: Doesn't unsubscribe from topics
- public func cleanup() throws {
- try cleanupService.cleanup()
- }
-#endif
-
private func setUpPublishers() {
appRespondSubscriber.onResponse = { [unowned self] (id, result) in
authResponsePublisherSubject.send((id, result))
@@ -184,9 +112,5 @@ public class AuthClient {
walletRequestSubscriber.onRequest = { [unowned self] request in
authRequestPublisherSubject.send(request)
}
-
- pingService.onResponse = { [unowned self] topic in
- pingResponsePublisherSubject.send(topic)
- }
}
}
diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift
index e0fe3f586..d2cb35859 100644
--- a/Sources/Auth/AuthClientFactory.swift
+++ b/Sources/Auth/AuthClientFactory.swift
@@ -7,48 +7,33 @@ import WalletConnectNetworking
public struct AuthClientFactory {
- public static func create(metadata: AppMetadata, account: Account?, relayClient: RelayClient) -> AuthClient {
+ public static func create(metadata: AppMetadata, account: Account?, projectId: String, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer) -> AuthClient {
let logger = ConsoleLogger(loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
- return AuthClientFactory.create(metadata: metadata, account: account, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, relayClient: relayClient)
+ return AuthClientFactory.create(metadata: metadata, account: account, projectId: projectId, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient, pairingRegisterer: pairingRegisterer, iatProvider: DefaultIATProvider())
}
- static func create(metadata: AppMetadata, account: Account?, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> AuthClient {
- let historyStorage = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)
- let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue)))
+ static func create(metadata: AppMetadata, account: Account?, projectId: String, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor, pairingRegisterer: PairingRegisterer, iatProvider: IATProvider) -> AuthClient {
let kms = KeyManagementService(keychain: keychainStorage)
- let serializer = Serializer(kms: kms)
- let history = RPCHistory(keyValueStore: historyStorage)
- let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history)
+ let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let messageFormatter = SIWEMessageFormatter()
- let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore)
- let appRequestService = AppRequestService(networkingInteractor: networkingInteractor, kms: kms, appMetadata: metadata, logger: logger)
- let messageSigner = MessageSigner(signer: Signer())
- let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingInteractor, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, messageFormatter: messageFormatter, pairingStorage: pairingStore)
- let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore)
- let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingInteractor, logger: logger, kms: kms, rpcHistory: history)
- let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingInteractor, logger: logger, kms: kms, messageFormatter: messageFormatter, address: account?.address, walletErrorResponder: walletErrorResponder)
- let walletRespondService = WalletRespondService(networkingInteractor: networkingInteractor, logger: logger, kms: kms, rpcHistory: history, walletErrorResponder: walletErrorResponder)
+ let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider)
+ let messageSigner = MessageSignerFactory.create(projectId: projectId)
+ let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter)
+ let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history)
+ let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, messageFormatter: messageFormatter, address: account?.address, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer)
+ let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, walletErrorResponder: walletErrorResponder)
let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history)
- let cleanupService = CleanupService(pairingStore: pairingStore, kms: kms)
- let deletePairingService = DeletePairingService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore, logger: logger)
- let pingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingInteractor, logger: logger)
- let pairingsProvider = PairingsProvider(pairingStorage: pairingStore)
- return AuthClient(appPairService: appPairService,
- appRequestService: appRequestService,
+ return AuthClient(appRequestService: appRequestService,
appRespondSubscriber: appRespondSubscriber,
- walletPairService: walletPairService,
walletRequestSubscriber: walletRequestSubscriber,
- walletRespondService: walletRespondService, deletePairingService: deletePairingService,
+ walletRespondService: walletRespondService,
account: account,
pendingRequestsProvider: pendingRequestsProvider,
- cleanupService: cleanupService,
logger: logger,
- pairingStorage: pairingStore,
- socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher,
- pingService: pingService,
- pairingsProvider: pairingsProvider)
+ socketConnectionStatusPublisher: networkingClient.socketConnectionStatusPublisher,
+ pairingRegisterer: pairingRegisterer)
}
}
diff --git a/Sources/Auth/AuthConfig.swift b/Sources/Auth/AuthConfig.swift
index b364ec507..00a98faed 100644
--- a/Sources/Auth/AuthConfig.swift
+++ b/Sources/Auth/AuthConfig.swift
@@ -2,7 +2,6 @@ import Foundation
extension Auth {
struct Config {
- let metadata: AppMetadata
let account: Account?
}
}
diff --git a/Sources/Auth/AuthProtocolMethod.swift b/Sources/Auth/AuthProtocolMethod.swift
index c3df72dae..85bab9cff 100644
--- a/Sources/Auth/AuthProtocolMethod.swift
+++ b/Sources/Auth/AuthProtocolMethod.swift
@@ -1,34 +1,26 @@
import Foundation
import WalletConnectNetworking
-enum AuthProtocolMethod: String, ProtocolMethod {
- case authRequest = "wc_authRequest"
- case pairingDelete = "wc_pairingDelete"
- case pairingPing = "wc_pairingPing"
-
- var method: String {
- return self.rawValue
- }
-
- var requestTag: Int {
- switch self {
- case .authRequest:
- return 3000
- case .pairingDelete:
- return 1000
- case .pairingPing:
- return 1002
- }
- }
-
- var responseTag: Int {
- switch self {
- case .authRequest:
- return 3001
- case .pairingDelete:
- return 1001
- case .pairingPing:
- return 1003
- }
- }
+struct AuthRequestProtocolMethod: ProtocolMethod {
+ let method: String = "wc_authRequest"
+
+ let requestConfig = RelayConfig(tag: 3000, prompt: true, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 3001, prompt: false, ttl: 86400)
+}
+
+struct PairingPingProtocolMethod: ProtocolMethod {
+ let method: String = "wc_pairingPing"
+
+ let requestConfig = RelayConfig(tag: 1002, prompt: false, ttl: 30)
+
+ let responseConfig = RelayConfig(tag: 1003, prompt: false, ttl: 30)
+}
+
+struct PairingDeleteProtocolMethod: ProtocolMethod {
+ let method: String = "wc_pairingDelete"
+
+ let requestConfig = RelayConfig(tag: 1000, prompt: false, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 1001, prompt: false, ttl: 86400)
}
diff --git a/Sources/Auth/Services/App/AppPairService.swift b/Sources/Auth/Services/App/AppPairService.swift
index dc05600b0..f790e06a6 100644
--- a/Sources/Auth/Services/App/AppPairService.swift
+++ b/Sources/Auth/Services/App/AppPairService.swift
@@ -19,7 +19,7 @@ actor AppPairService {
try await networkingInteractor.subscribe(topic: topic)
let symKey = try! kms.createSymmetricKey(topic)
let pairing = WCPairing(topic: topic)
- let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay, api: .auth)
+ let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay)
pairingStorage.setPairing(pairing)
return uri
}
diff --git a/Sources/Auth/Services/App/AppRequestService.swift b/Sources/Auth/Services/App/AppRequestService.swift
index da88c993a..6ea6ab339 100644
--- a/Sources/Auth/Services/App/AppRequestService.swift
+++ b/Sources/Auth/Services/App/AppRequestService.swift
@@ -9,28 +9,30 @@ actor AppRequestService {
private let appMetadata: AppMetadata
private let kms: KeyManagementService
private let logger: ConsoleLogging
+ private let iatProvader: IATProvider
init(networkingInteractor: NetworkInteracting,
kms: KeyManagementService,
appMetadata: AppMetadata,
- logger: ConsoleLogging) {
+ logger: ConsoleLogging,
+ iatProvader: IATProvider) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.appMetadata = appMetadata
self.logger = logger
+ self.iatProvader = iatProvader
}
func request(params: RequestParams, topic: String) async throws {
let pubKey = try kms.createX25519KeyPair()
let responseTopic = pubKey.rawRepresentation.sha256().toHexString()
let requester = AuthRequestParams.Requester(publicKey: pubKey.hexRepresentation, metadata: appMetadata)
- let issueAt = ISO8601DateFormatter().string(from: Date())
- let payload = AuthPayload(requestParams: params, iat: issueAt)
+ let payload = AuthPayload(requestParams: params, iat: iatProvader.iat)
let params = AuthRequestParams(requester: requester, payloadParams: payload)
let request = RPCRequest(method: "wc_authRequest", params: params)
try kms.setPublicKey(publicKey: pubKey, for: responseTopic)
logger.debug("AppRequestService: Subscribibg for response topic: \(responseTopic)")
- try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: AuthProtocolMethod.authRequest.responseTag)
+ try await networkingInteractor.requestNetworkAck(request, topic: topic, protocolMethod: AuthRequestProtocolMethod())
try await networkingInteractor.subscribe(topic: responseTopic)
}
}
diff --git a/Sources/Auth/Services/App/AppRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift
index 2b3b353fa..4b5628221 100644
--- a/Sources/Auth/Services/App/AppRespondSubscriber.swift
+++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift
@@ -7,11 +7,11 @@ import WalletConnectPairing
class AppRespondSubscriber {
private let networkingInteractor: NetworkInteracting
- private let pairingStorage: WCPairingStorage
private let logger: ConsoleLogging
private let rpcHistory: RPCHistory
private let signatureVerifier: MessageSignatureVerifying
private let messageFormatter: SIWEMessageFormatting
+ private let pairingRegisterer: PairingRegisterer
private var publishers = [AnyCancellable]()
var onResponse: ((_ id: RPCID, _ result: Result) -> Void)?
@@ -20,28 +20,28 @@ class AppRespondSubscriber {
logger: ConsoleLogging,
rpcHistory: RPCHistory,
signatureVerifier: MessageSignatureVerifying,
- messageFormatter: SIWEMessageFormatting,
- pairingStorage: WCPairingStorage) {
+ pairingRegisterer: PairingRegisterer,
+ messageFormatter: SIWEMessageFormatting) {
self.networkingInteractor = networkingInteractor
self.logger = logger
self.rpcHistory = rpcHistory
self.signatureVerifier = signatureVerifier
self.messageFormatter = messageFormatter
- self.pairingStorage = pairingStorage
+ self.pairingRegisterer = pairingRegisterer
subscribeForResponse()
}
private func subscribeForResponse() {
- networkingInteractor.responseErrorSubscription(on: AuthProtocolMethod.authRequest)
- .sink { [unowned self] payload in
+ networkingInteractor.responseErrorSubscription(on: AuthRequestProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in
guard let error = AuthError(code: payload.error.code) else { return }
onResponse?(payload.id, .failure(error))
}.store(in: &publishers)
- networkingInteractor.responseSubscription(on: AuthProtocolMethod.authRequest)
+ networkingInteractor.responseSubscription(on: AuthRequestProtocolMethod())
.sink { [unowned self] (payload: ResponseSubscriptionPayload) in
- activatePairingIfNeeded(id: payload.id)
+ pairingRegisterer.activate(pairingTopic: payload.topic)
networkingInteractor.unsubscribe(topic: payload.topic)
let requestId = payload.id
@@ -56,22 +56,20 @@ class AppRespondSubscriber {
guard messageFormatter.formatMessage(from: requestPayload.payloadParams, address: address) == message
else { self.onResponse?(requestId, .failure(.messageCompromised)); return }
- guard let _ = try? signatureVerifier.verify(signature: cacao.s, message: message, address: address)
- else { self.onResponse?(requestId, .failure(.signatureVerificationFailed)); return }
-
- onResponse?(requestId, .success(cacao))
-
+ Task(priority: .high) {
+ do {
+ try await signatureVerifier.verify(
+ signature: cacao.s,
+ message: message,
+ address: address,
+ chainId: requestPayload.payloadParams.chainId
+ )
+ onResponse?(requestId, .success(cacao))
+ } catch {
+ logger.error("Signature verification failed with: \(error.localizedDescription)")
+ onResponse?(requestId, .failure(.signatureVerificationFailed))
+ }
+ }
}.store(in: &publishers)
}
-
- private func activatePairingIfNeeded(id: RPCID) {
- guard let record = rpcHistory.get(recordId: id) else { return }
- let pairingTopic = record.topic
- guard var pairing = pairingStorage.getPairing(forTopic: pairingTopic) else { return }
- if !pairing.active {
- pairing.activate()
- } else {
- try? pairing.updateExpiry()
- }
- }
}
diff --git a/Sources/Auth/Services/Common/DeletePairingService.swift b/Sources/Auth/Services/Common/DeletePairingService.swift
deleted file mode 100644
index 721bdc81f..000000000
--- a/Sources/Auth/Services/Common/DeletePairingService.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Foundation
-import WalletConnectNetworking
-import JSONRPC
-import WalletConnectKMS
-import WalletConnectUtils
-import WalletConnectPairing
-
-class DeletePairingService {
- enum Errors: Error {
- case pairingNotFound
- }
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let pairingStorage: WCPairingStorage
- private let logger: ConsoleLogging
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- pairingStorage: WCPairingStorage,
- logger: ConsoleLogging) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.pairingStorage = pairingStorage
- self.logger = logger
- }
-
- func delete(topic: String) async throws {
- guard pairingStorage.hasPairing(forTopic: topic) else { throw Errors.pairingNotFound}
- let reason = AuthError.userDisconnected
- logger.debug("Will delete pairing for reason: message: \(reason.message) code: \(reason.code)")
- let request = RPCRequest(method: AuthProtocolMethod.pairingDelete.rawValue, params: reason)
- try await networkingInteractor.request(request, topic: topic, tag: AuthProtocolMethod.pairingDelete.requestTag)
- pairingStorage.delete(topic: topic)
- kms.deleteSymmetricKey(for: topic)
- networkingInteractor.unsubscribe(topic: topic)
- }
-}
diff --git a/Sources/Auth/Services/Common/IATProvider.swift b/Sources/Auth/Services/Common/IATProvider.swift
new file mode 100644
index 000000000..1d386c11e
--- /dev/null
+++ b/Sources/Auth/Services/Common/IATProvider.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+protocol IATProvider {
+ var iat: String { get }
+}
+
+struct DefaultIATProvider: IATProvider {
+ var iat: String {
+ return ISO8601DateFormatter().string(from: Date())
+ }
+}
diff --git a/Sources/Auth/Services/Common/PairingsProvider.swift b/Sources/Auth/Services/Common/PairingsProvider.swift
deleted file mode 100644
index 081f3fa6d..000000000
--- a/Sources/Auth/Services/Common/PairingsProvider.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Foundation
-import WalletConnectPairing
-
-public class PairingsProvider {
- private let pairingStorage: WCPairingStorage
-
- public init(pairingStorage: WCPairingStorage) {
- self.pairingStorage = pairingStorage
- }
-
- func getPairings() -> [Pairing] {
- pairingStorage.getAll()
- .map {Pairing(topic: $0.topic, peer: $0.peerMetadata, expiryDate: $0.expiryDate)}
- }
-}
diff --git a/Sources/Auth/Services/Signer/EIP1271/EIP1271Verifier.swift b/Sources/Auth/Services/Signer/EIP1271/EIP1271Verifier.swift
new file mode 100644
index 000000000..a68dd7742
--- /dev/null
+++ b/Sources/Auth/Services/Signer/EIP1271/EIP1271Verifier.swift
@@ -0,0 +1,44 @@
+import Foundation
+import JSONRPC
+import WalletConnectNetworking
+import WalletConnectUtils
+
+actor EIP1271Verifier {
+ private let projectId: String
+ private let httpClient: HTTPClient
+
+ init(projectId: String, httpClient: HTTPClient) {
+ self.projectId = projectId
+ self.httpClient = httpClient
+ }
+
+ func verify(signature: Data, message: Data, address: String, chainId: String) async throws {
+ let encoder = ValidSignatureMethod(signature: signature, messageHash: message.keccak256)
+ let call = EthCall(to: address, data: encoder.encode())
+ let params = AnyCodable([AnyCodable(call), AnyCodable("latest")])
+ let request = RPCRequest(method: "eth_call", params: params)
+ let data = try JSONEncoder().encode(request)
+ let httpService = RPCService(data: data, projectId: projectId, chainId: chainId)
+ let response = try await httpClient.request(RPCResponse.self, at: httpService)
+ try validateResponse(response)
+ }
+
+ private func validateResponse(_ response: RPCResponse) throws {
+ guard
+ let result = try response.result?.get(String.self),
+ result.starts(with: ValidSignatureMethod.methodHash)
+ else { throw Errors.invalidSignature }
+ }
+}
+
+extension EIP1271Verifier {
+
+ enum Errors: Error {
+ case invalidSignature
+ }
+
+ struct EthCall: Codable {
+ let to: String
+ let data: String
+ }
+}
diff --git a/Sources/Auth/Services/Signer/EIP1271/RPCService.swift b/Sources/Auth/Services/Signer/EIP1271/RPCService.swift
new file mode 100644
index 000000000..c7c81c995
--- /dev/null
+++ b/Sources/Auth/Services/Signer/EIP1271/RPCService.swift
@@ -0,0 +1,31 @@
+import Foundation
+import WalletConnectNetworking
+
+struct RPCService: HTTPService {
+ let data: Data
+ let projectId: String
+ let chainId: String
+
+ var path: String {
+ return "/v1"
+ }
+
+ var method: HTTPMethod {
+ return .post
+ }
+
+ var scheme: String {
+ return "https"
+ }
+
+ var body: Data? {
+ return data
+ }
+
+ var queryParameters: [String: String]? {
+ return [
+ "chainId": chainId,
+ "projectId": projectId
+ ]
+ }
+}
diff --git a/Sources/Auth/Services/Signer/EIP1271/ValidSignatureMethod.swift b/Sources/Auth/Services/Signer/EIP1271/ValidSignatureMethod.swift
new file mode 100644
index 000000000..134d62427
--- /dev/null
+++ b/Sources/Auth/Services/Signer/EIP1271/ValidSignatureMethod.swift
@@ -0,0 +1,28 @@
+import Foundation
+
+struct ValidSignatureMethod {
+ static let methodHash = "0x1626ba7e"
+ static let paddingIndex = "0000000000000000000000000000000000000000000000000000000000000040"
+ static let signatureLength = "0000000000000000000000000000000000000000000000000000000000000041"
+ static let signaturePadding = "00000000000000000000000000000000000000000000000000000000000000"
+
+ let signature: Data
+ let messageHash: Data
+
+ func encode() -> String {
+ return [
+ ValidSignatureMethod.methodHash,
+ leadingZeros(for: messageHash.toHexString(), end: false),
+ ValidSignatureMethod.paddingIndex,
+ ValidSignatureMethod.signatureLength,
+ leadingZeros(for: signature.toHexString(), end: true),
+ ValidSignatureMethod.signaturePadding
+ ].joined()
+ }
+
+ private func leadingZeros(for value: String, end: Bool) -> String {
+ let count = max(0, value.count % 32 - 2)
+ let padding = String(repeating: "0", count: count)
+ return end ? padding + value : value + padding
+ }
+}
diff --git a/Sources/Auth/Services/Signer/EIP191/EIP191Verifier.swift b/Sources/Auth/Services/Signer/EIP191/EIP191Verifier.swift
new file mode 100644
index 000000000..7622cae14
--- /dev/null
+++ b/Sources/Auth/Services/Signer/EIP191/EIP191Verifier.swift
@@ -0,0 +1,36 @@
+import Foundation
+import Web3
+
+actor EIP191Verifier {
+
+ func verify(signature: Data, message: Data, address: String) async throws {
+ let sig = decompose(signature: signature)
+ let publicKey = try EthereumPublicKey.init(
+ message: message.bytes,
+ v: EthereumQuantity(quantity: BigUInt(sig.v)),
+ r: EthereumQuantity(sig.r),
+ s: EthereumQuantity(sig.s)
+ )
+ try verifyPublicKey(publicKey, address: address)
+ }
+
+ private func decompose(signature: Data) -> Signer.Signature {
+ let v = signature.bytes[signature.count-1]
+ let r = signature.bytes[0..<32]
+ let s = signature.bytes[32..<64]
+ return (UInt(v), [UInt8](r), [UInt8](s))
+ }
+
+ private func verifyPublicKey(_ publicKey: EthereumPublicKey, address: String) throws {
+ guard publicKey.address.hex(eip55: false) == address.lowercased() else {
+ throw Errors.invalidSignature
+ }
+ }
+}
+
+extension EIP191Verifier {
+
+ enum Errors: Error {
+ case invalidSignature
+ }
+}
diff --git a/Sources/Auth/Services/Signer/MessageSigner.swift b/Sources/Auth/Services/Signer/MessageSigner.swift
index 3f6252efd..fd4ffffbb 100644
--- a/Sources/Auth/Services/Signer/MessageSigner.swift
+++ b/Sources/Auth/Services/Signer/MessageSigner.swift
@@ -1,37 +1,65 @@
import Foundation
-protocol MessageSignatureVerifying {
- func verify(signature: CacaoSignature, message: String, address: String) throws
+public protocol MessageSignatureVerifying {
+ func verify(signature: CacaoSignature, message: String, address: String, chainId: String) async throws
}
-protocol MessageSigning {
- func sign(message: String, privateKey: Data) throws -> CacaoSignature
+public protocol MessageSigning {
+ func sign(message: String, privateKey: Data, type: CacaoSignatureType) throws -> CacaoSignature
}
-public struct MessageSigner: MessageSignatureVerifying, MessageSigning {
+struct MessageSigner: MessageSignatureVerifying, MessageSigning {
enum Errors: Error {
- case signatureValidationFailed
case utf8EncodingFailed
}
private let signer: Signer
+ private let eip191Verifier: EIP191Verifier
+ private let eip1271Verifier: EIP1271Verifier
- public init(signer: Signer = Signer()) {
+ init(signer: Signer, eip191Verifier: EIP191Verifier, eip1271Verifier: EIP1271Verifier) {
self.signer = signer
+ self.eip191Verifier = eip191Verifier
+ self.eip1271Verifier = eip1271Verifier
}
- public func sign(message: String, privateKey: Data) throws -> CacaoSignature {
+ func sign(message: String, privateKey: Data, type: CacaoSignatureType) throws -> CacaoSignature {
guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed }
- let signature = try signer.sign(message: messageData, with: privateKey)
+ let signature = try signer.sign(message: prefixed(messageData), with: privateKey)
let prefixedHexSignature = "0x" + signature.toHexString()
- return CacaoSignature(t: "eip191", s: prefixedHexSignature)
+ return CacaoSignature(t: type, s: prefixedHexSignature)
}
- public func verify(signature: CacaoSignature, message: String, address: String) throws {
- guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed }
+ func verify(signature: CacaoSignature, message: String, address: String, chainId: String) async throws {
+ guard let messageData = message.data(using: .utf8) else {
+ throw Errors.utf8EncodingFailed
+ }
+
let signatureData = Data(hex: signature.s)
- guard try signer.isValid(signature: signatureData, message: messageData, address: address)
- else { throw Errors.signatureValidationFailed }
+
+ switch signature.t {
+ case .eip191:
+ return try await eip191Verifier.verify(
+ signature: signatureData,
+ message: prefixed(messageData),
+ address: address
+ )
+ case .eip1271:
+ return try await eip1271Verifier.verify(
+ signature: signatureData,
+ message: prefixed(messageData),
+ address: address,
+ chainId: chainId
+ )
+ }
+ }
+}
+
+private extension MessageSigner {
+
+ private func prefixed(_ message: Data) -> Data {
+ return "\u{19}Ethereum Signed Message:\n\(message.count)"
+ .data(using: .utf8)! + message
}
}
diff --git a/Sources/Auth/Services/Signer/MessageSignerFactory.swift b/Sources/Auth/Services/Signer/MessageSignerFactory.swift
new file mode 100644
index 000000000..81ba54de5
--- /dev/null
+++ b/Sources/Auth/Services/Signer/MessageSignerFactory.swift
@@ -0,0 +1,20 @@
+import Foundation
+import WalletConnectNetworking
+
+public struct MessageSignerFactory {
+
+ public static func create() -> MessageSigning & MessageSignatureVerifying {
+ return create(projectId: Networking.projectId)
+ }
+
+ static func create(projectId: String) -> MessageSigning & MessageSignatureVerifying {
+ return MessageSigner(
+ signer: Signer(),
+ eip191Verifier: EIP191Verifier(),
+ eip1271Verifier: EIP1271Verifier(
+ projectId: projectId,
+ httpClient: HTTPNetworkClient(host: "rpc.walletconnect.com")
+ )
+ )
+ }
+}
diff --git a/Sources/Auth/Services/Signer/Signer.swift b/Sources/Auth/Services/Signer/Signer.swift
index 3d5903296..cbd56ca89 100644
--- a/Sources/Auth/Services/Signer/Signer.swift
+++ b/Sources/Auth/Services/Signer/Signer.swift
@@ -1,44 +1,19 @@
import Foundation
import Web3
-public struct Signer {
+struct Signer {
typealias Signature = (v: UInt, r: [UInt8], s: [UInt8])
- public init() {}
+ init() {}
func sign(message: Data, with key: Data) throws -> Data {
- let prefixed = prefixed(message: message)
let privateKey = try EthereumPrivateKey(privateKey: key.bytes)
- let signature = try privateKey.sign(message: prefixed.bytes)
+ let signature = try privateKey.sign(message: message.bytes)
return serialized(signature: signature)
}
- func isValid(signature: Data, message: Data, address: String) throws -> Bool {
- let sig = decompose(signature: signature)
- let prefixed = prefixed(message: message)
- let publicKey = try EthereumPublicKey(
- message: prefixed.bytes,
- v: EthereumQuantity(quantity: BigUInt(sig.v)),
- r: EthereumQuantity(sig.r),
- s: EthereumQuantity(sig.s)
- )
- return publicKey.address.hex(eip55: false) == address.lowercased()
- }
-
- private func decompose(signature: Data) -> Signature {
- let v = signature.bytes[signature.count-1]
- let r = signature.bytes[0..<32]
- let s = signature.bytes[32..<64]
- return (UInt(v), [UInt8](r), [UInt8](s))
- }
-
private func serialized(signature: Signature) -> Data {
return Data(signature.r + signature.s + [UInt8(signature.v)])
}
-
- private func prefixed(message: Data) -> Data {
- return "\u{19}Ethereum Signed Message:\n\(message.count)"
- .data(using: .utf8)! + message
- }
}
diff --git a/Sources/Auth/Services/Wallet/WalletErrorResponder.swift b/Sources/Auth/Services/Wallet/WalletErrorResponder.swift
index 10e318c24..3234b86fd 100644
--- a/Sources/Auth/Services/Wallet/WalletErrorResponder.swift
+++ b/Sources/Auth/Services/Wallet/WalletErrorResponder.swift
@@ -25,16 +25,14 @@ actor WalletErrorResponder {
self.rpcHistory = rpcHistory
}
-
func respondError(_ error: AuthError, requestId: RPCID) async throws {
let authRequestParams = try getAuthRequestParams(requestId: requestId)
let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams)
try kms.setAgreementSecret(keys, topic: topic)
- let tag = AuthProtocolMethod.authRequest.responseTag
let envelopeType = Envelope.EnvelopeType.type1(pubKey: keys.publicKey.rawRepresentation)
- try await networkingInteractor.respondError(topic: topic, requestId: requestId, tag: tag, reason: error, envelopeType: envelopeType)
+ try await networkingInteractor.respondError(topic: topic, requestId: requestId, protocolMethod: AuthRequestProtocolMethod(), reason: error, envelopeType: envelopeType)
}
private func getAuthRequestParams(requestId: RPCID) throws -> AuthRequestParams {
@@ -52,7 +50,7 @@ actor WalletErrorResponder {
let topic = peerPubKey.rawRepresentation.sha256().toHexString()
let selfPubKey = try kms.createX25519KeyPair()
let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.hexRepresentation)
- //TODO - remove keys
+ // TODO - remove keys
return (topic, keys)
}
}
diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
index c204cc526..fde464a43 100644
--- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
+++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
@@ -4,6 +4,7 @@ import JSONRPC
import WalletConnectNetworking
import WalletConnectUtils
import WalletConnectKMS
+import WalletConnectPairing
class WalletRequestSubscriber {
private let networkingInteractor: NetworkInteracting
@@ -13,6 +14,7 @@ class WalletRequestSubscriber {
private var publishers = [AnyCancellable]()
private let messageFormatter: SIWEMessageFormatting
private let walletErrorResponder: WalletErrorResponder
+ private let pairingRegisterer: PairingRegisterer
var onRequest: ((AuthRequest) -> Void)?
init(networkingInteractor: NetworkInteracting,
@@ -20,20 +22,22 @@ class WalletRequestSubscriber {
kms: KeyManagementServiceProtocol,
messageFormatter: SIWEMessageFormatting,
address: String?,
- walletErrorResponder: WalletErrorResponder) {
+ walletErrorResponder: WalletErrorResponder,
+ pairingRegisterer: PairingRegisterer) {
self.networkingInteractor = networkingInteractor
self.logger = logger
self.kms = kms
self.address = address
self.messageFormatter = messageFormatter
self.walletErrorResponder = walletErrorResponder
+ self.pairingRegisterer = pairingRegisterer
subscribeForRequest()
}
private func subscribeForRequest() {
guard let address = address else { return }
- networkingInteractor.requestSubscription(on: AuthProtocolMethod.authRequest)
+ pairingRegisterer.register(method: AuthRequestProtocolMethod())
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
logger.debug("WalletRequestSubscriber: Received request")
guard let message = messageFormatter.formatMessage(from: payload.request.payloadParams, address: address) else {
@@ -42,8 +46,8 @@ class WalletRequestSubscriber {
}
return
}
+ pairingRegisterer.activate(pairingTopic: payload.topic)
onRequest?(.init(id: payload.id, message: message))
}.store(in: &publishers)
}
}
-
diff --git a/Sources/Auth/Services/Wallet/WalletRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift
index f9a53b10d..32090f956 100644
--- a/Sources/Auth/Services/Wallet/WalletRespondService.swift
+++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift
@@ -39,7 +39,7 @@ actor WalletRespondService {
let responseParams = AuthResponseParams(h: header, p: payload, s: signature)
let response = RPCResponse(id: requestId, result: responseParams)
- try await networkingInteractor.respond(topic: topic, response: response, tag: AuthProtocolMethod.authRequest.responseTag, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
+ try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: AuthRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
}
func respondError(requestId: RPCID) async throws {
diff --git a/Sources/Auth/Types/Cacao/CacaoSignature.swift b/Sources/Auth/Types/Cacao/CacaoSignature.swift
index b34ee1a40..7137f9dea 100644
--- a/Sources/Auth/Types/Cacao/CacaoSignature.swift
+++ b/Sources/Auth/Types/Cacao/CacaoSignature.swift
@@ -1,11 +1,16 @@
import Foundation
+public enum CacaoSignatureType: String, Codable {
+ case eip191
+ case eip1271
+}
+
public struct CacaoSignature: Codable, Equatable {
- let t: String
+ let t: CacaoSignatureType
let s: String
let m: String?
- public init(t: String, s: String, m: String? = nil) {
+ public init(t: CacaoSignatureType, s: String, m: String? = nil) {
self.t = t
self.s = s
self.m = m
diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift
index 9918dbc70..b5af0d7b2 100644
--- a/Sources/Chat/ChatClientFactory.swift
+++ b/Sources/Chat/ChatClientFactory.swift
@@ -15,7 +15,7 @@ public struct ChatClientFactory {
) -> ChatClient {
let topicToRegistryRecordStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
let serialiser = Serializer(kms: kms)
- let rpcHistory = RPCHistory(keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue))
+ let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serialiser, logger: logger, rpcHistory: rpcHistory)
let invitePayloadStore = CodableStore>(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.invite.rawValue)
let registryService = RegistryService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore)
diff --git a/Sources/Chat/HTTPServices/RegisterService.swift b/Sources/Chat/HTTPServices/RegisterService.swift
index 0734be114..618c64035 100644
--- a/Sources/Chat/HTTPServices/RegisterService.swift
+++ b/Sources/Chat/HTTPServices/RegisterService.swift
@@ -1,5 +1,5 @@
import Foundation
-import WalletConnectRelay
+import WalletConnectNetworking
struct RegisterService: HTTPService {
diff --git a/Sources/Chat/HTTPServices/ResolveService.swift b/Sources/Chat/HTTPServices/ResolveService.swift
index 993833c64..795584000 100644
--- a/Sources/Chat/HTTPServices/ResolveService.swift
+++ b/Sources/Chat/HTTPServices/ResolveService.swift
@@ -1,5 +1,5 @@
import Foundation
-import WalletConnectRelay
+import WalletConnectNetworking
import WalletConnectUtils
struct ResolveService: HTTPService {
diff --git a/Sources/Chat/ProtocolServices/Common/MessagingService.swift b/Sources/Chat/ProtocolServices/Common/MessagingService.swift
index 20edfb6b5..e4c746cf3 100644
--- a/Sources/Chat/ProtocolServices/Common/MessagingService.swift
+++ b/Sources/Chat/ProtocolServices/Common/MessagingService.swift
@@ -29,12 +29,13 @@ class MessagingService {
func send(topic: String, messageString: String) async throws {
// TODO - manage author account
+ let protocolMethod = ChatMessageProtocolMethod()
let thread = await threadStore.first {$0.topic == topic}
guard let authorAccount = thread?.selfAccount else { throw Errors.threadDoNotExist}
let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
let message = Message(topic: topic, message: messageString, authorAccount: authorAccount, timestamp: timestamp)
- let request = RPCRequest(method: ChatProtocolMethod.message.method, params: message)
- try await networkingInteractor.request(request, topic: topic, tag: ChatProtocolMethod.message.requestTag)
+ let request = RPCRequest(method: protocolMethod.method, params: message)
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
Task(priority: .background) {
await messagesStore.add(message)
onMessage?(message)
@@ -42,14 +43,14 @@ class MessagingService {
}
private func setUpResponseHandling() {
- networkingInteractor.responseSubscription(on: ChatProtocolMethod.message)
- .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ networkingInteractor.responseSubscription(on: ChatMessageProtocolMethod())
+ .sink { [unowned self] (_: ResponseSubscriptionPayload) in
logger.debug("Received Message response")
}.store(in: &publishers)
}
private func setUpRequestHandling() {
- networkingInteractor.requestSubscription(on: ChatProtocolMethod.message)
+ networkingInteractor.requestSubscription(on: ChatMessageProtocolMethod())
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
var message = payload.request
message.topic = payload.topic
@@ -59,7 +60,7 @@ class MessagingService {
private func handleMessage(_ message: Message, topic: String, requestId: RPCID) {
Task(priority: .background) {
- try await networkingInteractor.respondSuccess(topic: topic, requestId: requestId, tag: ChatProtocolMethod.message.responseTag)
+ try await networkingInteractor.respondSuccess(topic: topic, requestId: requestId, protocolMethod: ChatMessageProtocolMethod())
await messagesStore.add(message)
logger.debug("Received message")
onMessage?(message)
diff --git a/Sources/Chat/ProtocolServices/Common/File.swift b/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift
similarity index 71%
rename from Sources/Chat/ProtocolServices/Common/File.swift
rename to Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift
index e821f03d7..c08738891 100644
--- a/Sources/Chat/ProtocolServices/Common/File.swift
+++ b/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift
@@ -22,11 +22,10 @@ class ResubscriptionService {
func setUpResubscription() {
networkingInteractor.socketConnectionStatusPublisher
.sink { [unowned self] status in
- if status == .connected {
- Task(priority: .background) {
- let topics = await threadStore.getAll().map {$0.topic}
- topics.forEach { topic in Task(priority: .background) { try? await networkingInteractor.subscribe(topic: topic) } }
- }
+ guard status == .connected else { return }
+ Task(priority: .background) {
+ let topics = await threadStore.getAll().map {$0.topic}
+ topics.forEach { topic in Task(priority: .background) { try? await networkingInteractor.subscribe(topic: topic) } }
}
}.store(in: &publishers)
}
diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift
index c6dd9aede..e3e30028c 100644
--- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift
+++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift
@@ -39,6 +39,8 @@ class InvitationHandlingService {
}
func accept(inviteId: String) async throws {
+ let protocolMethod = ChatInviteProtocolMethod()
+
guard let payload = try invitePayloadStore.get(key: inviteId) else { throw Error.inviteForIdNotFound }
let selfThreadPubKey = try kms.createX25519KeyPair()
@@ -47,7 +49,7 @@ class InvitationHandlingService {
let response = RPCResponse(id: payload.id, result: inviteResponse)
let responseTopic = try getInviteResponseTopic(requestTopic: payload.topic, invite: payload.request)
- try await networkingInteractor.respond(topic: responseTopic, response: response, tag: ChatProtocolMethod.invite.responseTag)
+ try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod)
let threadAgreementKeys = try kms.performKeyAgreement(selfPublicKey: selfThreadPubKey, peerPublicKey: payload.request.publicKey)
let threadTopic = threadAgreementKeys.derivedTopic()
@@ -71,13 +73,13 @@ class InvitationHandlingService {
let responseTopic = try getInviteResponseTopic(requestTopic: payload.topic, invite: payload.request)
- try await networkingInteractor.respondError(topic: responseTopic, requestId: payload.id, tag: ChatProtocolMethod.invite.responseTag, reason: ChatError.userRejected)
+ try await networkingInteractor.respondError(topic: responseTopic, requestId: payload.id, protocolMethod: ChatInviteProtocolMethod(), reason: ChatError.userRejected)
invitePayloadStore.delete(forKey: inviteId)
}
private func setUpRequestHandling() {
- networkingInteractor.requestSubscription(on: ChatProtocolMethod.invite)
+ networkingInteractor.requestSubscription(on: ChatInviteProtocolMethod())
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
logger.debug("did receive an invite")
invitePayloadStore.set(payload, forKey: payload.request.publicKey)
diff --git a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift
index 514138042..9568bfac4 100644
--- a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift
+++ b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift
@@ -33,6 +33,7 @@ class InviteService {
func invite(peerPubKey: String, peerAccount: Account, openingMessage: String, account: Account) async throws {
// TODO ad storage
+ let protocolMethod = ChatInviteProtocolMethod()
self.peerAccount = peerAccount
let selfPubKeyY = try kms.createX25519KeyPair()
let invite = Invite(message: openingMessage, account: account, publicKey: selfPubKeyY.hexRepresentation)
@@ -42,7 +43,7 @@ class InviteService {
// overrides on invite toipic
try kms.setSymmetricKey(symKeyI.sharedKey, for: inviteTopic)
- let request = RPCRequest(method: ChatProtocolMethod.invite.method, params: invite)
+ let request = RPCRequest(method: protocolMethod.method, params: invite)
// 2. Proposer subscribes to topic R which is the hash of the derived symKey
let responseTopic = symKeyI.derivedTopic()
@@ -50,13 +51,13 @@ class InviteService {
try kms.setSymmetricKey(symKeyI.sharedKey, for: responseTopic)
try await networkingInteractor.subscribe(topic: responseTopic)
- try await networkingInteractor.request(request, topic: inviteTopic, tag: ChatProtocolMethod.invite.requestTag, envelopeType: .type1(pubKey: selfPubKeyY.rawRepresentation))
+ try await networkingInteractor.request(request, topic: inviteTopic, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: selfPubKeyY.rawRepresentation))
logger.debug("invite sent on topic: \(inviteTopic)")
}
private func setUpResponseHandling() {
- networkingInteractor.responseSubscription(on: ChatProtocolMethod.invite)
+ networkingInteractor.responseSubscription(on: ChatInviteProtocolMethod())
.sink { [unowned self] (payload: ResponseSubscriptionPayload) in
logger.debug("Invite has been accepted")
diff --git a/Sources/Chat/Registry.swift b/Sources/Chat/Registry.swift
index e7f31d357..bd8b82a55 100644
--- a/Sources/Chat/Registry.swift
+++ b/Sources/Chat/Registry.swift
@@ -1,5 +1,5 @@
import Foundation
-import WalletConnectRelay
+import WalletConnectNetworking
import WalletConnectUtils
public protocol Registry {
diff --git a/Sources/Chat/Types/ChatProtocolMethod.swift b/Sources/Chat/Types/ChatProtocolMethod.swift
index a32e5d4bf..f24a5f971 100644
--- a/Sources/Chat/Types/ChatProtocolMethod.swift
+++ b/Sources/Chat/Types/ChatProtocolMethod.swift
@@ -1,34 +1,20 @@
import Foundation
import WalletConnectNetworking
-enum ChatProtocolMethod: ProtocolMethod {
- case invite
- case message
-
- var requestTag: Int {
- switch self {
- case .invite:
- return 2000
- case .message:
- return 2002
- }
- }
-
- var responseTag: Int {
- switch self {
- case .invite:
- return 2001
- case .message:
- return 2003
- }
- }
-
- var method: String {
- switch self {
- case .invite:
- return "wc_chatInvite"
- case .message:
- return "wc_chatMessage"
- }
- }
+struct ChatInviteProtocolMethod: ProtocolMethod {
+ let method: String = "wc_chatInvite"
+
+ let requestConfig = RelayConfig(tag: 2000, prompt: true, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 2001, prompt: false, ttl: 86400)
+
+}
+
+struct ChatMessageProtocolMethod: ProtocolMethod {
+ let method: String = "wc_chatMessage"
+
+ let requestConfig = RelayConfig(tag: 2002, prompt: true, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 2003, prompt: false, ttl: 86400)
+
}
diff --git a/Sources/JSONRPC/RPCID.swift b/Sources/JSONRPC/RPCID.swift
index ff0ef3c83..21dcfb682 100644
--- a/Sources/JSONRPC/RPCID.swift
+++ b/Sources/JSONRPC/RPCID.swift
@@ -1,3 +1,4 @@
+import Foundation
import Commons
import Foundation
@@ -15,3 +16,12 @@ struct IntIdentifierGenerator: IdentifierGenerator {
return RPCID(timestamp + random)
}
}
+
+extension RPCID {
+
+ public var timestamp: Date {
+ guard let id = self.right else { return .distantPast }
+ let interval = TimeInterval(id / 1000 / 1000)
+ return Date(timeIntervalSince1970: interval)
+ }
+}
diff --git a/Sources/JSONRPC/RPCResponse.swift b/Sources/JSONRPC/RPCResponse.swift
index ad6ba9ca6..d38ef9c49 100644
--- a/Sources/JSONRPC/RPCResponse.swift
+++ b/Sources/JSONRPC/RPCResponse.swift
@@ -10,65 +10,65 @@ public struct RPCResponse: Equatable {
public let id: RPCID?
public var result: AnyCodable? {
- if case .success(let value) = outcome { return value }
+ if case .response(let value) = outcome { return value }
return nil
}
public var error: JSONRPCError? {
- if case .failure(let error) = outcome { return error }
+ if case .error(let error) = outcome { return error }
return nil
}
- public let outcome: Result
+ public let outcome: RPCResult
- internal init(id: RPCID?, outcome: Result) {
+ internal init(id: RPCID?, outcome: RPCResult) {
self.jsonrpc = "2.0"
self.id = id
self.outcome = outcome
}
public init(matchingRequest: RPCRequest, result: C) where C: Codable {
- self.init(id: matchingRequest.id, outcome: .success(AnyCodable(result)))
+ self.init(id: matchingRequest.id, outcome: .response(AnyCodable(result)))
}
public init(matchingRequest: RPCRequest, error: JSONRPCError) {
- self.init(id: matchingRequest.id, outcome: .failure(error))
+ self.init(id: matchingRequest.id, outcome: .error(error))
}
public init(id: Int64, result: C) where C: Codable {
- self.init(id: RPCID(id), outcome: .success(AnyCodable(result)))
+ self.init(id: RPCID(id), outcome: .response(AnyCodable(result)))
}
public init(id: String, result: C) where C: Codable {
- self.init(id: RPCID(id), outcome: .success(AnyCodable(result)))
+ self.init(id: RPCID(id), outcome: .response(AnyCodable(result)))
}
public init(id: RPCID, result: C) where C: Codable {
- self.init(id: id, outcome: .success(AnyCodable(result)))
+ self.init(id: id, outcome: .response(AnyCodable(result)))
}
public init(id: RPCID?, error: JSONRPCError) {
- self.init(id: id, outcome: .failure(error))
+ self.init(id: id, outcome: .error(error))
}
public init(id: Int64, error: JSONRPCError) {
- self.init(id: RPCID(id), outcome: .failure(error))
+ self.init(id: RPCID(id), outcome: .error(error))
}
public init(id: String, error: JSONRPCError) {
- self.init(id: RPCID(id), outcome: .failure(error))
+ self.init(id: RPCID(id), outcome: .error(error))
}
public init(id: Int64, errorCode: Int, message: String, associatedData: AnyCodable? = nil) {
- self.init(id: RPCID(id), outcome: .failure(JSONRPCError(code: errorCode, message: message, data: associatedData)))
+ self.init(id: RPCID(id), outcome: .error(JSONRPCError(code: errorCode, message: message, data: associatedData)))
}
public init(id: String, errorCode: Int, message: String, associatedData: AnyCodable? = nil) {
- self.init(id: RPCID(id), outcome: .failure(JSONRPCError(code: errorCode, message: message, data: associatedData)))
+ self.init(id: RPCID(id), outcome: .error(JSONRPCError(code: errorCode, message: message, data: associatedData)))
}
public init(errorWithoutID: JSONRPCError) {
- self.init(id: nil, outcome: .failure(errorWithoutID))
+ self.init(id: nil, outcome: .error(errorWithoutID))
}
}
@@ -104,9 +104,9 @@ extension RPCResponse: Codable {
codingPath: [CodingKeys.result, CodingKeys.id],
debugDescription: "A success response must have a valid `id`."))
}
- outcome = .success(result)
+ outcome = .response(result)
} else if let error = error {
- outcome = .failure(error)
+ outcome = .error(error)
} else {
throw DecodingError.dataCorrupted(.init(
codingPath: [CodingKeys.result, CodingKeys.error],
@@ -119,9 +119,9 @@ extension RPCResponse: Codable {
try container.encode(jsonrpc, forKey: .jsonrpc)
try container.encode(id, forKey: .id)
switch outcome {
- case .success(let anyCodable):
+ case .response(let anyCodable):
try container.encode(anyCodable, forKey: .result)
- case .failure(let rpcError):
+ case .error(let rpcError):
try container.encode(rpcError, forKey: .error)
}
}
diff --git a/Sources/JSONRPC/RPCResult.swift b/Sources/JSONRPC/RPCResult.swift
new file mode 100644
index 000000000..a3b81e094
--- /dev/null
+++ b/Sources/JSONRPC/RPCResult.swift
@@ -0,0 +1,39 @@
+import Foundation
+import Commons
+
+public enum RPCResult: Codable, Equatable {
+ enum Errors: Error {
+ case decoding
+ }
+
+ case response(AnyCodable)
+ case error(JSONRPCError)
+
+ public var value: Codable {
+ switch self {
+ case .response(let value):
+ return value
+ case .error(let value):
+ return value
+ }
+ }
+
+ public init(from decoder: Decoder) throws {
+ if let value = try? JSONRPCError(from: decoder) {
+ self = .error(value)
+ } else if let value = try? AnyCodable(from: decoder) {
+ self = .response(value)
+ } else {
+ throw Errors.decoding
+ }
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ switch self {
+ case .error(let value):
+ try value.encode(to: encoder)
+ case .response(let value):
+ try value.encode(to: encoder)
+ }
+ }
+}
diff --git a/Sources/WalletConnectNetworking/HTTPClient/HTTPClient.swift b/Sources/WalletConnectNetworking/HTTPClient/HTTPClient.swift
new file mode 100644
index 000000000..0fb4c8a68
--- /dev/null
+++ b/Sources/WalletConnectNetworking/HTTPClient/HTTPClient.swift
@@ -0,0 +1,6 @@
+import Foundation
+
+public protocol HTTPClient {
+ func request(_ type: T.Type, at service: HTTPService) async throws -> T
+ func request(service: HTTPService) async throws
+}
diff --git a/Sources/WalletConnectRelay/HTTP/HTTPError.swift b/Sources/WalletConnectNetworking/HTTPClient/HTTPError.swift
similarity index 100%
rename from Sources/WalletConnectRelay/HTTP/HTTPError.swift
rename to Sources/WalletConnectNetworking/HTTPClient/HTTPError.swift
diff --git a/Sources/WalletConnectRelay/HTTP/HTTPClient.swift b/Sources/WalletConnectNetworking/HTTPClient/HTTPNetworkClient.swift
similarity index 85%
rename from Sources/WalletConnectRelay/HTTP/HTTPClient.swift
rename to Sources/WalletConnectNetworking/HTTPClient/HTTPNetworkClient.swift
index 03ec8626d..b87948bd6 100644
--- a/Sources/WalletConnectRelay/HTTP/HTTPClient.swift
+++ b/Sources/WalletConnectNetworking/HTTPClient/HTTPNetworkClient.swift
@@ -1,6 +1,6 @@
import Foundation
-public actor HTTPClient {
+public actor HTTPNetworkClient: HTTPClient {
let host: String
@@ -32,14 +32,14 @@ public actor HTTPClient {
}
}
- func request(_ type: T.Type, at service: HTTPService, completion: @escaping (Result) -> Void) {
+ private func request(_ type: T.Type, at service: HTTPService, completion: @escaping (Result) -> Void) {
guard let request = service.resolve(for: host) else {
completion(.failure(HTTPError.malformedURL(service)))
return
}
session.dataTask(with: request) { data, response, error in
do {
- try HTTPClient.validate(response, error)
+ try HTTPNetworkClient.validate(response, error)
guard let validData = data else {
throw HTTPError.responseDataNil
}
@@ -51,14 +51,14 @@ public actor HTTPClient {
}.resume()
}
- func request(service: HTTPService, completion: @escaping (Result) -> Void) {
+ private func request(service: HTTPService, completion: @escaping (Result) -> Void) {
guard let request = service.resolve(for: host) else {
completion(.failure(HTTPError.malformedURL(service)))
return
}
session.dataTask(with: request) { _, response, error in
do {
- try HTTPClient.validate(response, error)
+ try HTTPNetworkClient.validate(response, error)
completion(.success(()))
} catch {
completion(.failure(error))
diff --git a/Sources/WalletConnectRelay/HTTP/HTTPService.swift b/Sources/WalletConnectNetworking/HTTPClient/HTTPService.swift
similarity index 100%
rename from Sources/WalletConnectRelay/HTTP/HTTPService.swift
rename to Sources/WalletConnectNetworking/HTTPClient/HTTPService.swift
diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift
index 8dd02d382..f23d5b66d 100644
--- a/Sources/WalletConnectNetworking/NetworkInteracting.swift
+++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift
@@ -6,13 +6,14 @@ import WalletConnectRelay
public protocol NetworkInteracting {
var socketConnectionStatusPublisher: AnyPublisher { get }
+ var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { get }
func subscribe(topic: String) async throws
func unsubscribe(topic: String)
- func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
- func requestNetworkAck(_ request: RPCRequest, topic: String, tag: Int) async throws
- func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
- func respondSuccess(topic: String, requestId: RPCID, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
- func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws
+ func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
+ func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws
+ func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
+ func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
+ func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws
func requestSubscription(
on request: ProtocolMethod
@@ -22,23 +23,25 @@ public protocol NetworkInteracting {
on request: ProtocolMethod
) -> AnyPublisher, Never>
- func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher
+ func responseErrorSubscription(
+ on request: ProtocolMethod
+ ) -> AnyPublisher, Never>
}
extension NetworkInteracting {
- public func request(_ request: RPCRequest, topic: String, tag: Int) async throws {
- try await self.request(request, topic: topic, tag: tag, envelopeType: .type0)
+ public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws {
+ try await self.request(request, topic: topic, protocolMethod: protocolMethod, envelopeType: .type0)
}
- public func respond(topic: String, response: RPCResponse, tag: Int) async throws {
- try await self.respond(topic: topic, response: response, tag: tag, envelopeType: .type0)
+ public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod) async throws {
+ try await self.respond(topic: topic, response: response, protocolMethod: protocolMethod, envelopeType: .type0)
}
- public func respondSuccess(topic: String, requestId: RPCID, tag: Int) async throws {
- try await self.respondSuccess(topic: topic, requestId: requestId, tag: tag, envelopeType: .type0)
+ public func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod) async throws {
+ try await self.respondSuccess(topic: topic, requestId: requestId, protocolMethod: protocolMethod, envelopeType: .type0)
}
- public func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason) async throws {
- try await self.respondError(topic: topic, requestId: requestId, tag: tag, reason: reason, envelopeType: .type0)
+ public func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason) async throws {
+ try await self.respondError(topic: topic, requestId: requestId, protocolMethod: protocolMethod, reason: reason, envelopeType: .type0)
}
}
diff --git a/Sources/WalletConnectNetworking/Networking.swift b/Sources/WalletConnectNetworking/Networking.swift
new file mode 100644
index 000000000..fdad4f621
--- /dev/null
+++ b/Sources/WalletConnectNetworking/Networking.swift
@@ -0,0 +1,56 @@
+import WalletConnectRelay
+import WalletConnectUtils
+import WalletConnectKMS
+import Foundation
+
+public class Networking {
+
+ /// Networking client instance
+ public static var instance: NetworkingClient {
+ return Networking.interactor
+ }
+
+ public static var interactor: NetworkingInteractor = {
+ guard let _ = Networking.config else {
+ fatalError("Error - you must call Networking.configure(_:) before accessing the shared instance.")
+ }
+
+ return NetworkingClientFactory.create(relayClient: Relay.instance)
+ }()
+
+ public static var projectId: String {
+ guard let projectId = config?.projectId else {
+ fatalError("Error - you must configure projectId with Networking.configure(_:)")
+ }
+ return projectId
+ }
+
+ private static var config: Config?
+
+ private init() { }
+
+ /// Networking instance config method
+ /// - Parameters:
+ /// - relayHost: relay host
+ /// - projectId: project id
+ /// - socketFactory: web socket factory
+ /// - socketConnectionType: socket connection type
+ static public func configure(
+ relayHost: String = "relay.walletconnect.com",
+ projectId: String,
+ socketFactory: WebSocketFactory,
+ socketConnectionType: SocketConnectionType = .automatic
+ ) {
+ Networking.config = Networking.Config(
+ relayHost: relayHost,
+ projectId: projectId,
+ socketFactory: socketFactory,
+ socketConnectionType: socketConnectionType
+ )
+ Relay.configure(
+ relayHost: relayHost,
+ projectId: projectId,
+ socketFactory: socketFactory,
+ socketConnectionType: socketConnectionType)
+ }
+}
diff --git a/Sources/WalletConnectNetworking/NetworkingClient.swift b/Sources/WalletConnectNetworking/NetworkingClient.swift
new file mode 100644
index 000000000..f39e2790d
--- /dev/null
+++ b/Sources/WalletConnectNetworking/NetworkingClient.swift
@@ -0,0 +1,10 @@
+import Foundation
+import Combine
+import WalletConnectRelay
+
+public protocol NetworkingClient {
+ var socketConnectionStatusPublisher: AnyPublisher { get }
+ func connect() throws
+ func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
+ func getClientId() throws -> String
+}
diff --git a/Sources/WalletConnectNetworking/NetworkingClientFactory.swift b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift
new file mode 100644
index 000000000..91a1cdacf
--- /dev/null
+++ b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift
@@ -0,0 +1,28 @@
+import Foundation
+import WalletConnectKMS
+import WalletConnectRelay
+import WalletConnectUtils
+
+public struct NetworkingClientFactory {
+
+ public static func create(relayClient: RelayClient) -> NetworkingInteractor {
+ let logger = ConsoleLogger(loggingLevel: .off)
+ let keyValueStorage = UserDefaults.standard
+ let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
+ return NetworkingClientFactory.create(relayClient: relayClient, logger: logger, keychainStorage: keychainStorage, keyValueStorage: keyValueStorage)
+ }
+
+ public static func create(relayClient: RelayClient, logger: ConsoleLogging, keychainStorage: KeychainStorageProtocol, keyValueStorage: KeyValueStorage) -> NetworkingInteractor {
+ let kms = KeyManagementService(keychain: keychainStorage)
+
+ let serializer = Serializer(kms: kms)
+
+ let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
+
+ return NetworkingInteractor(
+ relayClient: relayClient,
+ serializer: serializer,
+ logger: logger,
+ rpcHistory: rpcHistory)
+ }
+}
diff --git a/Sources/WalletConnectNetworking/NetworkingConfig.swift b/Sources/WalletConnectNetworking/NetworkingConfig.swift
new file mode 100644
index 000000000..c569d813b
--- /dev/null
+++ b/Sources/WalletConnectNetworking/NetworkingConfig.swift
@@ -0,0 +1,11 @@
+import Foundation
+import WalletConnectRelay
+
+extension Networking {
+ struct Config {
+ let relayHost: String
+ let projectId: String
+ let socketFactory: WebSocketFactory
+ let socketConnectionType: SocketConnectionType
+ }
+}
diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift
similarity index 67%
rename from Sources/WalletConnectNetworking/NetworkInteractor.swift
rename to Sources/WalletConnectNetworking/NetworkingInteractor.swift
index 13859ead6..66647efd2 100644
--- a/Sources/WalletConnectNetworking/NetworkInteractor.swift
+++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift
@@ -15,7 +15,7 @@ public class NetworkingInteractor: NetworkInteracting {
private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>()
private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse), Never>()
- private var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
+ public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
requestPublisherSubject.eraseToAnyPublisher()
}
@@ -36,10 +36,14 @@ public class NetworkingInteractor: NetworkInteracting {
self.rpcHistory = rpcHistory
self.logger = logger
self.socketConnectionStatusPublisher = relayClient.socketConnectionStatusPublisher
- relayClient.messagePublisher.sink { [unowned self] (topic, message) in
- manageSubscription(topic, message)
- }
- .store(in: &publishers)
+ setupRelaySubscribtion()
+ }
+
+ private func setupRelaySubscribtion() {
+ relayClient.messagePublisher
+ .sink { [unowned self] (topic, message) in
+ manageSubscription(topic, message)
+ }.store(in: &publishers)
}
public func subscribe(topic: String) async throws {
@@ -56,11 +60,13 @@ public class NetworkingInteractor: NetworkInteracting {
}
}
- public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
+ public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return requestPublisher
- .filter { $0.request.method == request.method }
+ .filter { rpcRequest in
+ return rpcRequest.request.method == request.method
+ }
.compactMap { topic, rpcRequest in
- guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil }
+ guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(RequestParams.self) else { return nil }
return RequestSubscriptionPayload(id: id, topic: topic, request: request)
}
.eraseToAnyPublisher()
@@ -68,7 +74,9 @@ public class NetworkingInteractor: NetworkInteracting {
public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return responsePublisher
- .filter { $0.request.method == request.method }
+ .filter { rpcRequest in
+ return rpcRequest.request.method == request.method
+ }
.compactMap { topic, rpcRequest, rpcResponse in
guard
let id = rpcRequest.id,
@@ -79,31 +87,31 @@ public class NetworkingInteractor: NetworkInteracting {
.eraseToAnyPublisher()
}
- public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher {
+ public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return responsePublisher
.filter { $0.request.method == request.method }
- .compactMap { (_, _, rpcResponse) in
- guard let id = rpcResponse.id, let error = rpcResponse.error else { return nil }
- return ResponseSubscriptionErrorPayload(id: id, error: error)
+ .compactMap { (topic, rpcRequest, rpcResponse) in
+ guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil }
+ return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error)
}
.eraseToAnyPublisher()
}
- public func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
+ 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)
- try await relayClient.publish(topic: topic, payload: message, tag: tag)
+ try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.requestConfig.tag, prompt: protocolMethod.requestConfig.prompt, ttl: protocolMethod.requestConfig.ttl)
}
/// Completes with an acknowledgement from the relay network.
/// completes with error if networking client was not able to send a message
/// TODO - relay client should provide async function - continualion should be removed from here
- public func requestNetworkAck(_ request: RPCRequest, topic: String, tag: Int) async throws {
+ public func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws {
do {
try rpcHistory.set(request, forTopic: topic, emmitedBy: .local)
let message = try serializer.serialize(topic: topic, encodable: request)
return try await withCheckedThrowingContinuation { continuation in
- relayClient.publish(topic: topic, payload: message, tag: tag) { error in
+ relayClient.publish(topic: topic, payload: message, tag: protocolMethod.requestConfig.tag, prompt: protocolMethod.requestConfig.prompt, ttl: protocolMethod.requestConfig.ttl) { error in
if let error = error {
continuation.resume(throwing: error)
} else {
@@ -116,21 +124,21 @@ public class NetworkingInteractor: NetworkInteracting {
}
}
- public func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
+ public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
try rpcHistory.resolve(response)
let message = try! serializer.serialize(topic: topic, encodable: response, envelopeType: envelopeType)
- try await relayClient.publish(topic: topic, payload: message, tag: tag)
+ try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.responseConfig.tag, prompt: protocolMethod.responseConfig.prompt, ttl: protocolMethod.responseConfig.ttl)
}
- public func respondSuccess(topic: String, requestId: RPCID, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
+ public func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
let response = RPCResponse(id: requestId, result: true)
- try await respond(topic: topic, response: response, tag: tag, envelopeType: envelopeType)
+ try await respond(topic: topic, response: response, protocolMethod: protocolMethod, envelopeType: envelopeType)
}
- public func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws {
+ public func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws {
let error = JSONRPCError(code: reason.code, message: reason.message)
let response = RPCResponse(id: requestId, error: error)
- try await respond(topic: topic, response: response, tag: tag, envelopeType: envelopeType)
+ try await respond(topic: topic, response: response, protocolMethod: protocolMethod, envelopeType: envelopeType)
}
private func manageSubscription(_ topic: String, _ encodedEnvelope: String) {
@@ -162,3 +170,17 @@ public class NetworkingInteractor: NetworkInteracting {
}
}
}
+
+extension NetworkingInteractor: NetworkingClient {
+ public func connect() throws {
+ try relayClient.connect()
+ }
+
+ public func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws {
+ try relayClient.disconnect(closeCode: closeCode)
+ }
+
+ public func getClientId() throws -> String {
+ try relayClient.getClientId()
+ }
+}
diff --git a/Sources/WalletConnectNetworking/ProtocolMethod.swift b/Sources/WalletConnectNetworking/ProtocolMethod.swift
index dea8bd255..624d74aa2 100644
--- a/Sources/WalletConnectNetworking/ProtocolMethod.swift
+++ b/Sources/WalletConnectNetworking/ProtocolMethod.swift
@@ -2,6 +2,18 @@ import Foundation
public protocol ProtocolMethod {
var method: String { get }
- var requestTag: Int { get }
- var responseTag: Int { get }
+ var requestConfig: RelayConfig { get }
+ var responseConfig: RelayConfig { get }
+}
+
+public struct RelayConfig {
+ let tag: Int
+ let prompt: Bool
+ let ttl: Int
+
+ public init(tag: Int, prompt: Bool, ttl: Int) {
+ self.tag = tag
+ self.prompt = prompt
+ self.ttl = ttl
+ }
}
diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift
index fa6b0e8db..d8767d692 100644
--- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift
+++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift
@@ -1,7 +1,7 @@
import Foundation
import JSONRPC
-public struct RequestSubscriptionPayload: Codable {
+public struct RequestSubscriptionPayload: Codable, SubscriptionPayload {
public let id: RPCID
public let topic: String
public let request: Request
diff --git a/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift b/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift
index 8b38df244..589ebbcc2 100644
--- a/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift
+++ b/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift
@@ -1,12 +1,16 @@
import Foundation
import JSONRPC
-public struct ResponseSubscriptionErrorPayload {
+public struct ResponseSubscriptionErrorPayload: Codable, SubscriptionPayload {
public let id: RPCID
+ public let topic: String
+ public let request: Request
public let error: JSONRPCError
- public init(id: RPCID, error: JSONRPCError) {
+ public init(id: RPCID, topic: String, request: Request, error: JSONRPCError) {
self.id = id
+ self.topic = topic
+ self.request = request
self.error = error
}
}
diff --git a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift
index 21043eb9d..93b21538a 100644
--- a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift
+++ b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift
@@ -1,7 +1,7 @@
import Foundation
import JSONRPC
-public struct ResponseSubscriptionPayload {
+public struct ResponseSubscriptionPayload: SubscriptionPayload {
public let id: RPCID
public let topic: String
public let request: Request
diff --git a/Sources/WalletConnectNetworking/SubscriptionPayload.swift b/Sources/WalletConnectNetworking/SubscriptionPayload.swift
new file mode 100644
index 000000000..ad5e60eab
--- /dev/null
+++ b/Sources/WalletConnectNetworking/SubscriptionPayload.swift
@@ -0,0 +1,7 @@
+import Foundation
+import JSONRPC
+
+public protocol SubscriptionPayload {
+ var id: RPCID { get }
+ var topic: String { get }
+}
diff --git a/Sources/WalletConnectPairing/Pair.swift b/Sources/WalletConnectPairing/Pair.swift
new file mode 100644
index 000000000..b016ed6e6
--- /dev/null
+++ b/Sources/WalletConnectPairing/Pair.swift
@@ -0,0 +1,43 @@
+import Foundation
+import WalletConnectNetworking
+import Combine
+
+public class Pair {
+
+ /// Pairing client instance
+ public static var instance: PairingInteracting {
+ return Pair.client
+ }
+
+ public static var registerer: PairingRegisterer {
+ return Pair.client
+ }
+
+ public static var metadata: AppMetadata {
+ guard let metadata = config?.metadata else {
+ fatalError("Error - you must configure metadata with Pair.configure(metadata:)")
+ }
+ return metadata
+ }
+
+ private static var config: Config?
+
+ private init() { }
+
+ /// Pairing instance config method
+ /// - Parameters:
+ /// - metadata: App metadata
+ static public func configure(metadata: AppMetadata) {
+ Pair.config = Pair.Config(metadata: metadata)
+ }
+}
+
+private extension Pair {
+
+ static var client: PairingClient = {
+ guard let config = Pair.config else {
+ fatalError("Error - you must call Pair.configure(_:) before accessing the shared instance.")
+ }
+ return PairingClientFactory.create(networkingClient: Networking.interactor)
+ }()
+}
diff --git a/Sources/WalletConnectPairing/PairConfig.swift b/Sources/WalletConnectPairing/PairConfig.swift
new file mode 100644
index 000000000..38f7e35ed
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairConfig.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+extension Pair {
+
+ public struct Config {
+ public let metadata: AppMetadata
+
+ public init(metadata: AppMetadata) {
+ self.metadata = metadata
+ }
+ }
+}
diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift
new file mode 100644
index 000000000..2b115c4e5
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairingClient.swift
@@ -0,0 +1,129 @@
+import Foundation
+import WalletConnectUtils
+import WalletConnectRelay
+import WalletConnectNetworking
+import Combine
+import JSONRPC
+
+public class PairingClient: PairingRegisterer, PairingInteracting {
+ public var pingResponsePublisher: AnyPublisher<(String), Never> {
+ pingResponsePublisherSubject.eraseToAnyPublisher()
+ }
+ public let socketConnectionStatusPublisher: AnyPublisher
+
+ private let walletPairService: WalletPairService
+ private let appPairService: AppPairService
+ private let appPairActivateService: AppPairActivationService
+ private let appUpdateMetadataService: AppUpdateMetadataService
+ private var pingResponsePublisherSubject = PassthroughSubject()
+ private let logger: ConsoleLogging
+ private let pingService: PairingPingService
+ private let networkingInteractor: NetworkInteracting
+ private let pairingRequestsSubscriber: PairingRequestsSubscriber
+ private let pairingsProvider: PairingsProvider
+ private let deletePairingService: DeletePairingService
+ private let resubscribeService: ResubscribeService
+ private let expirationService: ExpirationService
+
+ private let cleanupService: CleanupService
+
+ init(appPairService: AppPairService,
+ networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ walletPairService: WalletPairService,
+ deletePairingService: DeletePairingService,
+ resubscribeService: ResubscribeService,
+ expirationService: ExpirationService,
+ pairingRequestsSubscriber: PairingRequestsSubscriber,
+ appPairActivateService: AppPairActivationService,
+ appUpdateMetadataService: AppUpdateMetadataService,
+ cleanupService: CleanupService,
+ pingService: PairingPingService,
+ socketConnectionStatusPublisher: AnyPublisher,
+ pairingsProvider: PairingsProvider
+ ) {
+ self.appPairService = appPairService
+ self.walletPairService = walletPairService
+ self.networkingInteractor = networkingInteractor
+ self.socketConnectionStatusPublisher = socketConnectionStatusPublisher
+ self.logger = logger
+ self.deletePairingService = deletePairingService
+ self.appPairActivateService = appPairActivateService
+ self.appUpdateMetadataService = appUpdateMetadataService
+ self.resubscribeService = resubscribeService
+ self.expirationService = expirationService
+ self.cleanupService = cleanupService
+ self.pingService = pingService
+ self.pairingRequestsSubscriber = pairingRequestsSubscriber
+ self.pairingsProvider = pairingsProvider
+ setUpPublishers()
+ setUpExpiration()
+ }
+
+ private func setUpPublishers() {
+ pingService.onResponse = { [unowned self] topic in
+ pingResponsePublisherSubject.send(topic)
+ }
+ }
+
+ private func setUpExpiration() {
+ expirationService.setupExpirationHandling()
+ }
+
+ /// For wallet to establish a pairing
+ /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests.
+ /// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking.
+ ///
+ /// Throws Error:
+ /// - When URI is invalid format or missing params
+ /// - When topic is already in use
+ public func pair(uri: WalletConnectURI) async throws {
+ try await walletPairService.pair(uri)
+ }
+
+ public func create() async throws -> WalletConnectURI {
+ return try await appPairService.create()
+ }
+
+ public func activate(pairingTopic: String) {
+ appPairActivateService.activate(for: pairingTopic)
+ }
+
+ public func updateMetadata(_ topic: String, metadata: AppMetadata) {
+ appUpdateMetadataService.updatePairingMetadata(topic: topic, metadata: metadata)
+ }
+
+ public func getPairings() -> [Pairing] {
+ pairingsProvider.getPairings()
+ }
+
+ public func getPairing(for topic: String) throws -> Pairing {
+ try pairingsProvider.getPairing(for: topic)
+ }
+
+ public func ping(topic: String) async throws {
+ try await pingService.ping(topic: topic)
+ }
+
+ public func disconnect(topic: String) async throws {
+ try await deletePairingService.delete(topic: topic)
+ }
+
+ public func validatePairingExistance(_ topic: String) throws {
+ _ = try pairingsProvider.getPairing(for: topic)
+ }
+
+ public func register(method: ProtocolMethod) -> AnyPublisher, Never> {
+ logger.debug("Pairing Client - registering for \(method.method)")
+ return pairingRequestsSubscriber.subscribeForRequest(method)
+ }
+
+#if DEBUG
+ /// Delete all stored data such as: pairings, keys
+ ///
+ /// - Note: Doesn't unsubscribe from topics
+ public func cleanup() throws {
+ try cleanupService.cleanup()
+ }
+#endif
+}
diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift
new file mode 100644
index 000000000..9926292e0
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairingClientFactory.swift
@@ -0,0 +1,48 @@
+import Foundation
+import WalletConnectRelay
+import WalletConnectUtils
+import WalletConnectKMS
+import WalletConnectNetworking
+
+public struct PairingClientFactory {
+
+ public static func create(networkingClient: NetworkingInteractor) -> PairingClient {
+ let logger = ConsoleLogger(loggingLevel: .off)
+ let keyValueStorage = UserDefaults.standard
+ let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
+ return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient)
+ }
+
+ public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor) -> PairingClient {
+ let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue)))
+ let kms = KeyManagementService(keychain: keychainStorage)
+ let appPairService = AppPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore)
+ let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore)
+ let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingClient, pairingStorage: pairingStore, logger: logger)
+ let pairingsProvider = PairingsProvider(pairingStorage: pairingStore)
+ let cleanupService = CleanupService(pairingStore: pairingStore, kms: kms)
+ let deletePairingService = DeletePairingService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, logger: logger)
+ let pingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger)
+ let appPairActivateService = AppPairActivationService(pairingStorage: pairingStore, logger: logger)
+ let appUpdateMetadataService = AppUpdateMetadataService(pairingStore: pairingStore)
+ let expirationService = ExpirationService(pairingStorage: pairingStore, networkInteractor: networkingClient, kms: kms)
+ let resubscribeService = ResubscribeService(networkInteractor: networkingClient, pairingStorage: pairingStore)
+
+ return PairingClient(
+ appPairService: appPairService,
+ networkingInteractor: networkingClient,
+ logger: logger,
+ walletPairService: walletPairService,
+ deletePairingService: deletePairingService,
+ resubscribeService: resubscribeService,
+ expirationService: expirationService,
+ pairingRequestsSubscriber: pairingRequestsSubscriber,
+ appPairActivateService: appPairActivateService,
+ appUpdateMetadataService: appUpdateMetadataService,
+ cleanupService: cleanupService,
+ pingService: pingService,
+ socketConnectionStatusPublisher: networkingClient.socketConnectionStatusPublisher,
+ pairingsProvider: pairingsProvider
+ )
+ }
+}
diff --git a/Sources/WalletConnectPairing/PairingInteracting.swift b/Sources/WalletConnectPairing/PairingInteracting.swift
new file mode 100644
index 000000000..90b85bd51
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairingInteracting.swift
@@ -0,0 +1,18 @@
+import Foundation
+import WalletConnectUtils
+
+public protocol PairingInteracting {
+ func pair(uri: WalletConnectURI) async throws
+
+ func create() async throws -> WalletConnectURI
+
+ func getPairings() -> [Pairing]
+
+ func getPairing(for topic: String) throws -> Pairing
+
+ func ping(topic: String) async throws
+
+ func disconnect(topic: String) async throws
+
+ func cleanup() throws
+}
diff --git a/Sources/WalletConnectPairing/PairingProtocolMethod.swift b/Sources/WalletConnectPairing/PairingProtocolMethod.swift
deleted file mode 100644
index a6f9d14cf..000000000
--- a/Sources/WalletConnectPairing/PairingProtocolMethod.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import Foundation
-import WalletConnectNetworking
-
-enum PairingProtocolMethod: String, ProtocolMethod {
- case ping = "wc_pairingPing"
-
- var method: String {
- return self.rawValue
- }
-
- var requestTag: Int {
- return 1002
- }
-
- var responseTag: Int {
- return 1003
- }
-}
diff --git a/Sources/WalletConnectPairing/PairingRegisterer.swift b/Sources/WalletConnectPairing/PairingRegisterer.swift
new file mode 100644
index 000000000..f6b136d74
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairingRegisterer.swift
@@ -0,0 +1,14 @@
+import Foundation
+import WalletConnectNetworking
+import Combine
+import JSONRPC
+
+public protocol PairingRegisterer {
+ func register(
+ method: ProtocolMethod
+ ) -> AnyPublisher, Never>
+
+ func activate(pairingTopic: String)
+ func validatePairingExistance(_ topic: String) throws
+ func updateMetadata(_ topic: String, metadata: AppMetadata)
+}
diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift
new file mode 100644
index 000000000..f10808792
--- /dev/null
+++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift
@@ -0,0 +1,43 @@
+import Foundation
+import Combine
+import WalletConnectUtils
+import WalletConnectNetworking
+import JSONRPC
+
+public class PairingRequestsSubscriber {
+ private let networkingInteractor: NetworkInteracting
+ private let pairingStorage: PairingStorage
+ private var publishers = Set()
+ private var registeredProtocolMethods = SetStore(label: "com.walletconnect.sdk.pairing.registered_protocol_methods")
+ private let pairingProtocolMethods = PairingProtocolMethod.allCases.map { $0.method }
+ private let logger: ConsoleLogging
+
+ init(networkingInteractor: NetworkInteracting,
+ pairingStorage: PairingStorage,
+ logger: ConsoleLogging) {
+ self.networkingInteractor = networkingInteractor
+ self.pairingStorage = pairingStorage
+ self.logger = logger
+ handleUnregisteredRequests()
+ }
+
+ func subscribeForRequest(_ protocolMethod: ProtocolMethod) -> AnyPublisher, Never> {
+ registeredProtocolMethods.insert(protocolMethod.method)
+ return networkingInteractor.requestSubscription(on: protocolMethod).eraseToAnyPublisher()
+ }
+
+ func handleUnregisteredRequests() {
+ networkingInteractor.requestPublisher
+ .filter { [unowned self] in !pairingProtocolMethods.contains($0.request.method)}
+ .filter { [unowned self] in pairingStorage.hasPairing(forTopic: $0.topic)}
+ .filter { [unowned self] in !registeredProtocolMethods.contains($0.request.method)}
+ .sink { [unowned self] topic, request in
+ Task(priority: .high) {
+ let protocolMethod = UnsupportedProtocolMethod(method: request.method)
+ logger.debug("PairingRequestsSubscriber: responding unregistered request method")
+ try await networkingInteractor.respondError(topic: topic, requestId: request.id!, protocolMethod: protocolMethod, reason: PairError.methodUnsupported)
+ }
+ }.store(in: &publishers)
+ }
+
+}
diff --git a/Sources/WalletConnectPairing/Services/App/AppPairActivationService.swift b/Sources/WalletConnectPairing/Services/App/AppPairActivationService.swift
new file mode 100644
index 000000000..5b96e3908
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/App/AppPairActivationService.swift
@@ -0,0 +1,26 @@
+import Foundation
+import Combine
+import WalletConnectNetworking
+import WalletConnectUtils
+
+final class AppPairActivationService {
+ private let pairingStorage: PairingStorage
+ private let logger: ConsoleLogging
+
+ init(pairingStorage: PairingStorage, logger: ConsoleLogging) {
+ self.pairingStorage = pairingStorage
+ self.logger = logger
+ }
+
+ func activate(for topic: String) {
+ guard var pairing = pairingStorage.getPairing(forTopic: topic) else {
+ return logger.error("Pairing not found for topic: \(topic)")
+ }
+ if !pairing.active {
+ pairing.activate()
+ } else {
+ try? pairing.updateExpiry()
+ }
+ pairingStorage.setPairing(pairing)
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/App/AppPairService.swift b/Sources/WalletConnectPairing/Services/App/AppPairService.swift
new file mode 100644
index 000000000..fb51d8e9b
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/App/AppPairService.swift
@@ -0,0 +1,26 @@
+import Foundation
+import WalletConnectKMS
+import WalletConnectNetworking
+import WalletConnectUtils
+
+actor AppPairService {
+ private let networkingInteractor: NetworkInteracting
+ private let kms: KeyManagementServiceProtocol
+ private let pairingStorage: WCPairingStorage
+
+ init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, pairingStorage: WCPairingStorage) {
+ self.networkingInteractor = networkingInteractor
+ self.kms = kms
+ self.pairingStorage = pairingStorage
+ }
+
+ func create() async throws -> WalletConnectURI {
+ let topic = String.generateTopic()
+ try await networkingInteractor.subscribe(topic: topic)
+ let symKey = try! kms.createSymmetricKey(topic)
+ let pairing = WCPairing(topic: topic)
+ let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay)
+ pairingStorage.setPairing(pairing)
+ return uri
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/App/AppUpdateMetadataService.swift b/Sources/WalletConnectPairing/Services/App/AppUpdateMetadataService.swift
new file mode 100644
index 000000000..4901e567a
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/App/AppUpdateMetadataService.swift
@@ -0,0 +1,16 @@
+import Foundation
+
+final class AppUpdateMetadataService {
+
+ private let pairingStore: WCPairingStorage
+
+ init(pairingStore: WCPairingStorage) {
+ self.pairingStore = pairingStore
+ }
+
+ func updatePairingMetadata(topic: String, metadata: AppMetadata) {
+ guard var pairing = pairingStore.getPairing(forTopic: topic) else { return }
+ pairing.peerMetadata = metadata
+ pairingStore.setPairing(pairing)
+ }
+}
diff --git a/Sources/Auth/Services/Common/CleanupService.swift b/Sources/WalletConnectPairing/Services/Common/CleanupService.swift
similarity index 94%
rename from Sources/Auth/Services/Common/CleanupService.swift
rename to Sources/WalletConnectPairing/Services/Common/CleanupService.swift
index 6a5a01334..2ee49d54d 100644
--- a/Sources/Auth/Services/Common/CleanupService.swift
+++ b/Sources/WalletConnectPairing/Services/Common/CleanupService.swift
@@ -1,7 +1,6 @@
import Foundation
import WalletConnectKMS
import WalletConnectUtils
-import WalletConnectPairing
final class CleanupService {
diff --git a/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift b/Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift
similarity index 73%
rename from Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift
rename to Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift
index a6324481e..05369d56c 100644
--- a/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift
+++ b/Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift
@@ -1,7 +1,8 @@
import Foundation
+import JSONRPC
import WalletConnectKMS
import WalletConnectUtils
-import WalletConnectPairing
+import WalletConnectNetworking
class DeletePairingService {
private let networkingInteractor: NetworkInteracting
@@ -20,10 +21,11 @@ class DeletePairingService {
}
func delete(topic: String) async throws {
- let reasonCode = ReasonCode.userDisconnected
- let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message)
+ let reason = ReasonCode.userDisconnected
+ let protocolMethod = PairingProtocolMethod.delete
logger.debug("Will delete pairing for reason: message: \(reason.message) code: \(reason.code)")
- try await networkingInteractor.request(.wcSessionDelete(reason), onTopic: topic)
+ let request = RPCRequest(method: protocolMethod.method, params: reason)
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
pairingStorage.delete(topic: topic)
kms.deleteSymmetricKey(for: topic)
networkingInteractor.unsubscribe(topic: topic)
diff --git a/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift
new file mode 100644
index 000000000..d973e093a
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/Common/ExpirationService.swift
@@ -0,0 +1,22 @@
+import Foundation
+import WalletConnectNetworking
+import WalletConnectKMS
+
+final class ExpirationService {
+ private let pairingStorage: WCPairingStorage
+ private let networkInteractor: NetworkInteracting
+ private let kms: KeyManagementServiceProtocol
+
+ init(pairingStorage: WCPairingStorage, networkInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol) {
+ self.pairingStorage = pairingStorage
+ self.networkInteractor = networkInteractor
+ self.kms = kms
+ }
+
+ func setupExpirationHandling() {
+ pairingStorage.onPairingExpiration = { [weak self] pairing in
+ self?.kms.deleteSymmetricKey(for: pairing.topic)
+ self?.networkInteractor.unsubscribe(topic: pairing.topic)
+ }
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift
new file mode 100644
index 000000000..aa087a3c0
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/Common/PairingsProvider.swift
@@ -0,0 +1,24 @@
+import Foundation
+
+class PairingsProvider {
+ enum Errors: Error {
+ case noPairingMatchingTopic
+ }
+ private let pairingStorage: WCPairingStorage
+
+ public init(pairingStorage: WCPairingStorage) {
+ self.pairingStorage = pairingStorage
+ }
+
+ func getPairings() -> [Pairing] {
+ pairingStorage.getAll()
+ .map {Pairing($0)}
+ }
+
+ func getPairing(for topic: String) throws -> Pairing {
+ guard let pairing = pairingStorage.getPairing(forTopic: topic) else {
+ throw Errors.noPairingMatchingTopic
+ }
+ return Pairing(pairing)
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/PairingPingService.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PairingPingService.swift
similarity index 50%
rename from Sources/WalletConnectPairing/Services/PairingPingService.swift
rename to Sources/WalletConnectPairing/Services/Common/Ping/PairingPingService.swift
index 6baef98e0..8cd852eb9 100644
--- a/Sources/WalletConnectPairing/Services/PairingPingService.swift
+++ b/Sources/WalletConnectPairing/Services/Common/Ping/PairingPingService.swift
@@ -1,13 +1,14 @@
-import WalletConnectUtils
import Foundation
+import WalletConnectUtils
import WalletConnectNetworking
public class PairingPingService {
+ private let pairingStorage: WCPairingStorage
private let pingRequester: PingRequester
private let pingResponder: PingResponder
private let pingResponseSubscriber: PingResponseSubscriber
- public var onResponse: ((String)->())? {
+ public var onResponse: ((String)->Void)? {
get {
return pingResponseSubscriber.onResponse
}
@@ -20,13 +21,15 @@ public class PairingPingService {
pairingStorage: WCPairingStorage,
networkingInteractor: NetworkInteracting,
logger: ConsoleLogging) {
- pingRequester = PingRequester(pairingStorage: pairingStorage, networkingInteractor: networkingInteractor)
- pingResponder = PingResponder(networkingInteractor: networkingInteractor, logger: logger)
- pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, logger: logger)
+ let protocolMethod = PairingProtocolMethod.ping
+ self.pairingStorage = pairingStorage
+ self.pingRequester = PingRequester(networkingInteractor: networkingInteractor, method: protocolMethod)
+ self.pingResponder = PingResponder(networkingInteractor: networkingInteractor, method: protocolMethod, logger: logger)
+ self.pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, method: protocolMethod, logger: logger)
}
public func ping(topic: String) async throws {
+ guard pairingStorage.hasPairing(forTopic: topic) else { return }
try await pingRequester.ping(topic: topic)
}
-
}
diff --git a/Sources/WalletConnectPairing/Services/Common/Ping/PingRequester.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingRequester.swift
new file mode 100644
index 000000000..f5007c9c6
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/Common/Ping/PingRequester.swift
@@ -0,0 +1,18 @@
+import Foundation
+import JSONRPC
+import WalletConnectNetworking
+
+public class PingRequester {
+ private let method: ProtocolMethod
+ private let networkingInteractor: NetworkInteracting
+
+ public init(networkingInteractor: NetworkInteracting, method: ProtocolMethod) {
+ self.method = method
+ self.networkingInteractor = networkingInteractor
+ }
+
+ public func ping(topic: String) async throws {
+ let request = RPCRequest(method: method.method, params: PairingPingParams())
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: method)
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/PingResponder.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponder.swift
similarity index 70%
rename from Sources/WalletConnectPairing/Services/PingResponder.swift
rename to Sources/WalletConnectPairing/Services/Common/Ping/PingResponder.swift
index 9ed8c0806..60835c29c 100644
--- a/Sources/WalletConnectPairing/Services/PingResponder.swift
+++ b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponder.swift
@@ -2,24 +2,27 @@ import Combine
import WalletConnectUtils
import WalletConnectNetworking
-class PingResponder {
+public class PingResponder {
private let networkingInteractor: NetworkInteracting
+ private let method: ProtocolMethod
private let logger: ConsoleLogging
private var publishers = [AnyCancellable]()
- init(networkingInteractor: NetworkInteracting,
+ public init(networkingInteractor: NetworkInteracting,
+ method: ProtocolMethod,
logger: ConsoleLogging) {
self.networkingInteractor = networkingInteractor
+ self.method = method
self.logger = logger
subscribePingRequests()
}
private func subscribePingRequests() {
- networkingInteractor.requestSubscription(on: PairingProtocolMethod.ping)
+ networkingInteractor.requestSubscription(on: method)
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
logger.debug("Responding for pairing ping")
Task(priority: .high) {
- try? await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: PairingProtocolMethod.ping.responseTag)
+ try? await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: method)
}
}
.store(in: &publishers)
diff --git a/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponseSubscriber.swift
similarity index 51%
rename from Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift
rename to Sources/WalletConnectPairing/Services/Common/Ping/PingResponseSubscriber.swift
index d2ce74a89..7da5c8a56 100644
--- a/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift
+++ b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponseSubscriber.swift
@@ -2,24 +2,35 @@ import Combine
import WalletConnectUtils
import WalletConnectNetworking
-class PingResponseSubscriber {
+public class PingResponseSubscriber {
private let networkingInteractor: NetworkInteracting
+ private let method: ProtocolMethod
private let logger: ConsoleLogging
private var publishers = [AnyCancellable]()
- var onResponse: ((String)->())?
+ public var onResponse: ((String)->Void)?
- init(networkingInteractor: NetworkInteracting,
+ public init(networkingInteractor: NetworkInteracting,
+ method: ProtocolMethod,
logger: ConsoleLogging) {
self.networkingInteractor = networkingInteractor
+ self.method = method
self.logger = logger
subscribePingResponses()
}
private func subscribePingResponses() {
- networkingInteractor.responseSubscription(on: PairingProtocolMethod.ping)
+ networkingInteractor.responseSubscription(on: method)
.sink { [unowned self] (payload: ResponseSubscriptionPayload) in
onResponse?(payload.topic)
+
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(
+ topic: payload.topic,
+ requestId: payload.id,
+ protocolMethod: method
+ )
+ }
}
.store(in: &publishers)
}
diff --git a/Sources/WalletConnectPairing/Services/Common/ResubscribeService.swift b/Sources/WalletConnectPairing/Services/Common/ResubscribeService.swift
new file mode 100644
index 000000000..0c5c091fd
--- /dev/null
+++ b/Sources/WalletConnectPairing/Services/Common/ResubscribeService.swift
@@ -0,0 +1,29 @@
+import Foundation
+import Combine
+import WalletConnectNetworking
+
+final class ResubscribeService {
+
+ private var publishers = Set()
+
+ private let networkInteractor: NetworkInteracting
+ private let pairingStorage: PairingStorage
+
+ init(networkInteractor: NetworkInteracting, pairingStorage: PairingStorage) {
+ self.networkInteractor = networkInteractor
+ self.pairingStorage = pairingStorage
+ setUpResubscription()
+ }
+
+ func setUpResubscription() {
+ networkInteractor.socketConnectionStatusPublisher
+ .sink { [unowned self] status in
+ guard status == .connected else { return }
+ pairingStorage.getAll()
+ .forEach { pairing in
+ Task(priority: .high) { try await networkInteractor.subscribe(topic: pairing.topic) }
+ }
+ }
+ .store(in: &publishers)
+ }
+}
diff --git a/Sources/WalletConnectPairing/Services/PingRequester.swift b/Sources/WalletConnectPairing/Services/PingRequester.swift
deleted file mode 100644
index f936a8c9c..000000000
--- a/Sources/WalletConnectPairing/Services/PingRequester.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import Foundation
-import WalletConnectNetworking
-import JSONRPC
-
-class PingRequester {
- private let pairingStorage: WCPairingStorage
- private let networkingInteractor: NetworkInteracting
-
- init(pairingStorage: WCPairingStorage, networkingInteractor: NetworkInteracting) {
- self.pairingStorage = pairingStorage
- self.networkingInteractor = networkingInteractor
- }
-
- func ping(topic: String) async throws {
- guard pairingStorage.hasPairing(forTopic: topic) else { return }
- let request = RPCRequest(method: PairingProtocolMethod.ping.rawValue, params: PairingPingParams())
- try await networkingInteractor.request(request, topic: topic, tag: PairingProtocolMethod.ping.requestTag)
- }
-}
diff --git a/Sources/Auth/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
similarity index 88%
rename from Sources/Auth/Services/Wallet/WalletPairService.swift
rename to Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
index 7868c01a4..97b168564 100644
--- a/Sources/Auth/Services/Wallet/WalletPairService.swift
+++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift
@@ -1,15 +1,15 @@
import Foundation
import WalletConnectKMS
-import WalletConnectPairing
import WalletConnectNetworking
+import WalletConnectUtils
actor WalletPairService {
enum Errors: Error {
case pairingAlreadyExist
}
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
+ let networkingInteractor: NetworkInteracting
+ let kms: KeyManagementServiceProtocol
private let pairingStorage: WCPairingStorage
init(networkingInteractor: NetworkInteracting,
diff --git a/Sources/Auth/StorageDomainIdentifiers.swift b/Sources/WalletConnectPairing/StorageDomainIdentifiers.swift
similarity index 62%
rename from Sources/Auth/StorageDomainIdentifiers.swift
rename to Sources/WalletConnectPairing/StorageDomainIdentifiers.swift
index ed7156c67..c328999c6 100644
--- a/Sources/Auth/StorageDomainIdentifiers.swift
+++ b/Sources/WalletConnectPairing/StorageDomainIdentifiers.swift
@@ -1,6 +1,5 @@
import Foundation
enum StorageDomainIdentifiers: String {
- case jsonRpcHistory = "com.walletconnect.sdk.wc_jsonRpcHistoryRecord"
case pairings = "com.walletconnect.sdk.pairingSequences"
}
diff --git a/Sources/WalletConnectPairing/Types/AppMetadata.swift b/Sources/WalletConnectPairing/Types/AppMetadata.swift
index 531bd5fe3..5a039740d 100644
--- a/Sources/WalletConnectPairing/Types/AppMetadata.swift
+++ b/Sources/WalletConnectPairing/Types/AppMetadata.swift
@@ -11,6 +11,26 @@ import Foundation
*/
public struct AppMetadata: Codable, Equatable {
+ public struct Redirect: Codable, Equatable {
+ /// Native deeplink URL string.
+ public let native: String?
+
+ /// Universal link URL string.
+ public let universal: String?
+
+ /**
+ Creates a new Redirect object with the specified information.
+
+ - parameters:
+ - native: Native deeplink URL string.
+ - universal: Universal link URL string.
+ */
+ public init(native: String?, universal: String?) {
+ self.native = native
+ self.universal = universal
+ }
+ }
+
/// The name of the app.
public let name: String
@@ -23,6 +43,9 @@ public struct AppMetadata: Codable, Equatable {
/// An array of URL strings pointing to the icon assets on the web.
public let icons: [String]
+ /// Redirect links which could be manually used on wallet side
+ public let redirect: Redirect?
+
/**
Creates a new metadata object with the specified information.
@@ -32,10 +55,11 @@ public struct AppMetadata: Codable, Equatable {
- url: The URL string that identifies the official domain of the app.
- icons: An array of URL strings pointing to the icon assets on the web.
*/
- public init(name: String, description: String, url: String, icons: [String]) {
+ public init(name: String, description: String, url: String, icons: [String], redirect: Redirect? = nil) {
self.name = name
self.description = description
self.url = url
self.icons = icons
+ self.redirect = redirect
}
}
diff --git a/Sources/WalletConnectPairing/Types/PairError.swift b/Sources/WalletConnectPairing/Types/PairError.swift
new file mode 100644
index 000000000..419edd9b3
--- /dev/null
+++ b/Sources/WalletConnectPairing/Types/PairError.swift
@@ -0,0 +1,26 @@
+import WalletConnectNetworking
+
+public enum PairError: Codable, Equatable, Error, Reason {
+ case methodUnsupported
+
+ public init?(code: Int) {
+ switch code {
+ case Self.methodUnsupported.code:
+ self = .methodUnsupported
+ default:
+ return nil
+ }
+ }
+
+ public var code: Int {
+ switch self {
+ case .methodUnsupported:
+ return 10001
+ }
+ }
+
+ public var message: String {
+ return "Method Unsupported"
+ }
+
+}
diff --git a/Sources/WalletConnectPairing/Types/Pairing.swift b/Sources/WalletConnectPairing/Types/Pairing.swift
index f9886d6a2..03ed01a41 100644
--- a/Sources/WalletConnectPairing/Types/Pairing.swift
+++ b/Sources/WalletConnectPairing/Types/Pairing.swift
@@ -12,4 +12,10 @@ public struct Pairing {
self.peer = peer
self.expiryDate = expiryDate
}
+
+ init(_ pairing: WCPairing) {
+ self.topic = pairing.topic
+ self.peer = pairing.peerMetadata
+ self.expiryDate = pairing.expiryDate
+ }
}
diff --git a/Sources/WalletConnectPairing/Types/PairingProtocolMethod.swift b/Sources/WalletConnectPairing/Types/PairingProtocolMethod.swift
new file mode 100644
index 000000000..f121a62a9
--- /dev/null
+++ b/Sources/WalletConnectPairing/Types/PairingProtocolMethod.swift
@@ -0,0 +1,43 @@
+import Foundation
+import WalletConnectNetworking
+
+enum PairingProtocolMethod: CaseIterable, ProtocolMethod {
+ case ping
+ case delete
+
+ var method: String {
+ switch self {
+ case .ping:
+ return "wc_pairingPing"
+ case .delete:
+ return "wc_pairingDelete"
+ }
+ }
+
+ var requestConfig: RelayConfig {
+ switch self {
+ case .ping:
+ return RelayConfig(tag: 1002, prompt: false, ttl: 30)
+ case .delete:
+ return RelayConfig(tag: 1003, prompt: false, ttl: 30)
+ }
+ }
+
+ var responseConfig: RelayConfig {
+ switch self {
+ case .ping:
+ return RelayConfig(tag: 1003, prompt: false, ttl: 30)
+ case .delete:
+ return RelayConfig(tag: 1001, prompt: false, ttl: 86400)
+ }
+ }
+}
+
+struct UnsupportedProtocolMethod: ProtocolMethod {
+ let method: String
+
+ // TODO - spec tag
+ let requestConfig = RelayConfig(tag: 0, prompt: false, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 0, prompt: false, ttl: 86400)
+}
diff --git a/Sources/WalletConnectPairing/Types/ReasonCode.swift b/Sources/WalletConnectPairing/Types/ReasonCode.swift
new file mode 100644
index 000000000..e5f633835
--- /dev/null
+++ b/Sources/WalletConnectPairing/Types/ReasonCode.swift
@@ -0,0 +1,14 @@
+import Foundation
+import WalletConnectNetworking
+
+enum ReasonCode: Reason, Codable {
+ case userDisconnected
+
+ var code: Int {
+ return 6000
+ }
+
+ var message: String {
+ return "User Disconnected"
+ }
+}
diff --git a/Sources/WalletConnectPush/ProposalResponseSubscriber.swift b/Sources/WalletConnectPush/ProposalResponseSubscriber.swift
new file mode 100644
index 000000000..8a2e0b750
--- /dev/null
+++ b/Sources/WalletConnectPush/ProposalResponseSubscriber.swift
@@ -0,0 +1,33 @@
+import Foundation
+import Combine
+import JSONRPC
+import WalletConnectUtils
+import WalletConnectKMS
+import WalletConnectNetworking
+import WalletConnectPairing
+
+class ProposalResponseSubscriber {
+ private let networkingInteractor: NetworkInteracting
+ private let kms: KeyManagementServiceProtocol
+ private let logger: ConsoleLogging
+ private var publishers = [AnyCancellable]()
+ var onResponse: ((_ id: RPCID, _ result: Result) -> Void)?
+
+ init(networkingInteractor: NetworkInteracting,
+ kms: KeyManagementServiceProtocol,
+ logger: ConsoleLogging) {
+ self.networkingInteractor = networkingInteractor
+ self.kms = kms
+ self.logger = logger
+ subscribeForProposalErrors()
+ }
+
+ private func subscribeForProposalErrors() {
+ let protocolMethod = PushProposeProtocolMethod()
+ networkingInteractor.responseErrorSubscription(on: protocolMethod)
+ .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in
+ guard let error = PairError(code: payload.error.code) else { return }
+ onResponse?(payload.id, .failure(error))
+ }.store(in: &publishers)
+ }
+}
diff --git a/Sources/WalletConnectPush/PushClient.swift b/Sources/WalletConnectPush/PushClient.swift
new file mode 100644
index 000000000..4e4d170bd
--- /dev/null
+++ b/Sources/WalletConnectPush/PushClient.swift
@@ -0,0 +1,63 @@
+import Foundation
+import JSONRPC
+import Combine
+import WalletConnectKMS
+import WalletConnectUtils
+import WalletConnectNetworking
+import WalletConnectPairing
+
+public class PushClient {
+
+ private var publishers = Set()
+
+ private let requestPublisherSubject = PassthroughSubject<(topic: String, params: PushRequestParams), Never>()
+ private let responsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>()
+
+ public var proposalPublisher: AnyPublisher<(topic: String, params: PushRequestParams), Never> {
+ requestPublisherSubject.eraseToAnyPublisher()
+ }
+ public var responsePublisher: AnyPublisher<(id: RPCID, result: Result), Never> {
+ responsePublisherSubject.eraseToAnyPublisher()
+ }
+
+ public let logger: ConsoleLogging
+
+ private let pushProposer: PushProposer
+ private let networkInteractor: NetworkInteracting
+ private let pairingRegisterer: PairingRegisterer
+ private let proposalResponseSubscriber: ProposalResponseSubscriber
+
+ init(networkInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ kms: KeyManagementServiceProtocol,
+ pushProposer: PushProposer,
+ proposalResponseSubscriber: ProposalResponseSubscriber,
+ pairingRegisterer: PairingRegisterer) {
+ self.networkInteractor = networkInteractor
+ self.logger = logger
+ self.pushProposer = pushProposer
+ self.pairingRegisterer = pairingRegisterer
+ self.proposalResponseSubscriber = proposalResponseSubscriber
+ setupSubscriptions()
+ }
+
+ public func propose(topic: String) async throws {
+ try await pushProposer.request(topic: topic, params: PushRequestParams())
+ }
+}
+
+private extension PushClient {
+
+ func setupSubscriptions() {
+ let protocolMethod = PushProposeProtocolMethod()
+
+ pairingRegisterer.register(method: protocolMethod)
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ requestPublisherSubject.send((topic: payload.topic, params: payload.request))
+ }.store(in: &publishers)
+
+ proposalResponseSubscriber.onResponse = {[unowned self] (id, result) in
+ responsePublisherSubject.send((id, result))
+ }
+ }
+}
diff --git a/Sources/WalletConnectPush/PushClientFactory.swift b/Sources/WalletConnectPush/PushClientFactory.swift
new file mode 100644
index 000000000..f9715f86d
--- /dev/null
+++ b/Sources/WalletConnectPush/PushClientFactory.swift
@@ -0,0 +1,23 @@
+import Foundation
+import WalletConnectRelay
+import WalletConnectUtils
+import WalletConnectKMS
+import WalletConnectNetworking
+import WalletConnectPairing
+
+public struct PushClientFactory {
+
+ static public func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor, pairingClient: PairingClient) -> PushClient {
+ let kms = KeyManagementService(keychain: keychainStorage)
+ let pushProposer = PushProposer(networkingInteractor: networkingClient, kms: kms, logger: logger)
+ let proposalResponseSubscriber = ProposalResponseSubscriber(networkingInteractor: networkingClient, kms: kms, logger: logger)
+
+ return PushClient(
+ networkInteractor: networkingClient,
+ logger: logger,
+ kms: kms,
+ pushProposer: pushProposer, proposalResponseSubscriber: proposalResponseSubscriber,
+ pairingRegisterer: pairingClient
+ )
+ }
+}
diff --git a/Sources/WalletConnectPush/PushProposeProtocolMethod.swift b/Sources/WalletConnectPush/PushProposeProtocolMethod.swift
new file mode 100644
index 000000000..60d5ee6bf
--- /dev/null
+++ b/Sources/WalletConnectPush/PushProposeProtocolMethod.swift
@@ -0,0 +1,14 @@
+import Foundation
+import WalletConnectNetworking
+
+struct PushProposeProtocolMethod: ProtocolMethod {
+ let method: String = "wc_pushPropose"
+
+ let requestConfig: RelayConfig = RelayConfig(tag: 111, prompt: true, ttl: 300)
+
+ let responseConfig: RelayConfig = RelayConfig(tag: 112, prompt: true, ttl: 300)
+}
+
+public struct PushRequestParams: Codable {}
+
+public struct PushResponseParams: Codable, Equatable {}
diff --git a/Sources/WalletConnectPush/PushProposer.swift b/Sources/WalletConnectPush/PushProposer.swift
new file mode 100644
index 000000000..6a08305fe
--- /dev/null
+++ b/Sources/WalletConnectPush/PushProposer.swift
@@ -0,0 +1,27 @@
+import Foundation
+import Combine
+import JSONRPC
+import WalletConnectUtils
+import WalletConnectKMS
+import WalletConnectNetworking
+
+class PushProposer {
+ private let networkingInteractor: NetworkInteracting
+ private let kms: KeyManagementServiceProtocol
+ private let logger: ConsoleLogging
+
+ init(networkingInteractor: NetworkInteracting,
+ kms: KeyManagementServiceProtocol,
+ logger: ConsoleLogging) {
+ self.networkingInteractor = networkingInteractor
+ self.kms = kms
+ self.logger = logger
+ }
+
+ func request(topic: String, params: PushRequestParams) async throws {
+ logger.debug("Sending Push Proposal")
+ let protocolMethod = PushProposeProtocolMethod()
+ let request = RPCRequest(method: protocolMethod.method, params: params)
+ try await networkingInteractor.requestNetworkAck(request, topic: topic, protocolMethod: protocolMethod)
+ }
+}
diff --git a/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift b/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift
index 826ae67e6..7d945bcda 100644
--- a/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift
+++ b/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift
@@ -3,14 +3,18 @@ import WalletConnectKMS
protocol ClientIdStoring {
func getOrCreateKeyPair() throws -> SigningPrivateKey
+ func getClientId() throws -> String
}
struct ClientIdStorage: ClientIdStoring {
private let key = "com.walletconnect.iridium.client_id"
private let keychain: KeychainStorageProtocol
+ private let didKeyFactory: DIDKeyFactory
- init(keychain: KeychainStorageProtocol) {
+ init(keychain: KeychainStorageProtocol,
+ didKeyFactory: DIDKeyFactory) {
self.keychain = keychain
+ self.didKeyFactory = didKeyFactory
}
func getOrCreateKeyPair() throws -> SigningPrivateKey {
@@ -22,4 +26,10 @@ struct ClientIdStorage: ClientIdStoring {
return privateKey
}
}
+
+ func getClientId() throws -> String {
+ let privateKey: SigningPrivateKey = try keychain.read(key: key)
+ let pubKey = privateKey.publicKey.rawRepresentation
+ return didKeyFactory.make(pubKey: pubKey, prefix: true)
+ }
}
diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift
index 2d8be9ba5..d08c7e0bf 100644
--- a/Sources/WalletConnectRelay/Dispatching.swift
+++ b/Sources/WalletConnectRelay/Dispatching.swift
@@ -1,25 +1,33 @@
import Foundation
+import Combine
import WalletConnectUtils
protocol Dispatching {
- var onConnect: (() -> Void)? {get set}
- var onDisconnect: (() -> Void)? {get set}
- var onMessage: ((String) -> Void)? {get set}
- func send(_ string: String) async throws
+ var onMessage: ((String) -> Void)? { get set }
+ var socketConnectionStatusPublisher: AnyPublisher { get }
func send(_ string: String, completion: @escaping (Error?) -> Void)
+ func protectedSend(_ string: String, completion: @escaping (Error?) -> Void)
+ func protectedSend(_ string: String) async throws
func connect() throws
func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
}
final class Dispatcher: NSObject, Dispatching {
- var onConnect: (() -> Void)?
- var onDisconnect: (() -> Void)?
var onMessage: ((String) -> Void)?
- private var textFramesQueue = Queue()
- private let logger: ConsoleLogging
var socket: WebSocketConnecting
var socketConnectionHandler: SocketConnectionHandler
+ private let logger: ConsoleLogging
+ private let defaultTimeout: Int = 5
+
+ private let socketConnectionStatusPublisherSubject = PassthroughSubject()
+
+ var socketConnectionStatusPublisher: AnyPublisher {
+ socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
+ }
+
+ private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.dispatcher", attributes: .concurrent)
+
init(socket: WebSocketConnecting,
socketConnectionHandler: SocketConnectionHandler,
logger: ConsoleLogging) {
@@ -31,28 +39,48 @@ final class Dispatcher: NSObject, Dispatching {
setUpSocketConnectionObserving()
}
- func send(_ string: String) async throws {
- return try await withCheckedThrowingContinuation { continuation in
- if socket.isConnected {
- socket.write(string: string) {
- continuation.resume(returning: ())
- }
- } else {
- continuation.resume(throwing: NetworkError.webSocketNotConnected)
- }
- }
- }
-
func send(_ string: String, completion: @escaping (Error?) -> Void) {
- // TODO - add policy for retry and "single try"
if socket.isConnected {
self.socket.write(string: string) {
completion(nil)
}
- // TODO - enqueue if fails
} else {
completion(NetworkError.webSocketNotConnected)
-// textFramesQueue.enqueue(string)
+ }
+ }
+
+ func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) {
+ guard !socket.isConnected else {
+ return send(string, completion: completion)
+ }
+
+ var cancellable: AnyCancellable?
+ cancellable = socketConnectionStatusPublisher
+ .filter { $0 == .connected }
+ .setFailureType(to: NetworkError.self)
+ .timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .webSocketNotConnected })
+ .sink(receiveCompletion: { result in
+ switch result {
+ case .failure(let error):
+ cancellable?.cancel()
+ completion(error)
+ case .finished: break
+ }
+ }, receiveValue: { [unowned self] _ in
+ cancellable?.cancel()
+ send(string, completion: completion)
+ })
+ }
+
+ func protectedSend(_ string: String) async throws {
+ return try await withCheckedThrowingContinuation { continuation in
+ protectedSend(string) { error in
+ if let error = error {
+ continuation.resume(throwing: error)
+ } else {
+ continuation.resume(returning: ())
+ }
+ }
}
}
@@ -72,21 +100,10 @@ final class Dispatcher: NSObject, Dispatching {
private func setUpSocketConnectionObserving() {
socket.onConnect = { [unowned self] in
- self.dequeuePendingTextFrames()
- self.onConnect?()
+ self.socketConnectionStatusPublisherSubject.send(.connected)
}
socket.onDisconnect = { [unowned self] _ in
- self.onDisconnect?()
- }
- }
-
- private func dequeuePendingTextFrames() {
- while let frame = textFramesQueue.dequeue() {
- send(frame) { [unowned self] error in
- if let error = error {
- self.logger.error(error.localizedDescription)
- }
- }
+ self.socketConnectionStatusPublisherSubject.send(.disconnected)
}
}
}
diff --git a/Sources/WalletConnectRelay/EnvironmentInfo.swift b/Sources/WalletConnectRelay/EnvironmentInfo.swift
index e00872260..7dab63f3b 100644
--- a/Sources/WalletConnectRelay/EnvironmentInfo.swift
+++ b/Sources/WalletConnectRelay/EnvironmentInfo.swift
@@ -25,8 +25,9 @@ enum EnvironmentInfo {
#if os(iOS)
return "\(UIDevice.current.systemName)-\(UIDevice.current.systemVersion)"
#elseif os(macOS)
- let systemVersion = ProcessInfo.processInfo.operatingSystemVersion
- return "macOS-\(systemVersion)"
+ return "macOS-\(ProcessInfo.processInfo.operatingSystemVersion)"
+#elseif os(tvOS)
+ return "tvOS-\(ProcessInfo.processInfo.operatingSystemVersion)"
#endif
}
}
diff --git a/Sources/WalletConnectRelay/Misc/Time.swift b/Sources/WalletConnectRelay/Misc/Time.swift
deleted file mode 100644
index 5aaaafb87..000000000
--- a/Sources/WalletConnectRelay/Misc/Time.swift
+++ /dev/null
@@ -1,7 +0,0 @@
-//
-
-import Foundation
-
-enum Time {
- static let hour = 3600
-}
diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift
index 94ab8ceab..0a156c7f5 100644
--- a/Sources/WalletConnectRelay/RelayClient.swift
+++ b/Sources/WalletConnectRelay/RelayClient.swift
@@ -20,9 +20,6 @@ public final class RelayClient {
case subscriptionIdNotFound
}
- static let historyIdentifier = "com.walletconnect.sdk.relayer_client.subscription_json_rpc_record"
-
- let defaultTtl = 6*Time.hour
var subscriptions: [String: String] = [:]
public var messagePublisher: AnyPublisher<(topic: String, message: String), Never> {
@@ -30,11 +27,10 @@ public final class RelayClient {
}
public var socketConnectionStatusPublisher: AnyPublisher {
- socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
+ dispatcher.socketConnectionStatusPublisher
}
private let messagePublisherSubject = PassthroughSubject<(topic: String, message: String), Never>()
- private let socketConnectionStatusPublisherSubject = PassthroughSubject()
private let subscriptionResponsePublisherSubject = PassthroughSubject<(RPCID?, String), Never>()
private var subscriptionResponsePublisher: AnyPublisher<(RPCID?, String), Never> {
@@ -45,6 +41,8 @@ public final class RelayClient {
requestAcknowledgePublisherSubject.eraseToAnyPublisher()
}
+ private let clientIdStorage: ClientIdStoring
+
private var dispatcher: Dispatching
private let rpcHistory: RPCHistory
private let logger: ConsoleLogging
@@ -56,11 +54,13 @@ public final class RelayClient {
init(
dispatcher: Dispatching,
logger: ConsoleLogging,
- keyValueStorage: KeyValueStorage
+ keyValueStorage: KeyValueStorage,
+ clientIdStorage: ClientIdStoring
) {
self.logger = logger
self.dispatcher = dispatcher
- self.rpcHistory = RPCHistory(keyValueStore: CodableStore(defaults: keyValueStorage, identifier: Self.historyIdentifier))
+ self.rpcHistory = RPCHistoryFactory.createForRelay(keyValueStorage: keyValueStorage)
+ self.clientIdStorage = clientIdStorage
setUpBindings()
}
@@ -68,9 +68,6 @@ public final class RelayClient {
dispatcher.onMessage = { [weak self] payload in
self?.handlePayloadMessage(payload)
}
- dispatcher.onConnect = { [unowned self] in
- self.socketConnectionStatusPublisherSubject.send(.connected)
- }
}
/// Instantiates Relay Client
@@ -89,9 +86,11 @@ public final class RelayClient {
socketConnectionType: SocketConnectionType = .automatic,
logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off)
) {
+ let didKeyFactory = ED25519DIDKeyFactory()
+ let clientIdStorage = ClientIdStorage(keychain: keychainStorage, didKeyFactory: didKeyFactory)
let socketAuthenticator = SocketAuthenticator(
- clientIdStorage: ClientIdStorage(keychain: keychainStorage),
- didKeyFactory: ED25519DIDKeyFactory(),
+ clientIdStorage: clientIdStorage,
+ didKeyFactory: didKeyFactory,
relayHost: relayHost
)
let relayUrlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator)
@@ -108,7 +107,7 @@ public final class RelayClient {
socketConnectionHandler = ManualSocketConnectionHandler(socket: socket)
}
let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: socketConnectionHandler, logger: logger)
- self.init(dispatcher: dispatcher, logger: logger, keyValueStorage: keyValueStorage)
+ self.init(dispatcher: dispatcher, logger: logger, keyValueStorage: keyValueStorage, clientIdStorage: clientIdStorage)
}
/// Connects web socket
@@ -126,13 +125,13 @@ public final class RelayClient {
}
/// Completes when networking client sends a request, error if it fails on client side
- public func publish(topic: String, payload: String, tag: Int, prompt: Bool = false) async throws {
- let request = Publish(params: .init(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag))
+ public func publish(topic: String, payload: String, tag: Int, prompt: Bool, ttl: Int) async throws {
+ let request = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag))
.wrapToIRN()
.asRPCRequest()
let message = try request.asJSONEncodedString()
logger.debug("Publishing payload on topic: \(topic)")
- try await dispatcher.send(message)
+ try await dispatcher.protectedSend(message)
}
/// Completes with an acknowledgement from the relay network.
@@ -140,10 +139,11 @@ public final class RelayClient {
topic: String,
payload: String,
tag: Int,
- prompt: Bool = false,
+ prompt: Bool,
+ ttl: Int,
onNetworkAcknowledge: @escaping ((Error?) -> Void)
) {
- let rpc = Publish(params: .init(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag))
+ let rpc = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag))
let request = rpc
.wrapToIRN()
.asRPCRequest()
@@ -156,7 +156,7 @@ public final class RelayClient {
cancellable?.cancel()
onNetworkAcknowledge(nil)
}
- dispatcher.send(message) { [weak self] error in
+ dispatcher.protectedSend(message) { [weak self] error in
if let error = error {
self?.logger.debug("Failed to Publish Payload, error: \(error)")
cancellable?.cancel()
@@ -183,7 +183,7 @@ public final class RelayClient {
}
completion(nil)
}
- dispatcher.send(message) { [weak self] error in
+ dispatcher.protectedSend(message) { [weak self] error in
if let error = error {
self?.logger.debug("Failed to subscribe to topic \(error)")
cancellable?.cancel()
@@ -223,7 +223,7 @@ public final class RelayClient {
cancellable?.cancel()
completion(nil)
}
- dispatcher.send(message) { [weak self] error in
+ dispatcher.protectedSend(message) { [weak self] error in
if let error = error {
self?.logger.debug("Failed to unsubscribe from topic")
cancellable?.cancel()
@@ -237,6 +237,10 @@ public final class RelayClient {
}
}
+ public func getClientId() throws -> String {
+ try clientIdStorage.getClientId()
+ }
+
// FIXME: Parse data to string once before trying to decode -> respond error on fail
private func handlePayloadMessage(_ payload: String) {
if let request = tryDecode(RPCRequest.self, from: payload) {
@@ -253,13 +257,13 @@ public final class RelayClient {
}
} else if let response = tryDecode(RPCResponse.self, from: payload) {
switch response.outcome {
- case .success(let anyCodable):
+ case .response(let anyCodable):
if let _ = try? anyCodable.get(Bool.self) { // TODO: Handle success vs. error
requestAcknowledgePublisherSubject.send(response.id)
} else if let subscriptionId = try? anyCodable.get(String.self) {
subscriptionResponsePublisherSubject.send((response.id, subscriptionId))
}
- case .failure(let rpcError):
+ case .error(let rpcError):
logger.error("Received RPC error from relay network: \(rpcError)")
}
} else {
@@ -279,7 +283,7 @@ public final class RelayClient {
private func acknowledgeRequest(_ request: RPCRequest) throws {
let response = RPCResponse(matchingRequest: request, result: true)
let message = try response.asJSONEncodedString()
- dispatcher.send(message) { [unowned self] in
+ dispatcher.protectedSend(message) { [unowned self] in
if let error = $0 {
logger.debug("Failed to dispatch response: \(response), error: \(error)")
} else {
diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
index 229e2e698..9f8fcbcf1 100644
--- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
@@ -1,8 +1,10 @@
import Foundation
import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectKMS
import WalletConnectPairing
+import WalletConnectNetworking
final class ApproveEngine {
enum Errors: Error {
@@ -10,12 +12,12 @@ final class ApproveEngine {
case relayNotFound
case proposalPayloadsNotFound
case pairingNotFound
+ case sessionNotFound
case agreementMissingOrInvalid
- case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode)
}
var onSessionProposal: ((Session.Proposal) -> Void)?
- var onSessionRejected: ((Session.Proposal, SessionType.Reason) -> Void)?
+ var onSessionRejected: ((Session.Proposal, Reason) -> Void)?
var onSessionSettle: ((Session) -> Void)?
var settlingProposal: SessionProposal?
@@ -23,8 +25,9 @@ final class ApproveEngine {
private let networkingInteractor: NetworkInteracting
private let pairingStore: WCPairingStorage
private let sessionStore: WCSessionStorage
- private let proposalPayloadsStore: CodableStore
+ private let proposalPayloadsStore: CodableStore>
private let sessionToPairingTopic: CodableStore
+ private let pairingRegisterer: PairingRegisterer
private let metadata: AppMetadata
private let kms: KeyManagementServiceProtocol
private let logger: ConsoleLogging
@@ -33,8 +36,9 @@ final class ApproveEngine {
init(
networkingInteractor: NetworkInteracting,
- proposalPayloadsStore: CodableStore,
+ proposalPayloadsStore: CodableStore>,
sessionToPairingTopic: CodableStore,
+ pairingRegisterer: PairingRegisterer,
metadata: AppMetadata,
kms: KeyManagementServiceProtocol,
logger: ConsoleLogging,
@@ -44,22 +48,25 @@ final class ApproveEngine {
self.networkingInteractor = networkingInteractor
self.proposalPayloadsStore = proposalPayloadsStore
self.sessionToPairingTopic = sessionToPairingTopic
+ self.pairingRegisterer = pairingRegisterer
self.metadata = metadata
self.kms = kms
self.logger = logger
self.pairingStore = pairingStore
self.sessionStore = sessionStore
- setupNetworkingSubscriptions()
+ setupRequestSubscriptions()
+ setupResponseSubscriptions()
+ setupResponseErrorSubscriptions()
}
func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws {
- let payload = try proposalPayloadsStore.get(key: proposerPubKey)
-
- guard let payload = payload, case .sessionPropose(let proposal) = payload.wcRequest.params else {
+ guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else {
throw Errors.wrongRequestParams
}
+ let proposal = payload.request
+
proposalPayloadsStore.delete(forKey: proposerPubKey)
try Namespace.validate(sessionNamespaces)
@@ -79,14 +86,13 @@ final class ApproveEngine {
throw Errors.relayNotFound
}
- let proposeResponse = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation)
- let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(proposeResponse))
-
guard var pairing = pairingStore.getPairing(forTopic: payload.topic) else {
throw Errors.pairingNotFound
}
- try await networkingInteractor.respond(topic: payload.topic, response: .response(response), tag: payload.wcRequest.responseTag)
+ let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation)
+ let response = RPCResponse(id: payload.id, result: result)
+ try await networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod())
try pairing.updateExpiry()
pairingStore.setPairing(pairing)
@@ -99,7 +105,7 @@ final class ApproveEngine {
throw Errors.proposalPayloadsNotFound
}
proposalPayloadsStore.delete(forKey: proposerPubKey)
- try await networkingInteractor.respondError(payload: payload, reason: reason)
+ try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: SessionProposeProtocolMethod(), reason: reason)
// TODO: Delete pairing if inactive
}
@@ -140,7 +146,9 @@ final class ApproveEngine {
try await networkingInteractor.subscribe(topic: topic)
sessionStore.setSession(session)
- try await networkingInteractor.request(.wcSessionSettle(settleParams), onTopic: topic)
+ let protocolMethod = SessionSettleProtocolMethod()
+ let request = RPCRequest(method: protocolMethod.method, params: settleParams)
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
onSessionSettle?(session.publicRepresentation())
}
}
@@ -149,81 +157,62 @@ final class ApproveEngine {
private extension ApproveEngine {
- func setupNetworkingSubscriptions() {
- networkingInteractor.responsePublisher
- .sink { [unowned self] response in
- switch response.requestParams {
- case .sessionPropose(let proposal):
- handleSessionProposeResponse(response: response, proposal: proposal)
- case .sessionSettle:
- handleSessionSettleResponse(response: response)
- default:
- break
- }
+ func setupRequestSubscriptions() {
+ pairingRegisterer.register(method: SessionProposeProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ handleSessionProposeRequest(payload: payload)
+ }.store(in: &publishers)
+
+ networkingInteractor.requestSubscription(on: SessionSettleProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ handleSessionSettleRequest(payload: payload)
+ }.store(in: &publishers)
+ }
+
+ func setupResponseSubscriptions() {
+ networkingInteractor.responseSubscription(on: SessionProposeProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ handleSessionProposeResponse(payload: payload)
}.store(in: &publishers)
- networkingInteractor.wcRequestPublisher
- .sink { [unowned self] subscriptionPayload in
- do {
- switch subscriptionPayload.wcRequest.params {
- case .sessionPropose(let proposal):
- try handleSessionProposeRequest(payload: subscriptionPayload, proposal: proposal)
- case .sessionSettle(let settleParams):
- try handleSessionSettleRequest(payload: subscriptionPayload, settleParams: settleParams)
- default: return
- }
- } catch Errors.respondError(let payload, let reason) {
- respondError(payload: payload, reason: reason)
- } catch {
- logger.error("Unexpected Error: \(error.localizedDescription)")
- }
+ networkingInteractor.responseSubscription(on: SessionSettleProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ handleSessionSettleResponse(payload: payload)
}.store(in: &publishers)
}
- func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) {
- Task {
+ func setupResponseErrorSubscriptions() {
+ networkingInteractor.responseErrorSubscription(on: SessionProposeProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in
+ handleSessionProposeResponseError(payload: payload)
+ }.store(in: &publishers)
+
+ networkingInteractor.responseErrorSubscription(on: SessionSettleProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in
+ handleSessionSettleResponseError(payload: payload)
+ }.store(in: &publishers)
+ }
+
+ func respondError(payload: SubscriptionPayload, reason: ReasonCode, protocolMethod: ProtocolMethod) {
+ Task(priority: .high) {
do {
- try await networkingInteractor.respondError(payload: payload, reason: reason)
+ try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod, reason: reason)
} catch {
logger.error("Respond Error failed with: \(error.localizedDescription)")
}
}
}
- func updatePairingMetadata(topic: String, metadata: AppMetadata) {
- guard var pairing = pairingStore.getPairing(forTopic: topic) else { return }
- pairing.peerMetadata = metadata
- pairingStore.setPairing(pairing)
- }
-
// MARK: SessionProposeResponse
// TODO: Move to Non-Controller SettleEngine
- func handleSessionProposeResponse(response: WCResponse, proposal: SessionType.ProposeParams) {
+ func handleSessionProposeResponse(payload: ResponseSubscriptionPayload) {
do {
- let sessionTopic = try handleProposeResponse(
- pairingTopic: response.topic,
- proposal: proposal,
- result: response.result
- )
- settlingProposal = proposal
+ let pairingTopic = payload.topic
- Task(priority: .high) {
- try? await networkingInteractor.subscribe(topic: sessionTopic)
+ guard var pairing = pairingStore.getPairing(forTopic: pairingTopic) else {
+ throw Errors.pairingNotFound
}
- } catch {
- guard let error = error as? JSONRPCErrorResponse else {
- return logger.debug(error.localizedDescription)
- }
- onSessionRejected?(proposal.publicRepresentation(), SessionType.Reason(code: error.error.code, message: error.error.message))
- }
- }
-
- func handleProposeResponse(pairingTopic: String, proposal: SessionProposal, result: JsonRpcResult) throws -> String {
- guard var pairing = pairingStore.getPairing(forTopic: pairingTopic)
- else { throw Errors.pairingNotFound }
- switch result {
- case .response(let response):
// Activate the pairing
if !pairing.active {
pairing.activate()
@@ -233,9 +222,8 @@ private extension ApproveEngine {
pairingStore.setPairing(pairing)
- let selfPublicKey = try AgreementPublicKey(hex: proposal.proposer.publicKey)
- let proposeResponse = try response.result.get(SessionType.ProposeResponse.self)
- let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: proposeResponse.responderPublicKey)
+ let selfPublicKey = try AgreementPublicKey(hex: payload.request.proposer.publicKey)
+ let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: payload.response.responderPublicKey)
let sessionTopic = agreementKeys.derivedTopic()
logger.debug("Received Session Proposal response")
@@ -243,64 +231,94 @@ private extension ApproveEngine {
try kms.setAgreementSecret(agreementKeys, topic: sessionTopic)
sessionToPairingTopic.set(pairingTopic, forKey: sessionTopic)
- return sessionTopic
+ settlingProposal = payload.request
- case .error(let error):
- if !pairing.active {
- kms.deleteSymmetricKey(for: pairing.topic)
- networkingInteractor.unsubscribe(topic: pairing.topic)
- pairingStore.delete(topic: pairingTopic)
+ Task(priority: .high) {
+ try await networkingInteractor.subscribe(topic: sessionTopic)
}
- logger.debug("Session Proposal has been rejected")
- kms.deletePrivateKey(for: proposal.proposer.publicKey)
- throw error
+ } catch {
+ return logger.debug(error.localizedDescription)
+ }
+ }
+
+ func handleSessionProposeResponseError(payload: ResponseSubscriptionErrorPayload) {
+ guard let pairing = pairingStore.getPairing(forTopic: payload.topic) else {
+ return logger.debug(Errors.pairingNotFound.localizedDescription)
}
+
+ if !pairing.active {
+ kms.deleteSymmetricKey(for: pairing.topic)
+ networkingInteractor.unsubscribe(topic: pairing.topic)
+ pairingStore.delete(topic: payload.topic)
+ }
+ logger.debug("Session Proposal has been rejected")
+ kms.deletePrivateKey(for: payload.request.proposer.publicKey)
+
+ onSessionRejected?(
+ payload.request.publicRepresentation(),
+ SessionType.Reason(code: payload.error.code, message: payload.error.message)
+ )
}
// MARK: SessionSettleResponse
- func handleSessionSettleResponse(response: WCResponse) {
- guard let session = sessionStore.getSession(forTopic: response.topic) else { return }
- switch response.result {
- case .response:
- logger.debug("Received session settle response")
- guard var session = sessionStore.getSession(forTopic: response.topic) else { return }
- session.acknowledge()
- sessionStore.setSession(session)
- case .error(let error):
- logger.error("Error - session rejected, Reason: \(error)")
- networkingInteractor.unsubscribe(topic: response.topic)
- sessionStore.delete(topic: response.topic)
- kms.deleteAgreementSecret(for: response.topic)
- kms.deletePrivateKey(for: session.publicKey!)
+ func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) {
+ guard var session = sessionStore.getSession(forTopic: payload.topic) else {
+ return logger.debug(Errors.sessionNotFound.localizedDescription)
}
+
+ logger.debug("Received session settle response")
+ session.acknowledge()
+ sessionStore.setSession(session)
+ }
+
+ func handleSessionSettleResponseError(payload: ResponseSubscriptionErrorPayload) {
+ guard let session = sessionStore.getSession(forTopic: payload.topic) else {
+ return logger.debug(Errors.sessionNotFound.localizedDescription)
+ }
+
+ logger.error("Error - session rejected, Reason: \(payload.error)")
+ networkingInteractor.unsubscribe(topic: payload.topic)
+ sessionStore.delete(topic: payload.topic)
+ kms.deleteAgreementSecret(for: payload.topic)
+ kms.deletePrivateKey(for: session.publicKey!)
}
// MARK: SessionProposeRequest
- func handleSessionProposeRequest(payload: WCRequestSubscriptionPayload, proposal: SessionType.ProposeParams) throws {
+ func handleSessionProposeRequest(payload: RequestSubscriptionPayload) {
logger.debug("Received Session Proposal")
- do { try Namespace.validate(proposal.requiredNamespaces) } catch { throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) }
+ let proposal = payload.request
+ do { try Namespace.validate(proposal.requiredNamespaces) } catch {
+ return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: SessionProposeProtocolMethod())
+ }
proposalPayloadsStore.set(payload, forKey: proposal.proposer.publicKey)
onSessionProposal?(proposal.publicRepresentation())
}
// MARK: SessionSettleRequest
- func handleSessionSettleRequest(payload: WCRequestSubscriptionPayload, settleParams: SessionType.SettleParams) throws {
+
+ func handleSessionSettleRequest(payload: RequestSubscriptionPayload) {
logger.debug("Did receive session settle request")
- guard let proposedNamespaces = settlingProposal?.requiredNamespaces
- else { throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) }
+ let protocolMethod = SessionSettleProtocolMethod()
+
+ guard let proposedNamespaces = settlingProposal?.requiredNamespaces else {
+ return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod)
+ }
settlingProposal = nil
- let sessionNamespaces = settleParams.namespaces
+ let params = payload.request
+ let sessionNamespaces = params.namespaces
do {
try Namespace.validate(sessionNamespaces)
try Namespace.validateApproved(sessionNamespaces, against: proposedNamespaces)
} catch WalletConnectError.unsupportedNamespace(let reason) {
- throw Errors.respondError(payload: payload, reason: reason)
+ return respondError(payload: payload, reason: reason, protocolMethod: protocolMethod)
+ } catch {
+ return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod)
}
let topic = payload.topic
@@ -310,20 +328,22 @@ private extension ApproveEngine {
metadata: metadata
)
if let pairingTopic = try? sessionToPairingTopic.get(key: topic) {
- updatePairingMetadata(topic: pairingTopic, metadata: settleParams.controller.metadata)
+ pairingRegisterer.updateMetadata(pairingTopic, metadata: params.controller.metadata)
}
let session = WCSession(
topic: topic,
timestamp: Date(),
selfParticipant: selfParticipant,
- peerParticipant: settleParams.controller,
- settleParams: settleParams,
+ peerParticipant: params.controller,
+ settleParams: params,
requiredNamespaces: proposedNamespaces,
acknowledged: true
)
sessionStore.setSession(session)
- networkingInteractor.respondSuccess(for: payload)
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
+ }
onSessionSettle?(session.publicRepresentation())
}
}
diff --git a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
index 561af3507..be2be4c42 100644
--- a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
+++ b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift
@@ -1,6 +1,8 @@
import Foundation
+import JSONRPC
import WalletConnectKMS
import WalletConnectUtils
+import WalletConnectNetworking
class DeleteSessionService {
private let networkingInteractor: NetworkInteracting
@@ -20,9 +22,11 @@ class DeleteSessionService {
func delete(topic: String) async throws {
let reasonCode = ReasonCode.userDisconnected
+ let protocolMethod = SessionDeleteProtocolMethod()
let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message)
logger.debug("Will delete session for reason: message: \(reason.message) code: \(reason.code)")
- try await networkingInteractor.request(.wcSessionDelete(reason), onTopic: topic)
+ let request = RPCRequest(method: protocolMethod.method, params: reason)
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
sessionStore.delete(topic: topic)
kms.deleteSymmetricKey(for: topic)
networkingInteractor.unsubscribe(topic: topic)
diff --git a/Sources/WalletConnectSign/Engine/Common/DisconnectService.swift b/Sources/WalletConnectSign/Engine/Common/DisconnectService.swift
deleted file mode 100644
index d0b417a7f..000000000
--- a/Sources/WalletConnectSign/Engine/Common/DisconnectService.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import Foundation
-import WalletConnectPairing
-
-class DisconnectService {
- enum Errors: Error {
- case objectForTopicNotFound
- }
-
- private let deletePairingService: DeletePairingService
- private let deleteSessionService: DeleteSessionService
- private let pairingStorage: WCPairingStorage
- private let sessionStorage: WCSessionStorage
-
- init(deletePairingService: DeletePairingService,
- deleteSessionService: DeleteSessionService,
- pairingStorage: WCPairingStorage,
- sessionStorage: WCSessionStorage) {
- self.deletePairingService = deletePairingService
- self.deleteSessionService = deleteSessionService
- self.pairingStorage = pairingStorage
- self.sessionStorage = sessionStorage
- }
-
- func disconnect(topic: String) async throws {
- if pairingStorage.hasPairing(forTopic: topic) {
- try await deletePairingService.delete(topic: topic)
- } else if sessionStorage.hasSession(forTopic: topic) {
- try await deleteSessionService.delete(topic: topic)
- } else {
- throw Errors.objectForTopicNotFound
- }
- }
-}
diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift
deleted file mode 100644
index d9ec3ac3c..000000000
--- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift
+++ /dev/null
@@ -1,133 +0,0 @@
-import Foundation
-import Combine
-import WalletConnectPairing
-import WalletConnectUtils
-import WalletConnectKMS
-
-final class PairingEngine {
-
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let pairingStore: WCPairingStorage
- private var metadata: AppMetadata
- private var publishers = [AnyCancellable]()
- private let logger: ConsoleLogging
- private let topicInitializer: () -> String
-
- init(
- networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- pairingStore: WCPairingStorage,
- metadata: AppMetadata,
- logger: ConsoleLogging,
- topicGenerator: @escaping () -> String = String.generateTopic
- ) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.metadata = metadata
- self.pairingStore = pairingStore
- self.logger = logger
- self.topicInitializer = topicGenerator
- setupNetworkingSubscriptions()
- setupExpirationHandling()
- }
-
- func hasPairing(for topic: String) -> Bool {
- return pairingStore.hasPairing(forTopic: topic)
- }
-
- func getSettledPairing(for topic: String) -> WCPairing? {
- guard let pairing = pairingStore.getPairing(forTopic: topic) else {
- return nil
- }
- return pairing
- }
-
- func getPairings() -> [Pairing] {
- pairingStore.getAll()
- .map {Pairing(topic: $0.topic, peer: $0.peerMetadata, expiryDate: $0.expiryDate)}
- }
-
- func create() async throws -> WalletConnectURI {
- let topic = topicInitializer()
- try await networkingInteractor.subscribe(topic: topic)
- let symKey = try! kms.createSymmetricKey(topic)
- let pairing = WCPairing(topic: topic)
- let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay)
- pairingStore.setPairing(pairing)
- return uri
- }
-
- func propose(pairingTopic: String, namespaces: [String: ProposalNamespace], relay: RelayProtocolOptions) async throws {
- logger.debug("Propose Session on topic: \(pairingTopic)")
- try Namespace.validate(namespaces)
- let publicKey = try! kms.createX25519KeyPair()
- let proposer = Participant(
- publicKey: publicKey.hexRepresentation,
- metadata: metadata)
- let proposal = SessionProposal(
- relays: [relay],
- proposer: proposer,
- requiredNamespaces: namespaces)
- return try await withCheckedThrowingContinuation { continuation in
- networkingInteractor.requestNetworkAck(.wcSessionPropose(proposal), onTopic: pairingTopic) { error in
- if let error = error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume()
- }
- }
- }
- }
-
- func ping(topic: String, completion: @escaping ((Result) -> Void)) {
- guard pairingStore.hasPairing(forTopic: topic) else {
- logger.debug("Could not find pairing to ping for topic \(topic)")
- return
- }
- networkingInteractor.requestPeerResponse(.wcPairingPing, onTopic: topic) { [unowned self] result in
- switch result {
- case .success:
- logger.debug("Did receive ping response")
- completion(.success(()))
- case .failure(let error):
- logger.debug("error: \(error)")
- }
- }
- }
-}
-
-// MARK: Private
-
-private extension PairingEngine {
-
- func setupNetworkingSubscriptions() {
- networkingInteractor.transportConnectionPublisher
- .sink { [unowned self] (_) in
- let topics = pairingStore.getAll()
- .map {$0.topic}
- topics.forEach { topic in Task {try? await networkingInteractor.subscribe(topic: topic)}}
- }.store(in: &publishers)
-
- networkingInteractor.wcRequestPublisher
- .sink { [unowned self] subscriptionPayload in
- switch subscriptionPayload.wcRequest.params {
- case .pairingPing:
- wcPairingPing(subscriptionPayload)
- default:
- return
- }
- }.store(in: &publishers)
- }
-
- func wcPairingPing(_ payload: WCRequestSubscriptionPayload) {
- networkingInteractor.respondSuccess(for: payload)
- }
-
- func setupExpirationHandling() {
- pairingStore.onPairingExpiration = { [weak self] pairing in
- self?.kms.deleteSymmetricKey(for: pairing.topic)
- self?.networkingInteractor.unsubscribe(topic: pairing.topic)
- }
- }
-}
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
index a1197c8e1..028b4016a 100644
--- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
@@ -1,11 +1,12 @@
import Foundation
import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectKMS
+import WalletConnectNetworking
final class SessionEngine {
enum Errors: Error {
- case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode)
case sessionNotFound(topic: String)
}
@@ -32,7 +33,9 @@ final class SessionEngine {
self.sessionStore = sessionStore
self.logger = logger
- setupNetworkingSubscriptions()
+ setupConnectionSubscriptions()
+ setupRequestSubscriptions()
+ setupResponseSubscriptions()
setupExpirationSubscriptions()
}
@@ -44,22 +47,6 @@ final class SessionEngine {
sessionStore.getAll().map {$0.publicRepresentation()}
}
- func ping(topic: String, completion: @escaping (Result) -> Void) {
- guard sessionStore.hasSession(forTopic: topic) else {
- logger.debug("Could not find session to ping for topic \(topic)")
- return
- }
- networkingInteractor.requestPeerResponse(.wcSessionPing, onTopic: topic) { [unowned self] result in
- switch result {
- case .success:
- logger.debug("Did receive ping response")
- completion(.success(()))
- case .failure(let error):
- logger.debug("error: \(error)")
- }
- }
- }
-
func request(_ request: Request) async throws {
logger.debug("will request on session topic: \(request.topic)")
guard let session = sessionStore.getSession(forTopic: request.topic), session.acknowledged else {
@@ -71,17 +58,21 @@ final class SessionEngine {
}
let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params)
let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId)
- try await networkingInteractor.request(.wcSessionRequest(sessionRequestParams), onTopic: request.topic)
+ let protocolMethod = SessionRequestProtocolMethod()
+ let rpcRequest = RPCRequest(method: protocolMethod.method, params: sessionRequestParams)
+ try await networkingInteractor.request(rpcRequest, topic: request.topic, protocolMethod: SessionRequestProtocolMethod())
}
- func respondSessionRequest(topic: String, response: JsonRpcResult) async throws {
+ func respondSessionRequest(topic: String, requestId: RPCID, response: RPCResult) async throws {
guard sessionStore.hasSession(forTopic: topic) else {
throw Errors.sessionNotFound(topic: topic)
}
- try await networkingInteractor.respond(topic: topic, response: response, tag: 1109) // FIXME: Hardcoded tag
+ let response = RPCResponse(id: requestId, result: response)
+ try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: SessionRequestProtocolMethod())
}
func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws {
+ let protocolMethod = SessionEventProtocolMethod()
guard let session = sessionStore.getSession(forTopic: topic) else {
logger.debug("Could not find session for topic \(topic)")
return
@@ -89,8 +80,8 @@ final class SessionEngine {
guard session.hasPermission(forEvent: event.name, onChain: chainId) else {
throw WalletConnectError.invalidEvent
}
- let params = SessionType.EventParams(event: event, chainId: chainId)
- try await networkingInteractor.request(.wcSessionEvent(params), onTopic: topic)
+ let rpcRequest = RPCRequest(method: protocolMethod.method, params: SessionType.EventParams(event: event, chainId: chainId))
+ try await networkingInteractor.request(rpcRequest, topic: topic, protocolMethod: protocolMethod)
}
}
@@ -98,117 +89,125 @@ final class SessionEngine {
private extension SessionEngine {
- func setupNetworkingSubscriptions() {
- networkingInteractor.wcRequestPublisher.sink { [unowned self] subscriptionPayload in
- do {
- switch subscriptionPayload.wcRequest.params {
- case .sessionDelete(let deleteParams):
- try onSessionDelete(subscriptionPayload, deleteParams: deleteParams)
- case .sessionRequest(let sessionRequestParams):
- try onSessionRequest(subscriptionPayload, payloadParams: sessionRequestParams)
- case .sessionPing:
- onSessionPing(subscriptionPayload)
- case .sessionEvent(let eventParams):
- try onSessionEvent(subscriptionPayload, eventParams: eventParams)
- default: return
- }
- } catch Errors.respondError(let payload, let reason) {
- respondError(payload: payload, reason: reason)
- } catch {
- logger.error("Unexpected Error: \(error.localizedDescription)")
+ func setupConnectionSubscriptions() {
+ networkingInteractor.socketConnectionStatusPublisher
+ .sink { [unowned self] status in
+ guard status == .connected else { return }
+ sessionStore.getAll()
+ .forEach { session in
+ Task(priority: .high) { try await networkingInteractor.subscribe(topic: session.topic) }
+ }
}
- }.store(in: &publishers)
+ .store(in: &publishers)
+ }
+
+ func setupRequestSubscriptions() {
+ networkingInteractor.requestSubscription(on: SessionDeleteProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionDelete(payload: payload)
+ }.store(in: &publishers)
+
+ networkingInteractor.requestSubscription(on: SessionRequestProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionRequest(payload: payload)
+ }.store(in: &publishers)
- networkingInteractor.transportConnectionPublisher
- .sink { [unowned self] (_) in
- let topics = sessionStore.getAll().map {$0.topic}
- topics.forEach { topic in Task { try? await networkingInteractor.subscribe(topic: topic) } }
+ networkingInteractor.requestSubscription(on: SessionPingProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionPing(payload: payload)
}.store(in: &publishers)
- networkingInteractor.responsePublisher
- .sink { [unowned self] response in
- self.handleResponse(response)
+ networkingInteractor.requestSubscription(on: SessionEventProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionEvent(payload: payload)
}.store(in: &publishers)
}
- func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) {
- Task {
+ func setupResponseSubscriptions() {
+ networkingInteractor.responseSubscription(on: SessionRequestProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ onSessionResponse?(Response(
+ id: payload.id,
+ topic: payload.topic,
+ chainId: payload.request.chainId.absoluteString,
+ result: payload.response
+ ))
+ }
+ .store(in: &publishers)
+ }
+
+ func setupExpirationSubscriptions() {
+ sessionStore.onSessionExpiration = { [weak self] session in
+ self?.kms.deletePrivateKey(for: session.selfParticipant.publicKey)
+ self?.kms.deleteAgreementSecret(for: session.topic)
+ }
+ }
+
+ func respondError(payload: SubscriptionPayload, reason: ReasonCode, protocolMethod: ProtocolMethod) {
+ Task(priority: .high) {
do {
- try await networkingInteractor.respondError(payload: payload, reason: reason)
+ try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod, reason: reason)
} catch {
logger.error("Respond Error failed with: \(error.localizedDescription)")
}
}
}
- func onSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) throws {
+ func onSessionDelete(payload: RequestSubscriptionPayload) {
+ let protocolMethod = SessionDeleteProtocolMethod()
let topic = payload.topic
guard sessionStore.hasSession(forTopic: topic) else {
- throw Errors.respondError(payload: payload, reason: .noSessionForTopic)
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
}
sessionStore.delete(topic: topic)
networkingInteractor.unsubscribe(topic: topic)
- networkingInteractor.respondSuccess(for: payload)
- onSessionDelete?(topic, deleteParams)
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
+ }
+ onSessionDelete?(topic, payload.request)
}
- func onSessionRequest(_ payload: WCRequestSubscriptionPayload, payloadParams: SessionType.RequestParams) throws {
+ func onSessionRequest(payload: RequestSubscriptionPayload) {
+ let protocolMethod = SessionRequestProtocolMethod()
let topic = payload.topic
- let jsonRpcRequest = JSONRPCRequest(id: payload.wcRequest.id, method: payloadParams.request.method, params: payloadParams.request.params)
let request = Request(
- id: jsonRpcRequest.id,
- topic: topic,
- method: jsonRpcRequest.method,
- params: jsonRpcRequest.params,
- chainId: payloadParams.chainId)
+ id: payload.id,
+ topic: payload.topic,
+ method: payload.request.request.method,
+ params: payload.request.request.params,
+ chainId: payload.request.chainId)
guard let session = sessionStore.getSession(forTopic: topic) else {
- throw Errors.respondError(payload: payload, reason: .noSessionForTopic)
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
}
- let chain = request.chainId
- guard session.hasNamespace(for: chain) else {
- throw Errors.respondError(payload: payload, reason: .unauthorizedChain)
+ guard session.hasNamespace(for: request.chainId) else {
+ return respondError(payload: payload, reason: .unauthorizedChain, protocolMethod: protocolMethod)
}
- guard session.hasPermission(forMethod: request.method, onChain: chain) else {
- throw Errors.respondError(payload: payload, reason: .unauthorizedMethod(request.method))
+ guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else {
+ return respondError(payload: payload, reason: .unauthorizedMethod(request.method), protocolMethod: protocolMethod)
}
onSessionRequest?(request)
}
- func onSessionPing(_ payload: WCRequestSubscriptionPayload) {
- networkingInteractor.respondSuccess(for: payload)
+ func onSessionPing(payload: SubscriptionPayload) {
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: SessionPingProtocolMethod())
+ }
}
- func onSessionEvent(_ payload: WCRequestSubscriptionPayload, eventParams: SessionType.EventParams) throws {
- let event = eventParams.event
+ func onSessionEvent(payload: RequestSubscriptionPayload) {
+ let protocolMethod = SessionEventProtocolMethod()
+ let event = payload.request.event
let topic = payload.topic
guard let session = sessionStore.getSession(forTopic: topic) else {
- throw Errors.respondError(payload: payload, reason: .noSessionForTopic)
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
}
- guard
- session.peerIsController,
- session.hasPermission(forEvent: event.name, onChain: eventParams.chainId)
- else {
- throw Errors.respondError(payload: payload, reason: .unauthorizedEvent(event.name))
+ guard session.peerIsController, session.hasPermission(forEvent: event.name, onChain: payload.request.chainId) else {
+ return respondError(payload: payload, reason: .unauthorizedEvent(event.name), protocolMethod: protocolMethod)
}
- networkingInteractor.respondSuccess(for: payload)
- onEventReceived?(topic, event.publicRepresentation(), eventParams.chainId)
- }
-
- func setupExpirationSubscriptions() {
- sessionStore.onSessionExpiration = { [weak self] session in
- self?.kms.deletePrivateKey(for: session.selfParticipant.publicKey)
- self?.kms.deleteAgreementSecret(for: session.topic)
- }
- }
-
- func handleResponse(_ response: WCResponse) {
- switch response.requestParams {
- case .sessionRequest:
- let response = Response(topic: response.topic, chainId: response.chainId, result: response.result)
- onSessionResponse?(response)
- default:
- break
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
}
+ onEventReceived?(topic, event.publicRepresentation(), payload.request.chainId)
}
}
diff --git a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
index b76b63d50..748ae7713 100644
--- a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
+++ b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
@@ -1,7 +1,9 @@
import Foundation
+import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectKMS
-import Combine
+import WalletConnectNetworking
final class ControllerSessionStateMachine {
@@ -22,48 +24,54 @@ final class ControllerSessionStateMachine {
self.kms = kms
self.sessionStore = sessionStore
self.logger = logger
- networkingInteractor.responsePublisher.sink { [unowned self] response in
- handleResponse(response)
- }.store(in: &publishers)
+
+ setupSubscriptions()
}
func update(topic: String, namespaces: [String: SessionNamespace]) async throws {
let session = try getSession(for: topic)
+ let protocolMethod = SessionUpdateProtocolMethod()
try validateController(session)
try Namespace.validate(namespaces)
logger.debug("Controller will update methods")
sessionStore.setSession(session)
- try await networkingInteractor.request(.wcSessionUpdate(SessionType.UpdateParams(namespaces: namespaces)), onTopic: topic)
+ let request = RPCRequest(method: protocolMethod.method, params: SessionType.UpdateParams(namespaces: namespaces))
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
}
func extend(topic: String, by ttl: Int64) async throws {
var session = try getSession(for: topic)
+ let protocolMethod = SessionExtendProtocolMethod()
try validateController(session)
try session.updateExpiry(by: ttl)
let newExpiry = Int64(session.expiryDate.timeIntervalSince1970 )
sessionStore.setSession(session)
- try await networkingInteractor.request(.wcSessionExtend(SessionType.UpdateExpiryParams(expiry: newExpiry)), onTopic: topic)
+ let request = RPCRequest(method: protocolMethod.method, params: SessionType.UpdateExpiryParams(expiry: newExpiry))
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
}
// MARK: - Handle Response
- private func handleResponse(_ response: WCResponse) {
- switch response.requestParams {
- case .sessionUpdate(let payload):
- handleUpdateResponse(response: response, payload: payload)
- case .sessionExtend(let payload):
- handleUpdateExpiryResponse(response: response, payload: payload)
- default:
- break
- }
+ private func setupSubscriptions() {
+ networkingInteractor.responseSubscription(on: SessionUpdateProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ handleUpdateResponse(payload: payload)
+ }
+ .store(in: &publishers)
+
+ networkingInteractor.responseSubscription(on: SessionExtendProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ handleUpdateExpiryResponse(payload: payload)
+ }
+ .store(in: &publishers)
}
- private func handleUpdateResponse(response: WCResponse, payload: SessionType.UpdateParams) {
- guard var session = sessionStore.getSession(forTopic: response.topic) else { return }
- switch response.result {
+ private func handleUpdateResponse(payload: ResponseSubscriptionPayload) {
+ guard var session = sessionStore.getSession(forTopic: payload.topic) else { return }
+ switch payload.response {
case .response:
do {
- try session.updateNamespaces(payload.namespaces, timestamp: response.timestamp)
+ try session.updateNamespaces(payload.request.namespaces, timestamp: payload.id.timestamp)
if sessionStore.setSessionIfNewer(session) {
onNamespacesUpdate?(session.topic, session.namespaces)
@@ -76,12 +84,12 @@ final class ControllerSessionStateMachine {
}
}
- private func handleUpdateExpiryResponse(response: WCResponse, payload: SessionType.UpdateExpiryParams) {
- guard var session = sessionStore.getSession(forTopic: response.topic) else { return }
- switch response.result {
+ private func handleUpdateExpiryResponse(payload: ResponseSubscriptionPayload) {
+ guard var session = sessionStore.getSession(forTopic: payload.topic) else { return }
+ switch payload.response {
case .response:
do {
- try session.updateExpiry(to: payload.expiry)
+ try session.updateExpiry(to: payload.request.expiry)
sessionStore.setSession(session)
onExtend?(session.topic, session.expiryDate)
} catch {
diff --git a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift b/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift
deleted file mode 100644
index 9ad8956b7..000000000
--- a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import Foundation
-import WalletConnectKMS
-import WalletConnectPairing
-
-actor PairEngine {
- private let networkingInteractor: NetworkInteracting
- private let kms: KeyManagementServiceProtocol
- private let pairingStore: WCPairingStorage
-
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- pairingStore: WCPairingStorage) {
- self.networkingInteractor = networkingInteractor
- self.kms = kms
- self.pairingStore = pairingStore
- }
-
- func pair(_ uri: WalletConnectURI) async throws {
- guard !hasPairing(for: uri.topic) else {
- throw WalletConnectError.pairingAlreadyExist
- }
- var pairing = WCPairing(uri: uri)
- let symKey = try SymmetricKey(hex: uri.symKey)
- try kms.setSymmetricKey(symKey, for: pairing.topic)
- pairing.activate()
- pairingStore.setPairing(pairing)
- try await networkingInteractor.subscribe(topic: pairing.topic)
- }
-
- func hasPairing(for topic: String) -> Bool {
- return pairingStore.hasPairing(forTopic: topic)
- }
-}
diff --git a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
index 0e4e22f66..dbde21388 100644
--- a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
+++ b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
@@ -1,12 +1,10 @@
import Foundation
import WalletConnectUtils
import WalletConnectKMS
+import WalletConnectNetworking
import Combine
final class NonControllerSessionStateMachine {
- enum Errors: Error {
- case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode)
- }
var onNamespacesUpdate: ((String, [String: SessionNamespace]) -> Void)?
var onExtend: ((String, Date) -> Void)?
@@ -25,32 +23,25 @@ final class NonControllerSessionStateMachine {
self.kms = kms
self.sessionStore = sessionStore
self.logger = logger
- setUpWCRequestHandling()
+ setupSubscriptions()
}
- private func setUpWCRequestHandling() {
- networkingInteractor.wcRequestPublisher
- .sink { [unowned self] subscriptionPayload in
- do {
- switch subscriptionPayload.wcRequest.params {
- case .sessionUpdate(let updateParams):
- try onSessionUpdateNamespacesRequest(payload: subscriptionPayload, updateParams: updateParams)
- case .sessionExtend(let updateExpiryParams):
- try onSessionUpdateExpiry(subscriptionPayload, updateExpiryParams: updateExpiryParams)
- default: return
- }
- } catch Errors.respondError(let payload, let reason) {
- respondError(payload: payload, reason: reason)
- } catch {
- logger.error("Unexpected Error: \(error.localizedDescription)")
- }
+ private func setupSubscriptions() {
+ networkingInteractor.requestSubscription(on: SessionUpdateProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionUpdateNamespacesRequest(payload: payload, updateParams: payload.request)
+ }.store(in: &publishers)
+
+ networkingInteractor.requestSubscription(on: SessionExtendProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionUpdateExpiry(payload: payload, updateExpiryParams: payload.request)
}.store(in: &publishers)
}
- private func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) {
- Task {
+ private func respondError(payload: SubscriptionPayload, reason: ReasonCode, protocolMethod: ProtocolMethod) {
+ Task(priority: .high) {
do {
- try await networkingInteractor.respondError(payload: payload, reason: reason)
+ try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod, reason: reason)
} catch {
logger.error("Respond Error failed with: \(error.localizedDescription)")
}
@@ -58,43 +49,53 @@ final class NonControllerSessionStateMachine {
}
// TODO: Update stored session namespaces
- private func onSessionUpdateNamespacesRequest(payload: WCRequestSubscriptionPayload, updateParams: SessionType.UpdateParams) throws {
+ private func onSessionUpdateNamespacesRequest(payload: SubscriptionPayload, updateParams: SessionType.UpdateParams) {
+ let protocolMethod = SessionUpdateProtocolMethod()
do {
try Namespace.validate(updateParams.namespaces)
} catch {
- throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest)
+ return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod)
}
guard var session = sessionStore.getSession(forTopic: payload.topic) else {
- throw Errors.respondError(payload: payload, reason: .noSessionForTopic)
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
}
guard session.peerIsController else {
- throw Errors.respondError(payload: payload, reason: .unauthorizedUpdateRequest)
+ return respondError(payload: payload, reason: .unauthorizedUpdateRequest, protocolMethod: protocolMethod)
}
do {
- try session.updateNamespaces(updateParams.namespaces, timestamp: payload.timestamp)
+ try session.updateNamespaces(updateParams.namespaces, timestamp: payload.id.timestamp)
} catch {
- throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest)
+ return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod)
}
sessionStore.setSession(session)
- networkingInteractor.respondSuccess(for: payload)
+
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
+ }
+
onNamespacesUpdate?(session.topic, updateParams.namespaces)
}
- private func onSessionUpdateExpiry(_ payload: WCRequestSubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) throws {
+ private func onSessionUpdateExpiry(payload: SubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) {
+ let protocolMethod = SessionExtendProtocolMethod()
let topic = payload.topic
guard var session = sessionStore.getSession(forTopic: topic) else {
- throw Errors.respondError(payload: payload, reason: .noSessionForTopic)
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
}
guard session.peerIsController else {
- throw Errors.respondError(payload: payload, reason: .unauthorizedExtendRequest)
+ return respondError(payload: payload, reason: .unauthorizedExtendRequest, protocolMethod: protocolMethod)
}
do {
try session.updateExpiry(to: updateExpiryParams.expiry)
} catch {
- throw Errors.respondError(payload: payload, reason: .invalidExtendRequest)
+ return respondError(payload: payload, reason: .invalidExtendRequest, protocolMethod: protocolMethod)
}
sessionStore.setSession(session)
- networkingInteractor.respondSuccess(for: payload)
+
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
+ }
+
onExtend?(session.topic, session.expiryDate)
}
}
diff --git a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift b/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift
deleted file mode 100644
index 321bb5a62..000000000
--- a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-import Foundation
-import WalletConnectUtils
-
-protocol JsonRpcHistoryRecording {
- func get(id: Int64) -> JsonRpcRecord?
- func set(topic: String, request: WCRequest, chainId: String?) throws
- func delete(topic: String)
- func resolve(response: JsonRpcResult) throws -> JsonRpcRecord
- func exist(id: Int64) -> Bool
-}
-// TODO -remove and use jsonrpc history only from utils
-class JsonRpcHistory: JsonRpcHistoryRecording {
- let storage: CodableStore
- let logger: ConsoleLogging
-
- init(logger: ConsoleLogging, keyValueStore: CodableStore) {
- self.logger = logger
- self.storage = keyValueStore
- }
-
- func get(id: Int64) -> JsonRpcRecord? {
- try? storage.get(key: "\(id)")
- }
-
- 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, chainId: chainId)
- storage.set(record, forKey: "\(request.id)")
- }
-
- func delete(topic: String) {
- storage.getAll().forEach { record in
- if record.topic == topic {
- storage.delete(forKey: "\(record.id)")
- }
- }
- }
-
- func resolve(response: JsonRpcResult) throws -> JsonRpcRecord {
- logger.debug("Resolving JSON-RPC response - ID: \(response.id)")
- 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
- storage.set(record, forKey: "\(record.id)")
- return record
- }
- }
-
- func exist(id: Int64) -> Bool {
- return (try? storage.get(key: "\(id)")) != nil
- }
-
- public func getPending() -> [JsonRpcRecord] {
- storage.getAll().filter {$0.response == nil}
- }
-}
diff --git a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift b/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift
deleted file mode 100644
index edfb14dc8..000000000
--- a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Foundation
-import WalletConnectUtils
-
-struct JsonRpcRecord: Codable {
- let id: Int64
- let topic: String
- let request: Request
- var response: JsonRpcResult?
- let chainId: String?
-
- struct Request: Codable {
- let method: WCRequest.Method
- let params: WCRequest.Params
- }
-}
diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift
deleted file mode 100644
index 3389771b3..000000000
--- a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift
+++ /dev/null
@@ -1,259 +0,0 @@
-import Foundation
-import Combine
-import WalletConnectUtils
-import WalletConnectKMS
-
-protocol NetworkInteracting: AnyObject {
- var transportConnectionPublisher: AnyPublisher {get}
- var wcRequestPublisher: AnyPublisher {get}
- var responsePublisher: AnyPublisher {get}
- /// Completes when request sent from a networking client
- func request(_ wcMethod: WCMethod, onTopic topic: String) async throws
- /// Completes with an acknowledgement from the relay network
- func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void))
- /// Completes with a peer response
- func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?)
- func respond(topic: String, response: JsonRpcResult, tag: Int) async throws
- func respondSuccess(payload: WCRequestSubscriptionPayload) async throws
- func respondSuccess(for payload: WCRequestSubscriptionPayload)
- func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws
- func subscribe(topic: String) async throws
- func unsubscribe(topic: String)
-}
-
-extension NetworkInteracting {
- func request(_ wcMethod: WCMethod, onTopic topic: String) {
- requestPeerResponse(wcMethod, onTopic: topic, completion: nil)
- }
-}
-
-class NetworkInteractor: NetworkInteracting {
-
- private var publishers = Set()
-
- private var relayClient: NetworkRelaying
- private let serializer: Serializing
- private let jsonRpcHistory: JsonRpcHistoryRecording
-
- private let transportConnectionPublisherSubject = PassthroughSubject()
- private let responsePublisherSubject = PassthroughSubject()
- private let wcRequestPublisherSubject = PassthroughSubject()
-
- var transportConnectionPublisher: AnyPublisher {
- transportConnectionPublisherSubject.eraseToAnyPublisher()
- }
- var wcRequestPublisher: AnyPublisher {
- wcRequestPublisherSubject.eraseToAnyPublisher()
- }
- var responsePublisher: AnyPublisher {
- responsePublisherSubject.eraseToAnyPublisher()
- }
-
- let logger: ConsoleLogging
-
- init(relayClient: NetworkRelaying,
- serializer: Serializing,
- logger: ConsoleLogging,
- jsonRpcHistory: JsonRpcHistoryRecording) {
- self.relayClient = relayClient
- self.serializer = serializer
- self.logger = logger
- self.jsonRpcHistory = jsonRpcHistory
- setUpPublishers()
- }
-
- func request(_ wcMethod: WCMethod, onTopic topic: String) async throws {
- try await request(topic: topic, payload: wcMethod.asRequest())
- }
-
- /// Completes when networking client sends a request
- func request(topic: String, payload: WCRequest) async throws {
- try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload))
- let message = try serializer.serialize(topic: topic, encodable: payload)
- let prompt = shouldPrompt(payload.method)
- try await relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt)
- }
-
- func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) {
- let payload = wcMethod.asRequest()
- do {
- try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload))
- let message = try serializer.serialize(topic: topic, encodable: payload)
- let prompt = shouldPrompt(payload.method)
- relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { [weak self] error in
- guard let self = self else {return}
- if let error = error {
- self.logger.error(error)
- } else {
- var cancellable: AnyCancellable!
- cancellable = self.responsePublisher
- .filter {$0.result.id == payload.id}
- .sink { (response) in
- cancellable.cancel()
- self.logger.debug("WC Relay - received response on topic: \(topic)")
- switch response.result {
- case .response(let response):
- completion?(.success(response))
- case .error(let error):
- self.logger.debug("Request error: \(error)")
- completion?(.failure(error))
- }
- }
- }
- }
- } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) {
- logger.info("Info: Json Rpc Duplicate Detected")
- } catch {
- logger.error(error)
- }
- }
-
- /// Completes with an acknowledgement from the relay network.
- /// completes with error if networking client was not able to send a message
- func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) {
- do {
- let payload = wcMethod.asRequest()
- try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload))
- let message = try serializer.serialize(topic: topic, encodable: payload)
- let prompt = shouldPrompt(payload.method)
- relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { error in
- completion(error)
- }
- } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) {
- logger.info("Info: Json Rpc Duplicate Detected")
- } catch {
- logger.error(error)
- }
- }
-
- func respond(topic: String, response: JsonRpcResult, tag: Int) async throws {
- _ = try jsonRpcHistory.resolve(response: response)
-
- let message = try serializer.serialize(topic: topic, encodable: response.value)
- logger.debug("Responding....topic: \(topic)")
-
- do {
- try await relayClient.publish(topic: topic, payload: message, tag: tag, prompt: false)
- } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) {
- logger.info("Info: Json Rpc Duplicate Detected")
- }
- }
-
- func respondSuccess(payload: WCRequestSubscriptionPayload) async throws {
- let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(true))
- try await respond(topic: payload.topic, response: JsonRpcResult.response(response), tag: payload.wcRequest.responseTag)
- }
-
- func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws {
- let response = JSONRPCErrorResponse(id: payload.wcRequest.id, error: JSONRPCErrorResponse.Error(code: reason.code, message: reason.message))
- try await respond(topic: payload.topic, response: JsonRpcResult.error(response), tag: payload.wcRequest.responseTag)
- }
-
- // TODO: Move to async
- func respondSuccess(for payload: WCRequestSubscriptionPayload) {
- Task(priority: .background) {
- do {
- try await respondSuccess(payload: payload)
- } catch {
- self.logger.error("Respond Success failed with: \(error.localizedDescription)")
- }
- }
- }
-
- func subscribe(topic: String) async throws {
- try await relayClient.subscribe(topic: topic)
- }
-
- func unsubscribe(topic: String) {
- relayClient.unsubscribe(topic: topic) { [weak self] error in
- if let error = error {
- self?.logger.error(error)
- } else {
- self?.jsonRpcHistory.delete(topic: topic)
- }
- }
- }
-
- // MARK: - Private
-
- private func setUpPublishers() {
- relayClient.socketConnectionStatusPublisher.sink { [weak self] status in
- if status == .connected {
- self?.transportConnectionPublisherSubject.send()
- }
- }.store(in: &publishers)
-
- relayClient.messagePublisher.sink { [weak self] (topic, message) in
- self?.manageSubscription(topic, message)
- }
- .store(in: &publishers)
- }
-
- private func manageSubscription(_ topic: String, _ encodedEnvelope: String) {
- if let deserializedJsonRpcRequest: WCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
- handleWCRequest(topic: topic, request: deserializedJsonRpcRequest)
- } else if let deserializedJsonRpcResponse: JSONRPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
- handleJsonRpcResponse(response: deserializedJsonRpcResponse)
- } else if let deserializedJsonRpcError: JSONRPCErrorResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
- handleJsonRpcErrorResponse(response: deserializedJsonRpcError)
- } else {
- logger.warn("Warning: Networking Interactor - Received unknown object type from networking relay")
- }
- }
-
- private func handleWCRequest(topic: String, request: WCRequest) {
- do {
- try jsonRpcHistory.set(topic: topic, request: request, chainId: getChainId(request))
- let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: request)
- wcRequestPublisherSubject.send(payload)
- } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) {
- logger.info("Info: Json Rpc Duplicate Detected")
- } catch {
- logger.error(error)
- }
- }
-
- private func handleJsonRpcResponse(response: JSONRPCResponse) {
- do {
- let record = try jsonRpcHistory.resolve(response: JsonRpcResult.response(response))
- let wcResponse = WCResponse(
- topic: record.topic,
- chainId: record.chainId,
- requestMethod: record.request.method,
- requestParams: record.request.params,
- result: JsonRpcResult.response(response))
- responsePublisherSubject.send(wcResponse)
- } catch {
- logger.info("Info: \(error.localizedDescription)")
- }
- }
-
- private func handleJsonRpcErrorResponse(response: JSONRPCErrorResponse) {
- do {
- let record = try jsonRpcHistory.resolve(response: JsonRpcResult.error(response))
- let wcResponse = WCResponse(
- topic: record.topic,
- chainId: record.chainId,
- requestMethod: record.request.method,
- requestParams: record.request.params,
- result: JsonRpcResult.error(response))
- responsePublisherSubject.send(wcResponse)
- } catch {
- logger.info("Info: \(error.localizedDescription)")
- }
- }
-
- private func shouldPrompt(_ method: WCRequest.Method) -> Bool {
- switch method {
- case .sessionRequest:
- return true
- default:
- return false
- }
- }
-
- func getChainId(_ request: WCRequest) -> String? {
- guard case let .sessionRequest(payload) = request.params else {return nil}
- return payload.chainId.absoluteString
- }
-}
diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift
deleted file mode 100644
index 5574cf826..000000000
--- a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import Foundation
-import WalletConnectRelay
-import Combine
-
-extension RelayClient: NetworkRelaying {}
-
-protocol NetworkRelaying {
- var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { get }
- var socketConnectionStatusPublisher: AnyPublisher { get }
- func connect() throws
- func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
- func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws
- /// - returns: request id
- func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void))
- func subscribe(topic: String, completion: @escaping (Error?) -> Void)
- func subscribe(topic: String) async throws
- /// - returns: request id
- func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void))
-}
diff --git a/Sources/WalletConnectSign/Reason.swift b/Sources/WalletConnectSign/Reason.swift
deleted file mode 100644
index d49aab825..000000000
--- a/Sources/WalletConnectSign/Reason.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-// TODO: Refactor into codes. Reference: https://docs.walletconnect.com/2.0/protocol/reason-codes
-public struct Reason {
-
- public let code: Int
- public let message: String
-
- public init(code: Int, message: String) {
- self.code = code
- self.message = message
- }
-}
diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift
index da55722b2..5cca66797 100644
--- a/Sources/WalletConnectSign/Request.swift
+++ b/Sources/WalletConnectSign/Request.swift
@@ -1,14 +1,15 @@
import Foundation
+import JSONRPC
import WalletConnectUtils
public struct Request: Codable, Equatable {
- public let id: Int64
+ public let id: RPCID
public let topic: String
public let method: String
public let params: AnyCodable
public let chainId: Blockchain
- internal init(id: Int64, topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
+ internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
self.id = id
self.topic = topic
self.method = method
@@ -17,10 +18,10 @@ public struct Request: Codable, Equatable {
}
public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
- self.init(id: JsonRpcID.generate(), topic: topic, method: method, params: params, chainId: chainId)
+ self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId)
}
- internal init(id: Int64, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable {
+ internal init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable {
self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId)
}
}
diff --git a/Sources/WalletConnectSign/Response.swift b/Sources/WalletConnectSign/Response.swift
index 8dd4a0f0f..aaaaf02fa 100644
--- a/Sources/WalletConnectSign/Response.swift
+++ b/Sources/WalletConnectSign/Response.swift
@@ -1,8 +1,10 @@
import Foundation
+import JSONRPC
import WalletConnectUtils
public struct Response: Codable {
+ public let id: RPCID
public let topic: String
public let chainId: String?
- public let result: JsonRpcResult
+ public let result: RPCResult
}
diff --git a/Sources/WalletConnectSign/Services/App/AppProposeService.swift b/Sources/WalletConnectSign/Services/App/AppProposeService.swift
new file mode 100644
index 000000000..f24db9b39
--- /dev/null
+++ b/Sources/WalletConnectSign/Services/App/AppProposeService.swift
@@ -0,0 +1,41 @@
+import Foundation
+import JSONRPC
+import WalletConnectNetworking
+import WalletConnectKMS
+import WalletConnectUtils
+
+final class AppProposeService {
+ private let metadata: AppMetadata
+ private let networkingInteractor: NetworkInteracting
+ private let kms: KeyManagementServiceProtocol
+ private let logger: ConsoleLogging
+
+ init(
+ metadata: AppMetadata,
+ networkingInteractor: NetworkInteracting,
+ kms: KeyManagementServiceProtocol,
+ logger: ConsoleLogging
+ ) {
+ self.metadata = metadata
+ self.networkingInteractor = networkingInteractor
+ self.kms = kms
+ self.logger = logger
+ }
+
+ func propose(pairingTopic: String, namespaces: [String: ProposalNamespace], relay: RelayProtocolOptions) async throws {
+ logger.debug("Propose Session on topic: \(pairingTopic)")
+ try Namespace.validate(namespaces)
+ let protocolMethod = SessionProposeProtocolMethod()
+ let publicKey = try! kms.createX25519KeyPair()
+ let proposer = Participant(
+ publicKey: publicKey.hexRepresentation,
+ metadata: metadata)
+ let proposal = SessionProposal(
+ relays: [relay],
+ proposer: proposer,
+ requiredNamespaces: namespaces)
+
+ let request = RPCRequest(method: protocolMethod.method, params: proposal)
+ try await networkingInteractor.requestNetworkAck(request, topic: pairingTopic, protocolMethod: protocolMethod)
+ }
+}
diff --git a/Sources/WalletConnectSign/Services/DisconnectService.swift b/Sources/WalletConnectSign/Services/DisconnectService.swift
new file mode 100644
index 000000000..a0fab00e8
--- /dev/null
+++ b/Sources/WalletConnectSign/Services/DisconnectService.swift
@@ -0,0 +1,24 @@
+import Foundation
+
+class DisconnectService {
+ enum Errors: Error {
+ case sessionForTopicNotFound
+ }
+
+ private let deleteSessionService: DeleteSessionService
+ private let sessionStorage: WCSessionStorage
+
+ init(deleteSessionService: DeleteSessionService,
+ sessionStorage: WCSessionStorage) {
+ self.deleteSessionService = deleteSessionService
+ self.sessionStorage = sessionStorage
+ }
+
+ func disconnect(topic: String) async throws {
+ if sessionStorage.hasSession(forTopic: topic) {
+ try await deleteSessionService.delete(topic: topic)
+ } else {
+ throw Errors.sessionForTopicNotFound
+ }
+ }
+}
diff --git a/Sources/WalletConnectSign/Services/SessionPingService.swift b/Sources/WalletConnectSign/Services/SessionPingService.swift
new file mode 100644
index 000000000..8bc03dbad
--- /dev/null
+++ b/Sources/WalletConnectSign/Services/SessionPingService.swift
@@ -0,0 +1,36 @@
+import Foundation
+import WalletConnectPairing
+import WalletConnectUtils
+import WalletConnectNetworking
+
+class SessionPingService {
+ private let sessionStorage: WCSessionStorage
+ private let pingRequester: PingRequester
+ private let pingResponder: PingResponder
+ private let pingResponseSubscriber: PingResponseSubscriber
+
+ var onResponse: ((String)->Void)? {
+ get {
+ return pingResponseSubscriber.onResponse
+ }
+ set {
+ pingResponseSubscriber.onResponse = newValue
+ }
+ }
+
+ init(
+ sessionStorage: WCSessionStorage,
+ networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging) {
+ let protocolMethod = SessionPingProtocolMethod()
+ self.sessionStorage = sessionStorage
+ self.pingRequester = PingRequester(networkingInteractor: networkingInteractor, method: protocolMethod)
+ self.pingResponder = PingResponder(networkingInteractor: networkingInteractor, method: protocolMethod, logger: logger)
+ self.pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, method: protocolMethod, logger: logger)
+ }
+
+ func ping(topic: String) async throws {
+ guard sessionStorage.hasSession(forTopic: topic) else { return }
+ try await pingRequester.ping(topic: topic)
+ }
+}
diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift
index 42685d660..d61e7b0ff 100644
--- a/Sources/WalletConnectSign/Sign/Sign.swift
+++ b/Sources/WalletConnectSign/Sign/Sign.swift
@@ -1,10 +1,15 @@
import Foundation
+import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectRelay
-import Combine
+import WalletConnectNetworking
+import WalletConnectPairing
public typealias Account = WalletConnectUtils.Account
public typealias Blockchain = WalletConnectUtils.Blockchain
+public typealias Reason = WalletConnectNetworking.Reason
+public typealias RPCID = JSONRPC.RPCID
/// Sign instatnce wrapper
///
@@ -21,15 +26,14 @@ public class Sign {
/// Sign client instance
public static var instance: SignClient = {
- guard let metadata = Sign.metadata else {
- fatalError("Error - you must call Sign.configure(_:) before accessing the shared instance.")
- }
return SignClientFactory.create(
- metadata: metadata,
- relayClient: Relay.instance
+ metadata: Sign.metadata ?? Pair.metadata,
+ pairingClient: Pair.instance as! PairingClient,
+ networkingClient: Networking.instance as! NetworkingInteractor
)
}()
+ @available(*, deprecated, message: "Remove after clients migration")
private static var metadata: AppMetadata?
private init() { }
@@ -37,7 +41,9 @@ public class Sign {
/// Sign instance config method
/// - Parameters:
/// - metadata: App metadata
+ @available(*, deprecated, message: "Use Pair.configure(metadata:) instead")
static public func configure(metadata: AppMetadata) {
+ Pair.configure(metadata: metadata)
Sign.metadata = metadata
}
}
diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift
index befbca947..27a156a63 100644
--- a/Sources/WalletConnectSign/Sign/SignClient.swift
+++ b/Sources/WalletConnectSign/Sign/SignClient.swift
@@ -1,8 +1,10 @@
import Foundation
-import WalletConnectRelay
+import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectKMS
-import Combine
+import WalletConnectNetworking
+import WalletConnectPairing
/// WalletConnect Sign Client
///
@@ -10,6 +12,9 @@ import Combine
///
/// Access via `Sign.instance`
public final class SignClient {
+ enum Errors: Error {
+ case sessionForTopicNotFound
+ }
// MARK: - Public Properties
@@ -81,20 +86,29 @@ public final class SignClient {
sessionExtendPublisherSubject.eraseToAnyPublisher()
}
+ /// Publisher that sends session topic when session ping received
+ ///
+ /// Event will be emited on controller and non-controller clients.
+ public var pingResponsePublisher: AnyPublisher {
+ pingResponsePublisherSubject.eraseToAnyPublisher()
+ }
+
/// An object that loggs SDK's errors and info messages
public let logger: ConsoleLogging
// MARK: - Private properties
- private let relayClient: RelayClient
- private let pairingEngine: PairingEngine
- private let pairEngine: PairEngine
+ private let pairingClient: PairingClient
+ private let networkingClient: NetworkingInteractor
private let sessionEngine: SessionEngine
private let approveEngine: ApproveEngine
private let disconnectService: DisconnectService
+ private let pairingPingService: PairingPingService
+ private let sessionPingService: SessionPingService
private let nonControllerSessionStateMachine: NonControllerSessionStateMachine
private let controllerSessionStateMachine: ControllerSessionStateMachine
- private let history: JsonRpcHistory
+ private let appProposeService: AppProposeService
+ private let history: RPCHistory
private let cleanupService: CleanupService
private let sessionProposalPublisherSubject = PassthroughSubject()
@@ -107,34 +121,40 @@ public final class SignClient {
private let sessionUpdatePublisherSubject = PassthroughSubject<(sessionTopic: String, namespaces: [String: SessionNamespace]), Never>()
private let sessionEventPublisherSubject = PassthroughSubject<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never>()
private let sessionExtendPublisherSubject = PassthroughSubject<(sessionTopic: String, date: Date), Never>()
+ private let pingResponsePublisherSubject = PassthroughSubject()
private var publishers = Set()
// MARK: - Initialization
init(logger: ConsoleLogging,
- relayClient: RelayClient,
- pairingEngine: PairingEngine,
- pairEngine: PairEngine,
+ networkingClient: NetworkingInteractor,
sessionEngine: SessionEngine,
approveEngine: ApproveEngine,
+ pairingPingService: PairingPingService,
+ sessionPingService: SessionPingService,
nonControllerSessionStateMachine: NonControllerSessionStateMachine,
controllerSessionStateMachine: ControllerSessionStateMachine,
+ appProposeService: AppProposeService,
disconnectService: DisconnectService,
- history: JsonRpcHistory,
- cleanupService: CleanupService
+ history: RPCHistory,
+ cleanupService: CleanupService,
+ pairingClient: PairingClient
) {
self.logger = logger
- self.relayClient = relayClient
- self.pairingEngine = pairingEngine
- self.pairEngine = pairEngine
+ self.networkingClient = networkingClient
self.sessionEngine = sessionEngine
self.approveEngine = approveEngine
+ self.pairingPingService = pairingPingService
+ self.sessionPingService = sessionPingService
self.nonControllerSessionStateMachine = nonControllerSessionStateMachine
self.controllerSessionStateMachine = controllerSessionStateMachine
+ self.appProposeService = appProposeService
self.history = history
self.cleanupService = cleanupService
self.disconnectService = disconnectService
+ self.pairingClient = pairingClient
+
setUpConnectionObserving()
setUpEnginesCallbacks()
}
@@ -147,22 +167,43 @@ public final class SignClient {
/// - requiredNamespaces: required namespaces for a session
/// - topic: Optional parameter - use it if you already have an established pairing with peer client.
/// - Returns: Pairing URI that should be shared with responder out of bound. Common way is to present it as a QR code. Pairing URI will be nil if you are going to establish a session on existing Pairing and `topic` function parameter was provided.
+ @available(*, deprecated, message: "use Pair.instance.create() and connect(requiredNamespaces: [String: ProposalNamespace]): instead")
public func connect(requiredNamespaces: [String: ProposalNamespace], topic: String? = nil) async throws -> WalletConnectURI? {
logger.debug("Connecting Application")
if let topic = topic {
- guard let pairing = pairingEngine.getSettledPairing(for: topic) else {
- throw WalletConnectError.noPairingMatchingTopic(topic)
- }
- logger.debug("Proposing session on existing pairing")
- try await pairingEngine.propose(pairingTopic: topic, namespaces: requiredNamespaces, relay: pairing.relay)
+ try pairingClient.validatePairingExistance(topic)
+ try await appProposeService.propose(
+ pairingTopic: topic,
+ namespaces: requiredNamespaces,
+ relay: RelayProtocolOptions(protocol: "irn", data: nil)
+ )
return nil
} else {
- let pairingURI = try await pairingEngine.create()
- try await pairingEngine.propose(pairingTopic: pairingURI.topic, namespaces: requiredNamespaces, relay: pairingURI.relay)
+ let pairingURI = try await pairingClient.create()
+ try await appProposeService.propose(
+ pairingTopic: pairingURI.topic,
+ namespaces: requiredNamespaces,
+ relay: RelayProtocolOptions(protocol: "irn", data: nil)
+ )
return pairingURI
}
}
+ /// For a dApp to propose a session to a wallet.
+ /// Function will propose a session on existing pairing.
+ /// - Parameters:
+ /// - requiredNamespaces: required namespaces for a session
+ /// - topic: pairing topic
+ public func connect(requiredNamespaces: [String: ProposalNamespace], topic: String) async throws {
+ logger.debug("Connecting Application")
+ try pairingClient.validatePairingExistance(topic)
+ try await appProposeService.propose(
+ pairingTopic: topic,
+ namespaces: requiredNamespaces,
+ relay: RelayProtocolOptions(protocol: "irn", data: nil)
+ )
+ }
+
/// For wallet to receive a session proposal from a dApp
/// Responder should call this function in order to accept peer's pairing and be able to subscribe for future session proposals.
/// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp.
@@ -170,11 +211,9 @@ public final class SignClient {
/// Should Error:
/// - When URI has invalid format or missing params
/// - When topic is already in use
+ @available(*, deprecated, message: "use Pair.instance.pair(uri: WalletConnectURI): instead")
public func pair(uri: WalletConnectURI) async throws {
- guard uri.api == .sign else {
- throw WalletConnectError.pairingUriWrongApiParam
- }
- try await pairEngine.pair(uri)
+ try await pairingClient.pair(uri: uri)
}
/// For a wallet to approve a session proposal.
@@ -221,30 +260,22 @@ public final class SignClient {
/// For the wallet to respond on pending dApp's JSON-RPC request
/// - Parameters:
/// - topic: Topic of the session for which the request was received.
+ /// - requestId: RPC request ID
/// - response: Your JSON RPC response or an error.
- public func respond(topic: String, response: JsonRpcResult) async throws {
- try await sessionEngine.respondSessionRequest(topic: topic, response: response)
+ public func respond(topic: String, requestId: RPCID, response: RPCResult) async throws {
+ try await sessionEngine.respondSessionRequest(topic: topic, requestId: requestId, response: response)
}
/// Ping method allows to check if peer client is online and is subscribing for given topic
///
/// Should Error:
/// - When the session topic is not found
- /// - When the response is neither result or error
///
/// - Parameters:
- /// - topic: Topic of a session or a pairing
- /// - completion: Result will be success on response or an error
- public func ping(topic: String, completion: @escaping ((Result) -> Void)) {
- if pairingEngine.hasPairing(for: topic) {
- pairingEngine.ping(topic: topic) { result in
- completion(result)
- }
- } else if sessionEngine.hasSession(for: topic) {
- sessionEngine.ping(topic: topic) { result in
- completion(result)
- }
- }
+ /// - topic: Topic of a session
+ public func ping(topic: String) async throws {
+ guard sessionEngine.hasSession(for: topic) else { throw Errors.sessionForTopicNotFound }
+ try await sessionPingService.ping(topic: topic)
}
/// For the wallet to emit an event to a dApp
@@ -280,8 +311,9 @@ public final class SignClient {
/// Query pairings
/// - Returns: All pairings
+ @available(*, deprecated, message: "use Pair.instance.getPairings(uri: WalletConnectURI): instead")
public func getPairings() -> [Pairing] {
- pairingEngine.getPairings()
+ pairingClient.getPairings()
}
/// Query pending requests
@@ -289,10 +321,9 @@ public final class SignClient {
/// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions.
public func getPendingRequests(topic: String? = nil) -> [Request] {
let pendingRequests: [Request] = history.getPending()
- .filter {$0.request.method == .sessionRequest}
.compactMap {
- guard case let .sessionRequest(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)
+ guard let request = try? $0.request.params?.get(SessionType.RequestParams.self) else { return nil }
+ return Request(id: $0.id, topic: $0.topic, method: request.request.method, params: request.request.params, chainId: request.chainId)
}
if let topic = topic {
return pendingRequests.filter {$0.topic == topic}
@@ -303,11 +334,13 @@ public final class SignClient {
/// - Parameter id: id of a wc_sessionRequest jsonrpc request
/// - Returns: json rpc record object for given id or nil if record for give id does not exits
- public func getSessionRequestRecord(id: Int64) -> WalletConnectUtils.JsonRpcRecord? {
- guard let record = history.get(id: id),
- case .sessionRequest(let payload) = record.request.params else {return nil}
- let request = WalletConnectUtils.JsonRpcRecord.Request(method: payload.request.method, params: payload.request.params)
- return WalletConnectUtils.JsonRpcRecord(id: record.id, topic: record.topic, request: request, response: record.response, chainId: record.chainId)
+ public func getSessionRequestRecord(id: RPCID) -> Request? {
+ guard
+ let record = history.get(recordId: id),
+ let request = try? record.request.params?.get(SessionType.RequestParams.self)
+ else { return nil }
+
+ return Request(id: record.id, topic: record.topic, method: record.request.method, params: request, chainId: request.chainId)
}
#if DEBUG
@@ -326,7 +359,7 @@ public final class SignClient {
sessionProposalPublisherSubject.send(proposal)
}
approveEngine.onSessionRejected = { [unowned self] proposal, reason in
- sessionRejectionPublisherSubject.send((proposal, reason.publicRepresentation()))
+ sessionRejectionPublisherSubject.send((proposal, reason))
}
approveEngine.onSessionSettle = { [unowned self] settledSession in
sessionSettlePublisherSubject.send(settledSession)
@@ -335,7 +368,7 @@ public final class SignClient {
sessionRequestPublisherSubject.send(sessionRequest)
}
sessionEngine.onSessionDelete = { [unowned self] topic, reason in
- sessionDeletePublisherSubject.send((topic, reason.publicRepresentation()))
+ sessionDeletePublisherSubject.send((topic, reason))
}
controllerSessionStateMachine.onNamespacesUpdate = { [unowned self] topic, namespaces in
sessionUpdatePublisherSubject.send((topic, namespaces))
@@ -355,10 +388,16 @@ public final class SignClient {
sessionEngine.onSessionResponse = { [unowned self] response in
sessionResponsePublisherSubject.send(response)
}
+ pairingPingService.onResponse = { [unowned self] topic in
+ pingResponsePublisherSubject.send(topic)
+ }
+ sessionPingService.onResponse = { [unowned self] topic in
+ pingResponsePublisherSubject.send(topic)
+ }
}
private func setUpConnectionObserving() {
- relayClient.socketConnectionStatusPublisher.sink { [weak self] status in
+ networkingClient.socketConnectionStatusPublisher.sink { [weak self] status in
self?.socketConnectionStatusPublisherSubject.send(status)
}.store(in: &publishers)
}
diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
index 9a0412ad4..a1c086783 100644
--- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift
+++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
@@ -3,6 +3,7 @@ import WalletConnectRelay
import WalletConnectUtils
import WalletConnectKMS
import WalletConnectPairing
+import WalletConnectNetworking
public struct SignClientFactory {
@@ -15,44 +16,45 @@ public struct SignClientFactory {
/// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults
///
/// WalletConnect Client is not a singleton but once you create an instance, you should not deinitialize it. Usually only one instance of a client is required in the application.
- public static func create(metadata: AppMetadata, relayClient: RelayClient) -> SignClient {
+ public static func create(metadata: AppMetadata, pairingClient: PairingClient, networkingClient: NetworkingInteractor) -> SignClient {
let logger = ConsoleLogger(loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
- return SignClientFactory.create(metadata: metadata, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, relayClient: relayClient)
+ return SignClientFactory.create(metadata: metadata, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, pairingClient: pairingClient, networkingClient: networkingClient)
}
- static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> SignClient {
+ static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, pairingClient: PairingClient, networkingClient: NetworkingInteractor) -> SignClient {
let kms = KeyManagementService(keychain: keychainStorage)
- let serializer = Serializer(kms: kms)
- let history = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue))
- let networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history)
+ let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue)))
let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.sessions.rawValue)))
let sessionToPairingTopic = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.sessionToPairingTopic.rawValue)
- let proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.proposals.rawValue)
- let pairingEngine = PairingEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore, metadata: metadata, logger: logger)
- let sessionEngine = SessionEngine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- let pairEngine = PairEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore)
- let approveEngine = ApproveEngine(networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore)
+ let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.proposals.rawValue)
+ let sessionEngine = SessionEngine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore)
let cleanupService = CleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic)
- let deletePairingService = DeletePairingService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore, logger: logger)
- let deleteSessionService = DeleteSessionService(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- let disconnectService = DisconnectService(deletePairingService: deletePairingService, deleteSessionService: deleteSessionService, pairingStorage: pairingStore, sessionStorage: sessionStore)
+ let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore)
+ let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger)
+ let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger)
+ let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger)
let client = SignClient(
logger: logger,
- relayClient: relayClient,
- pairingEngine: pairingEngine,
- pairEngine: pairEngine,
+ networkingClient: networkingClient,
sessionEngine: sessionEngine,
approveEngine: approveEngine,
+ pairingPingService: pairingPingService,
+ sessionPingService: sessionPingService,
nonControllerSessionStateMachine: nonControllerSessionStateMachine,
- controllerSessionStateMachine: controllerSessionStateMachine, disconnectService: disconnectService,
- history: history,
- cleanupService: cleanupService
+ controllerSessionStateMachine: controllerSessionStateMachine,
+ appProposeService: appProposerService,
+ disconnectService: disconnectService,
+ history: rpcHistory,
+ cleanupService: cleanupService,
+ pairingClient: pairingClient
)
return client
}
diff --git a/Sources/WalletConnectSign/StorageDomainIdentifiers.swift b/Sources/WalletConnectSign/StorageDomainIdentifiers.swift
index 1e0c2a799..89a0b52a1 100644
--- a/Sources/WalletConnectSign/StorageDomainIdentifiers.swift
+++ b/Sources/WalletConnectSign/StorageDomainIdentifiers.swift
@@ -1,7 +1,6 @@
import Foundation
enum StorageDomainIdentifiers: String {
- case jsonRpcHistory = "com.walletconnect.sdk.wc_jsonRpcHistoryRecord"
case pairings = "com.walletconnect.sdk.pairingSequences"
case sessions = "com.walletconnect.sdk.sessionSequences"
case proposals = "com.walletconnect.sdk.sessionProposals"
diff --git a/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift b/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift
deleted file mode 100644
index 91efa9654..000000000
--- a/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Foundation
-import WalletConnectUtils
-
-struct WCRequestSubscriptionPayload: Codable {
- let topic: String
- let wcRequest: WCRequest
-
- var timestamp: Date {
- return JsonRpcID.timestamp(from: wcRequest.id)
- }
-}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionDeleteProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionDeleteProtocolMethod.swift
new file mode 100644
index 000000000..927f100af
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionDeleteProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionDeleteProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionDelete"
+
+ let requestConfig = RelayConfig(tag: 1112, prompt: false, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 1113, prompt: false, ttl: 86400)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionEventProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionEventProtocolMethod.swift
new file mode 100644
index 000000000..bee208ed4
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionEventProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionEventProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionEvent"
+
+ let requestConfig = RelayConfig(tag: 1110, prompt: true, ttl: 300)
+
+ let responseConfig = RelayConfig(tag: 1111, prompt: false, ttl: 300)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionExtendProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionExtendProtocolMethod.swift
new file mode 100644
index 000000000..b77e5b9ff
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionExtendProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionExtendProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionExtend"
+
+ let requestConfig = RelayConfig(tag: 1106, prompt: false, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 1107, prompt: false, ttl: 86400)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionPingProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionPingProtocolMethod.swift
new file mode 100644
index 000000000..91ff13035
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionPingProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionPingProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionPing"
+
+ let requestConfig = RelayConfig(tag: 1114, prompt: false, ttl: 30)
+
+ let responseConfig = RelayConfig(tag: 1115, prompt: false, ttl: 30)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionProposeProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionProposeProtocolMethod.swift
new file mode 100644
index 000000000..47191c4a2
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionProposeProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionProposeProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionPropose"
+
+ let requestConfig = RelayConfig(tag: 1100, prompt: true, ttl: 300)
+
+ let responseConfig = RelayConfig(tag: 1101, prompt: false, ttl: 300)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift
new file mode 100644
index 000000000..e2c0e4234
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionRequestProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionRequest"
+
+ let requestConfig = RelayConfig(tag: 1108, prompt: true, ttl: 300)
+
+ let responseConfig = RelayConfig(tag: 1109, prompt: false, ttl: 300)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionSettleProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionSettleProtocolMethod.swift
new file mode 100644
index 000000000..131560b4c
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionSettleProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionSettleProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionSettle"
+
+ let requestConfig = RelayConfig(tag: 1102, prompt: false, ttl: 300)
+
+ let responseConfig = RelayConfig(tag: 1103, prompt: false, ttl: 300)
+}
diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionUpdateProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionUpdateProtocolMethod.swift
new file mode 100644
index 000000000..eb7936b4b
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionUpdateProtocolMethod.swift
@@ -0,0 +1,10 @@
+import Foundation
+import WalletConnectNetworking
+
+struct SessionUpdateProtocolMethod: ProtocolMethod {
+ let method: String = "wc_sessionUpdate"
+
+ let requestConfig = RelayConfig(tag: 1104, prompt: false, ttl: 86400)
+
+ let responseConfig = RelayConfig(tag: 1105, prompt: false, ttl: 86400)
+}
diff --git a/Sources/WalletConnectSign/Types/ReasonCode.swift b/Sources/WalletConnectSign/Types/ReasonCode.swift
index cdc4f8db5..8ca23bd4c 100644
--- a/Sources/WalletConnectSign/Types/ReasonCode.swift
+++ b/Sources/WalletConnectSign/Types/ReasonCode.swift
@@ -1,4 +1,6 @@
-enum ReasonCode: Codable, Equatable {
+import WalletConnectNetworking
+
+enum ReasonCode: Reason, Codable, Equatable {
enum Context: String, Codable {
case pairing = "pairing"
diff --git a/Sources/WalletConnectSign/Types/Session/SessionType.swift b/Sources/WalletConnectSign/Types/Session/SessionType.swift
index 054704eae..f450e94aa 100644
--- a/Sources/WalletConnectSign/Types/Session/SessionType.swift
+++ b/Sources/WalletConnectSign/Types/Session/SessionType.swift
@@ -1,5 +1,6 @@
import Foundation
import WalletConnectUtils
+import WalletConnectNetworking
// Internal namespace for session payloads.
internal enum SessionType {
@@ -24,7 +25,7 @@ internal enum SessionType {
typealias DeleteParams = SessionType.Reason
- struct Reason: Codable, Equatable {
+ struct Reason: Codable, Equatable, WalletConnectNetworking.Reason {
let code: Int
let message: String
@@ -64,15 +65,3 @@ internal enum SessionType {
let expiry: Int64
}
}
-
-internal extension Reason {
- func internalRepresentation() -> SessionType.Reason {
- SessionType.Reason(code: self.code, message: self.message)
- }
-}
-
-extension SessionType.Reason {
- func publicRepresentation() -> Reason {
- Reason(code: self.code, message: self.message)
- }
-}
diff --git a/Sources/WalletConnectSign/Types/WCMethod.swift b/Sources/WalletConnectSign/Types/WCMethod.swift
deleted file mode 100644
index e2002188a..000000000
--- a/Sources/WalletConnectSign/Types/WCMethod.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Foundation
-import WalletConnectPairing
-
-enum WCMethod {
- case wcPairingPing
- case wcSessionPropose(SessionType.ProposeParams)
- case wcSessionSettle(SessionType.SettleParams)
- case wcSessionUpdate(SessionType.UpdateParams)
- case wcSessionExtend(SessionType.UpdateExpiryParams)
- case wcSessionDelete(SessionType.DeleteParams)
- case wcSessionRequest(SessionType.RequestParams)
- case wcSessionPing
- case wcSessionEvent(SessionType.EventParams)
-
- func asRequest() -> WCRequest {
- switch self {
- case .wcPairingPing:
- return WCRequest(method: .pairingPing, params: .pairingPing(PairingType.PingParams()))
- case .wcSessionPropose(let proposalParams):
- return WCRequest(method: .sessionPropose, params: .sessionPropose(proposalParams))
- case .wcSessionSettle(let settleParams):
- return WCRequest(method: .sessionSettle, params: .sessionSettle(settleParams))
- case .wcSessionUpdate(let updateParams):
- return WCRequest(method: .sessionUpdate, params: .sessionUpdate(updateParams))
- case .wcSessionExtend(let updateExpiryParams):
- return WCRequest(method: .sessionExtend, params: .sessionExtend(updateExpiryParams))
- case .wcSessionDelete(let deleteParams):
- return WCRequest(method: .sessionDelete, params: .sessionDelete(deleteParams))
- case .wcSessionRequest(let payloadParams):
- return WCRequest(method: .sessionRequest, params: .sessionRequest(payloadParams))
- case .wcSessionPing:
- return WCRequest(method: .sessionPing, params: .sessionPing(SessionType.PingParams()))
- case .wcSessionEvent(let eventParams):
- return WCRequest(method: .sessionEvent, params: .sessionEvent(eventParams))
- }
- }
-}
diff --git a/Sources/WalletConnectSign/Types/WCRequest.swift b/Sources/WalletConnectSign/Types/WCRequest.swift
deleted file mode 100644
index ec9edfdf8..000000000
--- a/Sources/WalletConnectSign/Types/WCRequest.swift
+++ /dev/null
@@ -1,179 +0,0 @@
-import Foundation
-import WalletConnectPairing
-import WalletConnectUtils
-
-struct WCRequest: Codable {
- let id: Int64
- let jsonrpc: String
- let method: Method
- let params: Params
-
- enum CodingKeys: CodingKey {
- case id
- case jsonrpc
- case method
- case params
- }
-
- internal init(id: Int64 = JsonRpcID.generate(), jsonrpc: String = "2.0", method: Method, params: Params) {
- self.id = id
- self.jsonrpc = jsonrpc
- self.method = method
- self.params = params
- }
-
- init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- id = try container.decode(Int64.self, forKey: .id)
- jsonrpc = try container.decode(String.self, forKey: .jsonrpc)
- method = try container.decode(Method.self, forKey: .method)
- switch method {
- case .pairingDelete:
- let paramsValue = try container.decode(PairingType.DeleteParams.self, forKey: .params)
- params = .pairingDelete(paramsValue)
- case .pairingPing:
- let paramsValue = try container.decode(PairingType.PingParams.self, forKey: .params)
- params = .pairingPing(paramsValue)
- case .sessionPropose:
- let paramsValue = try container.decode(SessionType.ProposeParams.self, forKey: .params)
- params = .sessionPropose(paramsValue)
- case .sessionSettle:
- let paramsValue = try container.decode(SessionType.SettleParams.self, forKey: .params)
- params = .sessionSettle(paramsValue)
- case .sessionUpdate:
- let paramsValue = try container.decode(SessionType.UpdateParams.self, forKey: .params)
- params = .sessionUpdate(paramsValue)
- case .sessionDelete:
- let paramsValue = try container.decode(SessionType.DeleteParams.self, forKey: .params)
- params = .sessionDelete(paramsValue)
- case .sessionRequest:
- let paramsValue = try container.decode(SessionType.RequestParams.self, forKey: .params)
- params = .sessionRequest(paramsValue)
- case .sessionPing:
- let paramsValue = try container.decode(SessionType.PingParams.self, forKey: .params)
- params = .sessionPing(paramsValue)
- case .sessionExtend:
- let paramsValue = try container.decode(SessionType.UpdateExpiryParams.self, forKey: .params)
- params = .sessionExtend(paramsValue)
- case .sessionEvent:
- let paramsValue = try container.decode(SessionType.EventParams.self, forKey: .params)
- params = .sessionEvent(paramsValue)
- }
- }
-
- func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encode(id, forKey: .id)
- try container.encode(jsonrpc, forKey: .jsonrpc)
- try container.encode(method.rawValue, forKey: .method)
- switch params {
- case .pairingDelete(let params):
- try container.encode(params, forKey: .params)
- case .pairingPing(let params):
- try container.encode(params, forKey: .params)
- case .sessionPropose(let params):
- try container.encode(params, forKey: .params)
- case .sessionSettle(let params):
- try container.encode(params, forKey: .params)
- case .sessionUpdate(let params):
- try container.encode(params, forKey: .params)
- case .sessionExtend(let params):
- try container.encode(params, forKey: .params)
- case .sessionDelete(let params):
- try container.encode(params, forKey: .params)
- case .sessionRequest(let params):
- try container.encode(params, forKey: .params)
- case .sessionPing(let params):
- try container.encode(params, forKey: .params)
- case .sessionEvent(let params):
- try container.encode(params, forKey: .params)
- }
- }
-}
-
-extension WCRequest {
- enum Method: String, Codable {
- case pairingDelete = "wc_pairingDelete"
- case pairingPing = "wc_pairingPing"
- case sessionPropose = "wc_sessionPropose"
- case sessionSettle = "wc_sessionSettle"
- case sessionUpdate = "wc_sessionUpdate"
- case sessionExtend = "wc_sessionExtend"
- case sessionDelete = "wc_sessionDelete"
- case sessionRequest = "wc_sessionRequest"
- case sessionPing = "wc_sessionPing"
- case sessionEvent = "wc_sessionEvent"
- }
-}
-
-extension WCRequest {
- enum Params: Codable, Equatable {
- case pairingDelete(PairingType.DeleteParams)
- case pairingPing(PairingType.PingParams)
- case sessionPropose(SessionType.ProposeParams)
- case sessionSettle(SessionType.SettleParams)
- case sessionUpdate(SessionType.UpdateParams)
- case sessionExtend(SessionType.UpdateExpiryParams)
- case sessionDelete(SessionType.DeleteParams)
- case sessionRequest(SessionType.RequestParams)
- case sessionPing(SessionType.PingParams)
- case sessionEvent(SessionType.EventParams)
-
- static func == (lhs: Params, rhs: Params) -> Bool {
- switch (lhs, rhs) {
- case (.pairingDelete(let lhsParam), pairingDelete(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionPropose(let lhsParam), sessionPropose(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionSettle(let lhsParam), sessionSettle(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionUpdate(let lhsParam), sessionUpdate(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionExtend(let lhsParam), sessionExtend(let rhsParams)):
- return lhsParam == rhsParams
- case (.sessionDelete(let lhsParam), sessionDelete(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionRequest(let lhsParam), sessionRequest(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionPing(let lhsParam), sessionPing(let rhsParam)):
- return lhsParam == rhsParam
- case (.sessionEvent(let lhsParam), sessionEvent(let rhsParam)):
- return lhsParam == rhsParam
- default:
- return false
- }
- }
- }
-}
-
-extension WCRequest {
-
- var tag: Int {
- switch method {
- case .pairingDelete:
- return 1000
- case .pairingPing:
- return 1002
- case .sessionPropose:
- return 1100
- case .sessionSettle:
- return 1102
- case .sessionUpdate:
- return 1104
- case .sessionExtend:
- return 1106
- case .sessionDelete:
- return 1112
- case .sessionRequest:
- return 1108
- case .sessionPing:
- return 1114
- case .sessionEvent:
- return 1110
- }
- }
-
- var responseTag: Int {
- return tag + 1
- }
-}
diff --git a/Sources/WalletConnectSign/Types/WCResponse.swift b/Sources/WalletConnectSign/Types/WCResponse.swift
deleted file mode 100644
index b457b9ed0..000000000
--- a/Sources/WalletConnectSign/Types/WCResponse.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-import Foundation
-import WalletConnectUtils
-
-struct WCResponse: Codable {
- let topic: String
- let chainId: String?
- let requestMethod: WCRequest.Method
- let requestParams: WCRequest.Params
- let result: JsonRpcResult
-
- var timestamp: Date {
- return JsonRpcID.timestamp(from: result.id)
- }
-}
diff --git a/Sources/WalletConnectSign/WalletConnectError.swift b/Sources/WalletConnectSign/WalletConnectError.swift
index afd7f73b8..5579b1d6f 100644
--- a/Sources/WalletConnectSign/WalletConnectError.swift
+++ b/Sources/WalletConnectSign/WalletConnectError.swift
@@ -1,7 +1,6 @@
enum WalletConnectError: Error {
case pairingProposalFailed
- case pairingUriWrongApiParam
case noPairingMatchingTopic(String)
case noSessionMatchingTopic(String)
case sessionNotAcknowledged(String)
@@ -29,8 +28,6 @@ extension WalletConnectError {
switch self {
case .pairingProposalFailed:
return "Pairing proposal failed."
- case .pairingUriWrongApiParam:
- return "Pairing URI containt wrong API param"
case .noPairingMatchingTopic(let topic):
return "There is no existing pairing matching the topic: \(topic)."
case .noSessionMatchingTopic(let topic):
diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift
deleted file mode 100644
index 15f2c3de1..000000000
--- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import Foundation
-
-public struct JSONRPCErrorResponse: Error, Equatable, Codable {
- public let jsonrpc = "2.0"
- public let id: Int64
- public let error: JSONRPCErrorResponse.Error
-
- enum CodingKeys: String, CodingKey {
- case jsonrpc
- case id
- case error
- }
-
- public init(id: Int64, error: JSONRPCErrorResponse.Error) {
- self.id = id
- self.error = error
- }
-
- public struct Error: Codable, Equatable {
- public let code: Int
- public let message: String
- public init(code: Int, message: String) {
- self.code = code
- self.message = message
- }
- }
-}
diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift
deleted file mode 100644
index 2c3f57bb9..000000000
--- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import Foundation
-
-public struct JSONRPCRequest: Codable, Equatable {
-
- public let id: Int64
- public let jsonrpc: String
- public let method: String
- public let params: T
-
- public enum CodingKeys: CodingKey {
- case id
- case jsonrpc
- case method
- case params
- }
-
- public init(id: Int64 = JsonRpcID.generate(), method: String, params: T) {
- self.id = id
- self.jsonrpc = "2.0"
- self.method = method
- self.params = params
- }
-}
diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift
deleted file mode 100644
index e7dd48923..000000000
--- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import Foundation
-
-public struct JSONRPCResponse: Codable, Equatable {
- public let jsonrpc = "2.0"
- public let id: Int64
- public let result: T
-
- enum CodingKeys: String, CodingKey {
- case jsonrpc
- case id
- case result
- }
-
- public init(id: Int64, result: T) {
- self.id = id
- self.result = result
- }
-}
diff --git a/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift b/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift
deleted file mode 100644
index 611712500..000000000
--- a/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-import Foundation
-
-public enum JsonRpcResult: Codable {
- case error(JSONRPCErrorResponse)
- case response(JSONRPCResponse)
-
- public var id: Int64 {
- switch self {
- case .error(let value):
- return value.id
- case .response(let value):
- return value.id
- }
- }
-
- public var value: Codable {
- switch self {
- case .error(let value):
- return value
- case .response(let value):
- return value
- }
- }
-}
diff --git a/Sources/WalletConnectUtils/JsonRpcHistory.swift b/Sources/WalletConnectUtils/JsonRpcHistory.swift
deleted file mode 100644
index 9616b794b..000000000
--- a/Sources/WalletConnectUtils/JsonRpcHistory.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-import Foundation
-
-public class JsonRpcHistory where T: Codable&Equatable {
- enum RecordingError: Error {
- case jsonRpcDuplicateDetected
- case noJsonRpcRequestMatchingResponse
- }
- private let storage: CodableStore
- private let logger: ConsoleLogging
-
- public init(logger: ConsoleLogging, keyValueStore: CodableStore) {
- self.logger = logger
- self.storage = keyValueStore
- }
-
- public func get(id: Int64) -> JsonRpcRecord? {
- try? storage.get(key: "\(id)")
- }
-
- 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, chainId: chainId)
- storage.set(record, forKey: "\(request.id)")
- }
-
- public func delete(topic: String) {
- storage.getAll().forEach { record in
- if record.topic == topic {
- storage.delete(forKey: "\(record.id)")
- }
- }
- }
-
- public func resolve(response: JsonRpcResult) 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
- storage.set(record, forKey: "\(record.id)")
- return record
- }
- }
-
- public func exist(id: Int64) -> Bool {
- return (try? storage.get(key: "\(id)")) != nil
- }
-
- public func getPending() -> [JsonRpcRecord] {
- storage.getAll().filter {$0.response == nil}
- }
-}
diff --git a/Sources/WalletConnectUtils/JsonRpcRecord.swift b/Sources/WalletConnectUtils/JsonRpcRecord.swift
deleted file mode 100644
index 48013221f..000000000
--- a/Sources/WalletConnectUtils/JsonRpcRecord.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import Foundation
-
-public struct JsonRpcRecord: Codable {
- public let id: Int64
- public let topic: String
- public let request: Request
- public var response: JsonRpcResult?
- public let chainId: String?
-
- public init(id: Int64, topic: String, request: JsonRpcRecord.Request, response: JsonRpcResult? = nil, chainId: String?) {
- self.id = id
- self.topic = topic
- self.request = request
- self.response = response
- self.chainId = chainId
- }
-
- public struct Request: Codable {
- public let method: String
- public let params: AnyCodable
-
- public init(method: String, params: AnyCodable) {
- self.method = method
- self.params = params
- }
- }
-}
diff --git a/Sources/WalletConnectUtils/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift
similarity index 97%
rename from Sources/WalletConnectUtils/RPCHistory.swift
rename to Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift
index 255801b24..c7f382c1b 100644
--- a/Sources/WalletConnectUtils/RPCHistory.swift
+++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift
@@ -24,7 +24,7 @@ public final class RPCHistory {
private let storage: CodableStore
- public init(keyValueStore: CodableStore) {
+ init(keyValueStore: CodableStore) {
self.storage = keyValueStore
}
diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistoryFactory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistoryFactory.swift
new file mode 100644
index 000000000..817ca2d17
--- /dev/null
+++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistoryFactory.swift
@@ -0,0 +1,20 @@
+import Foundation
+
+public struct RPCHistoryFactory {
+
+ private static let networkIdentifier = "com.walletconnect.sdk.wc_jsonRpcHistoryRecord"
+ private static let relayIdentifier = "com.walletconnect.sdk.relayer_client.subscription_json_rpc_record"
+
+ public static func createForNetwork(keyValueStorage: KeyValueStorage) -> RPCHistory {
+ return RPCHistoryFactory.create(keyValueStorage: keyValueStorage, identifier: RPCHistoryFactory.networkIdentifier)
+ }
+
+ public static func createForRelay(keyValueStorage: KeyValueStorage) -> RPCHistory {
+ return RPCHistoryFactory.create(keyValueStorage: keyValueStorage, identifier: RPCHistoryFactory.relayIdentifier)
+ }
+
+ static func create(keyValueStorage: KeyValueStorage, identifier: String) -> RPCHistory {
+ let keyValueStore = CodableStore(defaults: keyValueStorage, identifier: identifier)
+ return RPCHistory(keyValueStore: keyValueStore)
+ }
+}
diff --git a/Sources/WalletConnectUtils/SetStore.swift b/Sources/WalletConnectUtils/SetStore.swift
new file mode 100644
index 000000000..a39508150
--- /dev/null
+++ b/Sources/WalletConnectUtils/SetStore.swift
@@ -0,0 +1,36 @@
+import Foundation
+
+public class SetStore: CustomStringConvertible {
+
+ private let concurrentQueue: DispatchQueue
+
+ private var store: Set = Set()
+
+ public init(label: String) {
+ self.concurrentQueue = DispatchQueue(label: label, attributes: .concurrent)
+ }
+
+ public func insert(_ element: T) {
+ concurrentQueue.async(flags: .barrier) { [weak self] in
+ self?.store.insert(element)
+ }
+ }
+
+ public func remove(_ element: T) {
+ concurrentQueue.async(flags: .barrier) { [weak self] in
+ self?.store.remove(element)
+ }
+ }
+
+ public func contains(_ element: T) -> Bool {
+ var contains = false
+ concurrentQueue.sync { [unowned self] in
+ contains = store.contains(element)
+ }
+ return contains
+ }
+
+ public var description: String {
+ return store.description
+ }
+}
diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift
index cd4fa03f7..7db772582 100644
--- a/Sources/WalletConnectUtils/WalletConnectURI.swift
+++ b/Sources/WalletConnectUtils/WalletConnectURI.swift
@@ -2,36 +2,20 @@ import Foundation
public struct WalletConnectURI: Equatable {
- public enum TargetAPI: String, CaseIterable {
- case sign
- case chat
- case auth
- }
-
public let topic: String
public let version: String
public let symKey: String
public let relay: RelayProtocolOptions
- public var api: TargetAPI {
- return apiType ?? .sign
- }
-
public var absoluteString: String {
- if let api = apiType {
- return "wc:\(api.rawValue)-\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)"
- }
return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)"
}
- private let apiType: TargetAPI?
-
- public init(topic: String, symKey: String, relay: RelayProtocolOptions, api: TargetAPI? = nil) {
+ public init(topic: String, symKey: String, relay: RelayProtocolOptions) {
self.version = "2"
self.topic = topic
self.symKey = symKey
self.relay = relay
- self.apiType = api
}
public init?(string: String) {
@@ -41,21 +25,19 @@ public struct WalletConnectURI: Equatable {
let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value }
guard
- let userString = components.user,
+ let topic = components.user,
let version = components.host,
let symKey = query?["symKey"],
let relayProtocol = query?["relay-protocol"]
else {
return nil
}
- let uriUser = Self.parseURIUser(from: userString)
let relayData = query?["relay-data"]
self.version = version
- self.topic = uriUser.topic
+ self.topic = topic
self.symKey = symKey
self.relay = RelayProtocolOptions(protocol: relayProtocol, data: relayData)
- self.apiType = uriUser.api
}
private var relayQuery: String {
@@ -73,13 +55,4 @@ public struct WalletConnectURI: Equatable {
let urlString = !string.hasPrefix("wc://") ? string.replacingOccurrences(of: "wc:", with: "wc://") : string
return URLComponents(string: urlString)
}
-
- private static func parseURIUser(from string: String) -> (topic: String, api: TargetAPI?) {
- let splits = string.split(separator: "-")
- if splits.count == 2, let apiFromPrefix = TargetAPI(rawValue: String(splits[0])) {
- return (String(splits[1]), apiFromPrefix)
- } else {
- return (string, nil)
- }
- }
}
diff --git a/Tests/AuthTests/AppRespondSubscriberTests.swift b/Tests/AuthTests/AppRespondSubscriberTests.swift
index 98b8f47dd..07492323e 100644
--- a/Tests/AuthTests/AppRespondSubscriberTests.swift
+++ b/Tests/AuthTests/AppRespondSubscriberTests.swift
@@ -1,8 +1,8 @@
import Foundation
import XCTest
@testable import Auth
-import WalletConnectUtils
-import WalletConnectNetworking
+@testable import WalletConnectUtils
+@testable import WalletConnectNetworking
@testable import WalletConnectKMS
@testable import TestingUtils
import JSONRPC
@@ -15,23 +15,24 @@ class AppRespondSubscriberTests: XCTestCase {
let defaultTimeout: TimeInterval = 0.01
let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")!
let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f")
- var messageSigner: MessageSigner!
+ var messageSigner: (MessageSigning & MessageSignatureVerifying)!
var pairingStorage: WCPairingStorageMock!
+ var pairingRegisterer: PairingRegistererMock!
override func setUp() {
networkingInteractor = NetworkingInteractorMock()
messageFormatter = SIWEMessageFormatter()
- messageSigner = MessageSigner()
- let historyStorage = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)
- rpcHistory = RPCHistory(keyValueStore: historyStorage)
+ messageSigner = MessageSignerFactory.create(projectId: "project-id")
+ rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: RuntimeKeyValueStorage())
pairingStorage = WCPairingStorageMock()
+ pairingRegisterer = PairingRegistererMock()
sut = AppRespondSubscriber(
networkingInteractor: networkingInteractor,
logger: ConsoleLoggerMock(),
rpcHistory: rpcHistory,
signatureVerifier: messageSigner,
- messageFormatter: messageFormatter,
- pairingStorage: pairingStorage)
+ pairingRegisterer: pairingRegisterer,
+ messageFormatter: messageFormatter)
}
func testMessageCompromisedFailure() {
@@ -56,7 +57,7 @@ class AppRespondSubscriberTests: XCTestCase {
let payload = CacaoPayload(params: AuthPayload.stub(nonce: "compromised nonce"), didpkh: DIDPKH(account: walletAccount))
let message = try! messageFormatter.formatMessage(from: payload)
- let cacaoSignature = try! messageSigner.sign(message: message, privateKey: prvKey)
+ let cacaoSignature = try! messageSigner.sign(message: message, privateKey: prvKey, type: .eip191)
let cacao = Cacao(h: header, p: payload, s: cacaoSignature)
@@ -64,6 +65,7 @@ class AppRespondSubscriberTests: XCTestCase {
networkingInteractor.responsePublisherSubject.send((topic, request, response))
wait(for: [messageExpectation], timeout: defaultTimeout)
+ XCTAssertTrue(pairingRegisterer.isActivateCalled)
XCTAssertEqual(result, .failure(AuthError.messageCompromised))
XCTAssertEqual(messageId, requestId)
}
diff --git a/Tests/AuthTests/CacaoSignerTests.swift b/Tests/AuthTests/CacaoSignerTests.swift
index 159eebc41..95a01a538 100644
--- a/Tests/AuthTests/CacaoSignerTests.swift
+++ b/Tests/AuthTests/CacaoSignerTests.swift
@@ -5,6 +5,8 @@ import TestingUtils
class CacaoSignerTest: XCTestCase {
+ let signer = MessageSignerFactory.create(projectId: "project-id")
+
let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a")
let message: String =
@@ -24,17 +26,13 @@ class CacaoSignerTest: XCTestCase {
- https://example.com/my-web2-claim.json
"""
- let signature = CacaoSignature(t: "eip191", s: "0x438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b")
+ let signature = CacaoSignature(t: .eip191, s: "0x438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b")
func testCacaoSign() throws {
- let signer = MessageSigner(signer: Signer())
-
- XCTAssertEqual(try signer.sign(message: message, privateKey: privateKey), signature)
+ XCTAssertEqual(try signer.sign(message: message, privateKey: privateKey, type: .eip191), signature)
}
- func testCacaoVerify() throws {
- let signer = MessageSigner(signer: Signer())
-
- try signer.verify(signature: signature, message: message, address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")
+ func testCacaoVerify() async throws {
+ try await signer.verify(signature: signature, message: message, address: "0x15bca56b6e2728aec2532df9d436bd1600e86688", chainId: "eip155:1")
}
}
diff --git a/Tests/AuthTests/EIP1271VerifierTests.swift b/Tests/AuthTests/EIP1271VerifierTests.swift
new file mode 100644
index 000000000..796c146ff
--- /dev/null
+++ b/Tests/AuthTests/EIP1271VerifierTests.swift
@@ -0,0 +1,38 @@
+import Foundation
+import XCTest
+@testable import Auth
+import JSONRPC
+import TestingUtils
+
+class EIP1271VerifierTests: XCTestCase {
+
+ let signature = Data(hex: "c1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c")
+ let message = Data(hex: "3aaa8393796c7388e4e062861d8238503de7584c977676fe9d8d551c30e11f84")
+ let address = "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71"
+ let chainId = "eip155:1"
+
+ func testSuccessVerify() async throws {
+ let response = RPCResponse(id: "1", result: "0x1626ba7e00000000000000000000000000000000000000000000000000000000")
+ let httpClient = HTTPClientMock(object: response)
+ let verifier = EIP1271Verifier(projectId: "project-id", httpClient: httpClient)
+ try await verifier.verify(
+ signature: signature,
+ message: message,
+ address: address,
+ chainId: chainId
+ )
+ }
+
+ func testFailureVerify() async throws {
+ let response = RPCResponse(id: "1", error: .internalError)
+ let httpClient = HTTPClientMock(object: response)
+ let verifier = EIP1271Verifier(projectId: "project-id", httpClient: httpClient)
+
+ await XCTAssertThrowsErrorAsync(try await verifier.verify(
+ signature: signature,
+ message: message,
+ address: address,
+ chainId: chainId
+ ))
+ }
+}
diff --git a/Tests/AuthTests/EIP191VerifierTests.swift b/Tests/AuthTests/EIP191VerifierTests.swift
new file mode 100644
index 000000000..7b683ab14
--- /dev/null
+++ b/Tests/AuthTests/EIP191VerifierTests.swift
@@ -0,0 +1,41 @@
+import Foundation
+import XCTest
+import TestingUtils
+@testable import Auth
+
+class EIP191VerifierTests: XCTestCase {
+
+ private let verifier = EIP191Verifier()
+
+ private let address = "0x15bca56b6e2728aec2532df9d436bd1600e86688"
+ private let message = "\u{19}Ethereum Signed Message:\n7Message".data(using: .utf8)!
+ private let signature = Data(hex: "66121e60cccc01fbf7fcba694a1e08ac5db35fb4ec6c045bedba7860445b95c021cad2c595f0bf68ff896964c7c02bb2f3a3e9540e8e4595c98b737ce264cc541b")
+
+ func testVerify() async throws {
+ try await verifier.verify(signature: signature, message: message, address: address)
+ }
+
+ func testEtherscanSignature() async throws {
+ let address = "0x6721591d424c18b7173d55895efa1839aa57d9c2"
+ let message = "\u{19}Ethereum Signed Message:\n139[Etherscan.io 12/08/2022 09:26:23] I, hereby verify that I am the owner/creator of the address [0x7e77dcb127f99ece88230a64db8d595f31f1b068]".data(using: .utf8)!
+ let signature = Data(hex: "60eb9cfe362210f1b4855f4865eafc378bd442c406de22354cc9f643fb84cb265b7f6d9d10b13199e450558c328814a9038884d9993d9feb79b727366736853d1b")
+
+ try await verifier.verify(signature: signature, message: message, address: address)
+ }
+
+ func testInvalidMessage() async throws {
+ let message = Data(hex: "0xdeadbeaf")
+ await XCTAssertThrowsErrorAsync(try await verifier.verify(signature: signature, message: message, address: address))
+ }
+
+ func testInvalidPubKey() async throws {
+ let address = "0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07"
+ await XCTAssertThrowsErrorAsync(try await verifier.verify(signature: signature, message: message, address: address))
+ }
+
+ func testInvalidSignature() async throws {
+ let signature = Data(hex: "86deb09d045608f2753ef12f46e8da5fc2559e3a9162e580df3e62c875df7c3f64433462a59bc4ff38ce52412bff10527f4b99cc078f63ef2bb4a6f7427080aa01")
+
+ await XCTAssertThrowsErrorAsync(try await verifier.verify(signature: signature, message: message, address: address))
+ }
+}
diff --git a/Tests/AuthTests/SignerTests.swift b/Tests/AuthTests/SignerTests.swift
index 5f546bd08..80b19ecfb 100644
--- a/Tests/AuthTests/SignerTests.swift
+++ b/Tests/AuthTests/SignerTests.swift
@@ -4,12 +4,13 @@ import XCTest
import secp256k1
import Web3
import WalletConnectUtils
+import WalletConnectRelay
class SignerTest: XCTestCase {
private let signer = Signer()
- private let message = "Message".data(using: .utf8)!
+ private let message = "\u{19}Ethereum Signed Message:\n7Message".data(using: .utf8)!
private let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a")
private let signature = Data(hex: "66121e60cccc01fbf7fcba694a1e08ac5db35fb4ec6c045bedba7860445b95c021cad2c595f0bf68ff896964c7c02bb2f3a3e9540e8e4595c98b737ce264cc541b")
private var address = "0x15bca56b6e2728aec2532df9d436bd1600e86688"
@@ -18,37 +19,11 @@ class SignerTest: XCTestCase {
let result = try signer.sign(message: message, with: privateKey)
XCTAssertEqual(signature.toHexString(), result.toHexString())
- XCTAssertTrue(try signer.isValid(signature: result, message: message, address: address))
}
- func testEtherscanSignature() throws {
- let addressFromEtherscan = "0x6721591d424c18b7173d55895efa1839aa57d9c2"
- let message = "[Etherscan.io 12/08/2022 09:26:23] I, hereby verify that I am the owner/creator of the address [0x7e77dcb127f99ece88230a64db8d595f31f1b068]"
- let signedMessageFromEtherscan = message.data(using: .utf8)!
- let signatureHashFromEtherscan = Data(hex: "60eb9cfe362210f1b4855f4865eafc378bd442c406de22354cc9f643fb84cb265b7f6d9d10b13199e450558c328814a9038884d9993d9feb79b727366736853d1b")
- XCTAssertTrue(try signer.isValid(
- signature: signatureHashFromEtherscan,
- message: signedMessageFromEtherscan,
- address: addressFromEtherscan
- ))
- }
-
- func testInvalidMessage() throws {
- let message = "Message One".data(using: .utf8)!
-
- XCTAssertFalse(try signer.isValid(signature: signature, message: message, address: address))
- }
-
- func testInvalidPubKey() throws {
- let address = "0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07"
-
- XCTAssertFalse(try signer.isValid(signature: signature, message: message, address: address))
- }
-
- func testInvalidSignature() throws {
- let signature = Data(hex: "86deb09d045608f2753ef12f46e8da5fc2559e3a9162e580df3e62c875df7c3f64433462a59bc4ff38ce52412bff10527f4b99cc078f63ef2bb4a6f7427080aa01")
-
- XCTAssertFalse(try signer.isValid(signature: signature, message: message, address: address))
+ private func prefixed(_ message: Data) -> Data {
+ return "\u{19}Ethereum Signed Message:\n\(message.count)"
+ .data(using: .utf8)! + message
}
func testSignerAddressFromIss() throws {
diff --git a/Tests/AuthTests/WalletRequestSubscriberTests.swift b/Tests/AuthTests/WalletRequestSubscriberTests.swift
index 0fb214242..de7dbe28d 100644
--- a/Tests/AuthTests/WalletRequestSubscriberTests.swift
+++ b/Tests/AuthTests/WalletRequestSubscriberTests.swift
@@ -1,27 +1,31 @@
import Foundation
import XCTest
import JSONRPC
-import WalletConnectUtils
-import WalletConnectNetworking
+@testable import WalletConnectUtils
+@testable import WalletConnectNetworking
@testable import Auth
@testable import WalletConnectKMS
@testable import TestingUtils
class WalletRequestSubscriberTests: XCTestCase {
- var networkingInteractor: NetworkingInteractorMock!
+ var pairingRegisterer: PairingRegistererMock!
var sut: WalletRequestSubscriber!
var messageFormatter: SIWEMessageFormatterMock!
+
let defaultTimeout: TimeInterval = 0.01
override func setUp() {
- networkingInteractor = NetworkingInteractorMock()
+ let networkingInteractor = NetworkingInteractorMock()
+ pairingRegisterer = PairingRegistererMock()
messageFormatter = SIWEMessageFormatterMock()
let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingInteractor, logger: ConsoleLoggerMock(), kms: KeyManagementServiceMock(), rpcHistory: RPCHistory(keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")))
sut = WalletRequestSubscriber(networkingInteractor: networkingInteractor,
logger: ConsoleLoggerMock(),
kms: KeyManagementServiceMock(),
- messageFormatter: messageFormatter, address: "", walletErrorResponder: walletErrorResponder)
+ messageFormatter: messageFormatter, address: "",
+ walletErrorResponder: walletErrorResponder,
+ pairingRegisterer: pairingRegisterer)
}
func testSubscribeRequest() {
@@ -37,10 +41,12 @@ class WalletRequestSubscriberTests: XCTestCase {
messageExpectation.fulfill()
}
- let request = RPCRequest(method: AuthProtocolMethod.authRequest.method, params: AuthRequestParams.stub(id: expectedRequestId), id: expectedRequestId.right!)
- networkingInteractor.requestPublisherSubject.send(("123", request))
+ let payload = RequestSubscriptionPayload(id: expectedRequestId, topic: "123", request: AuthRequestParams.stub(id: expectedRequestId))
+
+ pairingRegisterer.subject.send(payload)
wait(for: [messageExpectation], timeout: defaultTimeout)
+ XCTAssertTrue(pairingRegisterer.isActivateCalled)
XCTAssertEqual(message, expectedMessage)
XCTAssertEqual(messageId, expectedRequestId)
}
diff --git a/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift b/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift
index 38e27a073..aa58c9761 100644
--- a/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift
+++ b/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift
@@ -6,16 +6,33 @@ import WalletConnectKMS
final class ClientIdStorageTests: XCTestCase {
- func testGetOrCreate() throws {
- let keychain = KeychainStorageMock()
- let storage = ClientIdStorage(keychain: keychain)
+ var sut: ClientIdStorage!
+ var keychain: KeychainStorageMock!
+ var didKeyFactory: ED25519DIDKeyFactoryMock!
+
+ override func setUp() {
+ keychain = KeychainStorageMock()
+ didKeyFactory = ED25519DIDKeyFactoryMock()
+ sut = ClientIdStorage(keychain: keychain, didKeyFactory: didKeyFactory)
+ }
+ func testGetOrCreate() throws {
XCTAssertThrowsError(try keychain.read(key: "com.walletconnect.iridium.client_id") as SigningPrivateKey)
- let saved = try storage.getOrCreateKeyPair()
+ let saved = try sut.getOrCreateKeyPair()
XCTAssertEqual(saved, try keychain.read(key: "com.walletconnect.iridium.client_id"))
- let restored = try storage.getOrCreateKeyPair()
+ let restored = try sut.getOrCreateKeyPair()
XCTAssertEqual(saved, restored)
}
+
+ func testGetClientId() {
+ let did = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH"
+ didKeyFactory.did = did
+ _ = try! sut.getOrCreateKeyPair()
+
+ let clientId = try! sut.getClientId()
+ XCTAssertEqual(did, clientId)
+
+ }
}
diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift
index 78103a30c..9d32b380d 100644
--- a/Tests/RelayerTests/DispatcherTests.swift
+++ b/Tests/RelayerTests/DispatcherTests.swift
@@ -1,5 +1,6 @@
import Foundation
import XCTest
+import Combine
@testable import WalletConnectRelay
import TestingUtils
import Combine
@@ -29,6 +30,7 @@ class WebSocketMock: WebSocketConnecting {
}
final class DispatcherTests: XCTestCase {
+ var publishers = Set()
var sut: Dispatcher!
var webSocket: WebSocketMock!
var networkMonitor: NetworkMonitoringMock!
@@ -66,18 +68,21 @@ final class DispatcherTests: XCTestCase {
func testOnConnect() {
let expectation = expectation(description: "on connect")
- sut.onConnect = {
+ sut.socketConnectionStatusPublisher.sink { status in
+ guard status == .connected else { return }
expectation.fulfill()
- }
+ }.store(in: &publishers)
webSocket.onConnect?()
waitForExpectations(timeout: 0.001)
}
- func testOnDisconnect() {
+ func testOnDisconnect() throws {
let expectation = expectation(description: "on disconnect")
- sut.onDisconnect = {
+ try sut.connect()
+ sut.socketConnectionStatusPublisher.sink { status in
+ guard status == .disconnected else { return }
expectation.fulfill()
- }
+ }.store(in: &publishers)
webSocket.onDisconnect?(nil)
waitForExpectations(timeout: 0.001)
}
diff --git a/Tests/RelayerTests/Helpers/Error+Extension.swift b/Tests/RelayerTests/Helpers/Error+Extension.swift
index bee887b0d..cf5525e24 100644
--- a/Tests/RelayerTests/Helpers/Error+Extension.swift
+++ b/Tests/RelayerTests/Helpers/Error+Extension.swift
@@ -1,5 +1,6 @@
import Foundation
@testable import WalletConnectRelay
+@testable import WalletConnectNetworking
extension NSError {
diff --git a/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift b/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift
index 2453c6174..5d26c6d34 100644
--- a/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift
+++ b/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift
@@ -3,9 +3,14 @@ import WalletConnectKMS
import Foundation
class ClientIdStorageMock: ClientIdStoring {
+
var keyPair: SigningPrivateKey!
func getOrCreateKeyPair() throws -> SigningPrivateKey {
return keyPair
}
+
+ func getClientId() throws -> String {
+ fatalError()
+ }
}
diff --git a/Tests/RelayerTests/Mocks/DispatcherMock.swift b/Tests/RelayerTests/Mocks/DispatcherMock.swift
index 97ddac5dc..d5088bf61 100644
--- a/Tests/RelayerTests/Mocks/DispatcherMock.swift
+++ b/Tests/RelayerTests/Mocks/DispatcherMock.swift
@@ -1,18 +1,34 @@
import Foundation
import JSONRPC
+import Combine
@testable import WalletConnectRelay
class DispatcherMock: Dispatching {
+ private var publishers = Set()
+ private let socketConnectionStatusPublisherSubject = CurrentValueSubject(.disconnected)
+ var socketConnectionStatusPublisher: AnyPublisher {
+ return socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
+ }
- var onConnect: (() -> Void)?
- var onDisconnect: (() -> Void)?
+ var sent = false
+ var lastMessage: String = ""
var onMessage: ((String) -> Void)?
- func connect() {}
- func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) {}
+ func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) {
+ send(string, completion: completion)
+ }
- var sent = false
- var lastMessage: String = ""
+ func protectedSend(_ string: String) async throws {
+ try await send(string)
+ }
+
+ func connect() {
+ socketConnectionStatusPublisherSubject.send(.connected)
+ }
+
+ func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) {
+ socketConnectionStatusPublisherSubject.send(.disconnected)
+ }
func send(_ string: String, completion: @escaping (Error?) -> Void) {
sent = true
diff --git a/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift b/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift
index 23afd5c3c..11ef10631 100644
--- a/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift
+++ b/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift
@@ -2,7 +2,7 @@ import WalletConnectKMS
@testable import WalletConnectRelay
import Foundation
-struct ED25519DIDKeyFactoryMock: DIDKeyFactory {
+class ED25519DIDKeyFactoryMock: DIDKeyFactory {
var did: String!
func make(pubKey: Data, prefix: Bool) -> String {
return did
diff --git a/Tests/RelayerTests/RelayClientTests.swift b/Tests/RelayerTests/RelayClientTests.swift
index f9c9384c2..f37148fca 100644
--- a/Tests/RelayerTests/RelayClientTests.swift
+++ b/Tests/RelayerTests/RelayClientTests.swift
@@ -9,13 +9,13 @@ final class RelayClientTests: XCTestCase {
var sut: RelayClient!
var dispatcher: DispatcherMock!
-
var publishers = Set()
override func setUp() {
dispatcher = DispatcherMock()
let logger = ConsoleLogger()
- sut = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage())
+ let clientIdStorage = ClientIdStorageMock()
+ sut = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage(), clientIdStorage: clientIdStorage)
}
override func tearDown() {
@@ -55,7 +55,7 @@ final class RelayClientTests: XCTestCase {
func testPublishRequestAcknowledge() {
let expectation = expectation(description: "Publish must callback on relay server acknowledgement")
- sut.publish(topic: "", payload: "{}", tag: 0) { error in
+ sut.publish(topic: "", payload: "{}", tag: 0, prompt: false, ttl: 60) { error in
XCTAssertNil(error)
expectation.fulfill()
}
@@ -93,7 +93,7 @@ final class RelayClientTests: XCTestCase {
}
func testSendOnPublish() {
- sut.publish(topic: "", payload: "", tag: 0, onNetworkAcknowledge: { _ in})
+ sut.publish(topic: "", payload: "", tag: 0, prompt: false, ttl: 60, onNetworkAcknowledge: { _ in})
XCTAssertTrue(dispatcher.sent)
}
diff --git a/Tests/TestingUtils/Mocks/HTTPClientMock.swift b/Tests/TestingUtils/Mocks/HTTPClientMock.swift
new file mode 100644
index 000000000..c769c2b68
--- /dev/null
+++ b/Tests/TestingUtils/Mocks/HTTPClientMock.swift
@@ -0,0 +1,19 @@
+import Foundation
+import WalletConnectNetworking
+
+public final class HTTPClientMock: HTTPClient {
+
+ private let object: T
+
+ public init(object: T) {
+ self.object = object
+ }
+
+ public func request(_ type: T.Type, at service: HTTPService) async throws -> T where T: Decodable {
+ return object as! T
+ }
+
+ public func request(service: HTTPService) async throws {
+
+ }
+}
diff --git a/Tests/TestingUtils/Mocks/PairingRegistererMock.swift b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift
new file mode 100644
index 000000000..9e48d13da
--- /dev/null
+++ b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift
@@ -0,0 +1,27 @@
+import Foundation
+import WalletConnectPairing
+import Combine
+import WalletConnectNetworking
+
+public class PairingRegistererMock: PairingRegisterer where RequestParams: Codable {
+
+ public let subject = PassthroughSubject, Never>()
+
+ public var isActivateCalled: Bool = false
+
+ public func register(method: ProtocolMethod) -> AnyPublisher, Never> where RequestParams: Decodable, RequestParams: Encodable {
+ subject.eraseToAnyPublisher() as! AnyPublisher, Never>
+ }
+
+ public func activate(pairingTopic: String) {
+ isActivateCalled = true
+ }
+
+ public func validatePairingExistance(_ topic: String) throws {
+
+ }
+
+ public func updateMetadata(_ topic: String, metadata: AppMetadata) {
+
+ }
+}
diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift
index 10f42fc08..36bee9173 100644
--- a/Tests/TestingUtils/NetworkingInteractorMock.swift
+++ b/Tests/TestingUtils/NetworkingInteractorMock.swift
@@ -8,6 +8,21 @@ import WalletConnectNetworking
public class NetworkingInteractorMock: NetworkInteracting {
private(set) var subscriptions: [String] = []
+ private(set) var unsubscriptions: [String] = []
+
+ private(set) var requests: [(topic: String, request: RPCRequest)] = []
+
+ private(set) var didRespondSuccess = false
+ private(set) var didRespondError = false
+ private(set) var didCallSubscribe = false
+ private(set) var didCallUnsubscribe = false
+ private(set) var didRespondOnTopic: String?
+ private(set) var lastErrorCode = -1
+
+ private(set) var requestCallCount = 0
+ var didCallRequest: Bool { requestCallCount > 0 }
+
+ var onSubscribeCalled: (() -> Void)?
public let socketConnectionStatusPublisherSubject = PassthroughSubject()
public var socketConnectionStatusPublisher: AnyPublisher {
@@ -17,7 +32,7 @@ public class NetworkingInteractorMock: NetworkInteracting {
public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>()
public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse), Never>()
- private var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
+ public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
requestPublisherSubject.eraseToAnyPublisher()
}
@@ -25,9 +40,12 @@ public class NetworkingInteractorMock: NetworkInteracting {
responsePublisherSubject.eraseToAnyPublisher()
}
+ // TODO: Avoid copy paste from NetworkInteractor
public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return requestPublisher
- .filter { $0.request.method == request.method }
+ .filter { rpcRequest in
+ return rpcRequest.request.method == request.method
+ }
.compactMap { topic, rpcRequest in
guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil }
return RequestSubscriptionPayload(id: id, topic: topic, request: request)
@@ -35,9 +53,12 @@ public class NetworkingInteractorMock: NetworkInteracting {
.eraseToAnyPublisher()
}
+ // TODO: Avoid copy paste from NetworkInteractor
public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return responsePublisher
- .filter { $0.request.method == request.method }
+ .filter { rpcRequest in
+ return rpcRequest.request.method == request.method
+ }
.compactMap { topic, rpcRequest, rpcResponse in
guard
let id = rpcRequest.id,
@@ -48,45 +69,56 @@ public class NetworkingInteractorMock: NetworkInteracting {
.eraseToAnyPublisher()
}
- public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher {
+ // TODO: Avoid copy paste from NetworkInteractor
+ public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> {
return responsePublisher
.filter { $0.request.method == request.method }
- .compactMap { (_, _, rpcResponse) in
- guard let id = rpcResponse.id, let error = rpcResponse.error else { return nil }
- return ResponseSubscriptionErrorPayload(id: id, error: error)
+ .compactMap { (topic, rpcRequest, rpcResponse) in
+ guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil }
+ return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error)
}
.eraseToAnyPublisher()
}
public func subscribe(topic: String) async throws {
+ defer { onSubscribeCalled?() }
subscriptions.append(topic)
+ didCallSubscribe = true
}
func didSubscribe(to topic: String) -> Bool {
- subscriptions.contains { $0 == topic }
+ subscriptions.contains { $0 == topic }
}
- public func unsubscribe(topic: String) {
-
+ func didUnsubscribe(to topic: String) -> Bool {
+ unsubscriptions.contains { $0 == topic }
}
- public func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
-
+ public func unsubscribe(topic: String) {
+ unsubscriptions.append(topic)
+ didCallUnsubscribe = true
}
- public func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
-
+ public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
+ requestCallCount += 1
+ requests.append((topic, request))
}
- public func respondSuccess(topic: String, requestId: RPCID, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
-
+ public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
+ didRespondOnTopic = topic
}
- public func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws {
-
+ public func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
+ didRespondSuccess = true
}
- public func requestNetworkAck(_ request: RPCRequest, topic: String, tag: Int) async throws {
+ public func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws {
+ lastErrorCode = reason.code
+ didRespondError = true
+ }
+ public func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws {
+ requestCallCount += 1
+ requests.append((topic, request))
}
}
diff --git a/Tests/WalletConnectSignTests/Mocks/AppMetadata.swift b/Tests/TestingUtils/Stubs/AppMetadata+Stub.swift
similarity index 100%
rename from Tests/WalletConnectSignTests/Mocks/AppMetadata.swift
rename to Tests/TestingUtils/Stubs/AppMetadata+Stub.swift
diff --git a/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift b/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift
new file mode 100644
index 000000000..bd3db0839
--- /dev/null
+++ b/Tests/TestingUtils/Stubs/RelayProtocolOptions+Stub.swift
@@ -0,0 +1,8 @@
+import WalletConnectUtils
+
+extension RelayProtocolOptions {
+
+ public static func stub() -> RelayProtocolOptions {
+ RelayProtocolOptions(protocol: "", data: nil)
+ }
+}
diff --git a/Tests/WalletConnectSignTests/Stub/WalletConnectURI+Stub.swift b/Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift
similarity index 60%
rename from Tests/WalletConnectSignTests/Stub/WalletConnectURI+Stub.swift
rename to Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift
index 5d5129228..67ee5dfe9 100644
--- a/Tests/WalletConnectSignTests/Stub/WalletConnectURI+Stub.swift
+++ b/Tests/TestingUtils/Stubs/WalletConnectURI+Stub.swift
@@ -1,10 +1,9 @@
-@testable import WalletConnectSign
-@testable import WalletConnectKMS
-import CryptoKit
+import WalletConnectKMS
+import WalletConnectUtils
extension WalletConnectURI {
- static func stub(isController: Bool = false) -> WalletConnectURI {
+ public static func stub(isController: Bool = false) -> WalletConnectURI {
WalletConnectURI(
topic: String.generateTopic(),
symKey: SymmetricKey().hexRepresentation,
diff --git a/Tests/WalletConnectPairingTests/AppPairServiceTests.swift b/Tests/WalletConnectPairingTests/AppPairServiceTests.swift
new file mode 100644
index 000000000..d0cb8f9b9
--- /dev/null
+++ b/Tests/WalletConnectPairingTests/AppPairServiceTests.swift
@@ -0,0 +1,35 @@
+import XCTest
+@testable import WalletConnectPairing
+@testable import TestingUtils
+@testable import WalletConnectKMS
+import WalletConnectUtils
+
+final class AppPairServiceTests: XCTestCase {
+
+ var service: AppPairService!
+ var networkingInteractor: NetworkingInteractorMock!
+ var storageMock: WCPairingStorageMock!
+ var cryptoMock: KeyManagementServiceMock!
+
+ override func setUp() {
+ networkingInteractor = NetworkingInteractorMock()
+ storageMock = WCPairingStorageMock()
+ cryptoMock = KeyManagementServiceMock()
+ service = AppPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock)
+ }
+
+ override func tearDown() {
+ networkingInteractor = nil
+ storageMock = nil
+ cryptoMock = nil
+ service = nil
+ }
+
+ func testCreate() async {
+ let uri = try! await service.create()
+ XCTAssert(cryptoMock.hasSymmetricKey(for: uri.topic), "Proposer must store the symmetric key matching the URI.")
+ XCTAssert(storageMock.hasPairing(forTopic: uri.topic), "The engine must store a pairing after creating one")
+ XCTAssert(networkingInteractor.didSubscribe(to: uri.topic), "Proposer must subscribe to pairing topic.")
+ XCTAssert(storageMock.getPairing(forTopic: uri.topic)?.active == false, "Recently created pairing must be inactive.")
+ }
+}
diff --git a/Tests/WalletConnectPairingTests/ExpirationServiceTests.swift b/Tests/WalletConnectPairingTests/ExpirationServiceTests.swift
new file mode 100644
index 000000000..79b8b20d6
--- /dev/null
+++ b/Tests/WalletConnectPairingTests/ExpirationServiceTests.swift
@@ -0,0 +1,40 @@
+import XCTest
+@testable import WalletConnectPairing
+@testable import TestingUtils
+@testable import WalletConnectKMS
+import WalletConnectUtils
+import WalletConnectNetworking
+
+final class ExpirationServiceTestsTests: XCTestCase {
+
+ var service: ExpirationService!
+ var appPairService: AppPairService!
+ var networkingInteractor: NetworkingInteractorMock!
+ var storageMock: WCPairingStorageMock!
+ var cryptoMock: KeyManagementServiceMock!
+
+ override func setUp() {
+ networkingInteractor = NetworkingInteractorMock()
+ storageMock = WCPairingStorageMock()
+ cryptoMock = KeyManagementServiceMock()
+ service = ExpirationService(
+ pairingStorage: storageMock,
+ networkInteractor: networkingInteractor,
+ kms: cryptoMock
+ )
+ appPairService = AppPairService(
+ networkingInteractor: networkingInteractor,
+ kms: cryptoMock,
+ pairingStorage: storageMock
+ )
+ }
+
+ func testPairingExpiration() async {
+ let uri = try! await appPairService.create()
+ let pairing = storageMock.getPairing(forTopic: uri.topic)!
+ service.setupExpirationHandling()
+ storageMock.onPairingExpiration?(pairing)
+ XCTAssertFalse(cryptoMock.hasSymmetricKey(for: uri.topic))
+ XCTAssert(networkingInteractor.didUnsubscribe(to: uri.topic))
+ }
+}
diff --git a/Tests/WalletConnectSignTests/WCPairingTests.swift b/Tests/WalletConnectPairingTests/WCPairingTests.swift
similarity index 98%
rename from Tests/WalletConnectSignTests/WCPairingTests.swift
rename to Tests/WalletConnectPairingTests/WCPairingTests.swift
index 6dcad44a0..f180efee2 100644
--- a/Tests/WalletConnectSignTests/WCPairingTests.swift
+++ b/Tests/WalletConnectPairingTests/WCPairingTests.swift
@@ -1,6 +1,7 @@
import XCTest
@testable import WalletConnectPairing
-@testable import WalletConnectSign
+@testable import WalletConnectUtils
+@testable import WalletConnectUtils
final class WCPairingTests: XCTestCase {
diff --git a/Tests/WalletConnectSignTests/PairEngineTests.swift b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
similarity index 53%
rename from Tests/WalletConnectSignTests/PairEngineTests.swift
rename to Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
index 9b581672d..7617aee57 100644
--- a/Tests/WalletConnectSignTests/PairEngineTests.swift
+++ b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift
@@ -1,50 +1,31 @@
import XCTest
-@testable import WalletConnectSign
+@testable import WalletConnectPairing
@testable import TestingUtils
@testable import WalletConnectKMS
import WalletConnectUtils
+import WalletConnectNetworking
-final class PairEngineTests: XCTestCase {
-
- var engine: PairEngine!
+final class WalletPairServiceTestsTests: XCTestCase {
+ var service: WalletPairService!
var networkingInteractor: NetworkingInteractorMock!
var storageMock: WCPairingStorageMock!
var cryptoMock: KeyManagementServiceMock!
- var proposalPayloadsStore: CodableStore!
-
- var topicGenerator: TopicGenerator!
override func setUp() {
networkingInteractor = NetworkingInteractorMock()
storageMock = WCPairingStorageMock()
cryptoMock = KeyManagementServiceMock()
- topicGenerator = TopicGenerator()
- proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
- setupEngine()
- }
-
- override func tearDown() {
- networkingInteractor = nil
- storageMock = nil
- cryptoMock = nil
- engine = nil
- }
-
- func setupEngine() {
- engine = PairEngine(
- networkingInteractor: networkingInteractor,
- kms: cryptoMock,
- pairingStore: storageMock)
+ service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock)
}
func testPairMultipleTimesOnSameURIThrows() async {
let uri = WalletConnectURI.stub()
for i in 1...10 {
if i == 1 {
- await XCTAssertNoThrowAsync(try await engine.pair(uri))
+ await XCTAssertNoThrowAsync(try await service.pair(uri))
} else {
- await XCTAssertThrowsErrorAsync(try await engine.pair(uri))
+ await XCTAssertThrowsErrorAsync(try await service.pair(uri))
}
}
}
@@ -52,7 +33,7 @@ final class PairEngineTests: XCTestCase {
func testPair() async {
let uri = WalletConnectURI.stub()
let topic = uri.topic
- try! await engine.pair(uri)
+ try! await service.pair(uri)
XCTAssert(networkingInteractor.didSubscribe(to: topic), "Responder must subscribe to pairing topic.")
XCTAssert(cryptoMock.hasSymmetricKey(for: topic), "Responder must store the symmetric key matching the pairing topic")
XCTAssert(storageMock.hasPairing(forTopic: topic), "The engine must store a pairing")
diff --git a/Tests/WalletConnectSignTests/PairingEngineTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift
similarity index 64%
rename from Tests/WalletConnectSignTests/PairingEngineTests.swift
rename to Tests/WalletConnectSignTests/AppProposalServiceTests.swift
index 785be6dc2..de37d08cf 100644
--- a/Tests/WalletConnectSignTests/PairingEngineTests.swift
+++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift
@@ -1,17 +1,22 @@
import XCTest
import Combine
+import JSONRPC
@testable import WalletConnectSign
@testable import TestingUtils
@testable import WalletConnectKMS
+@testable import WalletConnectPairing
import WalletConnectUtils
func deriveTopic(publicKey: String, privateKey: AgreementPrivateKey) -> String {
try! KeyManagementService.generateAgreementKey(from: privateKey, peerPublicKey: publicKey).derivedTopic()
}
-final class PairingEngineTests: XCTestCase {
+final class AppProposalServiceTests: XCTestCase {
- var engine: PairingEngine!
+ var service: AppProposeService!
+
+ var appPairService: AppPairService!
+ var pairingRegisterer: PairingRegistererMock!
var approveEngine: ApproveEngine!
var networkingInteractor: NetworkingInteractorMock!
@@ -26,7 +31,8 @@ final class PairingEngineTests: XCTestCase {
storageMock = WCPairingStorageMock()
cryptoMock = KeyManagementServiceMock()
topicGenerator = TopicGenerator()
- setupEngines()
+ pairingRegisterer = PairingRegistererMock()
+ setupServices()
}
override func tearDown() {
@@ -34,25 +40,30 @@ final class PairingEngineTests: XCTestCase {
storageMock = nil
cryptoMock = nil
topicGenerator = nil
- engine = nil
+ pairingRegisterer = nil
approveEngine = nil
}
- func setupEngines() {
+ func setupServices() {
let meta = AppMetadata.stub()
let logger = ConsoleLoggerMock()
- engine = PairingEngine(
+
+ appPairService = AppPairService(
networkingInteractor: networkingInteractor,
kms: cryptoMock,
- pairingStore: storageMock,
- metadata: meta,
- logger: logger,
- topicGenerator: topicGenerator.getTopic
+ pairingStorage: storageMock
+ )
+ service = AppProposeService(
+ metadata: .stub(),
+ networkingInteractor: networkingInteractor,
+ kms: cryptoMock,
+ logger: logger
)
approveEngine = ApproveEngine(
networkingInteractor: networkingInteractor,
proposalPayloadsStore: .init(defaults: RuntimeKeyValueStorage(), identifier: ""),
sessionToPairingTopic: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""),
+ pairingRegisterer: pairingRegisterer,
metadata: meta,
kms: cryptoMock,
logger: logger,
@@ -61,24 +72,16 @@ final class PairingEngineTests: XCTestCase {
)
}
- func testCreate() async {
- let uri = try! await engine.create()
- XCTAssert(cryptoMock.hasSymmetricKey(for: uri.topic), "Proposer must store the symmetric key matching the URI.")
- XCTAssert(storageMock.hasPairing(forTopic: uri.topic), "The engine must store a pairing after creating one")
- XCTAssert(networkingInteractor.didSubscribe(to: uri.topic), "Proposer must subscribe to pairing topic.")
- XCTAssert(storageMock.getPairing(forTopic: uri.topic)?.active == false, "Recently created pairing must be inactive.")
- }
-
func testPropose() async {
let pairing = Pairing.stub()
let topicA = pairing.topic
let relayOptions = RelayProtocolOptions(protocol: "", data: nil)
// FIXME: namespace stub
- try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
+ try! await service.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
guard let publishTopic = networkingInteractor.requests.first?.topic,
- let proposal = networkingInteractor.requests.first?.request.sessionProposal else {
+ let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else {
XCTFail("Proposer must publish a proposal request."); return
}
XCTAssert(cryptoMock.hasPrivateKey(for: proposal.proposer.publicKey), "Proposer must store the private key matching the public key sent through the proposal.")
@@ -86,17 +89,18 @@ final class PairingEngineTests: XCTestCase {
}
func testHandleSessionProposeResponse() async {
- let uri = try! await engine.create()
+ let exp = expectation(description: "testHandleSessionProposeResponse")
+ let uri = try! await appPairService.create()
let pairing = storageMock.getPairing(forTopic: uri.topic)!
let topicA = pairing.topic
let relayOptions = RelayProtocolOptions(protocol: "", data: nil)
// Client proposes session
// FIXME: namespace stub
- try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
+ try! await service.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
guard let request = networkingInteractor.requests.first?.request,
- let proposal = networkingInteractor.requests.first?.request.sessionProposal else {
+ let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else {
XCTFail("Proposer must publish session proposal request"); return
}
@@ -104,17 +108,19 @@ final class PairingEngineTests: XCTestCase {
let responder = Participant.stub()
let proposalResponse = SessionType.ProposeResponse(relay: relayOptions, responderPublicKey: responder.publicKey)
- let jsonRpcResponse = JSONRPCResponse(id: request.id, result: AnyCodable.decoded(proposalResponse))
- let response = WCResponse(topic: topicA,
- chainId: nil,
- requestMethod: request.method,
- requestParams: request.params,
- result: .response(jsonRpcResponse))
+ let response = RPCResponse(id: request.id!, result: RPCResult.response(AnyCodable(proposalResponse)))
- networkingInteractor.responsePublisherSubject.send(response)
+ networkingInteractor.onSubscribeCalled = {
+ exp.fulfill()
+ }
+
+ networkingInteractor.responsePublisherSubject.send((topicA, request, response))
let privateKey = try! cryptoMock.getPrivateKey(for: proposal.proposer.publicKey)!
let topicB = deriveTopic(publicKey: responder.publicKey, privateKey: privateKey)
let storedPairing = storageMock.getPairing(forTopic: topicA)!
+
+ wait(for: [exp], timeout: 5)
+
let sessionTopic = networkingInteractor.subscriptions.last!
XCTAssertTrue(networkingInteractor.didCallSubscribe)
@@ -123,22 +129,22 @@ final class PairingEngineTests: XCTestCase {
}
func testSessionProposeError() async {
- let uri = try! await engine.create()
+ let uri = try! await appPairService.create()
let pairing = storageMock.getPairing(forTopic: uri.topic)!
let topicA = pairing.topic
let relayOptions = RelayProtocolOptions(protocol: "", data: nil)
// Client propose session
// FIXME: namespace stub
- try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
+ try! await service.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
guard let request = networkingInteractor.requests.first?.request,
- let proposal = networkingInteractor.requests.first?.request.sessionProposal else {
+ let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else {
XCTFail("Proposer must publish session proposal request"); return
}
- let response = WCResponse.stubError(forRequest: request, topic: topicA)
- networkingInteractor.responsePublisherSubject.send(response)
+ let response = RPCResponse.stubError(forRequest: request)
+ networkingInteractor.responsePublisherSubject.send((topicA, request, response))
XCTAssert(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must unsubscribe if pairing is inactive.")
XCTAssertFalse(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must delete an inactive pairing.")
@@ -147,17 +153,17 @@ final class PairingEngineTests: XCTestCase {
}
func testSessionProposeErrorOnActivePairing() async {
- let uri = try! await engine.create()
+ let uri = try! await appPairService.create()
let pairing = storageMock.getPairing(forTopic: uri.topic)!
let topicA = pairing.topic
let relayOptions = RelayProtocolOptions(protocol: "", data: nil)
// Client propose session
// FIXME: namespace stub
- try? await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
+ try? await service.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions)
guard let request = networkingInteractor.requests.first?.request,
- let proposal = networkingInteractor.requests.first?.request.sessionProposal else {
+ let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else {
XCTFail("Proposer must publish session proposal request"); return
}
@@ -165,20 +171,12 @@ final class PairingEngineTests: XCTestCase {
storedPairing.activate()
storageMock.setPairing(storedPairing)
- let response = WCResponse.stubError(forRequest: request, topic: topicA)
- networkingInteractor.responsePublisherSubject.send(response)
+ let response = RPCResponse.stubError(forRequest: request)
+ networkingInteractor.responsePublisherSubject.send((topicA, request, response))
XCTAssertFalse(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must not unsubscribe if pairing is active.")
XCTAssert(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must not delete an active pairing.")
XCTAssert(cryptoMock.hasSymmetricKey(for: pairing.topic), "Proposer must not delete symmetric key if pairing is active.")
XCTAssertFalse(cryptoMock.hasPrivateKey(for: proposal.proposer.publicKey), "Proposer must remove private key for rejected session")
}
-
- func testPairingExpiration() async {
- let uri = try! await engine.create()
- let pairing = storageMock.getPairing(forTopic: uri.topic)!
- storageMock.onPairingExpiration?(pairing)
- XCTAssertFalse(cryptoMock.hasSymmetricKey(for: uri.topic))
- XCTAssert(networkingInteractor.didUnsubscribe(to: uri.topic))
- }
}
diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
index 88e7a541c..9730deefe 100644
--- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift
+++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
@@ -1,7 +1,9 @@
import XCTest
import Combine
+import JSONRPC
import WalletConnectUtils
import WalletConnectPairing
+import WalletConnectNetworking
@testable import WalletConnectSign
@testable import TestingUtils
@testable import WalletConnectKMS
@@ -14,7 +16,8 @@ final class ApproveEngineTests: XCTestCase {
var cryptoMock: KeyManagementServiceMock!
var pairingStorageMock: WCPairingStorageMock!
var sessionStorageMock: WCSessionStorageMock!
- var proposalPayloadsStore: CodableStore!
+ var pairingRegisterer: PairingRegistererMock!
+ var proposalPayloadsStore: CodableStore>!
var publishers = Set()
@@ -24,11 +27,13 @@ final class ApproveEngineTests: XCTestCase {
cryptoMock = KeyManagementServiceMock()
pairingStorageMock = WCPairingStorageMock()
sessionStorageMock = WCSessionStorageMock()
- proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
+ pairingRegisterer = PairingRegistererMock()
+ proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "")
engine = ApproveEngine(
networkingInteractor: networkingInteractor,
proposalPayloadsStore: proposalPayloadsStore,
sessionToPairingTopic: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""),
+ pairingRegisterer: pairingRegisterer,
metadata: metadata,
kms: cryptoMock,
logger: ConsoleLoggerMock(),
@@ -41,6 +46,7 @@ final class ApproveEngineTests: XCTestCase {
networkingInteractor = nil
metadata = nil
cryptoMock = nil
+ pairingRegisterer = nil
pairingStorageMock = nil
engine = nil
}
@@ -52,9 +58,7 @@ final class ApproveEngineTests: XCTestCase {
pairingStorageMock.setPairing(pairing)
let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
- let request = WCRequest(method: .sessionPropose, params: .sessionPropose(proposal))
- let payload = WCRequestSubscriptionPayload(topic: topicA, wcRequest: request)
- networkingInteractor.wcRequestPublisherSubject.send(payload)
+ pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal))
try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary())
@@ -74,14 +78,12 @@ final class ApproveEngineTests: XCTestCase {
var sessionProposed = false
let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
- let request = WCRequest(method: .sessionPropose, params: .sessionPropose(proposal))
- let payload = WCRequestSubscriptionPayload(topic: topicA, wcRequest: request)
engine.onSessionProposal = { _ in
sessionProposed = true
}
- networkingInteractor.wcRequestPublisherSubject.send(payload)
+ pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal))
XCTAssertNotNil(try! proposalPayloadsStore.get(key: proposal.proposer.publicKey), "Proposer must store proposal payload")
XCTAssertTrue(sessionProposed)
}
@@ -106,7 +108,9 @@ final class ApproveEngineTests: XCTestCase {
}
engine.settlingProposal = SessionProposal.stub()
- networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubSettle(topic: sessionTopic))
+ networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle()))
+
+ usleep(100)
XCTAssertTrue(sessionStorageMock.getSession(forTopic: sessionTopic)!.acknowledged, "Proposer must store acknowledged session on topic B")
XCTAssertTrue(networkingInteractor.didRespondSuccess, "Proposer must send acknowledge on settle request")
@@ -117,14 +121,10 @@ final class ApproveEngineTests: XCTestCase {
let session = WCSession.stub(isSelfController: true, acknowledged: false)
sessionStorageMock.setSession(session)
- let settleResponse = JSONRPCResponse(id: 1, result: AnyCodable(true))
- let response = WCResponse(
- topic: session.topic,
- chainId: nil,
- requestMethod: .sessionSettle,
- requestParams: .sessionSettle(SessionType.SettleParams.stub()),
- result: .response(settleResponse))
- networkingInteractor.responsePublisherSubject.send(response)
+ let request = RPCRequest(method: SessionSettleProtocolMethod().method, params: SessionType.SettleParams.stub())
+ let response = RPCResponse(matchingRequest: request, result: RPCResult.response(AnyCodable(true)))
+
+ networkingInteractor.responsePublisherSubject.send((session.topic, request, response))
XCTAssertTrue(sessionStorageMock.getSession(forTopic: session.topic)!.acknowledged, "Responder must acknowledged session")
}
@@ -136,13 +136,10 @@ final class ApproveEngineTests: XCTestCase {
cryptoMock.setAgreementSecret(AgreementKeys.stub(), topic: session.topic)
try! cryptoMock.setPrivateKey(privateKey)
- let response = WCResponse(
- topic: session.topic,
- chainId: nil,
- requestMethod: .sessionSettle,
- requestParams: .sessionSettle(SessionType.SettleParams.stub()),
- result: .error(JSONRPCErrorResponse(id: 1, error: JSONRPCErrorResponse.Error(code: 0, message: ""))))
- networkingInteractor.responsePublisherSubject.send(response)
+ let request = RPCRequest(method: SessionSettleProtocolMethod().method, params: SessionType.SettleParams.stub())
+ let response = RPCResponse.stubError(forRequest: request)
+
+ networkingInteractor.responsePublisherSubject.send((session.topic, request, response))
XCTAssertNil(sessionStorageMock.getSession(forTopic: session.topic), "Responder must remove session")
XCTAssertTrue(networkingInteractor.didUnsubscribe(to: session.topic), "Responder must unsubscribe topic B")
diff --git a/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift b/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift
deleted file mode 100644
index d3616f365..000000000
--- a/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-@testable import WalletConnectSign
-
-extension WCRequest {
-
- var sessionProposal: SessionProposal? {
- guard case .sessionPropose(let proposal) = self.params else { return nil }
- return proposal
- }
-}
diff --git a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift b/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift
deleted file mode 100644
index dd98115f7..000000000
--- a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift
+++ /dev/null
@@ -1,84 +0,0 @@
-import Foundation
-import XCTest
-import TestingUtils
-import WalletConnectUtils
-import WalletConnectPairing
-@testable import WalletConnectSign
-
-final class JsonRpcHistoryTests: XCTestCase {
-
- var sut: WalletConnectSign.JsonRpcHistory!
-
- override func setUp() {
- sut = JsonRpcHistory(logger: ConsoleLoggerMock(), keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""))
- }
-
- override func tearDown() {
- sut = nil
- }
-
- func testSetRecord() {
- 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 = 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 = 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 = JsonRpcResult.response(jsonRpcResponse)
- _ = try! sut.resolve(response: response)
- XCTAssertNotNil(sut.get(id: jsonRpcResponse.id)?.response)
- }
-
- func testThrowsOnResolveDuplicate() {
- let recordinput = getTestJsonRpcRecordInput()
- try! sut.set(topic: recordinput.topic, request: recordinput.request)
- let jsonRpcResponse = JSONRPCResponse(id: recordinput.request.id, result: AnyCodable(""))
- let response = JsonRpcResult.response(jsonRpcResponse)
- _ = try! sut.resolve(response: response)
- XCTAssertThrowsError(try sut.resolve(response: response))
- }
-
- func testThrowsOnSetDuplicate() {
- 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 = 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 = JsonRpcResult.response(jsonRpcResponse)
- _ = try! sut.resolve(response: response)
- XCTAssertEqual(sut.getPending().count, 1)
- }
-}
-
-private let testTopic = "test_topic"
-private func getTestJsonRpcRecordInput(id: Int64 = 0) -> (topic: String, request: WCRequest) {
- let request = WCRequest(id: id, jsonrpc: "2.0", method: .pairingPing, params: WCRequest.Params.pairingPing(PairingType.PingParams()))
- return (topic: testTopic, request: request)
-}
diff --git a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
index eac4dacb5..4044c4b2e 100644
--- a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
+++ b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
@@ -3,42 +3,42 @@ import Foundation
@testable import WalletConnectRelay
@testable import WalletConnectSign
-class MockedRelayClient: NetworkRelaying {
-
- var messagePublisherSubject = PassthroughSubject<(topic: String, message: String), Never>()
- var messagePublisher: AnyPublisher<(topic: String, message: String), Never> {
- messagePublisherSubject.eraseToAnyPublisher()
- }
-
- var socketConnectionStatusPublisherSubject = PassthroughSubject()
- var socketConnectionStatusPublisher: AnyPublisher {
- socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
- }
-
- var error: Error?
- var prompt = false
-
- func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws {
- self.prompt = prompt
- }
-
- func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) {
- self.prompt = prompt
- onNetworkAcknowledge(error)
- }
-
- func subscribe(topic: String) async throws {}
-
- func subscribe(topic: String, completion: @escaping (Error?) -> Void) {
- }
-
- func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) {
- }
-
- func connect() {
- }
-
- func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) {
- }
-
-}
+// class MockedRelayClient: NetworkRelaying {
+//
+// var messagePublisherSubject = PassthroughSubject<(topic: String, message: String), Never>()
+// var messagePublisher: AnyPublisher<(topic: String, message: String), Never> {
+// messagePublisherSubject.eraseToAnyPublisher()
+// }
+//
+// var socketConnectionStatusPublisherSubject = PassthroughSubject