Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pairing] Pairing API #511

Merged
merged 46 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f4d9f4f
savepoint
llbartekll Sep 12, 2022
f59f7bf
savepoint
llbartekll Sep 12, 2022
f9f87f2
savepoint
llbartekll Sep 12, 2022
3959625
SessionEngine
flypaper0 Sep 6, 2022
a67e09d
ApproveEngine
flypaper0 Sep 6, 2022
bfcd3af
Sign: Networking package imported
flypaper0 Sep 8, 2022
41a54d5
Sign new ping
flypaper0 Sep 8, 2022
3a734ea
Cleanup
flypaper0 Sep 8, 2022
e19148d
Unit tests fixed
flypaper0 Sep 8, 2022
12587e4
Sample apps build errors fixed
flypaper0 Sep 9, 2022
fb9805b
Sample apps and UI tests repaired
flypaper0 Sep 12, 2022
f2dc91e
testSessionPing duplicate removed
flypaper0 Sep 12, 2022
dc469c2
Wallet sample app fixed
flypaper0 Sep 12, 2022
be2641e
UI Tests improvements
flypaper0 Sep 13, 2022
48d3adc
Approve Engine RPCResult replaced
flypaper0 Sep 14, 2022
707d335
Prompt
flypaper0 Sep 14, 2022
f794605
Unit test fixed
flypaper0 Sep 14, 2022
c4fa054
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
llbartekll Sep 19, 2022
ffbfc0d
pass pairing api integration test
llbartekll Sep 19, 2022
c2ba26c
refactor pairing
llbartekll Sep 20, 2022
73b824f
Merge branch 'flypaper0/sign-network-interactor' of github.com:Wallet…
llbartekll Sep 20, 2022
f50a816
update pairingable protocol
llbartekll Sep 20, 2022
70b7cda
add requestSubscription on requests to networking
llbartekll Sep 20, 2022
b8ca28b
savepoint
llbartekll Sep 20, 2022
2901a00
savepoint
llbartekll Sep 20, 2022
1bc9ce3
savepoint
llbartekll Sep 20, 2022
4538ea3
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
llbartekll Sep 21, 2022
1e1646e
remove socket connection observing from integration tests
llbartekll Sep 21, 2022
67e3b15
restore on connection publisher
llbartekll Sep 21, 2022
bc68317
Merge branch 'flypaper0/sign-network-interactor' of github.com:Wallet…
llbartekll Sep 21, 2022
1395b23
remove duplicated file
llbartekll Sep 21, 2022
227ac54
self review changes
llbartekll Sep 21, 2022
6aab69c
savepoint
llbartekll Sep 22, 2022
132dbc6
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
flypaper0 Sep 22, 2022
be56aab
Pairingable removed
flypaper0 Sep 22, 2022
1a3af64
PairingRegisterer
flypaper0 Sep 22, 2022
87006a7
Push propose error publisher
flypaper0 Sep 23, 2022
eb1ce87
Build errors
flypaper0 Sep 23, 2022
34728a2
Cache key updated
flypaper0 Sep 27, 2022
cc80680
Resolve Dependencies after cache restore
flypaper0 Sep 27, 2022
272e005
Update chache id
flypaper0 Sep 27, 2022
f58a867
testHandleSessionProposeResponse stability
flypaper0 Sep 27, 2022
ba90193
Update cache id
flypaper0 Sep 27, 2022
79e7adc
Default cache id
flypaper0 Sep 27, 2022
3938dfc
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
llbartekll Sep 27, 2022
81c4fd7
remove wait clients connected, refactor push protocol method
llbartekll Sep 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ jobs:
- name: Setup Xcode Version
uses: maxim-lobanov/setup-xcode@v1

- name: Resolve Dependencies
shell: bash
run: "
xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache; \
xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache"

- uses: actions/cache@v2
with:
path: |
Expand All @@ -43,6 +37,12 @@ jobs:
restore-keys: |
${{ runner.os }}-spm-

- name: Resolve Dependencies
shell: bash
run: "
xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache; \
xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache"

- uses: ./.github/actions/ci
with:
type: ${{ matrix.test-type }}
Expand Down
12 changes: 12 additions & 0 deletions Example/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644D279ED2FF00142511 /* SelectChainView.swift */; };
84CE6452279ED42B00142511 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6451279ED42B00142511 /* ConnectView.swift */; };
84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; };
84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; };
84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; };
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; };
84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; };
Expand Down Expand Up @@ -235,6 +236,7 @@
84CE6451279ED42B00142511 /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = "<group>"; };
84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = "<group>"; };
84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = "<group>"; };
84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = "<group>"; };
84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = "<group>"; };
84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = "<group>"; };
84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -581,6 +583,14 @@
path = Connect;
sourceTree = "<group>";
};
84CEC64728D8A98900D081A8 /* Pairing */ = {
isa = PBXGroup;
children = (
84CEC64528D89D6B00D081A8 /* PairingTests.swift */,
);
path = Pairing;
sourceTree = "<group>";
};
84D2A66728A4F5260088AE09 /* Auth */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1019,6 +1029,7 @@
A5E03DEE286464DB00888481 /* IntegrationTests */ = {
isa = PBXGroup;
children = (
84CEC64728D8A98900D081A8 /* Pairing */,
A5E03E0B28646AA500888481 /* Relay */,
A5E03E0A28646A8A00888481 /* Stubs */,
A5E03E0928646A8100888481 /* Sign */,
Expand Down Expand Up @@ -1455,6 +1466,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */,
767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */,
A5E03E03286466F400888481 /* ChatTests.swift in Sources */,
84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */,
Expand Down
56 changes: 56 additions & 0 deletions Example/IntegrationTests/Pairing/PairingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation
import XCTest
import WalletConnectUtils
@testable import WalletConnectKMS
import WalletConnectRelay
import Combine
import WalletConnectNetworking
@testable import WalletConnectPairing


final class PairingTests: XCTestCase {
var appPairingClient: PairingClient!
var walletPairingClient: PairingClient!

var appPushClient: PushClient!
var walletPushClient: PushClient!

var pairingStorage: PairingStorage!

private var publishers = [AnyCancellable]()

override func setUp() {
(appPairingClient, appPushClient) = makeClients(prefix: "🤖 App")
(walletPairingClient, walletPushClient) = makeClients(prefix: "🐶 Wallet")
}

func makeClients(prefix: String) -> (PairingClient, PushClient) {
let keychain = KeychainStorageMock()
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
let projectId = "3ca2919724fbfa5456a25194e369a8b4"
let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger)

let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient)

let pushClient = PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient, pairingClient: pairingClient)
return (pairingClient, pushClient)

}

func testProposePushOnPairing() async throws {
let exp = expectation(description: "testProposePushOnPairing")

walletPushClient.proposalPublisher.sink { _ in
exp.fulfill()
}.store(in: &publishers)

let uri = try await appPairingClient.create()

try await walletPairingClient.pair(uri: uri)

try await appPushClient.propose(topic: uri.topic)

wait(for: [exp], timeout: 2)
}
}

1 change: 1 addition & 0 deletions Sources/WalletConnectNetworking/NetworkInteracting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import WalletConnectRelay

public protocol NetworkInteracting {
var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> { get }
var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { get }
func subscribe(topic: String) async throws
func unsubscribe(topic: String)
func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws
Expand Down
6 changes: 3 additions & 3 deletions Sources/WalletConnectNetworking/NetworkInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class NetworkingInteractor: NetworkInteracting {
private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>()
private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse), Never>()

private var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> {
requestPublisherSubject.eraseToAnyPublisher()
}

Expand Down Expand Up @@ -56,13 +56,13 @@ public class NetworkingInteractor: NetworkInteracting {
}
}

public func requestSubscription<Request: Codable>(on request: ProtocolMethod) -> AnyPublisher<RequestSubscriptionPayload<Request>, Never> {
public func requestSubscription<RequestParams: Codable>(on request: ProtocolMethod) -> AnyPublisher<RequestSubscriptionPayload<RequestParams>, Never> {
return requestPublisher
.filter { rpcRequest in
return rpcRequest.request.method == request.method
}
.compactMap { topic, rpcRequest in
guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil }
guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(RequestParams.self) else { return nil }
return RequestSubscriptionPayload(id: id, topic: topic, request: request)
}
.eraseToAnyPublisher()
Expand Down
48 changes: 48 additions & 0 deletions Sources/WalletConnectPairing/PairingClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import WalletConnectUtils
import WalletConnectRelay
import WalletConnectNetworking
import Combine

public class PairingClient: PairingRegisterer {
private let walletPairService: WalletPairService
private let appPairService: AppPairService
public let socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>
private let logger: ConsoleLogging
private let networkingInteractor: NetworkInteracting
private let pairingRequestsSubscriber: PairingRequestsSubscriber

init(appPairService: AppPairService,
networkingInteractor: NetworkInteracting,
logger: ConsoleLogging,
walletPairService: WalletPairService,
pairingRequestsSubscriber: PairingRequestsSubscriber,
socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>
) {
self.appPairService = appPairService
self.walletPairService = walletPairService
self.networkingInteractor = networkingInteractor
self.socketConnectionStatusPublisher = socketConnectionStatusPublisher
self.logger = logger
self.pairingRequestsSubscriber = pairingRequestsSubscriber
}
/// For wallet to establish a pairing
/// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests.
/// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking.
///
/// Throws Error:
/// - When URI is invalid format or missing params
/// - When topic is already in use
public func pair(uri: WalletConnectURI) async throws {
try await walletPairService.pair(uri)
}

public func create() async throws -> WalletConnectURI {
return try await appPairService.create()
}

public func register(method: ProtocolMethod) {
pairingRequestsSubscriber.subscribeForRequest(method)
}
}

36 changes: 36 additions & 0 deletions Sources/WalletConnectPairing/PairingClientFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation
import WalletConnectRelay
import WalletConnectUtils
import WalletConnectKMS
import WalletConnectNetworking

public struct PairingClientFactory {

public static func create(relayClient: RelayClient) -> PairingClient {
let logger = ConsoleLogger(loggingLevel: .off)
let keyValueStorage = UserDefaults.standard
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, relayClient: relayClient)
}

static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient {
let kms = KeyManagementService(keychain: keychainStorage)
let serializer = Serializer(kms: kms)
let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage)
let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history)
let pairingStore = PairingStorage(storage: SequenceStore<WCPairing>(store: .init(defaults: keyValueStorage, identifier: "")))
let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore)
let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore)
let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingInteractor, pairingStorage: pairingStore, logger: logger)

return PairingClient(
appPairService: appPairService,
networkingInteractor: networkingInteractor,
logger: logger,
walletPairService: walletPairService,
pairingRequestsSubscriber: pairingRequestsSubscriber,
socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher
)
}
}

6 changes: 6 additions & 0 deletions Sources/WalletConnectPairing/PairingRegisterer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation
import WalletConnectNetworking

public protocol PairingRegisterer {
func register(method: ProtocolMethod)
}
27 changes: 27 additions & 0 deletions Sources/WalletConnectPairing/PairingRequester.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import Combine
import JSONRPC
import WalletConnectUtils
import WalletConnectKMS
import WalletConnectNetworking


public class PushProposer {
private let networkingInteractor: NetworkInteracting
private let kms: KeyManagementServiceProtocol
private let logger: ConsoleLogging

init(networkingInteractor: NetworkInteracting,
kms: KeyManagementServiceProtocol,
logger: ConsoleLogging) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.logger = logger
}

func request(topic: String, params: AnyCodable) async throws {
let protocolMethod = PushProposeProtocolMethod()
let request = RPCRequest(method: protocolMethod.method, params: params)
try await networkingInteractor.requestNetworkAck(request, topic: topic, protocolMethod: protocolMethod)
}
}
36 changes: 36 additions & 0 deletions Sources/WalletConnectPairing/PairingRequestsSubscriber.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation
import Combine
import WalletConnectUtils
import WalletConnectNetworking

public class PairingRequestsSubscriber {
private let networkingInteractor: NetworkInteracting
private let pairingStorage: PairingStorage
private var publishers = Set<AnyCancellable>()

init(networkingInteractor: NetworkInteracting, pairingStorage: PairingStorage, logger: ConsoleLogging) {
self.networkingInteractor = networkingInteractor
self.pairingStorage = pairingStorage
}

func subscribeForRequest(_ protocolMethod: ProtocolMethod) {
networkingInteractor.requestPublisher
// Pairing requests only
.filter { [unowned self] payload in
return pairingStorage.hasPairing(forTopic: payload.topic)
}
// Wrong method
.filter { payload in
return payload.request.method != protocolMethod.method
}
// Respond error
.sink { [unowned self] topic, request in
Task(priority: .high) {
// TODO - spec tag
try await networkingInteractor.respondError(topic: topic, requestId: request.id!, protocolMethod: protocolMethod, reason: PairError.methodUnsupported)
}

}.store(in: &publishers)
}

}
59 changes: 59 additions & 0 deletions Sources/WalletConnectPairing/Push/PushClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Foundation
import JSONRPC
import Combine
import WalletConnectKMS
import WalletConnectUtils
import WalletConnectNetworking

public class PushClient {

private var publishers = Set<AnyCancellable>()

let requestPublisherSubject = PassthroughSubject<(topic: String, params: PushRequestParams), Never>()

var proposalPublisher: AnyPublisher<(topic: String, params: PushRequestParams), Never> {
requestPublisherSubject.eraseToAnyPublisher()
}

public let logger: ConsoleLogging

private let pushProposer: PushProposer
private let networkInteractor: NetworkInteracting
private let pairingRegisterer: PairingRegisterer

init(networkInteractor: NetworkInteracting,
logger: ConsoleLogging,
kms: KeyManagementServiceProtocol,
pushProposer: PushProposer,
pairingRegisterer: PairingRegisterer) {
self.networkInteractor = networkInteractor
self.logger = logger
self.pushProposer = pushProposer
self.pairingRegisterer = pairingRegisterer

setupPairingSubscriptions()
}

public func propose(topic: String) async throws {
try await pushProposer.request(topic: topic, params: AnyCodable(PushRequestParams()))
}
}

private extension PushClient {

func setupPairingSubscriptions() {
let protocolMethod = PushProposeProtocolMethod()

pairingRegisterer.register(method: protocolMethod)

networkInteractor.responseErrorSubscription(on: protocolMethod)
.sink { [unowned self] (payload: ResponseSubscriptionErrorPayload<PushRequestParams>) in
logger.error(payload.error.localizedDescription)
}.store(in: &publishers)

networkInteractor.requestSubscription(on: protocolMethod)
.sink { [unowned self] (payload: RequestSubscriptionPayload<PushRequestParams>) in
requestPublisherSubject.send((payload.topic, payload.request))
}.store(in: &publishers)
}
}
Loading