diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index eb0c45b96..297e36c28 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,15 +20,39 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
- test-type: [ui-tests, unit-tests, integration-tests, build-example-wallet, build-example-dapp]
+ test-type: [unit-tests, integration-tests, build-example-wallet, build-example-dapp]
steps:
- uses: actions/checkout@v2
- name: Setup Xcode Version
uses: maxim-lobanov/setup-xcode@v1
+
+ - uses: actions/cache@v2
+ with:
+ path: |
+ .build
+ SourcePackages
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
+
+ - uses: ./.github/actions/ci
with:
- xcode-version: '13.2'
+ type: ${{ matrix.test-type }}
+
+ test-ui:
+ if: github.ref == 'refs/heads/main'
+ runs-on: macos-latest
+ strategy:
+ matrix:
+ test-type: [ui-tests]
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Setup Xcode Version
+ uses: maxim-lobanov/setup-xcode@v1
- uses: actions/cache@v2
with:
diff --git a/.github/workflows/intake.yml b/.github/workflows/intake.yml
new file mode 100644
index 000000000..4b79bc326
--- /dev/null
+++ b/.github/workflows/intake.yml
@@ -0,0 +1,43 @@
+# This workflow moves issues to the Swift board
+# when they receive the "accepted" label
+# When WalletConnect Org members create issues they
+# are automatically "accepted".
+# Else they need to manually receive that label during intake.
+name: intake
+
+on:
+ issues:
+ types: [opened, labeled]
+ pull_request:
+ types: [opened, labeled]
+
+jobs:
+ add-to-project:
+ name: Add issue to board
+ if: github.event.action == 'labeled' && github.event.label.name == 'accepted'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/add-to-project@v0.1.0
+ with:
+ project-url: https://github.com/orgs/WalletConnect/projects/5
+ github-token: ${{ secrets.ASSIGN_TO_PROJECT_GITHUB_TOKEN }}
+ labeled: accepted
+ label-operator: OR
+ auto-promote:
+ name: auto-promote
+ if: github.event.action == 'opened'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if organization member
+ id: is_organization_member
+ if: github.event.action == 'opened'
+ uses: JamesSingleton/is-organization-member@1.0.0
+ with:
+ organization: WalletConnect
+ username: ${{ github.event_name != 'pull_request' && github.event.issue.user.login || github.event.sender.login }}
+ token: ${{ secrets.ASSIGN_TO_PROJECT_GITHUB_TOKEN }}
+ - name: Label issues
+ uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90
+ with:
+ add-labels: "accepted"
+ repo-token: ${{ secrets.ASSIGN_TO_PROJECT_GITHUB_TOKEN }}
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme
new file mode 100644
index 000000000..b6a508a85
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
index 3f9debd20..75284ea37 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme
@@ -262,6 +262,16 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj
index 8b9592e3e..6018e9231 100644
--- a/Example/ExampleApp.xcodeproj/project.pbxproj
+++ b/Example/ExampleApp.xcodeproj/project.pbxproj
@@ -23,6 +23,7 @@
765056272821989600F9AE79 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 765056262821989600F9AE79 /* Color+Extension.swift */; };
76744CF726FE4D5400B77ED9 /* ActiveSessionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76744CF626FE4D5400B77ED9 /* ActiveSessionItem.swift */; };
76744CF926FE4D7400B77ED9 /* ActiveSessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76744CF826FE4D7400B77ED9 /* ActiveSessionCell.swift */; };
+ 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */; };
7694A5262874296A0001257E /* RegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7694A5252874296A0001257E /* RegistryTests.swift */; };
76B149F02821C03B00F05F91 /* Proposal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B149EF2821C03B00F05F91 /* Proposal.swift */; };
76B6E39F2807A3B6004DF775 /* WalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B6E39E2807A3B6004DF775 /* WalletViewController.swift */; };
@@ -182,6 +183,7 @@
765056262821989600F9AE79 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = ""; };
76744CF626FE4D5400B77ED9 /* ActiveSessionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionItem.swift; sourceTree = ""; };
76744CF826FE4D7400B77ED9 /* ActiveSessionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionCell.swift; sourceTree = ""; };
+ 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthSendTransaction.swift; sourceTree = ""; };
7694A5252874296A0001257E /* RegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistryTests.swift; sourceTree = ""; };
76B149EF2821C03B00F05F91 /* Proposal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Proposal.swift; sourceTree = ""; };
76B6E39E2807A3B6004DF775 /* WalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletViewController.swift; sourceTree = ""; };
@@ -436,6 +438,15 @@
name = Frameworks;
sourceTree = "";
};
+ 767DC83328997F7600080FA9 /* Helpers */ = {
+ isa = PBXGroup;
+ children = (
+ A5E03DF8286465C700888481 /* ClientDelegate.swift */,
+ 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */,
+ );
+ path = Helpers;
+ sourceTree = "";
+ };
8460DCFD274F98A90081F94C /* Request */ = {
isa = PBXGroup;
children = (
@@ -862,7 +873,7 @@
A5E03E0928646A8100888481 /* Sign */ = {
isa = PBXGroup;
children = (
- A5E03DF8286465C700888481 /* ClientDelegate.swift */,
+ 767DC83328997F7600080FA9 /* Helpers */,
A5E03DF9286465C700888481 /* SignClientTests.swift */,
);
path = Sign;
@@ -1248,6 +1259,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */,
A5E03DFB286465C700888481 /* ClientDelegate.swift in Sources */,
A5E03E03286466F400888481 /* ChatTests.swift in Sources */,
7694A5262874296A0001257E /* RegistryTests.swift in Sources */,
diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift
index 87093f6ef..812b34e05 100644
--- a/Example/IntegrationTests/Chat/ChatTests.swift
+++ b/Example/IntegrationTests/Chat/ChatTests.swift
@@ -16,25 +16,27 @@ final class ChatTests: XCTestCase {
registry = KeyValueRegistry()
invitee = makeClient(prefix: "🦖 Registered")
inviter = makeClient(prefix: "🍄 Inviter")
+
+ waitClientsConnected()
}
- private func waitClientsConnected() async {
- let group = DispatchGroup()
- group.enter()
+ private func waitClientsConnected() {
+ let expectation = expectation(description: "Wait Clients Connected")
+ expectation.expectedFulfillmentCount = 2
+
invitee.socketConnectionStatusPublisher.sink { status in
if status == .connected {
- group.leave()
+ expectation.fulfill()
}
}.store(in: &publishers)
- group.enter()
inviter.socketConnectionStatusPublisher.sink { status in
if status == .connected {
- group.leave()
+ expectation.fulfill()
}
}.store(in: &publishers)
- group.wait()
- return
+
+ wait(for: [expectation], timeout: 5)
}
func makeClient(prefix: String) -> ChatClient {
@@ -43,11 +45,10 @@ final class ChatTests: XCTestCase {
let projectId = "8ba9ee138960775e5231b70cc5ef1c3a"
let keychain = KeychainStorageMock()
let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)
- return ChatClient(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage())
+ return ChatClientFactory.create(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage())
}
func testInvite() async {
- await waitClientsConnected()
let inviteExpectation = expectation(description: "invitation expectation")
let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")!
@@ -59,61 +60,65 @@ final class ChatTests: XCTestCase {
wait(for: [inviteExpectation], timeout: 4)
}
-// func testAcceptAndCreateNewThread() async {
-// await waitClientsConnected()
-// let newThreadInviterExpectation = expectation(description: "new thread on inviting client expectation")
-// let newThreadinviteeExpectation = expectation(description: "new thread on invitee client expectation")
-// let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
-// let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")!
-// let pubKey = try! await invitee.register(account: inviteeAccount)
-//
-// try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount)
-//
-// invitee.invitePublisher.sink { [unowned self] invite in
-// Task {try! await invitee.accept(inviteId: invite.id)}
-// }.store(in: &publishers)
-//
-// invitee.newThreadPublisher.sink { _ in
-// newThreadinviteeExpectation.fulfill()
-// }.store(in: &publishers)
-//
-// inviter.newThreadPublisher.sink { _ in
-// newThreadInviterExpectation.fulfill()
-// }.store(in: &publishers)
-//
-// wait(for: [newThreadinviteeExpectation, newThreadInviterExpectation], timeout: 4)
-// }
-//
-// func testMessage() async {
-// await waitClientsConnected()
-// let messageExpectation = expectation(description: "message received")
-// messageExpectation.expectedFulfillmentCount = 2
-// let message = "message"
-// let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
-// let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")!
-// let pubKey = try! await invitee.register(account: inviteeAccount)
-// try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount)
-//
-// invitee.invitePublisher.sink { [unowned self] invite in
-// Task {try! await invitee.accept(inviteId: invite.id)}
-// }.store(in: &publishers)
-//
-// invitee.newThreadPublisher.sink { [unowned self] thread in
-// Task {try! await invitee.message(topic: thread.topic, message: message)}
-// }.store(in: &publishers)
-//
-// inviter.newThreadPublisher.sink { [unowned self] thread in
-// Task {try! await inviter.message(topic: thread.topic, message: message)}
-// }.store(in: &publishers)
-//
-// inviter.messagePublisher.sink { _ in
-// messageExpectation.fulfill()
-// }.store(in: &publishers)
-//
-// invitee.messagePublisher.sink { _ in
-// messageExpectation.fulfill()
-// }.store(in: &publishers)
-//
-// wait(for: [messageExpectation], timeout: 35)
-// }
+ func testAcceptAndCreateNewThread() {
+ let newThreadInviterExpectation = expectation(description: "new thread on inviting client expectation")
+ let newThreadinviteeExpectation = expectation(description: "new thread on invitee client expectation")
+ let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
+ let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")!
+
+ Task(priority: .background) {
+ let pubKey = try! await invitee.register(account: inviteeAccount)
+
+ try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount)
+ }
+
+ invitee.invitePublisher.sink { [unowned self] invite in
+ Task {try! await invitee.accept(inviteId: invite.id)}
+ }.store(in: &publishers)
+
+ invitee.newThreadPublisher.sink { _ in
+ newThreadinviteeExpectation.fulfill()
+ }.store(in: &publishers)
+
+ inviter.newThreadPublisher.sink { _ in
+ newThreadInviterExpectation.fulfill()
+ }.store(in: &publishers)
+
+ wait(for: [newThreadinviteeExpectation, newThreadInviterExpectation], timeout: 10)
+ }
+
+ func testMessage() {
+ let messageExpectation = expectation(description: "message received")
+ messageExpectation.expectedFulfillmentCount = 4 // expectedFulfillmentCount 4 because onMessage() called on send too
+
+ let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
+ let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")!
+
+ Task(priority: .background) {
+ let pubKey = try! await invitee.register(account: inviteeAccount)
+ try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount)
+ }
+
+ invitee.invitePublisher.sink { [unowned self] invite in
+ Task {try! await invitee.accept(inviteId: invite.id)}
+ }.store(in: &publishers)
+
+ invitee.newThreadPublisher.sink { [unowned self] thread in
+ Task {try! await invitee.message(topic: thread.topic, message: "message")}
+ }.store(in: &publishers)
+
+ inviter.newThreadPublisher.sink { [unowned self] thread in
+ Task {try! await inviter.message(topic: thread.topic, message: "message")}
+ }.store(in: &publishers)
+
+ inviter.messagePublisher.sink { _ in
+ messageExpectation.fulfill()
+ }.store(in: &publishers)
+
+ invitee.messagePublisher.sink { _ in
+ messageExpectation.fulfill()
+ }.store(in: &publishers)
+
+ wait(for: [messageExpectation], timeout: 10)
+ }
}
diff --git a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
index 12baaea83..44f329ac4 100644
--- a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
+++ b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift
@@ -22,7 +22,7 @@ final class RelayClientEndToEndTests: XCTestCase {
)
let urlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator)
let socket = WebSocket(url: urlFactory.create(host: relayHost, projectId: projectId))
-
+
let logger = ConsoleLogger()
let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: ManualSocketConnectionHandler(socket: socket), logger: logger)
return RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage())
diff --git a/Example/IntegrationTests/Sign/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift
similarity index 100%
rename from Example/IntegrationTests/Sign/ClientDelegate.swift
rename to Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift
diff --git a/Example/IntegrationTests/Sign/Helpers/EthSendTransaction.swift b/Example/IntegrationTests/Sign/Helpers/EthSendTransaction.swift
new file mode 100644
index 000000000..6e4db5b62
--- /dev/null
+++ b/Example/IntegrationTests/Sign/Helpers/EthSendTransaction.swift
@@ -0,0 +1,26 @@
+import Foundation
+
+struct EthSendTransaction: Codable, Equatable {
+ let from: String
+ let data: String
+ let value: String
+ let to: String
+ let gasPrice: String
+ let nonce: String
+
+ static func stub() -> EthSendTransaction {
+ try! JSONDecoder().decode(EthSendTransaction.self, from: ethSendTransactionJSON.data(using: .utf8)!)
+ }
+
+ private static let ethSendTransactionJSON = """
+{
+ "from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155",
+ "to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567",
+ "data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
+ "gas":"0x76c0",
+ "gasPrice":"0x9184e72a000",
+ "value":"0x9184e72a",
+ "nonce":"0x117"
+}
+"""
+}
diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift
index e2cd14006..cbf6566cf 100644
--- a/Example/IntegrationTests/Sign/SignClientTests.swift
+++ b/Example/IntegrationTests/Sign/SignClientTests.swift
@@ -8,8 +8,8 @@ final class SignClientTests: XCTestCase {
let defaultTimeout: TimeInterval = 5
- var proposer: ClientDelegate!
- var responder: ClientDelegate!
+ var dapp: ClientDelegate!
+ var wallet: ClientDelegate!
static private func makeClientDelegate(
name: String,
@@ -27,7 +27,7 @@ final class SignClientTests: XCTestCase {
socketConnectionType: .automatic,
logger: logger
)
- let client = SignClient(
+ let client = SignClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
logger: logger,
keyValueStorage: RuntimeKeyValueStorage(),
@@ -40,11 +40,11 @@ final class SignClientTests: XCTestCase {
private func listenForConnection() async {
let group = DispatchGroup()
group.enter()
- proposer.onConnected = {
+ dapp.onConnected = {
group.leave()
}
group.enter()
- responder.onConnected = {
+ wallet.onConnected = {
group.leave()
}
group.wait()
@@ -52,27 +52,29 @@ final class SignClientTests: XCTestCase {
}
override func setUp() async throws {
- proposer = Self.makeClientDelegate(name: "🍏P")
- responder = Self.makeClientDelegate(name: "🍎R")
+ dapp = Self.makeClientDelegate(name: "🍏P")
+ wallet = Self.makeClientDelegate(name: "🍎R")
await listenForConnection()
}
override func tearDown() {
- proposer = nil
- responder = nil
+ dapp = nil
+ wallet = nil
}
func testSessionPropose() async throws {
- let dapp = proposer!
- let wallet = responder!
let dappSettlementExpectation = expectation(description: "Dapp expects to settle a session")
let walletSettlementExpectation = expectation(description: "Wallet expects to settle a session")
let requiredNamespaces = ProposalNamespace.stubRequired()
let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
- wallet.onSessionProposal = { proposal in
+ wallet.onSessionProposal = { [unowned self] proposal in
Task(priority: .high) {
- do { try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") }
+ do {
+ try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces)
+ } catch {
+ XCTFail("\(error)")
+ }
}
}
dapp.onSessionSettled = { _ in
@@ -88,8 +90,6 @@ final class SignClientTests: XCTestCase {
}
func testSessionReject() async throws {
- let dapp = proposer!
- let wallet = responder!
let sessionRejectExpectation = expectation(description: "Proposer is notified on session rejection")
class Store { var rejectedProposal: Session.Proposal? }
@@ -98,7 +98,7 @@ final class SignClientTests: XCTestCase {
let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired())
try await wallet.client.pair(uri: uri!)
- wallet.onSessionProposal = { proposal in
+ wallet.onSessionProposal = { [unowned self] proposal in
Task(priority: .high) {
do {
try await wallet.client.reject(proposalId: proposal.id, reason: .disapprovedChains) // TODO: Review reason
@@ -114,18 +114,16 @@ final class SignClientTests: XCTestCase {
}
func testSessionDelete() async throws {
- let dapp = proposer!
- let wallet = responder!
let sessionDeleteExpectation = expectation(description: "Wallet expects session to be deleted")
let requiredNamespaces = ProposalNamespace.stubRequired()
let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
- wallet.onSessionProposal = { proposal in
+ wallet.onSessionProposal = { [unowned self] proposal in
Task(priority: .high) {
do { try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") }
}
}
- dapp.onSessionSettled = { settledSession in
+ dapp.onSessionSettled = { [unowned self] settledSession in
Task(priority: .high) {
try await dapp.client.disconnect(topic: settledSession.topic)
}
@@ -140,8 +138,6 @@ final class SignClientTests: XCTestCase {
}
func testNewPairingPing() async throws {
- let dapp = proposer!
- let wallet = responder!
let pongResponseExpectation = expectation(description: "Ping sender receives a pong response")
let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired())!
@@ -155,6 +151,99 @@ final class SignClientTests: XCTestCase {
wait(for: [pongResponseExpectation], timeout: defaultTimeout)
}
+ func testSessionRequest() async throws {
+ let requestExpectation = expectation(description: "Wallet expects to receive a request")
+ let responseExpectation = expectation(description: "Dapp expects to receive a response")
+ let requiredNamespaces = ProposalNamespace.stubRequired()
+ let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
+
+ let requestMethod = "eth_sendTransaction"
+ let requestParams = [EthSendTransaction.stub()]
+ let responseParams = "0xdeadbeef"
+ let chain = Blockchain("eip155:1")!
+
+ wallet.onSessionProposal = { [unowned self] proposal in
+ Task(priority: .high) {
+ do {
+ try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) }
+ catch {
+ XCTFail("\(error)")
+ }
+ }
+ }
+ dapp.onSessionSettled = { [unowned self] settledSession in
+ Task(priority: .high) {
+ let request = Request(id: 0, topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
+ try await dapp.client.request(params: request)
+ }
+ }
+ wallet.onSessionRequest = { [unowned self] sessionRequest in
+ let receivedParams = try! sessionRequest.params.get([EthSendTransaction].self)
+ XCTAssertEqual(receivedParams, requestParams)
+ 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))
+ }
+ }
+ dapp.onSessionResponse = { response in
+ switch response.result {
+ case .response(let response):
+ XCTAssertEqual(try! response.result.get(String.self), responseParams)
+ case .error:
+ XCTFail()
+ }
+ responseExpectation.fulfill()
+ }
+
+ let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
+ try await wallet.client.pair(uri: uri!)
+ wait(for: [requestExpectation, responseExpectation], timeout: defaultTimeout)
+ }
+
+ func testSessionRequestFailureResponse() async throws {
+ let expectation = expectation(description: "Dapp expects to receive an error response")
+ let requiredNamespaces = ProposalNamespace.stubRequired()
+ let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces)
+
+ let requestMethod = "eth_sendTransaction"
+ let requestParams = [EthSendTransaction.stub()]
+ let error = JSONRPCErrorResponse.Error(code: 0, message: "error")
+ let chain = Blockchain("eip155:1")!
+
+ 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
+ Task(priority: .high) {
+ let request = Request(id: 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))
+ }
+ }
+ dapp.onSessionResponse = { response in
+ switch response.result {
+ case .response:
+ XCTFail()
+ case .error(let errorResponse):
+ XCTAssertEqual(error, errorResponse.error)
+ }
+ expectation.fulfill()
+ }
+
+ let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)
+ try await wallet.client.pair(uri: uri!)
+ wait(for: [expectation], timeout: defaultTimeout)
+ }
+
//
// func testNewSessionOnExistingPairing() async {
// await waitClientsConnected()
@@ -186,82 +275,6 @@ final class SignClientTests: XCTestCase {
// wait(for: [proposerSettlesSessionExpectation, responderSettlesSessionExpectation], timeout: defaultTimeout)
// }
//
-//
-// func testProposerRequestSessionRequest() async {
-// await waitClientsConnected()
-// let requestExpectation = expectation(description: "Responder receives request")
-// let responseExpectation = expectation(description: "Proposer receives response")
-// let method = "eth_sendTransaction"
-// let params = [try! JSONDecoder().decode(EthSendTransaction.self, from: ethSendTransaction.data(using: .utf8)!)]
-// let responseParams = "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
-// let uri = try! await proposer.client.connect(namespaces: [Namespace.stub(methods: [method])])!
-//
-// _ = try! await responder.client.pair(uri: uri)
-// responder.onSessionProposal = {[unowned self] proposal in
-// try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
-// }
-// proposer.onSessionSettled = {[unowned self] settledSession in
-// let requestParams = Request(id: 0, topic: settledSession.topic, method: method, params: AnyCodable(params), chainId: Blockchain("eip155:1")!)
-// Task {
-// try await self.proposer.client.request(params: requestParams)
-// }
-// }
-// proposer.onSessionResponse = { response in
-// switch response.result {
-// case .response(let jsonRpcResponse):
-// let response = try! jsonRpcResponse.result.get(String.self)
-// XCTAssertEqual(response, responseParams)
-// responseExpectation.fulfill()
-// case .error(_):
-// XCTFail()
-// }
-// }
-// responder.onSessionRequest = {[unowned self] sessionRequest in
-// XCTAssertEqual(sessionRequest.method, method)
-// let ethSendTrancastionParams = try! sessionRequest.params.get([EthSendTransaction].self)
-// XCTAssertEqual(ethSendTrancastionParams, params)
-// let jsonrpcResponse = JSONRPCResponse(id: sessionRequest.id, result: AnyCodable(responseParams))
-// self.responder.client.respond(topic: sessionRequest.topic, response: .response(jsonrpcResponse))
-// requestExpectation.fulfill()
-// }
-// wait(for: [requestExpectation, responseExpectation], timeout: defaultTimeout)
-// }
-//
-//
-// func testSessionRequestFailureResponse() async {
-// await waitClientsConnected()
-// let failureResponseExpectation = expectation(description: "Proposer receives failure response")
-// let method = "eth_sendTransaction"
-// let params = [try! JSONDecoder().decode(EthSendTransaction.self, from: ethSendTransaction.data(using: .utf8)!)]
-// let error = JSONRPCErrorResponse.Error(code: 0, message: "error_message")
-// let uri = try! await proposer.client.connect(namespaces: [Namespace.stub(methods: [method])])!
-// _ = try! await responder.client.pair(uri: uri)
-// responder.onSessionProposal = {[unowned self] proposal in
-// try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
-// }
-// proposer.onSessionSettled = {[unowned self] settledSession in
-// let requestParams = Request(id: 0, topic: settledSession.topic, method: method, params: AnyCodable(params), chainId: Blockchain("eip155:1")!)
-// Task {
-// try await self.proposer.client.request(params: requestParams)
-// }
-// }
-// proposer.onSessionResponse = { response in
-// switch response.result {
-// case .response(_):
-// XCTFail()
-// case .error(let errorResponse):
-// XCTAssertEqual(error, errorResponse.error)
-// failureResponseExpectation.fulfill()
-// }
-//
-// }
-// responder.onSessionRequest = {[unowned self] sessionRequest in
-// let jsonrpcErrorResponse = JSONRPCErrorResponse(id: sessionRequest.id, error: error)
-// self.responder.client.respond(topic: sessionRequest.topic, response: .error(jsonrpcErrorResponse))
-// }
-// wait(for: [failureResponseExpectation], timeout: defaultTimeout)
-// }
-//
// func testSessionPing() async {
// await waitClientsConnected()
// let proposerReceivesPingResponseExpectation = expectation(description: "Proposer receives ping response")
@@ -368,26 +381,3 @@ final class SignClientTests: XCTestCase {
// wait(for: [proposerReceivesEventExpectation], timeout: defaultTimeout)
// }
}
-
-// public struct EthSendTransaction: Codable, Equatable {
-// public let from: String
-// public let data: String
-// public let value: String
-// public let to: String
-// public let gasPrice: String
-// public let nonce: String
-// }
-//
-//
-// fileprivate let ethSendTransaction = """
-// {
-// "from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155",
-// "to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567",
-// "data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
-// "gas":"0x76c0",
-// "gasPrice":"0x9184e72a000",
-// "value":"0x9184e72a",
-// "nonce":"0x117"
-// }
-// """
-//
diff --git a/Example/IntegrationTests/Stubs/Stubs.swift b/Example/IntegrationTests/Stubs/Stubs.swift
index 78f06afca..d4f1f9eda 100644
--- a/Example/IntegrationTests/Stubs/Stubs.swift
+++ b/Example/IntegrationTests/Stubs/Stubs.swift
@@ -5,7 +5,7 @@ extension ProposalNamespace {
return [
"eip155": ProposalNamespace(
chains: [Blockchain("eip155:1")!],
- methods: ["personal_sign"],
+ methods: ["personal_sign", "eth_sendTransaction"],
events: [])
]
}
diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
index 33eeaadf5..fe4d61fa7 100644
--- a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
+++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift
@@ -2,6 +2,7 @@ import Foundation
import Chat
import WalletConnectKMS
import WalletConnectRelay
+import WalletConnectUtils
class ChatFactory {
@@ -12,10 +13,11 @@ class ChatFactory {
let client = HTTPClient(host: "keys.walletconnect.com")
let registry = KeyserverRegistryProvider(client: client)
let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory())
- return ChatClient(
+ return ChatClientFactory.create(
registry: registry,
relayClient: relayClient,
kms: KeyManagementService(keychain: keychain),
+ logger: ConsoleLogger(),
keyValueStorage: UserDefaults.standard
)
}
diff --git a/Example/Showcase/Classes/PresentationLayer/Import/ImportInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Import/ImportInteractor.swift
index a6bcca7e8..81d70a023 100644
--- a/Example/Showcase/Classes/PresentationLayer/Import/ImportInteractor.swift
+++ b/Example/Showcase/Classes/PresentationLayer/Import/ImportInteractor.swift
@@ -1,4 +1,3 @@
-
final class ImportInteractor {
private let registerService: RegisterService
private let accountStorage: AccountStorage
diff --git a/Package.swift b/Package.swift
index 537aeed2a..a94e15017 100644
--- a/Package.swift
+++ b/Package.swift
@@ -15,18 +15,25 @@ let package = Package(
targets: ["WalletConnectSign"]),
.library(
name: "WalletConnectChat",
- targets: ["Chat"])
+ targets: ["Chat"]),
+ .library(
+ name: "WalletConnectPairing",
+ targets: ["WalletConnectPairing"])
],
dependencies: [],
targets: [
.target(
name: "WalletConnectSign",
- dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS"],
+ dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS", "WalletConnectPairing"],
path: "Sources/WalletConnectSign"),
.target(
name: "Chat",
dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS"],
path: "Sources/Chat"),
+ .target(
+ name: "Auth",
+ dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS", "WalletConnectPairing"],
+ path: "Sources/Auth"),
.target(
name: "WalletConnectRelay",
dependencies: ["WalletConnectUtils", "WalletConnectKMS"],
@@ -35,9 +42,12 @@ let package = Package(
name: "WalletConnectKMS",
dependencies: ["WalletConnectUtils"],
path: "Sources/WalletConnectKMS"),
+ .target(
+ name: "WalletConnectPairing",
+ dependencies: ["WalletConnectUtils"]),
.target(
name: "WalletConnectUtils",
- dependencies: ["Commons"]),
+ dependencies: ["Commons", "JSONRPC"]),
.target(
name: "JSONRPC",
dependencies: ["Commons"]),
@@ -50,6 +60,9 @@ let package = Package(
.testTarget(
name: "ChatTests",
dependencies: ["Chat", "WalletConnectUtils", "TestingUtils"]),
+ .testTarget(
+ name: "AuthTests",
+ dependencies: ["Auth", "WalletConnectUtils", "TestingUtils"]),
.testTarget(
name: "RelayerTests",
dependencies: ["WalletConnectRelay", "WalletConnectUtils", "TestingUtils"]),
@@ -58,11 +71,11 @@ let package = Package(
dependencies: ["WalletConnectKMS", "WalletConnectUtils", "TestingUtils"]),
.target(
name: "TestingUtils",
- dependencies: ["WalletConnectUtils", "WalletConnectKMS"],
+ dependencies: ["WalletConnectUtils", "WalletConnectKMS", "JSONRPC"],
path: "Tests/TestingUtils"),
.testTarget(
name: "WalletConnectUtilsTests",
- dependencies: ["WalletConnectUtils"]),
+ dependencies: ["WalletConnectUtils", "TestingUtils"]),
.testTarget(
name: "JSONRPCTests",
dependencies: ["JSONRPC", "TestingUtils"]),
diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift
new file mode 100644
index 000000000..a9c751104
--- /dev/null
+++ b/Sources/Auth/AuthClient.swift
@@ -0,0 +1,31 @@
+import Foundation
+
+class AuthClient {
+ enum Errors: Error {
+ case malformedPairingURI
+ }
+
+ private let appPairService: AppPairService
+ private let appRequestService: AuthRequestService
+
+ private let walletPairService: WalletPairService
+
+ init(appPairService: AppPairService, appRequestService: AuthRequestService, walletPairService: WalletPairService) {
+ self.appPairService = appPairService
+ self.appRequestService = appRequestService
+ self.walletPairService = walletPairService
+ }
+
+ func request(params: RequestParams) async throws -> String {
+ let uri = try await appPairService.create()
+ try await appRequestService.request(params: params, topic: uri.topic)
+ return uri.absoluteString
+ }
+
+ func pair(uri: String) async throws {
+ guard let pairingURI = WalletConnectURI(string: uri) else {
+ throw Errors.malformedPairingURI
+ }
+ try await walletPairService.pair(pairingURI)
+ }
+}
diff --git a/Sources/Auth/Services/App/AppPairService.swift b/Sources/Auth/Services/App/AppPairService.swift
new file mode 100644
index 000000000..05a309653
--- /dev/null
+++ b/Sources/Auth/Services/App/AppPairService.swift
@@ -0,0 +1,25 @@
+import Foundation
+import WalletConnectKMS
+import WalletConnectPairing
+
+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/Auth/Services/App/AuthRequestService.swift b/Sources/Auth/Services/App/AuthRequestService.swift
new file mode 100644
index 000000000..b82d5259b
--- /dev/null
+++ b/Sources/Auth/Services/App/AuthRequestService.swift
@@ -0,0 +1,30 @@
+import Foundation
+import WalletConnectUtils
+import WalletConnectKMS
+import JSONRPC
+
+actor AuthRequestService {
+ private let networkingInteractor: NetworkInteracting
+ private let appMetadata: AppMetadata
+ private let kms: KeyManagementService
+
+ init(networkingInteractor: NetworkInteracting,
+ kms: KeyManagementService,
+ appMetadata: AppMetadata) {
+ self.networkingInteractor = networkingInteractor
+ self.kms = kms
+ self.appMetadata = appMetadata
+ }
+
+ 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 params = AuthRequestParams(requester: requester, payloadParams: payload)
+ let request = RPCRequest(method: "wc_authRequest", params: params)
+ try await networkingInteractor.request(request, topic: topic, tag: AuthRequestParams.tag)
+ try await networkingInteractor.subscribe(topic: responseTopic)
+ }
+}
diff --git a/Sources/Auth/Services/Common/CacaoFormatter.swift b/Sources/Auth/Services/Common/CacaoFormatter.swift
new file mode 100644
index 000000000..43ddb9058
--- /dev/null
+++ b/Sources/Auth/Services/Common/CacaoFormatter.swift
@@ -0,0 +1,12 @@
+import Foundation
+import WalletConnectUtils
+
+protocol CacaoFormatting {
+ func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ issuer: Account) -> Cacao
+}
+
+class CacaoFormatter: CacaoFormatting {
+ func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ issuer: Account) -> Cacao {
+ fatalError("not implemented")
+ }
+}
diff --git a/Sources/Auth/Services/Common/NetworkingInteractor.swift b/Sources/Auth/Services/Common/NetworkingInteractor.swift
new file mode 100644
index 000000000..c81420b26
--- /dev/null
+++ b/Sources/Auth/Services/Common/NetworkingInteractor.swift
@@ -0,0 +1,53 @@
+import Foundation
+import WalletConnectRelay
+import WalletConnectUtils
+import Combine
+import WalletConnectKMS
+import JSONRPC
+
+protocol NetworkInteracting {
+ var requestPublisher: AnyPublisher {get}
+ func subscribe(topic: String) async throws
+ func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
+ func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
+}
+
+extension NetworkInteracting {
+ func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType = .type0) async throws {
+ try await self.request(request, topic: topic, tag: tag, envelopeType: envelopeType)
+ }
+}
+
+class NetworkingInteractor: NetworkInteracting {
+ private let relayClient: RelayClient
+ private let serializer: Serializing
+ private let rpcHistory: RPCHistory
+ var requestPublisher: AnyPublisher {
+ requestPublisherSubject.eraseToAnyPublisher()
+ }
+ private let requestPublisherSubject = PassthroughSubject()
+
+ init(relayClient: RelayClient,
+ serializer: Serializing,
+ rpcHistory: RPCHistory) {
+ self.relayClient = relayClient
+ self.serializer = serializer
+ self.rpcHistory = rpcHistory
+ }
+
+ func subscribe(topic: String) async throws {
+ try await relayClient.subscribe(topic: topic)
+ }
+
+ func request(_ request: RPCRequest, topic: String, tag: Int, 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)
+ }
+
+ func respond(topic: String, response: RPCResponse, tag: Int, 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)
+ }
+}
diff --git a/Sources/Auth/Services/Common/SIWEMessageFormatter.swift b/Sources/Auth/Services/Common/SIWEMessageFormatter.swift
new file mode 100644
index 000000000..edf489f74
--- /dev/null
+++ b/Sources/Auth/Services/Common/SIWEMessageFormatter.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+protocol SIWEMessageFormatting {
+ func formatMessage(from request: AuthRequestParams) throws -> String
+}
+
+struct SIWEMessageFormatter: SIWEMessageFormatting {
+ func formatMessage(from request: AuthRequestParams) throws -> String {
+ fatalError("not implemented")
+ }
+}
diff --git a/Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift b/Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift
new file mode 100644
index 000000000..d65a56bae
--- /dev/null
+++ b/Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift
@@ -0,0 +1,39 @@
+import Combine
+import Foundation
+import WalletConnectUtils
+import JSONRPC
+
+class AuthRequestSubscriber {
+ private let networkingInteractor: NetworkInteracting
+ private let logger: ConsoleLogging
+ private var publishers = [AnyCancellable]()
+ private let messageFormatter: SIWEMessageFormatting
+ var onRequest: ((_ id: RPCID, _ message: String)->())?
+
+ init(networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ messageFormatter: SIWEMessageFormatting) {
+ self.networkingInteractor = networkingInteractor
+ self.logger = logger
+ self.messageFormatter = messageFormatter
+ subscribeForRequest()
+ }
+
+ private func subscribeForRequest() {
+ networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in
+ guard subscriptionPayload.request.method == "wc_authRequest" else { return }
+ guard let authRequestParams = try? subscriptionPayload.request.params?.get(AuthRequestParams.self) else {
+ logger.debug("Malformed auth request params")
+ return
+ }
+ do {
+ let message = try messageFormatter.formatMessage(from: authRequestParams)
+ guard let requestId = subscriptionPayload.request.id else { return }
+ onRequest?(requestId, message)
+ } catch {
+ logger.debug(error)
+ }
+ }.store(in: &publishers)
+ }
+
+}
diff --git a/Sources/Auth/Services/Wallet/AuthRespondService.swift b/Sources/Auth/Services/Wallet/AuthRespondService.swift
new file mode 100644
index 000000000..006459d37
--- /dev/null
+++ b/Sources/Auth/Services/Wallet/AuthRespondService.swift
@@ -0,0 +1,41 @@
+import Foundation
+import WalletConnectKMS
+import JSONRPC
+import WalletConnectUtils
+
+actor AuthRespondService {
+ enum Errors: Error {
+ case recordForIdNotFound
+ case malformedAuthRequestParams
+ }
+ private let networkingInteractor: NetworkInteracting
+ private let kms: KeyManagementService
+ private let rpcHistory: RPCHistory
+ private let logger: ConsoleLogging
+
+ init(networkingInteractor: NetworkInteracting,
+ logger: ConsoleLogging,
+ kms: KeyManagementService,
+ rpcHistory: RPCHistory) {
+ self.networkingInteractor = networkingInteractor
+ self.logger = logger
+ self.kms = kms
+ self.rpcHistory = rpcHistory
+ }
+
+ func respond(respondParams: RespondParams, issuer: Account) async throws {
+ guard let request = rpcHistory.get(recordId: RPCID(respondParams.id))?.request else { throw Errors.recordForIdNotFound }
+ guard let authRequestParams = try? request.params?.get(AuthRequestParams.self) else { throw Errors.malformedAuthRequestParams }
+
+ let peerPubKey = authRequestParams.requester.publicKey
+ let responseTopic = peerPubKey.rawRepresentation.sha256().toHexString()
+ let selfPubKey = try kms.createX25519KeyPair()
+ let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey)
+ try kms.setAgreementSecret(agreementKeys, topic: responseTopic)
+
+ let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, issuer)
+ let response = RPCResponse(id: request.id!, result: cacao)
+
+ try await networkingInteractor.respond(topic: respondParams.topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: selfPubKey.rawRepresentation))
+ }
+}
diff --git a/Sources/Auth/Services/Wallet/WalletPairService.swift b/Sources/Auth/Services/Wallet/WalletPairService.swift
new file mode 100644
index 000000000..ef4fc2e28
--- /dev/null
+++ b/Sources/Auth/Services/Wallet/WalletPairService.swift
@@ -0,0 +1,37 @@
+import Foundation
+import WalletConnectKMS
+import WalletConnectPairing
+
+actor WalletPairService {
+ enum Errors: Error {
+ case pairingAlreadyExist
+ }
+
+ 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 pair(_ uri: WalletConnectURI) async throws {
+ guard !hasPairing(for: uri.topic) else {
+ throw Errors.pairingAlreadyExist
+ }
+ var pairing = WCPairing(uri: uri)
+ try await networkingInteractor.subscribe(topic: pairing.topic)
+ let symKey = try SymmetricKey(hex: uri.symKey)
+ try kms.setSymmetricKey(symKey, for: pairing.topic)
+ pairing.activate()
+ pairingStorage.setPairing(pairing)
+ }
+
+ func hasPairing(for topic: String) -> Bool {
+ return pairingStorage.hasPairing(forTopic: topic)
+ }
+}
diff --git a/Sources/Auth/Types/AppMetadata.swift b/Sources/Auth/Types/AppMetadata.swift
new file mode 100644
index 000000000..bd646f855
--- /dev/null
+++ b/Sources/Auth/Types/AppMetadata.swift
@@ -0,0 +1,3 @@
+import WalletConnectPairing
+
+public typealias AppMetadata = WalletConnectPairing.AppMetadata
diff --git a/Sources/Auth/Types/AuthPayload.swift b/Sources/Auth/Types/AuthPayload.swift
new file mode 100644
index 000000000..b281c19d0
--- /dev/null
+++ b/Sources/Auth/Types/AuthPayload.swift
@@ -0,0 +1,31 @@
+import Foundation
+
+struct AuthPayload: Codable, Equatable {
+ let type: String
+ let chainId: String
+ let domain: String
+ let aud: String
+ let version: String
+ let nonce: String
+ let iat: String
+ let nbf: String?
+ let exp: String?
+ let statement: String?
+ let requestId: String?
+ let resources: String?
+
+ init(requestParams: RequestParams, iat: String) {
+ self.type = "eip4361"
+ self.chainId = requestParams.chainId
+ self.domain = requestParams.domain
+ self.aud = requestParams.aud
+ self.version = "1"
+ self.nonce = requestParams.nonce
+ self.iat = iat
+ self.nbf = requestParams.nbf
+ self.exp = requestParams.exp
+ self.statement = requestParams.statement
+ self.requestId = requestParams.requestId
+ self.resources = requestParams.resources
+ }
+}
diff --git a/Sources/Auth/Types/Cacao/Cacao.swift b/Sources/Auth/Types/Cacao/Cacao.swift
new file mode 100644
index 000000000..95a2d59c5
--- /dev/null
+++ b/Sources/Auth/Types/Cacao/Cacao.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct Cacao: Codable, Equatable {
+ let header: CacaoHeader
+ let payload: CacaoPayload
+ let signature: CacaoSignature
+}
diff --git a/Sources/Auth/Types/Cacao/CacaoHeader.swift b/Sources/Auth/Types/Cacao/CacaoHeader.swift
new file mode 100644
index 000000000..1461f3ae4
--- /dev/null
+++ b/Sources/Auth/Types/Cacao/CacaoHeader.swift
@@ -0,0 +1,5 @@
+import Foundation
+
+struct CacaoHeader: Codable, Equatable {
+ let t: String
+}
diff --git a/Sources/Auth/Types/Cacao/CacaoPayload.swift b/Sources/Auth/Types/Cacao/CacaoPayload.swift
new file mode 100644
index 000000000..c339bd18d
--- /dev/null
+++ b/Sources/Auth/Types/Cacao/CacaoPayload.swift
@@ -0,0 +1,15 @@
+import Foundation
+
+struct CacaoPayload: Codable, Equatable {
+ let iss: String
+ let domain: String
+ let aud: String
+ let version: String
+ let nonce: String
+ let iat: String
+ let nbf: String
+ let exp: String
+ let statement: String
+ let requestId: String
+ let resources: String
+}
diff --git a/Sources/Auth/Types/Cacao/CacaoSignature.swift b/Sources/Auth/Types/Cacao/CacaoSignature.swift
new file mode 100644
index 000000000..97c04c142
--- /dev/null
+++ b/Sources/Auth/Types/Cacao/CacaoSignature.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct CacaoSignature: Codable, Equatable {
+ let t: String
+ let s: String
+ let m: String
+}
diff --git a/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift b/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift
new file mode 100644
index 000000000..c39ba3489
--- /dev/null
+++ b/Sources/Auth/Types/ProtocolRPCParams/AuthRequestParams.swift
@@ -0,0 +1,18 @@
+import Foundation
+import WalletConnectUtils
+
+struct AuthRequestParams: Codable, Equatable {
+ let requester: Requester
+ let payloadParams: AuthPayload
+
+ static var tag: Int {
+ return 3000
+ }
+}
+
+extension AuthRequestParams {
+ struct Requester: Codable, Equatable {
+ let publicKey: String
+ let metadata: AppMetadata
+ }
+}
diff --git a/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift b/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift
new file mode 100644
index 000000000..a0d64b152
--- /dev/null
+++ b/Sources/Auth/Types/ProtocolRPCParams/AuthResponseParams.swift
@@ -0,0 +1,12 @@
+import Foundation
+import WalletConnectUtils
+
+struct AuthResponseParams: Codable, Equatable {
+ let header: CacaoHeader
+ let payload: CacaoPayload
+ let signature: CacaoSignature
+
+ static var tag: Int {
+ return 3001
+ }
+}
diff --git a/Sources/Auth/Types/RelayProtocolOptions.swift b/Sources/Auth/Types/RelayProtocolOptions.swift
new file mode 100644
index 000000000..d5b4b6c4f
--- /dev/null
+++ b/Sources/Auth/Types/RelayProtocolOptions.swift
@@ -0,0 +1,4 @@
+import Foundation
+import WalletConnectUtils
+
+typealias RelayProtocolOptions = WalletConnectUtils.RelayProtocolOptions
diff --git a/Sources/Auth/Types/RequestParams.swift b/Sources/Auth/Types/RequestParams.swift
new file mode 100644
index 000000000..7a6082f52
--- /dev/null
+++ b/Sources/Auth/Types/RequestParams.swift
@@ -0,0 +1,13 @@
+import Foundation
+
+struct RequestParams {
+ let domain: String
+ let chainId: String
+ let nonce: String
+ let aud: String
+ let nbf: String?
+ let exp: String?
+ let statement: String?
+ let requestId: String?
+ let resources: String?
+}
diff --git a/Sources/Auth/Types/RequestSubscriptionPayload.swift b/Sources/Auth/Types/RequestSubscriptionPayload.swift
new file mode 100644
index 000000000..b7865d715
--- /dev/null
+++ b/Sources/Auth/Types/RequestSubscriptionPayload.swift
@@ -0,0 +1,7 @@
+import Foundation
+import JSONRPC
+
+struct RequestSubscriptionPayload: Codable {
+ let id: Int64
+ let request: RPCRequest
+}
diff --git a/Sources/Auth/Types/RespondParams.swift b/Sources/Auth/Types/RespondParams.swift
new file mode 100644
index 000000000..fcd9b3293
--- /dev/null
+++ b/Sources/Auth/Types/RespondParams.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+struct RespondParams {
+ let id: Int64
+ let topic: String
+ let signature: CacaoSignature
+}
diff --git a/Sources/Auth/Types/WalletConnectURI.swift b/Sources/Auth/Types/WalletConnectURI.swift
new file mode 100644
index 000000000..a2cfe2de9
--- /dev/null
+++ b/Sources/Auth/Types/WalletConnectURI.swift
@@ -0,0 +1,4 @@
+import Foundation
+import WalletConnectUtils
+
+typealias WalletConnectURI = WalletConnectUtils.WalletConnectURI
diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift
index 25fad4bb0..1c44bbeb6 100644
--- a/Sources/Chat/ChatClient.swift
+++ b/Sources/Chat/ChatClient.swift
@@ -35,48 +35,39 @@ public class ChatClient {
messagePublisherSubject.eraseToAnyPublisher()
}
- public init(registry: Registry,
- relayClient: RelayClient,
+ // MARK: - Initialization
+
+ init(registry: Registry,
+ registryService: RegistryService,
+ messagingService: MessagingService,
+ invitationHandlingService: InvitationHandlingService,
+ inviteService: InviteService,
+ leaveService: LeaveService,
+ resubscriptionService: ResubscriptionService,
kms: KeyManagementService,
- logger: ConsoleLogging = ConsoleLogger(loggingLevel: .debug),
- keyValueStorage: KeyValueStorage) {
- let topicToRegistryRecordStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
+ threadStore: Database,
+ messagesStore: Database,
+ invitePayloadStore: CodableStore<(RequestSubscriptionPayload)>,
+ socketConnectionStatusPublisher: AnyPublisher
+ ) {
self.registry = registry
+ self.registryService = registryService
+ self.messagingService = messagingService
+ self.invitationHandlingService = invitationHandlingService
+ self.inviteService = inviteService
+ self.leaveService = leaveService
+ self.resubscriptionService = resubscriptionService
self.kms = kms
- let serialiser = Serializer(kms: kms)
- let jsonRpcHistory = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue))
- let networkingInteractor = NetworkingInteractor(
- relayClient: relayClient,
- serializer: serialiser,
- logger: logger,
- jsonRpcHistory: jsonRpcHistory)
- self.invitePayloadStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.invite.rawValue)
- self.registryService = RegistryService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore)
- threadStore = Database(keyValueStorage: keyValueStorage, identifier: StorageDomainIdentifiers.threads.rawValue)
- self.resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, threadStore: threadStore, logger: logger)
- self.invitationHandlingService = InvitationHandlingService(registry: registry,
- networkingInteractor: networkingInteractor,
- kms: kms,
- logger: logger,
- topicToRegistryRecordStore: topicToRegistryRecordStore,
- invitePayloadStore: invitePayloadStore,
- threadsStore: threadStore)
- self.inviteService = InviteService(
- networkingInteractor: networkingInteractor,
- kms: kms,
- threadStore: threadStore,
- logger: logger)
- self.leaveService = LeaveService()
- self.messagesStore = Database(keyValueStorage: keyValueStorage, identifier: StorageDomainIdentifiers.messages.rawValue)
- self.messagingService = MessagingService(
- networkingInteractor: networkingInteractor,
- messagesStore: messagesStore,
- threadStore: threadStore,
- logger: logger)
- socketConnectionStatusPublisher = relayClient.socketConnectionStatusPublisher
+ self.threadStore = threadStore
+ self.messagesStore = messagesStore
+ self.invitePayloadStore = invitePayloadStore
+ self.socketConnectionStatusPublisher = socketConnectionStatusPublisher
+
setUpEnginesCallbacks()
}
+ // MARK: - Public interface
+
/// Registers a new record on Chat keyserver,
/// record is a blockchain account with a client generated public key
/// - Parameter account: CAIP10 blockchain account
diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift
new file mode 100644
index 000000000..89f4907b1
--- /dev/null
+++ b/Sources/Chat/ChatClientFactory.swift
@@ -0,0 +1,45 @@
+import Foundation
+import WalletConnectRelay
+import WalletConnectUtils
+import WalletConnectKMS
+
+public struct ChatClientFactory {
+
+ public static func create(
+ registry: Registry,
+ relayClient: RelayClient,
+ kms: KeyManagementService,
+ logger: ConsoleLogging,
+ keyValueStorage: KeyValueStorage) -> ChatClient {
+ let topicToRegistryRecordStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
+ let serialiser = Serializer(kms: kms)
+ let jsonRpcHistory = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue))
+ let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serialiser, logger: logger, jsonRpcHistory: jsonRpcHistory)
+ let invitePayloadStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.invite.rawValue)
+ let registryService = RegistryService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore)
+ let threadStore = Database(keyValueStorage: keyValueStorage, identifier: StorageDomainIdentifiers.threads.rawValue)
+ let resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, threadStore: threadStore, logger: logger)
+ let invitationHandlingService = InvitationHandlingService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore, invitePayloadStore: invitePayloadStore, threadsStore: threadStore)
+ let inviteService = InviteService(networkingInteractor: networkingInteractor, kms: kms, threadStore: threadStore, logger: logger)
+ let leaveService = LeaveService()
+ let messagesStore = Database(keyValueStorage: keyValueStorage, identifier: StorageDomainIdentifiers.messages.rawValue)
+ let messagingService = MessagingService(networkingInteractor: networkingInteractor, messagesStore: messagesStore, threadStore: threadStore, logger: logger)
+
+ let client = ChatClient(
+ registry: registry,
+ registryService: registryService,
+ messagingService: messagingService,
+ invitationHandlingService: invitationHandlingService,
+ inviteService: inviteService,
+ leaveService: leaveService,
+ resubscriptionService: resubscriptionService,
+ kms: kms,
+ threadStore: threadStore,
+ messagesStore: messagesStore,
+ invitePayloadStore: invitePayloadStore,
+ socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher
+ )
+
+ return client
+ }
+}
diff --git a/Sources/Commons/Either.swift b/Sources/Commons/Either.swift
index 71e7b8dde..3c5f1fbdf 100644
--- a/Sources/Commons/Either.swift
+++ b/Sources/Commons/Either.swift
@@ -63,3 +63,15 @@ extension Either: Codable where L: Codable, R: Codable {
}
}
}
+
+extension Either: CustomStringConvertible {
+
+ public var description: String {
+ switch self {
+ case let .left(left):
+ return "\(left)"
+ case let .right(right):
+ return "\(right)"
+ }
+ }
+}
diff --git a/Sources/JSONRPC/RPCID.swift b/Sources/JSONRPC/RPCID.swift
index a4a7bec7e..9d52511d9 100644
--- a/Sources/JSONRPC/RPCID.swift
+++ b/Sources/JSONRPC/RPCID.swift
@@ -1,6 +1,6 @@
import Commons
-public typealias RPCID = Either
+public typealias RPCID = Either
public protocol IdentifierGenerator {
func next() -> RPCID
@@ -9,6 +9,6 @@ public protocol IdentifierGenerator {
struct IntIdentifierGenerator: IdentifierGenerator {
func next() -> RPCID {
- return RPCID(Int.random(in: Int.min...Int.max))
+ return RPCID(Int64.random(in: Int64.min...Int64.max))
}
}
diff --git a/Sources/JSONRPC/RPCRequest.swift b/Sources/JSONRPC/RPCRequest.swift
index 473550899..351d05a0d 100644
--- a/Sources/JSONRPC/RPCRequest.swift
+++ b/Sources/JSONRPC/RPCRequest.swift
@@ -37,7 +37,7 @@ public struct RPCRequest: Equatable {
try self.init(method: method, checkedParams: params, id: idGenerator.next())
}
- public init(method: String, checkedParams params: C, id: Int) throws where C: Codable {
+ public init(method: String, checkedParams params: C, id: Int64) throws where C: Codable {
try self.init(method: method, checkedParams: params, id: .right(id))
}
@@ -49,10 +49,14 @@ public struct RPCRequest: Equatable {
self.init(method: method, params: AnyCodable(params), id: idGenerator.next())
}
- public init(method: String, params: C, id: Int) where C: Codable {
+ public init(method: String, params: C, id: Int64) where C: Codable {
self.init(method: method, params: AnyCodable(params), id: .right(id))
}
+ public init(method: String, params: C, rpcid: RPCID) where C: Codable {
+ self.init(method: method, params: AnyCodable(params), id: rpcid)
+ }
+
public init(method: String, params: C, id: String) where C: Codable {
self.init(method: method, params: AnyCodable(params), id: .left(id))
}
@@ -61,7 +65,7 @@ public struct RPCRequest: Equatable {
self.init(method: method, params: nil, id: idGenerator.next())
}
- public init(method: String, id: Int) {
+ public init(method: String, id: Int64) {
self.init(method: method, params: nil, id: .right(id))
}
@@ -72,11 +76,11 @@ public struct RPCRequest: Equatable {
extension RPCRequest {
- static func notification(method: String, params: C) -> RPCRequest where C: Codable {
+ public static func notification(method: String, params: C) -> RPCRequest where C: Codable {
return RPCRequest(method: method, params: AnyCodable(params), id: nil)
}
- static func notification(method: String) -> RPCRequest {
+ public static func notification(method: String) -> RPCRequest {
return RPCRequest(method: method, params: nil, id: nil)
}
diff --git a/Sources/JSONRPC/RPCResponse.swift b/Sources/JSONRPC/RPCResponse.swift
index 0934adb7e..e0e60f06f 100644
--- a/Sources/JSONRPC/RPCResponse.swift
+++ b/Sources/JSONRPC/RPCResponse.swift
@@ -19,7 +19,7 @@ public struct RPCResponse: Equatable {
return nil
}
- private let outcome: Result
+ public let outcome: Result
internal init(id: RPCID?, outcome: Result) {
self.jsonrpc = "2.0"
@@ -27,7 +27,15 @@ public struct RPCResponse: Equatable {
self.outcome = outcome
}
- public init(id: Int, result: C) where C: Codable {
+ public init(matchingRequest: RPCRequest, result: C) where C: Codable {
+ self.init(id: matchingRequest.id, outcome: .success(AnyCodable(result)))
+ }
+
+ public init(matchingRequest: RPCRequest, error: JSONRPCError) {
+ self.init(id: matchingRequest.id, outcome: .failure(error))
+ }
+
+ public init(id: Int64, result: C) where C: Codable {
self.init(id: RPCID(id), outcome: .success(AnyCodable(result)))
}
@@ -35,7 +43,11 @@ public struct RPCResponse: Equatable {
self.init(id: RPCID(id), outcome: .success(AnyCodable(result)))
}
- public init(id: Int, error: JSONRPCError) {
+ public init(id: RPCID, result: C) where C: Codable {
+ self.init(id: id, outcome: .success(AnyCodable(result)))
+ }
+
+ public init(id: Int64, error: JSONRPCError) {
self.init(id: RPCID(id), outcome: .failure(error))
}
@@ -43,7 +55,7 @@ public struct RPCResponse: Equatable {
self.init(id: RPCID(id), outcome: .failure(error))
}
- public init(id: Int, errorCode: Int, message: String, associatedData: AnyCodable? = nil) {
+ 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)))
}
diff --git a/Sources/WalletConnectKMS/Crypto/Hash.swift b/Sources/WalletConnectKMS/Crypto/Hash.swift
index f38504b32..61220058d 100644
--- a/Sources/WalletConnectKMS/Crypto/Hash.swift
+++ b/Sources/WalletConnectKMS/Crypto/Hash.swift
@@ -1,5 +1,3 @@
-//
-
import Foundation
import CryptoKit
diff --git a/Sources/WalletConnectKMS/Keychain/KeychainStorage.swift b/Sources/WalletConnectKMS/Keychain/KeychainStorage.swift
index 5a3e9b548..c8c3ee083 100644
--- a/Sources/WalletConnectKMS/Keychain/KeychainStorage.swift
+++ b/Sources/WalletConnectKMS/Keychain/KeychainStorage.swift
@@ -28,6 +28,10 @@ public final class KeychainStorage: KeychainStorageProtocol {
let status = secItem.add(query as CFDictionary, nil)
+ guard status != errSecDuplicateItem else {
+ return try update(data: data, forKey: key)
+ }
+
guard status == errSecSuccess else {
throw KeychainError(status)
}
diff --git a/Sources/WalletConnectSign/Storage/PairingStorage.swift b/Sources/WalletConnectPairing/Storage/PairingStorage.swift
similarity index 58%
rename from Sources/WalletConnectSign/Storage/PairingStorage.swift
rename to Sources/WalletConnectPairing/Storage/PairingStorage.swift
index 211994e03..297ff35fd 100644
--- a/Sources/WalletConnectSign/Storage/PairingStorage.swift
+++ b/Sources/WalletConnectPairing/Storage/PairingStorage.swift
@@ -1,4 +1,7 @@
-protocol WCPairingStorage: AnyObject {
+import Foundation
+import WalletConnectUtils
+
+public protocol WCPairingStorage: AnyObject {
var onPairingExpiration: ((WCPairing) -> Void)? { get set }
func hasPairing(forTopic topic: String) -> Bool
func setPairing(_ pairing: WCPairing)
@@ -8,40 +11,40 @@ protocol WCPairingStorage: AnyObject {
func deleteAll()
}
-final class PairingStorage: WCPairingStorage {
+public final class PairingStorage: WCPairingStorage {
- var onPairingExpiration: ((WCPairing) -> Void)? {
+ public var onPairingExpiration: ((WCPairing) -> Void)? {
get { storage.onSequenceExpiration }
set { storage.onSequenceExpiration = newValue }
}
private let storage: SequenceStore
- init(storage: SequenceStore) {
+ public init(storage: SequenceStore) {
self.storage = storage
}
- func hasPairing(forTopic topic: String) -> Bool {
+ public func hasPairing(forTopic topic: String) -> Bool {
storage.hasSequence(forTopic: topic)
}
- func setPairing(_ pairing: WCPairing) {
+ public func setPairing(_ pairing: WCPairing) {
storage.setSequence(pairing)
}
- func getPairing(forTopic topic: String) -> WCPairing? {
+ public func getPairing(forTopic topic: String) -> WCPairing? {
try? storage.getSequence(forTopic: topic)
}
- func getAll() -> [WCPairing] {
+ public func getAll() -> [WCPairing] {
storage.getAll()
}
- func delete(topic: String) {
+ public func delete(topic: String) {
storage.delete(topic: topic)
}
- func deleteAll() {
+ public func deleteAll() {
storage.deleteAll()
}
}
diff --git a/Sources/WalletConnectSign/Types/Common/AppMetadata.swift b/Sources/WalletConnectPairing/Types/AppMetadata.swift
similarity index 100%
rename from Sources/WalletConnectSign/Types/Common/AppMetadata.swift
rename to Sources/WalletConnectPairing/Types/AppMetadata.swift
diff --git a/Sources/WalletConnectSign/Types/Pairing/PairingProposal.swift b/Sources/WalletConnectPairing/Types/PairingProposal.swift
similarity index 100%
rename from Sources/WalletConnectSign/Types/Pairing/PairingProposal.swift
rename to Sources/WalletConnectPairing/Types/PairingProposal.swift
diff --git a/Sources/WalletConnectPairing/Types/PairingType.swift b/Sources/WalletConnectPairing/Types/PairingType.swift
new file mode 100644
index 000000000..a7d279407
--- /dev/null
+++ b/Sources/WalletConnectPairing/Types/PairingType.swift
@@ -0,0 +1,18 @@
+import Foundation
+
+// Internal namespace for pairing payloads.
+public enum PairingType {
+
+ public struct DeleteParams: Codable, Equatable {
+ let reason: Reason
+ }
+
+ public struct Reason: Codable, Equatable {
+ let code: Int
+ let message: String
+ }
+
+ public struct PingParams: Codable, Equatable {
+ public init() { }
+ }
+}
diff --git a/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift b/Sources/WalletConnectPairing/Types/WCPairing.swift
similarity index 51%
rename from Sources/WalletConnectSign/Types/Pairing/WCPairing.swift
rename to Sources/WalletConnectPairing/Types/WCPairing.swift
index 794f36c86..b15a45e64 100644
--- a/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift
+++ b/Sources/WalletConnectPairing/Types/WCPairing.swift
@@ -1,28 +1,33 @@
import Foundation
-import WalletConnectKMS
+import WalletConnectUtils
-struct WCPairing: SequenceObject {
- let topic: String
- let relay: RelayProtocolOptions
- var peerMetadata: AppMetadata?
- private (set) var expiryDate: Date
- private (set) var active: Bool
+public struct WCPairing: SequenceObject {
+ enum Errors: Error {
+ case invalidUpdateExpiryValue
+ }
+
+ public let topic: String
+ public let relay: RelayProtocolOptions
+ public var peerMetadata: AppMetadata?
+
+ public private (set) var expiryDate: Date
+ public private (set) var active: Bool
#if DEBUG
- static var dateInitializer: () -> Date = Date.init
+ public static var dateInitializer: () -> Date = Date.init
#else
private static var dateInitializer: () -> Date = Date.init
#endif
- static var timeToLiveInactive: TimeInterval {
+ public static var timeToLiveInactive: TimeInterval {
5 * .minute
}
- static var timeToLiveActive: TimeInterval {
+ public static var timeToLiveActive: TimeInterval {
30 * .day
}
- init(topic: String, relay: RelayProtocolOptions, peerMetadata: AppMetadata, isActive: Bool = false, expiryDate: Date) {
+ public init(topic: String, relay: RelayProtocolOptions, peerMetadata: AppMetadata, isActive: Bool = false, expiryDate: Date) {
self.topic = topic
self.relay = relay
self.peerMetadata = peerMetadata
@@ -30,31 +35,31 @@ struct WCPairing: SequenceObject {
self.expiryDate = expiryDate
}
- init(topic: String) {
+ public init(topic: String) {
self.topic = topic
- self.relay = RelayProtocolOptions(protocol: "iridium", data: nil)
+ self.relay = RelayProtocolOptions(protocol: "irn", data: nil)
self.active = false
self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
}
- init(uri: WalletConnectURI) {
+ public init(uri: WalletConnectURI) {
self.topic = uri.topic
self.relay = uri.relay
self.active = false
self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive)
}
- mutating func activate() {
+ public mutating func activate() {
active = true
try? updateExpiry()
}
- mutating func updateExpiry(_ ttl: TimeInterval = WCPairing.timeToLiveActive) throws {
+ public mutating func updateExpiry(_ ttl: TimeInterval = WCPairing.timeToLiveActive) throws {
let now = Self.dateInitializer()
let newExpiryDate = now.advanced(by: ttl)
let maxExpiryDate = now.advanced(by: Self.timeToLiveActive)
guard newExpiryDate > expiryDate && newExpiryDate <= maxExpiryDate else {
- throw WalletConnectError.invalidUpdateExpiryValue
+ throw Errors.invalidUpdateExpiryValue
}
expiryDate = newExpiryDate
}
diff --git a/Sources/WalletConnectRelay/EnvironmentInfo.swift b/Sources/WalletConnectRelay/EnvironmentInfo.swift
index 767dc8752..f726b7578 100644
--- a/Sources/WalletConnectRelay/EnvironmentInfo.swift
+++ b/Sources/WalletConnectRelay/EnvironmentInfo.swift
@@ -1,4 +1,7 @@
+#if os(iOS)
import UIKit
+#endif
+import Foundation
enum EnvironmentInfo {
@@ -15,10 +18,15 @@ enum EnvironmentInfo {
}
static var sdkVersion: String {
- "v0.9.1-rc.0"
+ "v0.9.2-rc.0"
}
static var operatingSystem: String {
- "\(UIDevice.current.systemName)-\(UIDevice.current.systemVersion)"
+#if os(iOS)
+ return "\(UIDevice.current.systemName)-\(UIDevice.current.systemVersion)"
+#elseif os(macOS)
+ let systemVersion = ProcessInfo.processInfo.operatingSystemVersion
+ return "macOS-\(systemVersion)"
+#endif
}
}
diff --git a/Sources/WalletConnectRelay/RPC/Methods.swift b/Sources/WalletConnectRelay/RPC/Methods.swift
new file mode 100644
index 000000000..be94ac20e
--- /dev/null
+++ b/Sources/WalletConnectRelay/RPC/Methods.swift
@@ -0,0 +1,65 @@
+struct Subscribe: RelayRPC {
+
+ struct Params: Codable {
+ let topic: String
+ }
+
+ let params: Params
+
+ var method: String {
+ "subscribe"
+ }
+}
+
+struct Unsubscribe: RelayRPC {
+
+ struct Params: Codable {
+ let id: String
+ let topic: String
+ }
+
+ let params: Params
+
+ var method: String {
+ "unsubscribe"
+ }
+}
+
+struct Publish: RelayRPC {
+
+ struct Params: Codable {
+ let topic: String
+ let message: String
+ let ttl: Int
+ let prompt: Bool?
+ let tag: Int?
+ }
+
+ let params: Params
+
+ var method: String {
+ "publish"
+ }
+}
+
+struct Subscription: RelayRPC {
+
+ struct Params: Codable {
+ struct Contents: Codable {
+ let topic: String
+ let message: String
+ }
+ let id: String
+ let data: Contents
+ }
+
+ let params: Params
+
+ var method: String {
+ "subscription"
+ }
+
+ init(id: String, topic: String, message: String) {
+ self.params = Params(id: id, data: Params.Contents(topic: topic, message: message))
+ }
+}
diff --git a/Sources/WalletConnectRelay/RPC/RPCMethod.swift b/Sources/WalletConnectRelay/RPC/RPCMethod.swift
new file mode 100644
index 000000000..1f104f7c7
--- /dev/null
+++ b/Sources/WalletConnectRelay/RPC/RPCMethod.swift
@@ -0,0 +1,5 @@
+protocol RPCMethod {
+ associatedtype Parameters
+ var method: String { get }
+ var params: Parameters { get }
+}
diff --git a/Sources/WalletConnectRelay/RPC/RelayRPC.swift b/Sources/WalletConnectRelay/RPC/RelayRPC.swift
new file mode 100644
index 000000000..6950c42b5
--- /dev/null
+++ b/Sources/WalletConnectRelay/RPC/RelayRPC.swift
@@ -0,0 +1,34 @@
+import JSONRPC
+
+protocol RelayRPC: RPCMethod {}
+
+extension RelayRPC where Parameters: Codable {
+
+ var idGenerator: IdentifierGenerator {
+ return WalletConnectRPCID()
+ }
+
+ func wrapToIRN() -> PrefixDecorator {
+ return PrefixDecorator(rpcMethod: self, prefix: "irn")
+ }
+
+ func asRPCRequest() -> RPCRequest {
+ RPCRequest(method: self.method, params: self.params, idGenerator: self.idGenerator)
+ }
+}
+
+struct PrefixDecorator: RelayRPC where T: RelayRPC {
+
+ typealias Parameters = T.Parameters
+
+ let rpcMethod: T
+ let prefix: String
+
+ var method: String {
+ "\(prefix)_\(rpcMethod.method)"
+ }
+
+ var params: Parameters {
+ rpcMethod.params
+ }
+}
diff --git a/Sources/WalletConnectRelay/RPC/WalletConnectRPCID.swift b/Sources/WalletConnectRelay/RPC/WalletConnectRPCID.swift
new file mode 100644
index 000000000..b33eb4959
--- /dev/null
+++ b/Sources/WalletConnectRelay/RPC/WalletConnectRPCID.swift
@@ -0,0 +1,11 @@
+import Foundation
+import JSONRPC
+
+struct WalletConnectRPCID: IdentifierGenerator {
+
+ func next() -> RPCID {
+ let timestamp = Int64(Date().timeIntervalSince1970 * 1000) * 1000
+ let random = Int64.random(in: 0..<1000)
+ return .right(Int64(timestamp + random))
+ }
+}
diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift
index 47dc706b8..6be813de9 100644
--- a/Sources/WalletConnectRelay/RelayClient.swift
+++ b/Sources/WalletConnectRelay/RelayClient.swift
@@ -2,41 +2,45 @@ import Foundation
import Combine
import WalletConnectUtils
import WalletConnectKMS
+import JSONRPC
public enum SocketConnectionStatus {
case connected
case disconnected
}
+
public final class RelayClient {
- enum RelyerError: Error {
+
+ enum Errors: Error {
case subscriptionIdNotFound
}
- private typealias SubscriptionRequest = JSONRPCRequest
- private typealias SubscriptionResponse = JSONRPCResponse
- private typealias RequestAcknowledgement = JSONRPCResponse
- private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.relay_client",
- attributes: .concurrent)
- let jsonRpcSubscriptionsHistory: JsonRpcHistory
+
+ static let historyIdentifier = "com.walletconnect.sdk.relayer_client.subscription_json_rpc_record"
+
public var onMessage: ((String, String) -> Void)?
- private var dispatcher: Dispatching
- var subscriptions: [String: String] = [:]
+
let defaultTtl = 6*Time.hour
+ var subscriptions: [String: String] = [:]
public var socketConnectionStatusPublisher: AnyPublisher {
socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
}
private let socketConnectionStatusPublisherSubject = PassthroughSubject()
- private var subscriptionResponsePublisher: AnyPublisher, Never> {
+ private let subscriptionResponsePublisherSubject = PassthroughSubject<(RPCID?, String), Never>()
+ private var subscriptionResponsePublisher: AnyPublisher<(RPCID?, String), Never> {
subscriptionResponsePublisherSubject.eraseToAnyPublisher()
}
- private let subscriptionResponsePublisherSubject = PassthroughSubject, Never>()
- private var requestAcknowledgePublisher: AnyPublisher, Never> {
+ private let requestAcknowledgePublisherSubject = PassthroughSubject()
+ private var requestAcknowledgePublisher: AnyPublisher {
requestAcknowledgePublisherSubject.eraseToAnyPublisher()
}
- private let requestAcknowledgePublisherSubject = PassthroughSubject, Never>()
- let logger: ConsoleLogging
- static let historyIdentifier = "com.walletconnect.sdk.relayer_client.subscription_json_rpc_record"
+
+ private var dispatcher: Dispatching
+ private let rpcHistory: RPCHistory
+ private let logger: ConsoleLogging
+
+ private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.relay_client", attributes: .concurrent)
init(
dispatcher: Dispatching,
@@ -45,14 +49,22 @@ public final class RelayClient {
) {
self.logger = logger
self.dispatcher = dispatcher
-
- self.jsonRpcSubscriptionsHistory = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: Self.historyIdentifier))
+ self.rpcHistory = RPCHistory(keyValueStore: CodableStore(defaults: keyValueStorage, identifier: Self.historyIdentifier))
setUpBindings()
}
+ private func setUpBindings() {
+ dispatcher.onMessage = { [weak self] payload in
+ self?.handlePayloadMessage(payload)
+ }
+ dispatcher.onConnect = { [unowned self] in
+ self.socketConnectionStatusPublisherSubject.send(.connected)
+ }
+ }
+
/// Instantiates Relay Client
/// - Parameters:
- /// - relayHost: proxy server host that your application will use to connect to Iridium Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com`
+ /// - relayHost: proxy server host that your application will use to connect to Relay Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com`
/// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info.
/// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults
/// - socketConnectionType: socket connection type
@@ -98,65 +110,69 @@ 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 params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag)
- let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.method, params: params)
- logger.debug("Publishing Payload on Topic: \(topic)")
- let requestJson = try request.json()
- try await dispatcher.send(requestJson)
+ let request = Publish(params: .init(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag))
+ .wrapToIRN()
+ .asRPCRequest()
+ let message = try request.asJSONEncodedString()
+ logger.debug("Publishing payload on topic: \(topic)")
+ try await dispatcher.send(message)
}
/// Completes with an acknowledgement from the relay network.
- @discardableResult public func publish(
+ public func publish(
topic: String,
payload: String,
tag: Int,
prompt: Bool = false,
- onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 {
- let params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag)
- let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.method, params: params)
- let requestJson = try! request.json()
- logger.debug("iridium: Publishing Payload on Topic: \(topic)")
+ onNetworkAcknowledge: @escaping ((Error?) -> Void)
+ ) {
+ let rpc = Publish(params: .init(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag))
+ let request = rpc
+ .wrapToIRN()
+ .asRPCRequest()
+ let message = try! request.asJSONEncodedString()
+ logger.debug("Publishing Payload on Topic: \(topic)")
var cancellable: AnyCancellable?
- dispatcher.send(requestJson) { [weak self] error in
+ cancellable = requestAcknowledgePublisher
+ .filter { $0 == request.id }
+ .sink { (_) in
+ cancellable?.cancel()
+ onNetworkAcknowledge(nil)
+ }
+ dispatcher.send(message) { [weak self] error in
if let error = error {
self?.logger.debug("Failed to Publish Payload, error: \(error)")
cancellable?.cancel()
onNetworkAcknowledge(error)
}
}
- cancellable = requestAcknowledgePublisher
- .filter {$0.id == request.id}
- .sink { (_) in
- cancellable?.cancel()
- onNetworkAcknowledge(nil)
- }
- return request.id
}
@available(*, renamed: "subscribe(topic:)")
public func subscribe(topic: String, completion: @escaping (Error?) -> Void) {
- logger.debug("iridium: Subscribing on Topic: \(topic)")
- let params = RelayJSONRPC.SubscribeParams(topic: topic)
- let request = JSONRPCRequest(method: RelayJSONRPC.Method.subscribe.method, params: params)
- let requestJson = try! request.json()
+ logger.debug("Relay: Subscribing to topic: \(topic)")
+ let rpc = Subscribe(params: .init(topic: topic))
+ let request = rpc
+ .wrapToIRN()
+ .asRPCRequest()
+ let message = try! request.asJSONEncodedString()
var cancellable: AnyCancellable?
- dispatcher.send(requestJson) { [weak self] error in
+ cancellable = subscriptionResponsePublisher
+ .filter { $0.0 == request.id }
+ .sink { [weak self] subscriptionInfo in
+ cancellable?.cancel()
+ self?.concurrentQueue.async(flags: .barrier) {
+ self?.subscriptions[topic] = subscriptionInfo.1
+ }
+ completion(nil)
+ }
+ dispatcher.send(message) { [weak self] error in
if let error = error {
- self?.logger.debug("Failed to Subscribe on Topic \(error)")
+ self?.logger.debug("Failed to subscribe to topic \(error)")
cancellable?.cancel()
completion(error)
- } else {
- completion(nil)
}
}
- cancellable = subscriptionResponsePublisher
- .filter {$0.id == request.id}
- .sink { [weak self] (subscriptionResponse) in
- cancellable?.cancel()
- self?.concurrentQueue.async(flags: .barrier) {
- self?.subscriptions[topic] = subscriptionResponse.result
- }
- }
}
public func subscribe(topic: String) async throws {
@@ -171,20 +187,28 @@ public final class RelayClient {
}
}
- @discardableResult public func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) -> Int64? {
+ public func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) {
guard let subscriptionId = subscriptions[topic] else {
- completion(RelyerError.subscriptionIdNotFound)
- return nil
+ completion(Errors.subscriptionIdNotFound)
+ return
}
- logger.debug("iridium: Unsubscribing on Topic: \(topic)")
- let params = RelayJSONRPC.UnsubscribeParams(id: subscriptionId, topic: topic)
- let request = JSONRPCRequest(method: RelayJSONRPC.Method.unsubscribe.method, params: params)
- let requestJson = try! request.json()
+ logger.debug("Relay: Unsubscribing from topic: \(topic)")
+ let rpc = Unsubscribe(params: .init(id: subscriptionId, topic: topic))
+ let request = rpc
+ .wrapToIRN()
+ .asRPCRequest()
+ let message = try! request.asJSONEncodedString()
+ rpcHistory.deleteAll(forTopic: topic)
var cancellable: AnyCancellable?
- jsonRpcSubscriptionsHistory.delete(topic: topic)
- dispatcher.send(requestJson) { [weak self] error in
+ cancellable = requestAcknowledgePublisher
+ .filter { $0 == request.id }
+ .sink { (_) in
+ cancellable?.cancel()
+ completion(nil)
+ }
+ dispatcher.send(message) { [weak self] error in
if let error = error {
- self?.logger.debug("Failed to Unsubscribe on Topic")
+ self?.logger.debug("Failed to unsubscribe from topic")
cancellable?.cancel()
completion(error)
} else {
@@ -194,48 +218,38 @@ public final class RelayClient {
completion(nil)
}
}
- cancellable = requestAcknowledgePublisher
- .filter {$0.id == request.id}
- .sink { (_) in
- cancellable?.cancel()
- completion(nil)
- }
- return request.id
- }
-
- private func setUpBindings() {
- dispatcher.onMessage = { [weak self] payload in
- self?.handlePayloadMessage(payload)
- }
- dispatcher.onConnect = { [unowned self] in
- self.socketConnectionStatusPublisherSubject.send(.connected)
- }
}
+ // FIXME: Parse data to string once before trying to decode -> respond error on fail
private func handlePayloadMessage(_ payload: String) {
- if let request = tryDecode(SubscriptionRequest.self, from: payload), validate(request: request, method: .subscription) {
- do {
- try jsonRpcSubscriptionsHistory.set(topic: request.params.data.topic, request: request)
- onMessage?(request.params.data.topic, request.params.data.message)
- acknowledgeSubscription(requestId: request.id)
- } catch {
- logger.info("Relay Client Info: Json Rpc Duplicate Detected")
+ if let request = tryDecode(RPCRequest.self, from: payload) {
+ if let params = try? request.params?.get(Subscription.Params.self) {
+ do {
+ try rpcHistory.set(request, forTopic: params.data.topic, emmitedBy: .remote)
+ try acknowledgeRequest(request)
+ onMessage?(params.data.topic, params.data.message)
+ } catch {
+ logger.error("[RelayClient] RPC History 'set()' error: \(error)")
+ }
+ } else {
+ logger.error("Unexpected request from network")
+ }
+ } else if let response = tryDecode(RPCResponse.self, from: payload) {
+ switch response.outcome {
+ case .success(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):
+ logger.error("Received RPC error from relay network: \(rpcError)")
}
- } else if let response = tryDecode(RequestAcknowledgement.self, from: payload) {
- requestAcknowledgePublisherSubject.send(response)
- } else if let response = tryDecode(SubscriptionResponse.self, from: payload) {
- subscriptionResponsePublisherSubject.send(response)
- } else if let response = tryDecode(JSONRPCErrorResponse.self, from: payload) {
- logger.error("Received error message from iridium network, code: \(response.error.code), message: \(response.error.message)")
} else {
logger.error("Unexpected response from network")
}
}
- private func validate(request: JSONRPCRequest, method: RelayJSONRPC.Method) -> Bool {
- return request.method.contains(method.name)
- }
-
private func tryDecode(_ type: T.Type, from payload: String) -> T? {
if let data = payload.data(using: .utf8),
let response = try? JSONDecoder().decode(T.self, from: data) {
@@ -245,13 +259,13 @@ public final class RelayClient {
}
}
- private func acknowledgeSubscription(requestId: Int64) {
- let response = JSONRPCResponse(id: requestId, result: AnyCodable(true))
- let responseJson = try! response.json()
- _ = try? jsonRpcSubscriptionsHistory.resolve(response: JsonRpcResult.response(response))
- dispatcher.send(responseJson) { [weak self] error in
- if let error = error {
- self?.logger.debug("Failed to Respond for request id: \(requestId), error: \(error)")
+ private func acknowledgeRequest(_ request: RPCRequest) throws {
+ let response = RPCResponse(matchingRequest: request, result: true)
+ try rpcHistory.resolve(response)
+ let message = try response.asJSONEncodedString()
+ dispatcher.send(message) { [weak self] in
+ if let error = $0 {
+ self?.logger.debug("Failed to dispatch response: \(response), error: \(error)")
}
}
}
diff --git a/Sources/WalletConnectRelay/RelayJSONRPC.swift b/Sources/WalletConnectRelay/RelayJSONRPC.swift
deleted file mode 100644
index e83de4427..000000000
--- a/Sources/WalletConnectRelay/RelayJSONRPC.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-
-import Foundation
-
-enum RelayJSONRPC {
- enum Method {
- case subscribe
- case publish
- case subscription
- case unsubscribe
- }
-
- struct PublishParams: Codable, Equatable {
- let topic: String
- let message: String
- let ttl: Int
- let prompt: Bool?
- let tag: Int?
- }
-
- struct SubscribeParams: Codable, Equatable {
- let topic: String
- }
-
- struct SubscriptionData: Codable, Equatable {
- let topic: String
- let message: String
- }
-
- struct SubscriptionParams: Codable, Equatable {
- let id: String
- let data: SubscriptionData
- }
-
- struct UnsubscribeParams: Codable, Equatable {
- let id: String
- let topic: String
- }
-}
-
-extension RelayJSONRPC.Method {
-
- var prefix: String {
- return "iridium"
- }
-
- var name: String {
- switch self {
- case .subscribe:
- return "subscribe"
- case .publish:
- return "publish"
- case .subscription:
- return "subscription"
- case .unsubscribe:
- return "unsubscribe"
- }
- }
-
- var method: String {
- return "\(prefix)_\(name)"
- }
-}
diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
index 0b602f839..1211c63f5 100644
--- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
@@ -2,6 +2,7 @@ import Foundation
import Combine
import WalletConnectUtils
import WalletConnectKMS
+import WalletConnectPairing
final class ApproveEngine {
enum Errors: Error {
@@ -71,7 +72,6 @@ final class ApproveEngine {
peerPublicKey: proposal.proposer.publicKey
) else { throw Errors.agreementMissingOrInvalid }
- // TODO: Extend pairing
let sessionTopic = agreementKey.derivedTopic()
try kms.setAgreementSecret(agreementKey, topic: sessionTopic)
@@ -82,7 +82,15 @@ final class ApproveEngine {
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)
+
+ try pairing.updateExpiry()
+ pairingStore.setPairing(pairing)
+
try await settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces)
}
diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift
index 7b2231c82..106652d6e 100644
--- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift
@@ -1,5 +1,6 @@
import Foundation
import Combine
+import WalletConnectPairing
import WalletConnectUtils
import WalletConnectKMS
diff --git a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift b/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift
index f455738cb..969d4302b 100644
--- a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift
@@ -1,5 +1,6 @@
import Foundation
import WalletConnectKMS
+import WalletConnectPairing
actor PairEngine {
private let networkingInteractor: NetworkInteracting
diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift
index 8892ff6a7..2ea62420d 100644
--- a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift
+++ b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift
@@ -11,9 +11,9 @@ protocol NetworkRelaying {
func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws
/// - returns: request id
- @discardableResult func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64
+ 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
- @discardableResult func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) -> Int64?
+ func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void))
}
diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift
index a5182cb19..da55722b2 100644
--- a/Sources/WalletConnectSign/Request.swift
+++ b/Sources/WalletConnectSign/Request.swift
@@ -17,10 +17,10 @@ public struct Request: Codable, Equatable {
}
public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
- self.id = JsonRpcID.generate()
- self.topic = topic
- self.method = method
- self.params = params
- self.chainId = chainId
+ self.init(id: 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 {
+ self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId)
}
}
diff --git a/Sources/WalletConnectSign/Services/CelanupService.swift b/Sources/WalletConnectSign/Services/CelanupService.swift
index e0e083796..0058c179b 100644
--- a/Sources/WalletConnectSign/Services/CelanupService.swift
+++ b/Sources/WalletConnectSign/Services/CelanupService.swift
@@ -1,6 +1,7 @@
import Foundation
import WalletConnectKMS
import WalletConnectUtils
+import WalletConnectPairing
final class CleanupService {
diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift
index b2c653610..f00ae09be 100644
--- a/Sources/WalletConnectSign/Sign/Sign.swift
+++ b/Sources/WalletConnectSign/Sign/Sign.swift
@@ -23,7 +23,10 @@ public class Sign {
socketFactory: config.socketFactory,
socketConnectionType: config.socketConnectionType
)
- client = SignClient(metadata: config.metadata, relayClient: relayClient)
+ client = SignClientFactory.create(
+ metadata: config.metadata,
+ relayClient: relayClient
+ )
client.delegate = self
}
diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift
index 64f269a52..1dc88cae2 100644
--- a/Sources/WalletConnectSign/Sign/SignClient.swift
+++ b/Sources/WalletConnectSign/Sign/SignClient.swift
@@ -20,10 +20,10 @@ import UIKit
/// - delegate: The object that acts as the delegate of WalletConnect Client
/// - logger: An object for logging messages
public final class SignClient {
-
public weak var delegate: SignClientDelegate?
public let logger: ConsoleLogging
+ private let relayClient: RelayClient
private let pairingEngine: PairingEngine
private let pairEngine: PairEngine
private let sessionEngine: SessionEngine
@@ -34,48 +34,31 @@ public final class SignClient {
private let cleanupService: CleanupService
private var publishers = [AnyCancellable]()
- // MARK: - Initializers
+ // MARK: - Initialization
- /// Initializes and returns newly created WalletConnect Client Instance. Establishes a network connection with the relay
- ///
- /// - Parameters:
- /// - metadata: describes your application and will define pairing appearance in a web browser.
- /// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info.
- /// - relayHost: proxy server host that your application will use to connect to Iridium Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com`
- /// - 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 convenience init(metadata: AppMetadata, relayClient: RelayClient) {
- let logger = ConsoleLogger(loggingLevel: .off)
- let keyValueStorage = UserDefaults.standard
- let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
- self.init(
- metadata: metadata,
- logger: logger,
- keyValueStorage: keyValueStorage,
- keychainStorage: keychainStorage,
- relayClient: relayClient
- )
- }
-
- init(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) {
+ init(logger: ConsoleLogging,
+ relayClient: RelayClient,
+ pairingEngine: PairingEngine,
+ pairEngine: PairEngine,
+ sessionEngine: SessionEngine,
+ approveEngine: ApproveEngine,
+ nonControllerSessionStateMachine: NonControllerSessionStateMachine,
+ controllerSessionStateMachine: ControllerSessionStateMachine,
+ history: JsonRpcHistory,
+ cleanupService: CleanupService
+ ) {
self.logger = logger
- let kms = KeyManagementService(keychain: keychainStorage)
- let serializer = Serializer(kms: kms)
- self.history = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue))
- let networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history)
- 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)
- self.pairingEngine = PairingEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore, metadata: metadata, logger: logger)
- self.sessionEngine = SessionEngine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- self.nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- self.controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger)
- self.pairEngine = PairEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore)
- self.approveEngine = ApproveEngine(networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore)
- self.cleanupService = CleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic)
- setUpConnectionObserving(relayClient: relayClient)
+ self.relayClient = relayClient
+ self.pairingEngine = pairingEngine
+ self.pairEngine = pairEngine
+ self.sessionEngine = sessionEngine
+ self.approveEngine = approveEngine
+ self.nonControllerSessionStateMachine = nonControllerSessionStateMachine
+ self.controllerSessionStateMachine = controllerSessionStateMachine
+ self.history = history
+ self.cleanupService = cleanupService
+
+ setUpConnectionObserving()
setUpEnginesCallbacks()
}
@@ -292,7 +275,7 @@ public final class SignClient {
}
}
- private func setUpConnectionObserving(relayClient: RelayClient) {
+ private func setUpConnectionObserving() {
relayClient.socketConnectionStatusPublisher.sink { [weak self] status in
self?.delegate?.didChangeSocketConnectionStatus(status)
}.store(in: &publishers)
diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
new file mode 100644
index 000000000..8ab60ee52
--- /dev/null
+++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
@@ -0,0 +1,57 @@
+import Foundation
+import WalletConnectRelay
+import WalletConnectUtils
+import WalletConnectKMS
+import WalletConnectPairing
+
+public struct SignClientFactory {
+
+ /// Initializes and returns newly created WalletConnect Client Instance
+ ///
+ /// - Parameters:
+ /// - metadata: describes your application and will define pairing appearance in a web browser.
+ /// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info.
+ /// - relayHost: proxy server host that your application will use to connect to Iridium Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com`
+ /// - 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 {
+ 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)
+ }
+
+ static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> 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 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 cleanupService = CleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic)
+
+ let client = SignClient(
+ logger: logger,
+ relayClient: relayClient,
+ pairingEngine: pairingEngine,
+ pairEngine: pairEngine,
+ sessionEngine: sessionEngine,
+ approveEngine: approveEngine,
+ nonControllerSessionStateMachine: nonControllerSessionStateMachine,
+ controllerSessionStateMachine: controllerSessionStateMachine,
+ history: history,
+ cleanupService: cleanupService
+ )
+
+ return client
+ }
+}
diff --git a/Sources/WalletConnectSign/Sign/SignConfig.swift b/Sources/WalletConnectSign/Sign/SignConfig.swift
index b6167c473..4ba4edeaa 100644
--- a/Sources/WalletConnectSign/Sign/SignConfig.swift
+++ b/Sources/WalletConnectSign/Sign/SignConfig.swift
@@ -1,5 +1,5 @@
-import WalletConnectRelay
import Foundation
+import WalletConnectRelay
public extension Sign {
struct Config {
diff --git a/Sources/WalletConnectSign/Storage/SessionStorage.swift b/Sources/WalletConnectSign/Storage/SessionStorage.swift
index 1b08d90f7..d24a535ef 100644
--- a/Sources/WalletConnectSign/Storage/SessionStorage.swift
+++ b/Sources/WalletConnectSign/Storage/SessionStorage.swift
@@ -1,3 +1,6 @@
+import Foundation
+import WalletConnectUtils
+
protocol WCSessionStorage: AnyObject {
var onSessionExpiration: ((WCSession) -> Void)? { get set }
@discardableResult
diff --git a/Sources/WalletConnectSign/Types/AppMetadata.swift b/Sources/WalletConnectSign/Types/AppMetadata.swift
new file mode 100644
index 000000000..bd646f855
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/AppMetadata.swift
@@ -0,0 +1,3 @@
+import WalletConnectPairing
+
+public typealias AppMetadata = WalletConnectPairing.AppMetadata
diff --git a/Sources/WalletConnectSign/Types/Common/Participant.swift b/Sources/WalletConnectSign/Types/Common/Participant.swift
index 0f0f4502a..fc81036d5 100644
--- a/Sources/WalletConnectSign/Types/Common/Participant.swift
+++ b/Sources/WalletConnectSign/Types/Common/Participant.swift
@@ -1,3 +1,5 @@
+import Foundation
+
struct Participant: Codable, Equatable {
let publicKey: String
let metadata: AppMetadata
diff --git a/Sources/WalletConnectSign/Types/Common/RelayProtocolOptions.swift b/Sources/WalletConnectSign/Types/Common/RelayProtocolOptions.swift
deleted file mode 100644
index 2d0009aa9..000000000
--- a/Sources/WalletConnectSign/Types/Common/RelayProtocolOptions.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-import Foundation
-
-struct RelayProtocolOptions: Codable, Equatable {
- let `protocol`: String
- let data: String?
-}
diff --git a/Sources/WalletConnectSign/Types/Pairing/PairingType.swift b/Sources/WalletConnectSign/Types/Pairing/PairingType.swift
deleted file mode 100644
index d814e4449..000000000
--- a/Sources/WalletConnectSign/Types/Pairing/PairingType.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-import Foundation
-
-// Internal namespace for pairing payloads.
-internal enum PairingType {
-
- struct DeleteParams: Codable, Equatable {
- let reason: Reason
- }
-
- struct Reason: Codable, Equatable {
- let code: Int
- let message: String
- }
-
- struct PingParams: Codable, Equatable {}
-}
diff --git a/Sources/WalletConnectSign/Types/RelayProtocolOptions.swift b/Sources/WalletConnectSign/Types/RelayProtocolOptions.swift
new file mode 100644
index 000000000..d5b4b6c4f
--- /dev/null
+++ b/Sources/WalletConnectSign/Types/RelayProtocolOptions.swift
@@ -0,0 +1,4 @@
+import Foundation
+import WalletConnectUtils
+
+typealias RelayProtocolOptions = WalletConnectUtils.RelayProtocolOptions
diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift
index 4c4109bc6..cb0771d43 100644
--- a/Sources/WalletConnectSign/Types/Session/WCSession.swift
+++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift
@@ -57,7 +57,7 @@ struct WCSession: SequenceObject, Equatable {
peerParticipant: Participant,
namespaces: [String: SessionNamespace],
requiredNamespaces: [String: ProposalNamespace],
- events: Set,
+ events: Set,
accounts: Set,
acknowledged: Bool,
expiry: Int64
diff --git a/Sources/WalletConnectSign/Types/WCMethod.swift b/Sources/WalletConnectSign/Types/WCMethod.swift
index cbe97b754..e2002188a 100644
--- a/Sources/WalletConnectSign/Types/WCMethod.swift
+++ b/Sources/WalletConnectSign/Types/WCMethod.swift
@@ -1,3 +1,6 @@
+import Foundation
+import WalletConnectPairing
+
enum WCMethod {
case wcPairingPing
case wcSessionPropose(SessionType.ProposeParams)
diff --git a/Sources/WalletConnectSign/Types/WCRequest.swift b/Sources/WalletConnectSign/Types/WCRequest.swift
index 74ad5bebb..ec9edfdf8 100644
--- a/Sources/WalletConnectSign/Types/WCRequest.swift
+++ b/Sources/WalletConnectSign/Types/WCRequest.swift
@@ -1,4 +1,5 @@
import Foundation
+import WalletConnectPairing
import WalletConnectUtils
struct WCRequest: Codable {
diff --git a/Sources/WalletConnectSign/Types/WalletConnectURI.swift b/Sources/WalletConnectSign/Types/WalletConnectURI.swift
index 0d43287a6..a2cfe2de9 100644
--- a/Sources/WalletConnectSign/Types/WalletConnectURI.swift
+++ b/Sources/WalletConnectSign/Types/WalletConnectURI.swift
@@ -1,50 +1,4 @@
import Foundation
+import WalletConnectUtils
-public struct WalletConnectURI: Equatable {
-
- let topic: String
- let version: String
- let symKey: String
- let relay: RelayProtocolOptions
-
- init(topic: String, symKey: String, relay: RelayProtocolOptions) {
- self.version = "2"
- self.topic = topic
- self.symKey = symKey
- self.relay = relay
- }
-
- public init?(string: String) {
- guard string.hasPrefix("wc:") else {
- return nil
- }
- let urlString = !string.hasPrefix("wc://") ? string.replacingOccurrences(of: "wc:", with: "wc://") : string
- guard let components = URLComponents(string: urlString) else {
- return nil
- }
- let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value }
-
- guard let topic = components.user,
- let version = components.host,
- let symKey = query?["symKey"],
- let relayProtocol = query?["relay-protocol"]
- else { return nil }
- let relayData = query?["relay-data"]
- self.version = version
- self.topic = topic
- self.symKey = symKey
- self.relay = RelayProtocolOptions(protocol: relayProtocol, data: relayData)
- }
-
- public var absoluteString: String {
- return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)"
- }
-
- private var relayQuery: String {
- var query = "relay-protocol=\(relay.protocol)"
- if let relayData = relay.data {
- query = "\(query)&relay-data=\(relayData)"
- }
- return query
- }
-}
+typealias WalletConnectURI = WalletConnectUtils.WalletConnectURI
diff --git a/Sources/WalletConnectUtils/Encodable.swift b/Sources/WalletConnectUtils/Encodable.swift
index 74d14671c..4d18c65b9 100644
--- a/Sources/WalletConnectUtils/Encodable.swift
+++ b/Sources/WalletConnectUtils/Encodable.swift
@@ -8,6 +8,8 @@ public enum DataConversionError: Error {
}
public extension Encodable {
+
+ // TODO: Migrate
func json() throws -> String {
let data = try JSONEncoder().encode(self)
guard let string = String(data: data, encoding: .utf8) else {
@@ -15,4 +17,12 @@ public extension Encodable {
}
return string
}
+
+ func asJSONEncodedString() throws -> String {
+ let data = try JSONEncoder().encode(self)
+ guard let string = String(data: data, encoding: .utf8) else {
+ throw DataConversionError.dataToStringFailed
+ }
+ return string
+ }
}
diff --git a/Sources/WalletConnectUtils/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory.swift
new file mode 100644
index 000000000..8112cacc3
--- /dev/null
+++ b/Sources/WalletConnectUtils/RPCHistory.swift
@@ -0,0 +1,67 @@
+import JSONRPC
+
+public final class RPCHistory {
+
+ public struct Record: Codable {
+ public enum Origin: String, Codable {
+ case local
+ case remote
+ }
+ let id: RPCID
+ let topic: String
+ let origin: Origin
+ public let request: RPCRequest
+ var response: RPCResponse?
+ }
+
+ enum HistoryError: Error {
+ case unidentifiedRequest
+ case unidentifiedResponse
+ case requestDuplicateNotAllowed
+ case responseDuplicateNotAllowed
+ case requestMatchingResponseNotFound
+ }
+
+ private let storage: CodableStore
+
+ public init(keyValueStore: CodableStore) {
+ self.storage = keyValueStore
+ }
+
+ public func get(recordId: RPCID) -> Record? {
+ try? storage.get(key: "\(recordId)")
+ }
+
+ public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws {
+ guard let id = request.id else {
+ throw HistoryError.unidentifiedRequest
+ }
+ guard get(recordId: id) == nil else {
+ throw HistoryError.requestDuplicateNotAllowed
+ }
+ let record = Record(id: id, topic: topic, origin: origin, request: request)
+ storage.set(record, forKey: "\(record.id)")
+ }
+
+ public func resolve(_ response: RPCResponse) throws {
+ guard let id = response.id else {
+ throw HistoryError.unidentifiedResponse
+ }
+ guard var record = get(recordId: id) else {
+ throw HistoryError.requestMatchingResponseNotFound
+ }
+ guard record.response == nil else {
+ throw HistoryError.responseDuplicateNotAllowed
+ }
+ record.response = response
+ storage.set(record, forKey: "\(record.id)")
+ }
+
+ public func deleteAll(forTopic topic: String) {
+ storage.getAll().forEach { record in
+ if record.topic == topic {
+ storage.delete(forKey: "\(record.id)")
+ }
+ }
+ }
+}
diff --git a/Sources/WalletConnectUtils/RelayProtocolOptions.swift b/Sources/WalletConnectUtils/RelayProtocolOptions.swift
new file mode 100644
index 000000000..e437e9342
--- /dev/null
+++ b/Sources/WalletConnectUtils/RelayProtocolOptions.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+public struct RelayProtocolOptions: Codable, Equatable {
+ public let `protocol`: String
+ public let data: String?
+
+ public init(protocol: String, data: String?) {
+ self.protocol = `protocol`
+ self.data = data
+ }
+}
diff --git a/Sources/WalletConnectSign/Storage/SequenceStore.swift b/Sources/WalletConnectUtils/SequenceStore.swift
similarity index 63%
rename from Sources/WalletConnectSign/Storage/SequenceStore.swift
rename to Sources/WalletConnectUtils/SequenceStore.swift
index 7bbe62348..738831b74 100644
--- a/Sources/WalletConnectSign/Storage/SequenceStore.swift
+++ b/Sources/WalletConnectUtils/SequenceStore.swift
@@ -1,51 +1,45 @@
import Foundation
-import WalletConnectUtils
-protocol Expirable {
+public protocol SequenceObject: Codable {
var expiryDate: Date { get }
-}
-
-protocol Entitled {
var topic: String { get }
}
-typealias SequenceObject = Entitled & Expirable & Codable
-
-final class SequenceStore where T: SequenceObject {
+public final class SequenceStore where T: SequenceObject {
- var onSequenceExpiration: ((_ sequence: T) -> Void)?
+ public var onSequenceExpiration: ((_ sequence: T) -> Void)?
private let store: CodableStore
private let dateInitializer: () -> Date
- init(store: CodableStore, dateInitializer: @escaping () -> Date = Date.init) {
+ public init(store: CodableStore, dateInitializer: @escaping () -> Date = Date.init) {
self.store = store
self.dateInitializer = dateInitializer
}
- func hasSequence(forTopic topic: String) -> Bool {
+ public func hasSequence(forTopic topic: String) -> Bool {
(try? getSequence(forTopic: topic)) != nil
}
- func setSequence(_ sequence: T) {
+ public func setSequence(_ sequence: T) {
store.set(sequence, forKey: sequence.topic)
}
- func getSequence(forTopic topic: String) throws -> T? {
+ public func getSequence(forTopic topic: String) throws -> T? {
guard let value = try store.get(key: topic) else { return nil }
return verifyExpiry(on: value)
}
- func getAll() -> [T] {
+ public func getAll() -> [T] {
let values = store.getAll()
return values.compactMap { verifyExpiry(on: $0) }
}
- func delete(topic: String) {
+ public func delete(topic: String) {
store.delete(forKey: topic)
}
- func deleteAll() {
+ public func deleteAll() {
store.deleteAll()
}
}
diff --git a/Sources/WalletConnectUtils/WalletConnectURI.swift b/Sources/WalletConnectUtils/WalletConnectURI.swift
new file mode 100644
index 000000000..d149f7f70
--- /dev/null
+++ b/Sources/WalletConnectUtils/WalletConnectURI.swift
@@ -0,0 +1,50 @@
+import Foundation
+
+public struct WalletConnectURI: Equatable {
+
+ public let topic: String
+ public let version: String
+ public let symKey: String
+ public let relay: RelayProtocolOptions
+
+ public init(topic: String, symKey: String, relay: RelayProtocolOptions) {
+ self.version = "2"
+ self.topic = topic
+ self.symKey = symKey
+ self.relay = relay
+ }
+
+ public init?(string: String) {
+ guard string.hasPrefix("wc:") else {
+ return nil
+ }
+ let urlString = !string.hasPrefix("wc://") ? string.replacingOccurrences(of: "wc:", with: "wc://") : string
+ guard let components = URLComponents(string: urlString) else {
+ return nil
+ }
+ let query: [String: String]? = components.queryItems?.reduce(into: [:]) { $0[$1.name] = $1.value }
+
+ guard let topic = components.user,
+ let version = components.host,
+ let symKey = query?["symKey"],
+ let relayProtocol = query?["relay-protocol"]
+ else { return nil }
+ let relayData = query?["relay-data"]
+ self.version = version
+ self.topic = topic
+ self.symKey = symKey
+ self.relay = RelayProtocolOptions(protocol: relayProtocol, data: relayData)
+ }
+
+ public var absoluteString: String {
+ return "wc:\(topic)@\(version)?symKey=\(symKey)&\(relayQuery)"
+ }
+
+ private var relayQuery: String {
+ var query = "relay-protocol=\(relay.protocol)"
+ if let relayData = relay.data {
+ query = "\(query)&relay-data=\(relayData)"
+ }
+ return query
+ }
+}
diff --git a/Tests/AuthTests/AuthRequstSubscriberTests.swift b/Tests/AuthTests/AuthRequstSubscriberTests.swift
new file mode 100644
index 000000000..c457d9246
--- /dev/null
+++ b/Tests/AuthTests/AuthRequstSubscriberTests.swift
@@ -0,0 +1,42 @@
+import Foundation
+import XCTest
+@testable import Auth
+import WalletConnectUtils
+@testable import WalletConnectKMS
+@testable import TestingUtils
+import JSONRPC
+
+class AuthRequstSubscriberTests: XCTestCase {
+ var networkingInteractor: NetworkingInteractorMock!
+ var sut: AuthRequestSubscriber!
+ var messageFormatter: SIWEMessageFormatterMock!
+ let defaultTimeout: TimeInterval = 0.01
+
+ override func setUp() {
+ networkingInteractor = NetworkingInteractorMock()
+ messageFormatter = SIWEMessageFormatterMock()
+ sut = AuthRequestSubscriber(networkingInteractor: networkingInteractor,
+ logger: ConsoleLoggerMock(),
+ messageFormatter: messageFormatter)
+ }
+
+ func testSubscribeRequest() {
+ let expectedMessage = "Expected Message"
+ let expectedRequestId: RPCID = RPCID(1234)
+ let messageExpectation = expectation(description: "receives formatted message")
+ messageFormatter.formattedMessage = expectedMessage
+ var messageId: RPCID!
+ var message: String!
+ sut.onRequest = { id, formattedMessage in
+ messageId = id
+ message = formattedMessage
+ messageExpectation.fulfill()
+ }
+
+ networkingInteractor.requestPublisherSubject.send(RequestSubscriptionPayload.stub(id: expectedRequestId))
+
+ wait(for: [messageExpectation], timeout: defaultTimeout)
+ XCTAssertEqual(message, expectedMessage)
+ XCTAssertEqual(messageId, expectedRequestId)
+ }
+}
diff --git a/Tests/AuthTests/Mocks/NetworkingInteractorMock.swift b/Tests/AuthTests/Mocks/NetworkingInteractorMock.swift
new file mode 100644
index 000000000..32e8abe85
--- /dev/null
+++ b/Tests/AuthTests/Mocks/NetworkingInteractorMock.swift
@@ -0,0 +1,26 @@
+import Foundation
+import Combine
+@testable import Auth
+import JSONRPC
+import WalletConnectKMS
+
+struct NetworkingInteractorMock: NetworkInteracting {
+
+ let requestPublisherSubject = PassthroughSubject()
+ var requestPublisher: AnyPublisher {
+ requestPublisherSubject.eraseToAnyPublisher()
+ }
+
+ func subscribe(topic: String) async throws {
+
+ }
+
+ func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
+
+ }
+
+ func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws {
+
+ }
+
+}
diff --git a/Tests/AuthTests/Mocks/SIWEMessageFormatterMock.swift b/Tests/AuthTests/Mocks/SIWEMessageFormatterMock.swift
new file mode 100644
index 000000000..b576bfc68
--- /dev/null
+++ b/Tests/AuthTests/Mocks/SIWEMessageFormatterMock.swift
@@ -0,0 +1,9 @@
+import Foundation
+@testable import Auth
+
+class SIWEMessageFormatterMock: SIWEMessageFormatting {
+ var formattedMessage: String!
+ func formatMessage(from request: AuthRequestParams) throws -> String {
+ return formattedMessage
+ }
+}
diff --git a/Tests/AuthTests/Stubs/RequestParams.swift b/Tests/AuthTests/Stubs/RequestParams.swift
new file mode 100644
index 000000000..9a3890289
--- /dev/null
+++ b/Tests/AuthTests/Stubs/RequestParams.swift
@@ -0,0 +1,8 @@
+import Foundation
+@testable import Auth
+
+extension RequestParams {
+ static func stub() -> RequestParams {
+ return RequestParams(domain: "", chainId: "", nonce: "", aud: "", nbf: nil, exp: nil, statement: nil, requestId: nil, resources: nil)
+ }
+}
diff --git a/Tests/AuthTests/Stubs/RequestSubscriptionPayload.swift b/Tests/AuthTests/Stubs/RequestSubscriptionPayload.swift
new file mode 100644
index 000000000..afeb1c281
--- /dev/null
+++ b/Tests/AuthTests/Stubs/RequestSubscriptionPayload.swift
@@ -0,0 +1,15 @@
+import Foundation
+@testable import Auth
+import JSONRPC
+
+extension RequestSubscriptionPayload {
+ static func stub(id: RPCID) -> RequestSubscriptionPayload {
+ let appMetadata = AppMetadata(name: "", description: "", url: "", icons: [])
+ let requester = AuthRequestParams.Requester(publicKey: "", metadata: appMetadata)
+ let issueAt = ISO8601DateFormatter().string(from: Date())
+ let payload = AuthPayload(requestParams: RequestParams.stub(), iat: issueAt)
+ let params = AuthRequestParams(requester: requester, payloadParams: payload)
+ let request = RPCRequest(method: "wc_authRequest", params: params, rpcid: id)
+ return RequestSubscriptionPayload(id: 123, request: request)
+ }
+}
diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift
index b8d6e1040..78103a30c 100644
--- a/Tests/RelayerTests/DispatcherTests.swift
+++ b/Tests/RelayerTests/DispatcherTests.swift
@@ -7,7 +7,6 @@ import Combine
class WebSocketMock: WebSocketConnecting {
var request: URLRequest = URLRequest(url: URL(string: "wss://relay.walletconnect.com")!)
-
var onText: ((String) -> Void)?
var onConnect: (() -> Void)?
var onDisconnect: ((Error?) -> Void)?
diff --git a/Tests/RelayerTests/IridiumRelayTests.swift b/Tests/RelayerTests/IridiumRelayTests.swift
deleted file mode 100644
index 4ce71e96f..000000000
--- a/Tests/RelayerTests/IridiumRelayTests.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-import WalletConnectUtils
-import Foundation
-import Combine
-import XCTest
-@testable import WalletConnectRelay
-
-class IridiumRelayTests: XCTestCase {
- var iridiumRelay: RelayClient!
- var dispatcher: DispatcherMock!
-
- override func setUp() {
- dispatcher = DispatcherMock()
- let logger = ConsoleLogger()
- iridiumRelay = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage())
- }
-
- override func tearDown() {
- iridiumRelay = nil
- dispatcher = nil
- }
-
- func testNotifyOnSubscriptionRequest() {
- let subscriptionExpectation = expectation(description: "notifies with encoded message on a iridium subscription event")
- let topic = "0987"
- let message = "qwerty"
- let subscriptionId = "sub-id"
- let subscriptionParams = RelayJSONRPC.SubscriptionParams(id: subscriptionId, data: RelayJSONRPC.SubscriptionData(topic: topic, message: message))
- let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.method, params: subscriptionParams)
- iridiumRelay.onMessage = { subscriptionTopic, subscriptionMessage in
- XCTAssertEqual(subscriptionMessage, message)
- XCTAssertEqual(subscriptionTopic, topic)
- subscriptionExpectation.fulfill()
- }
- dispatcher.onMessage?(try! subscriptionRequest.json())
- waitForExpectations(timeout: 0.001, handler: nil)
- }
-
- func testPublishRequestAcknowledge() {
- let acknowledgeExpectation = expectation(description: "completion with no error on iridium request acknowledge after publish")
- let requestId = iridiumRelay.publish(topic: "", payload: "{}", tag: 0, onNetworkAcknowledge: { error in
- acknowledgeExpectation.fulfill()
- XCTAssertNil(error)
- })
- let response = try! JSONRPCResponse(id: requestId, result: true).json()
- dispatcher.onMessage?(response)
- waitForExpectations(timeout: 0.001, handler: nil)
- }
-
- func testUnsubscribeRequestAcknowledge() {
- let acknowledgeExpectation = expectation(description: "completion with no error on iridium request acknowledge after unsubscribe")
- let topic = "1234"
- iridiumRelay.subscriptions[topic] = ""
- let requestId = iridiumRelay.unsubscribe(topic: topic) { error in
- XCTAssertNil(error)
- acknowledgeExpectation.fulfill()
- }
- let response = try! JSONRPCResponse(id: requestId!, result: true).json()
- dispatcher.onMessage?(response)
- waitForExpectations(timeout: 0.001, handler: nil)
- }
-
- func testSubscriptionRequestDeliveredOnce() {
- let expectation = expectation(description: "Request duplicate not delivered")
- let subscriptionParams = RelayJSONRPC.SubscriptionParams(id: "sub_id", data: RelayJSONRPC.SubscriptionData(topic: "topic", message: "message"))
- let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.method, params: subscriptionParams)
- iridiumRelay.onMessage = { _, _ in
- expectation.fulfill()
- }
- dispatcher.onMessage?(try! subscriptionRequest.json())
- dispatcher.onMessage?(try! subscriptionRequest.json())
- waitForExpectations(timeout: 0.001, handler: nil)
- }
-
- func testSendOnPublish() {
- iridiumRelay.publish(topic: "", payload: "", tag: 0, onNetworkAcknowledge: { _ in})
- XCTAssertTrue(dispatcher.sent)
- }
-
- func testSendOnSubscribe() {
- iridiumRelay.subscribe(topic: "") {_ in }
- XCTAssertTrue(dispatcher.sent)
- }
-
- func testSendOnUnsubscribe() {
- let topic = "123"
- iridiumRelay.subscriptions[topic] = ""
- iridiumRelay.unsubscribe(topic: topic) {_ in }
- XCTAssertTrue(dispatcher.sent)
- }
-}
diff --git a/Tests/RelayerTests/Mocks/DispatcherMock.swift b/Tests/RelayerTests/Mocks/DispatcherMock.swift
index 4efc9c222..97ddac5dc 100644
--- a/Tests/RelayerTests/Mocks/DispatcherMock.swift
+++ b/Tests/RelayerTests/Mocks/DispatcherMock.swift
@@ -1,17 +1,32 @@
import Foundation
+import JSONRPC
@testable import WalletConnectRelay
class DispatcherMock: Dispatching {
+
var onConnect: (() -> Void)?
var onDisconnect: (() -> Void)?
var onMessage: ((String) -> Void)?
+
+ func connect() {}
+ func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) {}
+
var sent = false
+ var lastMessage: String = ""
+
func send(_ string: String, completion: @escaping (Error?) -> Void) {
sent = true
+ lastMessage = string
}
func send(_ string: String) async throws {
send(string, completion: { _ in })
}
- func connect() {}
- func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) {}
+}
+
+extension DispatcherMock {
+
+ func getLastRequestSent() -> RPCRequest {
+ let data = lastMessage.data(using: .utf8)!
+ return try! JSONDecoder().decode(RPCRequest.self, from: data)
+ }
}
diff --git a/Tests/RelayerTests/RelayClientTests.swift b/Tests/RelayerTests/RelayClientTests.swift
new file mode 100644
index 000000000..d45bb61fe
--- /dev/null
+++ b/Tests/RelayerTests/RelayClientTests.swift
@@ -0,0 +1,106 @@
+import WalletConnectUtils
+import Foundation
+import Combine
+import JSONRPC
+import XCTest
+@testable import WalletConnectRelay
+
+final class RelayClientTests: XCTestCase {
+
+ var sut: RelayClient!
+ var dispatcher: DispatcherMock!
+
+ override func setUp() {
+ dispatcher = DispatcherMock()
+ let logger = ConsoleLogger()
+ sut = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage())
+ }
+
+ override func tearDown() {
+ sut = nil
+ dispatcher = nil
+ }
+
+ func testNotifyOnSubscriptionRequest() {
+ let expectation = expectation(description: "Relay must notify listener on a Subscription request")
+ let topic = "0987"
+ let message = "qwerty"
+ let subscriptionId = "sub-id"
+ let subscription = Subscription(id: subscriptionId, topic: topic, message: message)
+ let request = subscription.asRPCRequest()
+
+ sut.onMessage = { subscriptionTopic, subscriptionMessage in
+ XCTAssertEqual(subscriptionMessage, message)
+ XCTAssertEqual(subscriptionTopic, topic)
+ expectation.fulfill()
+ }
+ dispatcher.onMessage?(try! request.asJSONEncodedString())
+ waitForExpectations(timeout: 0.001, handler: nil)
+ }
+
+ func testSubscribeRequestAcknowledge() {
+ let acknowledgeExpectation = expectation(description: "")
+ sut.subscribe(topic: "") { error in
+ XCTAssertNil(error)
+ acknowledgeExpectation.fulfill()
+ }
+ let request = dispatcher.getLastRequestSent()
+ let response = RPCResponse(matchingRequest: request, result: "id")
+ dispatcher.onMessage?(try! response.asJSONEncodedString())
+ waitForExpectations(timeout: 0.1, handler: nil)
+ }
+
+ func testPublishRequestAcknowledge() {
+ let expectation = expectation(description: "Publish must callback on relay server acknowledgement")
+ sut.publish(topic: "", payload: "{}", tag: 0) { error in
+ XCTAssertNil(error)
+ expectation.fulfill()
+ }
+ let request = dispatcher.getLastRequestSent()
+ let response = RPCResponse(matchingRequest: request, result: true)
+ dispatcher.onMessage?(try! response.asJSONEncodedString())
+ waitForExpectations(timeout: 0.1, handler: nil)
+ }
+
+ func testUnsubscribeRequestAcknowledge() {
+ let expectation = expectation(description: "Unsubscribe must callback on relay server acknowledgement")
+ let topic = String.randomTopic()
+ sut.subscriptions[topic] = ""
+ sut.unsubscribe(topic: topic) { error in
+ XCTAssertNil(error)
+ expectation.fulfill()
+ }
+ let request = dispatcher.getLastRequestSent()
+ let response = RPCResponse(matchingRequest: request, result: true)
+ dispatcher.onMessage?(try! response.asJSONEncodedString())
+ waitForExpectations(timeout: 0.1, handler: nil)
+ }
+
+ func testSubscriptionRequestDeliveredOnce() {
+ let expectation = expectation(description: "Duplicate Subscription requests must notify only the first time")
+ let request = Subscription.init(id: "sub_id", topic: "topic", message: "message").asRPCRequest()
+ sut.onMessage = { _, _ in
+ expectation.fulfill()
+ }
+ dispatcher.onMessage?(try! request.asJSONEncodedString())
+ dispatcher.onMessage?(try! request.asJSONEncodedString())
+ waitForExpectations(timeout: 0.1, handler: nil)
+ }
+
+ func testSendOnPublish() {
+ sut.publish(topic: "", payload: "", tag: 0, onNetworkAcknowledge: { _ in})
+ XCTAssertTrue(dispatcher.sent)
+ }
+
+ func testSendOnSubscribe() {
+ sut.subscribe(topic: "") {_ in }
+ XCTAssertTrue(dispatcher.sent)
+ }
+
+ func testSendOnUnsubscribe() {
+ let topic = "123"
+ sut.subscriptions[topic] = ""
+ sut.unsubscribe(topic: topic) {_ in }
+ XCTAssertTrue(dispatcher.sent)
+ }
+}
diff --git a/Tests/TestingUtils/Mocks/RPC.swift b/Tests/TestingUtils/Mocks/RPC.swift
new file mode 100644
index 000000000..a8255b7a0
--- /dev/null
+++ b/Tests/TestingUtils/Mocks/RPC.swift
@@ -0,0 +1,12 @@
+import JSONRPC
+
+public extension RPCRequest {
+
+ static func stub() -> RPCRequest {
+ RPCRequest(method: "method", params: EmptyCodable())
+ }
+
+ static func stub(method: String, id: Int64) -> RPCRequest {
+ RPCRequest(method: method, params: EmptyCodable(), id: id)
+ }
+}
diff --git a/Tests/WalletConnectKMSTests/KeychainStorageTests.swift b/Tests/WalletConnectKMSTests/KeychainStorageTests.swift
index 19c57bdaf..422f23bcf 100644
--- a/Tests/WalletConnectKMSTests/KeychainStorageTests.swift
+++ b/Tests/WalletConnectKMSTests/KeychainStorageTests.swift
@@ -32,13 +32,12 @@ final class KeychainStorageTests: XCTestCase {
XCTAssertNoThrow(try sut.add(privateKey, forKey: "id-2"))
}
- func testAddDuplicateItemError() {
+ func testAddDuplicateItem() throws {
let privateKey = Curve25519.KeyAgreement.PrivateKey()
- try? sut.add(privateKey, forKey: defaultIdentifier)
- XCTAssertThrowsError(try sut.add(privateKey, forKey: defaultIdentifier)) { error in
- guard let error = error as? KeychainError else { XCTFail(); return }
- XCTAssertEqual(error.status, errSecDuplicateItem)
- }
+ try sut.add(privateKey, forKey: defaultIdentifier)
+ let newPrivateKey = Curve25519.KeyAgreement.PrivateKey()
+ XCTAssertNoThrow(try sut.add(newPrivateKey, forKey: defaultIdentifier))
+ XCTAssertEqual(try sut.read(key: defaultIdentifier), newPrivateKey)
}
func testAddUnknownFailure() {
diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
index ac6098c34..88e7a541c 100644
--- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift
+++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift
@@ -1,9 +1,10 @@
import XCTest
import Combine
+import WalletConnectUtils
+import WalletConnectPairing
@testable import WalletConnectSign
@testable import TestingUtils
@testable import WalletConnectKMS
-import WalletConnectUtils
final class ApproveEngineTests: XCTestCase {
@@ -47,6 +48,8 @@ final class ApproveEngineTests: XCTestCase {
func testApproveProposal() async throws {
// Client receives a proposal
let topicA = String.generateTopic()
+ let pairing = WCPairing.stub(expiryDate: Date(timeIntervalSinceNow: 10000), topic: topicA)
+ pairingStorageMock.setPairing(pairing)
let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation
let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey)
let request = WCRequest(method: .sessionPropose, params: .sessionPropose(proposal))
@@ -57,9 +60,11 @@ final class ApproveEngineTests: XCTestCase {
let topicB = networkingInteractor.subscriptions.last!
+ let extendedPairing = pairingStorageMock.getPairing(forTopic: topicA)!
XCTAssertTrue(networkingInteractor.didCallSubscribe)
XCTAssert(cryptoMock.hasAgreementSecret(for: topicB), "Responder must store agreement key for topic B")
XCTAssertEqual(networkingInteractor.didRespondOnTopic!, topicA, "Responder must respond on topic A")
+ XCTAssertEqual(extendedPairing.expiryDate.timeIntervalSince1970, Date(timeIntervalSinceNow: 2_592_000).timeIntervalSince1970, accuracy: 1, "pairing expiry has been extended by 30 days")
}
func testReceiveProposal() {
diff --git a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift b/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift
index 01f4ed5fc..dd98115f7 100644
--- a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift
+++ b/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift
@@ -2,6 +2,7 @@ import Foundation
import XCTest
import TestingUtils
import WalletConnectUtils
+import WalletConnectPairing
@testable import WalletConnectSign
final class JsonRpcHistoryTests: XCTestCase {
diff --git a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
index b8de74a1a..98f57f0ef 100644
--- a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
+++ b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift
@@ -18,18 +18,17 @@ class MockedRelayClient: NetworkRelaying {
var onMessage: ((String, String) -> Void)?
var error: Error?
var prompt = false
- func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 {
+ func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) {
self.prompt = prompt
onNetworkAcknowledge(error)
- return 0
}
func subscribe(topic: String, completion: @escaping (Error?) -> Void) {
}
- func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) -> Int64? {
- return 0
+ func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) {
}
+
func connect() {
}
diff --git a/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift b/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift
index 302233b0f..49d220625 100644
--- a/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift
+++ b/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift
@@ -1,6 +1,7 @@
import Foundation
import Combine
import WalletConnectUtils
+import WalletConnectPairing
@testable import WalletConnectSign
@testable import TestingUtils
diff --git a/Tests/WalletConnectSignTests/Mocks/WCPairingStorageMock.swift b/Tests/WalletConnectSignTests/Mocks/WCPairingStorageMock.swift
index 62b8b34c5..f3c11550e 100644
--- a/Tests/WalletConnectSignTests/Mocks/WCPairingStorageMock.swift
+++ b/Tests/WalletConnectSignTests/Mocks/WCPairingStorageMock.swift
@@ -1,3 +1,4 @@
+import WalletConnectPairing
@testable import WalletConnectSign
final class WCPairingStorageMock: WCPairingStorage {
diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift
index 813243610..67c329247 100644
--- a/Tests/WalletConnectSignTests/Stub/Stubs.swift
+++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift
@@ -3,6 +3,7 @@ import Foundation
import WalletConnectKMS
import WalletConnectUtils
import TestingUtils
+import WalletConnectPairing
extension AppMetadata {
static func stub() -> AppMetadata {
@@ -16,14 +17,14 @@ extension AppMetadata {
}
extension Pairing {
- static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000)) -> Pairing {
- Pairing(topic: String.generateTopic(), peer: nil, expiryDate: expiryDate)
+ static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing {
+ Pairing(topic: topic, peer: nil, expiryDate: expiryDate)
}
}
extension WCPairing {
- static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), isActive: Bool = true) -> WCPairing {
- WCPairing(topic: String.generateTopic(), relay: RelayProtocolOptions.stub(), peerMetadata: AppMetadata.stub(), isActive: isActive, expiryDate: expiryDate)
+ static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), isActive: Bool = true, topic: String = String.generateTopic()) -> WCPairing {
+ WCPairing(topic: topic, relay: RelayProtocolOptions.stub(), peerMetadata: AppMetadata.stub(), isActive: isActive, expiryDate: expiryDate)
}
}
@@ -97,7 +98,7 @@ extension WCRequestSubscriptionPayload {
extension SessionProposal {
static func stub(proposerPubKey: String = "") -> SessionProposal {
- let relayOptions = RelayProtocolOptions(protocol: "iridium", data: nil)
+ let relayOptions = RelayProtocolOptions(protocol: "irn", data: nil)
return SessionType.ProposeParams(
relays: [relayOptions],
proposer: Participant(publicKey: proposerPubKey, metadata: AppMetadata.stub()),
diff --git a/Tests/WalletConnectSignTests/WCPairingTests.swift b/Tests/WalletConnectSignTests/WCPairingTests.swift
index 951fdb430..6826add6d 100644
--- a/Tests/WalletConnectSignTests/WCPairingTests.swift
+++ b/Tests/WalletConnectSignTests/WCPairingTests.swift
@@ -1,4 +1,5 @@
import XCTest
+import WalletConnectPairing
@testable import WalletConnectSign
final class WCPairingTests: XCTestCase {
diff --git a/Tests/WalletConnectSignTests/WCRelayTests.swift b/Tests/WalletConnectSignTests/WCRelayTests.swift
index 0196de9a4..500967141 100644
--- a/Tests/WalletConnectSignTests/WCRelayTests.swift
+++ b/Tests/WalletConnectSignTests/WCRelayTests.swift
@@ -2,6 +2,7 @@ import Foundation
import Combine
import XCTest
import WalletConnectUtils
+import WalletConnectPairing
@testable import TestingUtils
@testable import WalletConnectSign
@@ -57,7 +58,7 @@ private let testPayload =
{
"id":1630300527198334,
"jsonrpc":"2.0",
- "method":"iridium_subscription",
+ "method":"irn_subscription",
"params":{
"id":"0847f4e1dd19cf03a43dc7525f39896b630e9da33e4683c8efbc92ea671b5e07",
"data":{
diff --git a/Tests/WalletConnectSignTests/WalletConnectURITests.swift b/Tests/WalletConnectSignTests/WalletConnectURITests.swift
index f2331c626..e1e218a4b 100644
--- a/Tests/WalletConnectSignTests/WalletConnectURITests.swift
+++ b/Tests/WalletConnectSignTests/WalletConnectURITests.swift
@@ -3,9 +3,9 @@ import XCTest
private let stubTopic = "8097df5f14871126866252c1b7479a14aefb980188fc35ec97d130d24bd887c8"
private let stubSymKey = "587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303"
-private let stubProtocol = "iridium"
+private let stubProtocol = "irn"
-private let stubURI = "wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=iridium"
+private let stubURI = "wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=irn"
final class WalletConnectURITests: XCTestCase {
@@ -13,7 +13,7 @@ final class WalletConnectURITests: XCTestCase {
let inputURI = WalletConnectURI(
topic: "8097df5f14871126866252c1b7479a14aefb980188fc35ec97d130d24bd887c8",
symKey: "19c5ecc857963976fabb98ed6a3e0a6ab6b0d65c018b6e25fbdcd3a164def868",
- relay: RelayProtocolOptions(protocol: "iridium", data: nil))
+ relay: RelayProtocolOptions(protocol: "irn", data: nil))
let uriString = inputURI.absoluteString
let outputURI = WalletConnectURI(string: uriString)
XCTAssertEqual(inputURI, outputURI)
diff --git a/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift
new file mode 100644
index 000000000..10bfbc049
--- /dev/null
+++ b/Tests/WalletConnectUtilsTests/RPCHistoryTests.swift
@@ -0,0 +1,110 @@
+import XCTest
+import JSONRPC
+import TestingUtils
+@testable import WalletConnectUtils
+
+final class RPCHistoryTests: XCTestCase {
+
+ var sut: RPCHistory!
+
+ override func setUp() {
+ let storage = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
+ sut = RPCHistory(keyValueStore: storage)
+ }
+
+ override func tearDown() {
+ sut = nil
+ }
+
+ // MARK: History Storage Tests
+
+ func testRoundTrip() throws {
+ let request = RPCRequest.stub()
+ try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local)
+ let record = sut.get(recordId: request.id!)
+ XCTAssertNil(record?.response)
+ XCTAssertEqual(record?.request, request)
+ }
+
+ func testResolveSuccessAndError() throws {
+ let requestA = RPCRequest.stub()
+ let requestB = RPCRequest.stub()
+ let responseA = RPCResponse(matchingRequest: requestA, result: true)
+ let responseB = RPCResponse(matchingRequest: requestB, error: .internalError)
+ try sut.set(requestA, forTopic: String.randomTopic(), emmitedBy: .remote)
+ try sut.set(requestB, forTopic: String.randomTopic(), emmitedBy: .local)
+ try sut.resolve(responseA)
+ try sut.resolve(responseB)
+ let recordA = sut.get(recordId: requestA.id!)
+ let recordB = sut.get(recordId: requestB.id!)
+ XCTAssertEqual(recordA?.response, responseA)
+ XCTAssertEqual(recordB?.response, responseB)
+ }
+
+ func testDelete() throws {
+ let requests = (1...5).map { _ in RPCRequest.stub() }
+ let topic = String.randomTopic()
+ try requests.forEach { try sut.set($0, forTopic: topic, emmitedBy: .local) }
+ sut.deleteAll(forTopic: topic)
+ requests.forEach {
+ XCTAssertNil(sut.get(recordId: $0.id!))
+ }
+ }
+
+ // MARK: Error Cases Tests
+
+ func testSetUnidentifiedRequest() {
+ let expectedError = RPCHistory.HistoryError.unidentifiedRequest
+
+ let request = RPCRequest.notification(method: "notify")
+ XCTAssertThrowsError(try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local)) { error in
+ XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
+ }
+ }
+
+ func testSetDuplicateRequest() throws {
+ let expectedError = RPCHistory.HistoryError.requestDuplicateNotAllowed
+
+ let id = Int.random()
+ let requestA = RPCRequest.stub(method: "method-1", id: id)
+ let requestB = RPCRequest.stub(method: "method-2", id: id)
+ let topic = String.randomTopic()
+
+ try sut.set(requestA, forTopic: topic, emmitedBy: .local)
+ XCTAssertThrowsError(try sut.set(requestB, forTopic: topic, emmitedBy: .local)) { error in
+ XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
+ }
+ }
+
+ func testResolveResponseWithoutRequest() throws {
+ let expectedError = RPCHistory.HistoryError.requestMatchingResponseNotFound
+
+ let response = RPCResponse(id: 0, result: true)
+ XCTAssertThrowsError(try sut.resolve(response)) { error in
+ XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
+ }
+ }
+
+ func testResolveUnidentifiedResponse() throws {
+ let expectedError = RPCHistory.HistoryError.unidentifiedResponse
+
+ let response = RPCResponse(errorWithoutID: JSONRPCError.internalError)
+ XCTAssertThrowsError(try sut.resolve(response)) { error in
+ XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
+ }
+ }
+
+ func testResolveDuplicateResponse() throws {
+ let expectedError = RPCHistory.HistoryError.responseDuplicateNotAllowed
+
+ let request = RPCRequest.stub()
+ let responseA = RPCResponse(matchingRequest: request, result: true)
+ let responseB = RPCResponse(matchingRequest: request, result: false)
+
+ try sut.set(request, forTopic: String.randomTopic(), emmitedBy: .local)
+ try sut.resolve(responseA)
+ XCTAssertThrowsError(try sut.resolve(responseB)) { error in
+ XCTAssertEqual(expectedError, error as? RPCHistory.HistoryError)
+ }
+ }
+}