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

[Apps] Wallet pairing integration #541

Merged
merged 6 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion Example/DApp/Auth/AuthCoordinator.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI
import Auth
import WalletConnectPairing

final class AuthCoordinator {

Expand Down Expand Up @@ -30,7 +31,8 @@ final class AuthCoordinator {
url: "wallet.connect",
icons: ["https://avatars.githubusercontent.com/u/37784886"])

Auth.configure(metadata: metadata, account: nil)
Pair.configure(metadata: metadata)
Auth.configure(account: nil)

navigationController.viewControllers = [authViewController]
}
Expand Down
3 changes: 2 additions & 1 deletion Example/DApp/Sign/Connect/ConnectViewController.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Foundation
import UIKit
import WalletConnectSign
import WalletConnectPairing

class ConnectViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let uri: WalletConnectURI
let activePairings: [Pairing] = Sign.instance.getPairings()
let activePairings: [Pairing] = Pair.instance.getPairings()
let segmentedControl = UISegmentedControl(items: ["Pairings", "New Pairing"])

init(uri: WalletConnectURI) {
Expand Down
6 changes: 4 additions & 2 deletions Example/DApp/Sign/SelectChain/SelectChainViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import WalletConnectSign
import WalletConnectPairing
import UIKit
import Combine

Expand Down Expand Up @@ -34,8 +35,9 @@ class SelectChainViewController: UIViewController, UITableViewDataSource {
let blockchains: Set<Blockchain> = [Blockchain("eip155:1")!, Blockchain("eip155:137")!]
let namespaces: [String: ProposalNamespace] = ["eip155": ProposalNamespace(chains: blockchains, methods: methods, events: [], extensions: nil)]
Task {
let uri = try await Sign.instance.connect(requiredNamespaces: namespaces)
showConnectScreen(uri: uri!)
let uri = try await Pair.instance.create()
try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic)
showConnectScreen(uri: uri)
}
}

Expand Down
5 changes: 3 additions & 2 deletions Example/ExampleApp/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Combine
import WalletConnectSign
import WalletConnectNetworking
import WalletConnectRelay
import WalletConnectPairing
import Starscream

extension WebSocket: WebSocketConnecting { }
Expand All @@ -27,7 +28,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
icons: ["https://avatars.githubusercontent.com/u/37784886"])

Networking.configure(projectId: InputConfig.projectId, socketFactory: SocketFactory())
Sign.configure(metadata: metadata)
Pair.configure(metadata: metadata)
#if DEBUG
if CommandLine.arguments.contains("-cleanInstall") {
try? Sign.instance.cleanup()
Expand Down Expand Up @@ -56,7 +57,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

let wcUri = url.absoluteString.deletingPrefix("https://walletconnect.com/wc?uri=")
Task(priority: .high) {
try! await Sign.instance.pair(uri: WalletConnectURI(string: wcUri)!)
try! await Pair.instance.pair(uri: WalletConnectURI(string: wcUri)!)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Example/ExampleApp/Wallet/WalletViewController.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UIKit
import WalletConnectSign
import WalletConnectUtils
import WalletConnectPairing
import WalletConnectRouter
import Web3
import CryptoSwift
Expand Down Expand Up @@ -122,7 +123,7 @@ final class WalletViewController: UIViewController {
print("[WALLET] Pairing to: \(uri)")
Task {
do {
try await Sign.instance.pair(uri: uri)
try await Pair.instance.pair(uri: uri)
} catch {
print("[DAPP] Pairing connect error: \(error)")
}
Expand Down
18 changes: 0 additions & 18 deletions Example/IntegrationTests/Sign/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,24 +134,6 @@ final class SignClientTests: XCTestCase {
wait(for: [sessionDeleteExpectation], timeout: InputConfig.defaultTimeout)
}

func testPairingPing() async throws {
let pongResponseExpectation = expectation(description: "Ping sender receives a pong response")

let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired())!
try await wallet.client.pair(uri: uri)

let pairing = wallet.client.getPairings().first!

wallet.onPing = { topic in
XCTAssertEqual(topic, pairing.topic)
pongResponseExpectation.fulfill()
}

try await wallet.client.ping(topic: pairing.topic)

wait(for: [pongResponseExpectation], timeout: InputConfig.defaultTimeout)
}

func testSessionPing() async throws {
let expectation = expectation(description: "Proposer receives ping response")

Expand Down
2 changes: 1 addition & 1 deletion Sources/Auth/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Auth {
metadata: config.metadata,
account: config.account,
networkingClient: Networking.instance,
pairingRegisterer: Pair.instance
pairingRegisterer: Pair.instance as! PairingRegisterer
)
}()

Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnectPairing/Pair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Combine
public class Pair {

/// Pairing client instance
public static var instance: PairingClient = {
public static var instance: PairingInteracting = {
guard let config = Pair.config else {
fatalError("Error - you must call Pair.configure(_:) before accessing the shared instance.")
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnectPairing/PairingClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import WalletConnectNetworking
import Combine
import JSONRPC

public class PairingClient: PairingRegisterer {
public class PairingClient: PairingRegisterer, PairingInteracting {
public var pingResponsePublisher: AnyPublisher<(String), Never> {
pingResponsePublisherSubject.eraseToAnyPublisher()
}
Expand Down
18 changes: 18 additions & 0 deletions Sources/WalletConnectPairing/PairingInteracting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation
import WalletConnectUtils

public protocol PairingInteracting {
func pair(uri: WalletConnectURI) async throws

func create() async throws -> WalletConnectURI

func getPairings() -> [Pairing]

func getPairing(for topic: String) throws -> Pairing

func ping(topic: String) async throws

func disconnect(topic: String) async throws

func cleanup() throws
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation
import WalletConnectKMS
import WalletConnectUtils
import WalletConnectPairing

final class CleanupService {

Expand Down
14 changes: 4 additions & 10 deletions Sources/WalletConnectSign/Services/DisconnectService.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import Foundation
import WalletConnectPairing

class DisconnectService {
enum Errors: Error {
case objectForTopicNotFound
case sessionForTopicNotFound
}

private let deleteSessionService: DeleteSessionService
private let sessionStorage: WCSessionStorage
private let pairingClient: PairingClient

init(deleteSessionService: DeleteSessionService,
sessionStorage: WCSessionStorage,
pairingClient: PairingClient) {
sessionStorage: WCSessionStorage) {
self.deleteSessionService = deleteSessionService
self.sessionStorage = sessionStorage
self.pairingClient = pairingClient
}

func disconnect(topic: String) async throws {
if let _ = try? pairingClient.getPairing(for: topic) {
try await pairingClient.disconnect(topic: topic)
} else if sessionStorage.hasSession(forTopic: topic) {
if sessionStorage.hasSession(forTopic: topic) {
try await deleteSessionService.delete(topic: topic)
} else {
throw Errors.objectForTopicNotFound
throw Errors.sessionForTopicNotFound
}
}
}
2 changes: 1 addition & 1 deletion Sources/WalletConnectSign/Sign/Sign.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class Sign {
return SignClientFactory.create(
metadata: Sign.metadata ?? Pair.metadata,
relayClient: Relay.instance,
pairingClient: Pair.instance,
pairingClient: Pair.instance as! PairingClient,
networkingClient: Networking.instance
)
}()
Expand Down
32 changes: 24 additions & 8 deletions Sources/WalletConnectSign/Sign/SignClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import WalletConnectPairing
///
/// Access via `Sign.instance`
public final class SignClient {
enum Errors: Error {
case sessionForTopicNotFound
}

// MARK: - Public Properties

Expand Down Expand Up @@ -165,6 +168,7 @@ public final class SignClient {
/// - requiredNamespaces: required namespaces for a session
/// - topic: Optional parameter - use it if you already have an established pairing with peer client.
/// - Returns: Pairing URI that should be shared with responder out of bound. Common way is to present it as a QR code. Pairing URI will be nil if you are going to establish a session on existing Pairing and `topic` function parameter was provided.
@available(*, deprecated, message: "use Pair.instance.create() and connect(requiredNamespaces: [String: ProposalNamespace]): instead")
public func connect(requiredNamespaces: [String: ProposalNamespace], topic: String? = nil) async throws -> WalletConnectURI? {
logger.debug("Connecting Application")
if let topic = topic {
Expand All @@ -186,13 +190,29 @@ public final class SignClient {
}
}

/// For a dApp to propose a session to a wallet.
/// Function will propose a session on existing pairing.
/// - Parameters:
/// - requiredNamespaces: required namespaces for a session
/// - topic: pairing topic
public func connect(requiredNamespaces: [String: ProposalNamespace], topic: String) async throws {
logger.debug("Connecting Application")
try pairingClient.validatePairingExistance(topic)
try await appProposeService.propose(
pairingTopic: topic,
namespaces: requiredNamespaces,
relay: RelayProtocolOptions(protocol: "irn", data: nil)
)
}

/// For wallet to receive a session proposal from a dApp
/// Responder should call this function in order to accept peer's pairing and be able to subscribe for future session proposals.
/// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp.
///
/// Should Error:
/// - When URI has invalid format or missing params
/// - When topic is already in use
@available(*, deprecated, message: "use Pair.instance.pair(uri: WalletConnectURI): instead")
public func pair(uri: WalletConnectURI) async throws {
try await pairingClient.pair(uri: uri)
}
Expand Down Expand Up @@ -251,17 +271,12 @@ public final class SignClient {
///
/// Should Error:
/// - When the session topic is not found
/// - When the response is neither result or error
///
/// - Parameters:
/// - topic: Topic of a session or a pairing
/// - completion: Result will be success on response or an error
/// - topic: Topic of a session
public func ping(topic: String) async throws {
if let _ = try? pairingClient.validatePairingExistance(topic) {
try await pairingPingService.ping(topic: topic)
} else if sessionEngine.hasSession(for: topic) {
try await sessionPingService.ping(topic: topic)
}
guard sessionEngine.hasSession(for: topic) else { throw Errors.sessionForTopicNotFound }
try await sessionPingService.ping(topic: topic)
}

/// For the wallet to emit an event to a dApp
Expand Down Expand Up @@ -297,6 +312,7 @@ public final class SignClient {

/// Query pairings
/// - Returns: All pairings
@available(*, deprecated, message: "use Pair.instance.getPairings(uri: WalletConnectURI): instead")
public func getPairings() -> [Pairing] {
pairingClient.getPairings()
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnectSign/Sign/SignClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public struct SignClientFactory {
let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore)
let cleanupService = CleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic)
let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore, pairingClient: pairingClient)
let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore)
let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger)
let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger)
let appProposerService = AppProposeService(metadata: metadata, networkingInteractor: networkingClient, kms: kms, logger: logger)
Expand Down