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

Web3Wallet implementation + integration tests #638

Merged
merged 5 commits into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 8 additions & 1 deletion Example/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
845B8D8C2934B36C0084A966 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845B8D8B2934B36C0084A966 /* Account.swift */; };
8460DCFC274F98A10081F94C /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DCFB274F98A10081F94C /* RequestViewController.swift */; };
847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 847CF3AE28E3141700F1D760 /* WalletConnectPush */; settings = {ATTRIBUTES = (Required, ); }; };
849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* PushTests.swift */; };
849D7A90292665D3006A2BD4 /* WalletConnectVerify in Frameworks */ = {isa = PBXBuildFile; productRef = 849D7A8F292665D3006A2BD4 /* WalletConnectVerify */; };
849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* PushTests.swift */; };
84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; };
84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; };
84CE642127981DED00142511 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE642027981DED00142511 /* SceneDelegate.swift */; };
Expand Down Expand Up @@ -194,6 +194,7 @@
A5E22D222840C8D300E36487 /* WalletEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D212840C8D300E36487 /* WalletEngine.swift */; };
A5E22D242840C8DB00E36487 /* SafariEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D232840C8DB00E36487 /* SafariEngine.swift */; };
A5E22D2C2840EAC300E36487 /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D2B2840EAC300E36487 /* XCUIElement.swift */; };
C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */ = {isa = PBXBuildFile; productRef = C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -443,6 +444,7 @@
847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */,
A5C8BE85292FE20B006CC85C /* Web3 in Frameworks */,
84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */,
C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */,
A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1288,6 +1290,7 @@
84DDB4EC28ABB663003D66ED /* WalletConnectAuth */,
847CF3AE28E3141700F1D760 /* WalletConnectPush */,
A5C8BE84292FE20B006CC85C /* Web3 */,
C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */,
);
productName = IntegrationTests;
productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */;
Expand Down Expand Up @@ -2182,6 +2185,10 @@
isa = XCSwiftPackageProductDependency;
productName = WalletConnectChat;
};
C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */ = {
isa = XCSwiftPackageProductDependency;
productName = Web3Wallet;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 764E1D3426F8D3FC00A1FB15 /* Project object */;
Expand Down
12 changes: 11 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ let package = Package(
.library(
name: "WalletConnectAuth",
targets: ["Auth"]),
.library(
name: "Web3Wallet",
targets: ["Web3Wallet"]),
.library(
name: "WalletConnectPairing",
targets: ["WalletConnectPairing"]),
Expand Down Expand Up @@ -52,6 +55,10 @@ let package = Package(
name: "Auth",
dependencies: ["WalletConnectPairing"],
path: "Sources/Auth"),
.target(
name: "Web3Wallet",
dependencies: ["Auth", "WalletConnectSign"],
path: "Sources/Web3Wallet"),
.target(
name: "WalletConnectPush",
dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking"],
Expand Down Expand Up @@ -92,7 +99,10 @@ let package = Package(
dependencies: ["WalletConnectUtils"]),
.testTarget(
name: "WalletConnectSignTests",
dependencies: ["WalletConnectSign", "TestingUtils"]),
dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils"]),
.testTarget(
name: "Web3WalletTests",
dependencies: ["Web3Wallet", "TestingUtils"]),
.testTarget(
name: "WalletConnectPairingTests",
dependencies: ["WalletConnectPairing", "TestingUtils"]),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Combine
/// Cannot be instantiated outside of the SDK
///
/// Access via `Auth.instance`
public class AuthClient {
public class AuthClient: AuthClientProtocol {

// MARK: - Public Properties

Expand Down
10 changes: 10 additions & 0 deletions Sources/Auth/AuthClientProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import Combine

public protocol AuthClientProtocol {
var authRequestPublisher: AnyPublisher<AuthRequest, Never> { get }

func formatMessage(payload: AuthPayload, address: String) throws -> String
func respond(requestId: RPCID, signature: CacaoSignature, from account: Account) async throws
func getPendingRequests(account: Account) throws -> [AuthRequest]
}
2 changes: 1 addition & 1 deletion Sources/WalletConnectPairing/PairingClient.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Combine

public class PairingClient: PairingRegisterer, PairingInteracting {
public class PairingClient: PairingRegisterer, PairingInteracting, PairingClientProtocol {
public var pingResponsePublisher: AnyPublisher<(String), Never> {
pingResponsePublisherSubject.eraseToAnyPublisher()
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/WalletConnectPairing/PairingClientProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public protocol PairingClientProtocol {
func pair(uri: WalletConnectURI) async throws
}
2 changes: 1 addition & 1 deletion Sources/WalletConnectSign/Sign/SignClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Combine
/// Cannot be instantiated outside of the SDK
///
/// Access via `Sign.instance`
public final class SignClient {
public final class SignClient: SignClientProtocol {
enum Errors: Error {
case sessionForTopicNotFound
}
Expand Down
20 changes: 20 additions & 0 deletions Sources/WalletConnectSign/Sign/SignClientProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import Combine

public protocol SignClientProtocol {
var sessionProposalPublisher: AnyPublisher<Session.Proposal, Never> { get }
var sessionRequestPublisher: AnyPublisher<Request, Never> { get }

func approve(proposalId: String, namespaces: [String: SessionNamespace]) async throws
func reject(proposalId: String, reason: RejectionReason) async throws
func update(topic: String, namespaces: [String: SessionNamespace]) async throws
func extend(topic: String) async throws
func respond(topic: String, requestId: RPCID, response: RPCResult) async throws
func emit(topic: String, event: Session.Event, chainId: Blockchain) async throws
func pair(uri: WalletConnectURI) async throws
func disconnect(topic: String) async throws
func getSessions() -> [Session]

func getPendingRequests(topic: String?) -> [Request]
func getSessionRequestRecord(id: RPCID) -> Request?
}
47 changes: 47 additions & 0 deletions Sources/Web3Wallet/Web3Wallet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation
import Combine

import Auth
import WalletConnectSign
alexander-lsvk marked this conversation as resolved.
Show resolved Hide resolved

/// Web3Wallet instance wrapper
///
/// ```Swift
/// let metadata = AppMetadata(
/// name: "Swift wallet",
/// description: "wallet",
/// url: "wallet.connect",
/// icons: ["https://my_icon.com/1"]
/// )
/// Web3Wallet.configure(metadata: metadata, account: account)
/// Web3Wallet.instance.getSessions()
/// ```
public class Web3Wallet {
/// Web3Wallett client instance
public static var instance: Web3WalletClient = {
guard let config = Web3Wallet.config else {
fatalError("Error - you must call Wallet.configure(_:) before accessing the shared instance.")
}

return Web3WalletClientFactory.create(
alexander-lsvk marked this conversation as resolved.
Show resolved Hide resolved
authClient: Auth.instance,
signClient: Sign.instance,
pairingClient: Pair.instance as! PairingClient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@llbartekll as I remember we found the way how to avoid force casts for pairing client. Or we decided to leave it like that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have fixed it for Networking having two vars: instance and interactor I think we could do the same with pairing

)
}()

private static var config: Config?

private init() { }

/// Wallet instance wallet config method.
/// - Parameters:
/// - metadata: App metadata
/// - signerFactory: Auth signers factory
static public func configure(metadata: AppMetadata, signerFactory: SignerFactory) {
Pair.configure(metadata: metadata)
Auth.configure(signerFactory: signerFactory)

Web3Wallet.config = Web3Wallet.Config(signerFactory: signerFactory)
}
}
166 changes: 166 additions & 0 deletions Sources/Web3Wallet/Web3WalletClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import Foundation
import Combine

import Auth
import WalletConnectSign

/// Web3 Wallet Client
///
/// Cannot be instantiated outside of the SDK
///
/// Access via `Web3Wallet.instance`
public class Web3WalletClient {
// MARK: - Public Properties

/// Publisher that sends session proposal
///
/// event is emited on responder client only
public var sessionProposalPublisher: AnyPublisher<Session.Proposal, Never> {
signClient.sessionProposalPublisher.eraseToAnyPublisher()
}

/// Publisher that sends session request
///
/// In most cases event will be emited on wallet
public var sessionRequestPublisher: AnyPublisher<Request, Never> {
signClient.sessionRequestPublisher.eraseToAnyPublisher()
}

/// Publisher that sends authentication requests
///
/// Wallet should subscribe on events in order to receive auth requests.
public var authRequestPublisher: AnyPublisher<AuthRequest, Never> {
authClient.authRequestPublisher.eraseToAnyPublisher()
}

// MARK: - Private Properties
private let authClient: AuthClientProtocol
private let signClient: SignClientProtocol
private let pairingClient: PairingClientProtocol

private var account: Account?

init(
authClient: AuthClientProtocol,
signClient: SignClientProtocol,
pairingClient: PairingClientProtocol
) {
self.authClient = authClient
self.signClient = signClient
self.pairingClient = pairingClient
}

/// For a wallet to approve a session proposal.
/// - Parameters:
/// - proposalId: Session Proposal id
/// - namespaces: namespaces for given session, needs to contain at least required namespaces proposed by dApp.
public func approve(proposalId: String, namespaces: [String: SessionNamespace]) async throws {
try await signClient.approve(proposalId: proposalId, namespaces: namespaces)
}

/// For the wallet to reject a session proposal.
/// - Parameters:
/// - proposalId: Session Proposal id
/// - reason: Reason why the session proposal has been rejected. Conforms to CAIP25.
public func reject(proposalId: String, reason: RejectionReason) async throws {
try await signClient.reject(proposalId: proposalId, reason: reason)
}

/// For the wallet to update session namespaces
/// - Parameters:
/// - topic: Topic of the session that is intended to be updated.
/// - namespaces: Dictionary of namespaces that will replace existing ones.
public func update(topic: String, namespaces: [String: SessionNamespace]) async throws {
try await signClient.update(topic: topic, namespaces: namespaces)
}

/// For wallet to extend a session to 7 days
/// - Parameters:
/// - topic: Topic of the session that is intended to be extended.
public func extend(topic: String) async throws {
try await signClient.extend(topic: topic)
}

/// For the wallet to respond on pending dApp's JSON-RPC request
/// - Parameters:
/// - topic: Topic of the session for which the request was received.
/// - requestId: RPC request ID
/// - response: Your JSON RPC response or an error.
public func respond(topic: String, requestId: RPCID, response: RPCResult) async throws {
try await signClient.respond(topic: topic, requestId: requestId, response: response)
}

/// For the wallet to emit an event to a dApp
///
/// When a client wants to emit an event to its peer client (eg. chain changed or tx replaced)
///
/// Should Error:
/// - When the session topic is not found
/// - When the event params are invalid
/// - Parameters:
/// - topic: Session topic
/// - event: session event
/// - chainId: CAIP-2 chain
public func emit(topic: String, event: Session.Event, chainId: Blockchain) async throws {
try await signClient.emit(topic: topic, event: event, chainId: chainId)
}

/// 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
public func pair(uri: WalletConnectURI) async throws {
try await pairingClient.pair(uri: uri)
}

/// For a wallet and a dApp to terminate a session
///
/// Should Error:
/// - When the session topic is not found
/// - Parameters:
/// - topic: Session topic that you want to delete
public func disconnect(topic: String) async throws {
try await signClient.disconnect(topic: topic)
}


/// Query sessions
/// - Returns: All sessions
public func getSessions() -> [Session] {
signClient.getSessions()
}

public func formatMessage(payload: AuthPayload, address: String) throws -> String {
try authClient.formatMessage(payload: payload, address: address)
}

/// For a wallet to respond on authentication request
/// - Parameters:
/// - requestId: authentication request id
/// - signature: CACAO signature of requested message
public func respond(requestId: RPCID, signature: CacaoSignature, from account: Account) async throws {
try await authClient.respond(requestId: requestId, signature: signature, from: account)
}

/// Query pending requests
/// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method
/// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions.
public func getPendingRequests(topic: String? = nil) -> [Request] {
signClient.getPendingRequests(topic: topic)
}

/// - Parameter id: id of a wc_sessionRequest jsonrpc request
/// - Returns: json rpc record object for given id or nil if record for give id does not exits
public func getSessionRequestRecord(id: RPCID) -> Request? {
signClient.getSessionRequestRecord(id: id)
}

/// Query pending authentication requests
/// - Returns: Pending authentication requests
public func getPendingRequests(account: Account) throws -> [AuthRequest] {
try authClient.getPendingRequests(account: account)
}
}
18 changes: 18 additions & 0 deletions Sources/Web3Wallet/Web3WalletClientFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

import Auth
import WalletConnectSign

public struct Web3WalletClientFactory {
public static func create(
authClient: AuthClientProtocol,
signClient: SignClientProtocol,
pairingClient: PairingClientProtocol
) -> Web3WalletClient {
return Web3WalletClient(
authClient: authClient,
signClient: signClient,
pairingClient: pairingClient
)
}
}
9 changes: 9 additions & 0 deletions Sources/Web3Wallet/Web3WalletConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

import Auth

extension Web3Wallet {
struct Config {
let signerFactory: SignerFactory
}
}
3 changes: 3 additions & 0 deletions Sources/Web3Wallet/Web3WalletImports.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#if !CocoaPods
@_exported import WalletConnectPairing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also need to remove all packages imports from all files

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cocoapods putting all sources in single framework, so it doesn't know what is Auth, WalletConnectSign etc

Need to remove all

import WalletConnectSign
import Auth

And imports should be

@_exported import Auth
@_exported import WalletConnectSign

Agree it looks like a little wired but supporting both SPM and Cocoapods is a kind of pain

#endif
Loading