diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 8c95927cb..3f1b2ae5e 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -116,6 +116,25 @@ final class SignPresenter: ObservableObject { } } + @MainActor + func connectWalletWithSessionProposeLinkMode() { + Task { + do { + ActivityIndicatorManager.shared.start() + let _ = try await Sign.instance.connectLinkMode( + requiredNamespaces: Proposal.requiredNamespaces, + optionalNamespaces: Proposal.optionalNamespaces, + walletUniversalLink: "https://lab.web3modal.com/wallet" + ) + ActivityIndicatorManager.shared.stop() +// router.presentNewPairing(walletConnectUri: walletConnectUri!) + } catch { + AlertPresenter.present(message: error.localizedDescription, type: .error) + ActivityIndicatorManager.shared.stop() + } + } + } + @MainActor func openConfiguration() { router.openConfig() diff --git a/Example/DApp/Modules/Sign/SignView.swift b/Example/DApp/Modules/Sign/SignView.swift index c87e890c5..35cc52071 100644 --- a/Example/DApp/Modules/Sign/SignView.swift +++ b/Example/DApp/Modules/Sign/SignView.swift @@ -79,6 +79,18 @@ struct SignView: View { .background(Color(red: 95/255, green: 159/255, blue: 248/255)) .cornerRadius(16) } + + Button { + presenter.connectWalletWithSessionProposeLinkMode() + } label: { + Text("Session Propose Link Mode") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color(red: 95/255, green: 159/255, blue: 248/255)) + .cornerRadius(16) + } } .padding(.top, 10) } diff --git a/Sources/WalletConnectSign/Auth/Link/LinkAuthRequestSubscriber.swift b/Sources/WalletConnectSign/Auth/Link/LinkAuthRequestSubscriber.swift index 99000ef64..1ea7862aa 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkAuthRequestSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkAuthRequestSubscriber.swift @@ -61,3 +61,58 @@ class LinkAuthRequestSubscriber { } } + +class LinkModeSessionProposalSubscriber { + private let logger: ConsoleLogging + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + private let envelopesDispatcher: LinkEnvelopesDispatcher + private let verifyClient: VerifyClientProtocol + private let verifyContextStore: CodableStore + + + var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)? + + init( + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol, + envelopesDispatcher: LinkEnvelopesDispatcher, + verifyClient: VerifyClientProtocol, + verifyContextStore: CodableStore + ) { + self.logger = logger + self.kms = kms + self.envelopesDispatcher = envelopesDispatcher + self.verifyClient = verifyClient + self.verifyContextStore = verifyContextStore + + + subscribeForSessionProposal() + } + + private func subscribeForSessionProposal() { + envelopesDispatcher + .requestSubscription(on: SessionProposeProtocolMethod.responseApprove().method) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + + + let proposal = payload.request.publicRepresentation(pairingTopic: payload.topic) + let metadata = payload.request.proposer.metadata + + guard let redirect = metadata.redirect, + let universalLink = redirect.universal else { + logger.warn("redirect property not present") + return + } + + let verifyContext = verifyClient.createVerifyContextForLinkMode( + redirectUniversalLink: universalLink, + domain: metadata.url + ) + + onSessionProposal?(proposal, verifyContext) + }.store(in: &publishers) + } +} + + diff --git a/Sources/WalletConnectSign/Services/App/AppProposeService.swift b/Sources/WalletConnectSign/Services/App/AppProposeService.swift index 94ffcbe60..b3bc221fe 100644 --- a/Sources/WalletConnectSign/Services/App/AppProposeService.swift +++ b/Sources/WalletConnectSign/Services/App/AppProposeService.swift @@ -53,3 +53,59 @@ final class AppProposeService { try await networkingInteractor.request(request, topic: pairingTopic, protocolMethod: protocolMethod) } } + + +final class LinkAppProposeService { + private let metadata: AppMetadata + private let kms: KeyManagementServiceProtocol + let linkEnvelopesDispatcher: LinkEnvelopesDispatcher + + private let logger: ConsoleLogging + + init( + metadata: AppMetadata, + linkEnvelopesDispatcher: LinkEnvelopesDispatcher, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging + ) { + self.metadata = metadata + self.linkEnvelopesDispatcher = linkEnvelopesDispatcher + self.kms = kms + self.logger = logger + } + + func propose( + namespaces: [String: ProposalNamespace], + optionalNamespaces: [String: ProposalNamespace]? = nil, + sessionProperties: [String: String]? = nil, + scopedProperties: [String: String]? = nil, + walletUniversalLink: String + ) async throws -> String { + try Namespace.validate(namespaces) + if let optionalNamespaces { + try Namespace.validate(optionalNamespaces) + } + if let sessionProperties { + try SessionProperties.validate(sessionProperties) + } + let protocolMethod = SessionProposeProtocolMethod.responseApprove() + let publicKey = try! kms.createX25519KeyPair() + let proposer = Participant( + publicKey: publicKey.hexRepresentation, + metadata: metadata) + + let proposal = SessionProposal( + relays: [], + proposer: proposer, + requiredNamespaces: namespaces, + optionalNamespaces: optionalNamespaces ?? [:], + sessionProperties: sessionProperties, + scopedProperties: scopedProperties + ) + + let request = RPCRequest(method: protocolMethod.method, params: proposal) + let envelope = try await linkEnvelopesDispatcher.request(topic: UUID().uuidString,request: request, peerUniversalLink: walletUniversalLink, envelopeType: .type2) + + return envelope + } +} diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 734173c48..a0aed0fa8 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -193,6 +193,7 @@ public final class SignClient: SignClientProtocol { private let sessionResponderDispatcher: SessionResponderDispatcher private let linkSessionRequestResponseSubscriber: LinkSessionRequestResponseSubscriber private let messageVerifier: MessageVerifier + private let linkModeSessionProposalSubscriber: LinkModeSessionProposalSubscriber private var publishers = Set() @@ -231,7 +232,9 @@ public final class SignClient: SignClientProtocol { sessionResponderDispatcher: SessionResponderDispatcher, linkSessionRequestResponseSubscriber: LinkSessionRequestResponseSubscriber, authenticateTransportTypeSwitcher: AuthenticateTransportTypeSwitcher, - messageVerifier: MessageVerifier + messageVerifier: MessageVerifier, + linkAppProposeService: LinkAppProposeService, + linkModeSessionProposalSubscriber: LinkModeSessionProposalSubscriber ) { self.logger = logger self.networkingClient = networkingClient @@ -267,6 +270,8 @@ public final class SignClient: SignClientProtocol { self.linkSessionRequestResponseSubscriber = linkSessionRequestResponseSubscriber self.authenticateTransportTypeSwitcher = authenticateTransportTypeSwitcher self.messageVerifier = messageVerifier + self.linkAppProposeService = linkAppProposeService + self.linkModeSessionProposalSubscriber = linkModeSessionProposalSubscriber setUpConnectionObserving() setUpEnginesCallbacks() @@ -308,6 +313,26 @@ public final class SignClient: SignClientProtocol { return try await authenticateTransportTypeSwitcher.authenticate(params, walletUniversalLink: walletUniversalLink) } + let linkAppProposeService: LinkAppProposeService +#if DEBUG + public func connectLinkMode( + requiredNamespaces: [String: ProposalNamespace], + optionalNamespaces: [String: ProposalNamespace]? = nil, + sessionProperties: [String: String]? = nil, + scopedProperties: [String: String]? = nil, + walletUniversalLink: String + ) async throws -> String { + logger.debug("Connecting Application") + + return try await linkAppProposeService.propose( + namespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: sessionProperties, + scopedProperties: scopedProperties, + walletUniversalLink: walletUniversalLink + ) + } +#endif #if DEBUG @discardableResult public func authenticateLinkMode( @@ -523,6 +548,9 @@ public final class SignClient: SignClientProtocol { approveEngine.onSessionProposal = { [unowned self] (proposal, context) in sessionProposalPublisherSubject.send((proposal, context)) } + linkModeSessionProposalSubscriber.onSessionProposal = { [unowned self] (proposal, context) in + sessionProposalPublisherSubject.send((proposal, context)) + } approveEngine.onSessionRejected = { [unowned self] proposal, reason in sessionRejectionPublisherSubject.send((proposal, reason)) } diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 4f2d8ecad..fd18a7d6e 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -146,6 +146,10 @@ public struct SignClientFactory { let linkSessionRequestResponseSubscriber = LinkSessionRequestResponseSubscriber(envelopesDispatcher: linkEnvelopesDispatcher, eventsClient: eventsClient) let authenticateTransportTypeSwitcher = AuthenticateTransportTypeSwitcher(linkAuthRequester: linkAuthRequester, pairingClient: pairingClient, logger: logger, appRequestService: appRequestService, appProposeService: appProposerService) + + let linkAppProposeService = LinkAppProposeService(metadata: metadata, linkEnvelopesDispatcher: linkEnvelopesDispatcher, kms: kms, logger: logger) + + let linkModeSessionProposalSubscriber = LinkModeSessionProposalSubscriber(logger: logger, kms: kms, envelopesDispatcher: linkEnvelopesDispatcher, verifyClient: verifyClient, verifyContextStore: verifyContextStore) let client = SignClient( logger: logger, @@ -181,7 +185,9 @@ public struct SignClientFactory { sessionResponderDispatcher: sessionResponderDispatcher, linkSessionRequestResponseSubscriber: linkSessionRequestResponseSubscriber, authenticateTransportTypeSwitcher: authenticateTransportTypeSwitcher, - messageVerifier: signatureVerifier + messageVerifier: signatureVerifier, + linkAppProposeService: linkAppProposeService, + linkModeSessionProposalSubscriber: linkModeSessionProposalSubscriber ) return client }