diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index c9da83f45..b51a09e9f 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -16,6 +16,7 @@ on: jobs: build: runs-on: macos-12 + timeout-minutes: 15 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 532bc731b..fd3aa5788 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: test: needs: prepare runs-on: macos-12 + timeout-minutes: 15 strategy: fail-fast: false matrix: diff --git a/Example/DApp/Auth/AuthView.swift b/Example/DApp/Auth/AuthView.swift index 5acdaf08c..d3e699ed3 100644 --- a/Example/DApp/Auth/AuthView.swift +++ b/Example/DApp/Auth/AuthView.swift @@ -3,37 +3,39 @@ import SwiftUI struct AuthView: View { @ObservedObject var viewModel: AuthViewModel - + var body: some View { VStack(spacing: 16.0) { - + Spacer() - + Image(uiImage: viewModel.qrImage ?? UIImage()) .interpolation(.none) .resizable() .frame(width: 300, height: 300) - + signingLabel() .frame(maxWidth: .infinity) - + Spacer() - + Button("Connect Wallet", action: { }) .buttonStyle(CircleButtonStyle()) - + Button("Copy URI", action: { viewModel.copyDidPressed() }) .buttonStyle(CircleButtonStyle()) - + Button("Deeplink", action: { viewModel.deeplinkPressed() }) .buttonStyle(CircleButtonStyle()) + + } .padding(16.0) .onAppear { Task(priority: .userInitiated) { try await viewModel.setupInitialState() }} } - + @ViewBuilder private func signingLabel() -> some View { switch viewModel.state { diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 0907a126a..98b7c6e9e 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -18,6 +18,10 @@ 84474A0129B9EB74005F520B /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 84474A0029B9EB74005F520B /* Starscream */; }; 84474A0229B9ECA2005F520B /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 8448F1D327E4726F0000B866 /* WalletConnect */; }; + 84536D6E29EEAE1F008EA8DB /* Web3InboxModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84536D6D29EEAE1F008EA8DB /* Web3InboxModule.swift */; }; + 84536D7029EEAE28008EA8DB /* Web3InboxRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84536D6F29EEAE28008EA8DB /* Web3InboxRouter.swift */; }; + 84536D7229EEAE32008EA8DB /* Web3InboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84536D7129EEAE32008EA8DB /* Web3InboxViewController.swift */; }; + 84536D7429EEBCF0008EA8DB /* Web3Inbox in Frameworks */ = {isa = PBXBuildFile; productRef = 84536D7329EEBCF0008EA8DB /* Web3Inbox */; }; 845B8D8C2934B36C0084A966 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845B8D8B2934B36C0084A966 /* Account.swift */; }; 847BD1D62989492500076C90 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D12989492500076C90 /* MainViewController.swift */; }; 847BD1D82989492500076C90 /* MainModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1D32989492500076C90 /* MainModule.swift */; }; @@ -272,6 +276,7 @@ CF1A594D29E5876600AAC16B /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF1A594429E5876600AAC16B /* App.swift */; }; CF6704DF29E59DDC003326A4 /* XCUIElementQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704DE29E59DDC003326A4 /* XCUIElementQuery.swift */; }; CF6704E129E5A014003326A4 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6704E029E5A014003326A4 /* XCTestCase.swift */; }; + CF9C7E4A2A01802F0037C006 /* Web3Modal in Frameworks */ = {isa = PBXBuildFile; productRef = CF9C7E492A01802F0037C006 /* Web3Modal */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -338,6 +343,9 @@ 8439CB88293F658E00F2F2E2 /* PushMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessage.swift; sourceTree = ""; }; 844749F329B9E5B9005F520B /* RelayIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RelayIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 844749F529B9E5B9005F520B /* RelayClientEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayClientEndToEndTests.swift; sourceTree = ""; }; + 84536D6D29EEAE1F008EA8DB /* Web3InboxModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3InboxModule.swift; sourceTree = ""; }; + 84536D6F29EEAE28008EA8DB /* Web3InboxRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3InboxRouter.swift; sourceTree = ""; }; + 84536D7129EEAE32008EA8DB /* Web3InboxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3InboxViewController.swift; sourceTree = ""; }; 845AA7D929BA1EBA00F33739 /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = IntegrationTests.xctestplan; path = ExampleApp.xcodeproj/IntegrationTests.xctestplan; sourceTree = ""; }; 845AA7DC29BB424800F33739 /* SmokeTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SmokeTests.xctestplan; sourceTree = ""; }; 845B8D8B2934B36C0084A966 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; @@ -611,6 +619,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CF9C7E4A2A01802F0037C006 /* Web3Modal in Frameworks */, A58EC618299D665A00F3452A /* Web3Inbox in Frameworks */, A5629AEA2877F2D600094373 /* WalletConnectChat in Frameworks */, A59FAEC928B7B93A002BB66F /* Web3 in Frameworks */, @@ -647,6 +656,7 @@ files = ( C56EE27D293F56F8004840D1 /* WalletConnectChat in Frameworks */, C5133A78294125CC00A8314C /* Web3 in Frameworks */, + 84536D7429EEBCF0008EA8DB /* Web3Inbox in Frameworks */, C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */, C55D349929630D440004314A /* Web3Wallet in Frameworks */, 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */, @@ -730,6 +740,16 @@ path = RelayIntegrationTests; sourceTree = ""; }; + 84536D6C29EEAE0D008EA8DB /* Web3Inbox */ = { + isa = PBXGroup; + children = ( + 84536D6D29EEAE1F008EA8DB /* Web3InboxModule.swift */, + 84536D6F29EEAE28008EA8DB /* Web3InboxRouter.swift */, + 84536D7129EEAE32008EA8DB /* Web3InboxViewController.swift */, + ); + path = Web3Inbox; + sourceTree = ""; + }; 847BD1DB2989493F00076C90 /* Main */ = { isa = PBXGroup; children = ( @@ -1439,6 +1459,7 @@ C56EE229293F5668004840D1 /* Wallet */ = { isa = PBXGroup; children = ( + 84536D6C29EEAE0D008EA8DB /* Web3Inbox */, 847BD1DB2989493F00076C90 /* Main */, C55D3477295DD4AA0004314A /* Welcome */, C55D3474295DCB850004314A /* AuthRequest */, @@ -1751,6 +1772,7 @@ A59FAEC828B7B93A002BB66F /* Web3 */, A58EC610299D57B800F3452A /* AsyncButton */, A58EC617299D665A00F3452A /* Web3Inbox */, + CF9C7E492A01802F0037C006 /* Web3Modal */, ); productName = Showcase; productReference = A58E7CE828729F550082D443 /* Showcase.app */; @@ -1823,6 +1845,7 @@ C55D349829630D440004314A /* Web3Wallet */, C5B2F7042970573D000DBA0E /* SolanaSwift */, 84E6B85329787AAE00428BAF /* WalletConnectPush */, + 84536D7329EEBCF0008EA8DB /* Web3Inbox */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -2165,6 +2188,8 @@ C55D34B12965FB750004314A /* SessionProposalInteractor.swift in Sources */, C56EE247293F566D004840D1 /* ScanModule.swift in Sources */, C56EE28D293F5757004840D1 /* AppearanceConfigurator.swift in Sources */, + 84536D6E29EEAE1F008EA8DB /* Web3InboxModule.swift in Sources */, + 84536D7029EEAE28008EA8DB /* Web3InboxRouter.swift in Sources */, 847BD1D82989492500076C90 /* MainModule.swift in Sources */, 847BD1E7298A806800076C90 /* NotificationsInteractor.swift in Sources */, C56EE241293F566D004840D1 /* WalletModule.swift in Sources */, @@ -2192,6 +2217,7 @@ C55D347F295DD7140004314A /* AuthRequestModule.swift in Sources */, C56EE242293F566D004840D1 /* ScanPresenter.swift in Sources */, C56EE28B293F5757004840D1 /* SceneDelegate.swift in Sources */, + 84536D7229EEAE32008EA8DB /* Web3InboxViewController.swift in Sources */, C56EE276293F56D7004840D1 /* UIViewController.swift in Sources */, C56EE275293F56D7004840D1 /* InputConfig.swift in Sources */, C55D3493295DFA750004314A /* WelcomeModule.swift in Sources */, @@ -2369,7 +2395,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2427,7 +2453,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = NO; @@ -2494,7 +2520,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2528,7 +2554,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3001,6 +3027,10 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnect; }; + 84536D7329EEBCF0008EA8DB /* Web3Inbox */ = { + isa = XCSwiftPackageProductDependency; + productName = Web3Inbox; + }; 847CF3AE28E3141700F1D760 /* WalletConnectPush */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectPush; @@ -3111,6 +3141,10 @@ isa = XCSwiftPackageProductDependency; productName = Web3Wallet; }; + CF9C7E492A01802F0037C006 /* Web3Modal */ = { + isa = XCSwiftPackageProductDependency; + productName = Web3Modal; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 764E1D3426F8D3FC00A1FB15 /* Project object */; diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index ac89d4cc7..6fda1d84a 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -7,6 +7,7 @@ import Combine @testable import Auth import WalletConnectPairing import WalletConnectNetworking +import WalletConnectVerify final class AuthTests: XCTestCase { var appPairingClient: PairingClient! @@ -86,9 +87,9 @@ final class AuthTests: XCTestCase { Task(priority: .high) { let signerFactory = DefaultSignerFactory() let signer = MessageSignerFactory(signerFactory: signerFactory).create(projectId: InputConfig.projectId) - let payload = try! request.payload.cacaoPayload(address: walletAccount.address) + let payload = try! request.0.payload.cacaoPayload(address: walletAccount.address) let signature = try! signer.sign(payload: payload, privateKey: prvKey, type: .eip191) - try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: walletAccount) + try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: walletAccount) } } .store(in: &publishers) @@ -123,7 +124,7 @@ final class AuthTests: XCTestCase { walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) - try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: account) + try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: account) } } .store(in: &publishers) @@ -144,7 +145,7 @@ final class AuthTests: XCTestCase { walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) - try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: walletAccount) + try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: walletAccount) } } .store(in: &publishers) @@ -164,7 +165,7 @@ final class AuthTests: XCTestCase { try! await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { - try! await walletAuthClient.reject(requestId: request.id) + try! await walletAuthClient.reject(requestId: request.0.id) } } .store(in: &publishers) @@ -187,7 +188,7 @@ final class AuthTests: XCTestCase { Task(priority: .high) { let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" let cacaoSignature = CacaoSignature(t: .eip191, s: invalidSignature) - try! await walletAuthClient.respond(requestId: request.id, signature: cacaoSignature, from: walletAccount) + try! await walletAuthClient.respond(requestId: request.0.id, signature: cacaoSignature, from: walletAccount) } } .store(in: &publishers) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index dc83c0042..3be34d4f2 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -237,7 +237,50 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } - private func sign(_ message: String) -> SigningResult { + // Push Subscribe + func testWalletCreatesSubscription() async { + let expectation = expectation(description: "expects to create push subscription") + let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) + try! await walletPushClient.subscribe(metadata: metadata, account: Account.stub(), onSign: sign) + walletPushClient.subscriptionsPublisher + .first() + .sink { [unowned self] subscriptions in + XCTAssertNotNil(subscriptions.first) + Task { try! await walletPushClient.deleteSubscription(topic: subscriptions.first!.topic) } + expectation.fulfill() + }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + } + + func testWalletCreatesAndUpdatesSubscription() async { + let expectation = expectation(description: "expects to create and update push subscription") + let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) + let updateScope: Set = [NotificationScope.alerts] + try! await walletPushClient.subscribe(metadata: metadata, account: Account.stub(), onSign: sign) + walletPushClient.subscriptionsPublisher + .first() + .sink { [unowned self] subscriptions in + Task { try! await walletPushClient.update(topic: subscriptions.first!.topic, scope: updateScope) } + } + .store(in: &publishers) + + walletPushClient.updateSubscriptionPublisher + .sink { [unowned self] result in + guard case .success(let subscription) = result else { XCTFail(); return } + let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) + XCTAssertEqual(updatedScope, updateScope) + Task { try! await walletPushClient.deleteSubscription(topic: subscription.topic) } + expectation.fulfill() + }.store(in: &publishers) + + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + } + +} + + +private extension PushTests { + func sign(_ message: String) -> SigningResult { let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a") let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191)) diff --git a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift index e299d0316..e812661e2 100644 --- a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift +++ b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift @@ -33,12 +33,12 @@ class ClientDelegate { self.onConnected?() }.store(in: &publishers) - client.sessionProposalPublisher.sink { proposal in - self.onSessionProposal?(proposal) + client.sessionProposalPublisher.sink { result in + self.onSessionProposal?(result.proposal) }.store(in: &publishers) - client.sessionRequestPublisher.sink { request in - self.onSessionRequest?(request) + client.sessionRequestPublisher.sink { result in + self.onSessionRequest?(result.request) }.store(in: &publishers) client.sessionResponsePublisher.sink { response in diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index bc71628cf..583cd886a 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -164,7 +164,7 @@ final class SignClientTests: XCTestCase { let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)! try await wallet.client.pair(uri: uri) - wait(for: [expectation], timeout: .infinity) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) } func testSessionRequest() async throws { diff --git a/Example/IntegrationTests/Stubs/PushMessage.swift b/Example/IntegrationTests/Stubs/PushMessage.swift index db9f5d870..18bc68107 100644 --- a/Example/IntegrationTests/Stubs/PushMessage.swift +++ b/Example/IntegrationTests/Stubs/PushMessage.swift @@ -3,6 +3,6 @@ import WalletConnectPush extension PushMessage { static func stub() -> PushMessage { - return PushMessage(title: "test_push_message", body: "", icon: "", url: "") + return PushMessage(title: "test_push_message", body: "", icon: "", url: "", type: "") } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift index 85f613e26..bd2d8407f 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift @@ -4,7 +4,7 @@ import WalletConnectChat final class ChatListRouter { weak var viewController: UIViewController! - + private let app: Application init(app: Application) { @@ -33,3 +33,4 @@ final class ChatListRouter { WelcomeModule.create(app: app).present() } } + diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift index b31c33fb0..5e1111b53 100644 --- a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift +++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift @@ -18,7 +18,7 @@ final class Web3InboxViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - Web3Inbox.configure(account: importAccount.account, onSign: onSing) + Web3Inbox.configure(account: importAccount.account, config: [.pushEnabled: false], onSign: onSing, environment: .sandbox) edgesForExtendedLayout = [] navigationItem.title = "Web3Inbox SDK" diff --git a/Example/SmokeTests.xctestplan b/Example/SmokeTests.xctestplan index fc3ee53a5..34685f639 100644 --- a/Example/SmokeTests.xctestplan +++ b/Example/SmokeTests.xctestplan @@ -42,13 +42,16 @@ "ChatTests\/testInvite()", "EIP1271VerifierTests", "EIP191VerifierTests", + "EIP55Tests", "ENSResolverTests", "PairingTests", "PushTests\/testDappDeletePushSubscription()", "PushTests\/testRequestPush()", "PushTests\/testWalletApprovesPushRequest()", + "PushTests\/testWalletCreatesSubscription()", "PushTests\/testWalletDeletePushSubscription()", "PushTests\/testWalletRejectsPushRequest()", + "PushTests\/testWalletUpdatesSubscription()", "RegistryTests", "RelayClientEndToEndTests", "SignClientTests\/testCaip25SatisfyAllRequiredAllOptionalNamespacesSuccessful()", diff --git a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift index dec54cc78..58064d376 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift @@ -1,6 +1,6 @@ import WalletConnectNetworking import Web3Wallet -import WalletConnectPush +import Web3Inbox struct ThirdPartyConfigurator: Configurator { @@ -11,13 +11,25 @@ struct ThirdPartyConfigurator: Configurator { name: "Example Wallet", description: "wallet description", url: "example.wallet", - icons: ["https://avatars.githubusercontent.com/u/37784886"] + icons: ["https://avatars.githubusercontent.com/u/37784886"], + verifyUrl: "verify.walletconnect.com" ) Web3Wallet.configure(metadata: metadata, crypto: DefaultCryptoProvider(), environment: BuildConfiguration.shared.apnsEnvironment) - Push.configure(environment: BuildConfiguration.shared.apnsEnvironment) + + let account = Account(blockchain: Blockchain("eip155:1")!, address: EthKeyStore.shared.address)! + + Web3Inbox.configure(account: account, config: [.chatEnabled: false, .settingsEnabled: false], onSign: Web3InboxSigner.onSing, environment: BuildConfiguration.shared.apnsEnvironment) } } +class Web3InboxSigner { + static func onSing(_ message: String) -> SigningResult { + let privateKey = EthKeyStore.shared.privateKeyRaw + let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() + let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) + return .signed(signature) + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestModule.swift index 484e7d354..f5bba4ea4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestModule.swift @@ -1,12 +1,13 @@ import SwiftUI + import Web3Wallet final class AuthRequestModule { @discardableResult - static func create(app: Application, request: AuthRequest) -> UIViewController { + static func create(app: Application, request: AuthRequest, context: VerifyContext?) -> UIViewController { let router = AuthRequestRouter(app: app) let interactor = AuthRequestInteractor() - let presenter = AuthRequestPresenter(interactor: interactor, router: router, request: request) + let presenter = AuthRequestPresenter(interactor: interactor, router: router, request: request, context: context) let view = AuthRequestView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift index 56d11f511..257787ee4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift @@ -8,6 +8,7 @@ final class AuthRequestPresenter: ObservableObject { private let router: AuthRequestRouter let request: AuthRequest + let verified: Bool? var message: String { return interactor.formatted(request: request) @@ -18,12 +19,14 @@ final class AuthRequestPresenter: ObservableObject { init( interactor: AuthRequestInteractor, router: AuthRequestRouter, - request: AuthRequest + request: AuthRequest, + context: VerifyContext? ) { defer { setupInitialState() } self.interactor = interactor self.router = router self.request = request + self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift index 00086e83b..067a6b00e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift @@ -17,10 +17,28 @@ struct AuthRequestView: View { .resizable() .scaledToFit() - Text(presenter.request.payload.domain) - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) - .padding(.top, 10) + HStack { + Text(presenter.request.payload.domain) + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + + if let verified = presenter.verified { + if verified { + Image(systemName: "checkmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .green) + } else { + Image(systemName: "xmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .red) + } + } else { + Image(systemName: "exclamationmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .orange) + } + } + .padding(.top, 10) Text("would like to connect") .foregroundColor(.grey8) diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsInteractor.swift index c9fe17a65..27e2cf3d1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsInteractor.swift @@ -3,7 +3,7 @@ import Combine import Web3Wallet final class ConnectionDetailsInteractor { - var requestPublisher: AnyPublisher { + var requestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { return Web3Wallet.instance.authRequestPublisher } diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsRouter.swift index 4dcf67cd1..fd20f8d21 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsRouter.swift @@ -1,4 +1,5 @@ import UIKit + import Web3Wallet final class ConnectionDetailsRouter { @@ -10,8 +11,8 @@ final class ConnectionDetailsRouter { self.app = app } - func present(request: AuthRequest) { - AuthRequestModule.create(app: app, request: request) + func present(request: AuthRequest, context: VerifyContext) { + AuthRequestModule.create(app: app, request: request, context: context) .wrapToNavigationController() .present(from: viewController) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift index 7487db34c..a736d11d1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift @@ -1,11 +1,12 @@ +import Foundation import Combine + import Web3Wallet import WalletConnectPush -import Foundation final class MainInteractor { - var sessionProposalPublisher: AnyPublisher { + var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> { return Web3Wallet.instance.sessionProposalPublisher } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index d33cd6bf3..ee968adb0 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -14,7 +14,7 @@ final class MainPresenter { var viewControllers: [UIViewController] { return [ router.walletViewController(), - router.notificationsViewController(), + router.web3InboxViewController() ] } @@ -34,13 +34,14 @@ extension MainPresenter { interactor.pushRequestPublisher .receive(on: DispatchQueue.main) .sink { [weak self] request in - self?.router.present(pushRequest: request) + +// self?.router.present(pushRequest: request) }.store(in: &disposeBag) interactor.sessionProposalPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] proposal in - self?.router.present(proposal: proposal) + .sink { [weak self] session in + self?.router.present(proposal: session.proposal, context: session.context) } .store(in: &disposeBag) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index 75d71e842..1bd145592 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -1,6 +1,8 @@ import UIKit -import WalletConnectPush + import Web3Wallet +import WalletConnectPush + final class MainRouter { weak var viewController: UIViewController! @@ -11,9 +13,9 @@ final class MainRouter { return WalletModule.create(app: app) .wrapToNavigationController() } - - func present(proposal: Session.Proposal) { - SessionProposalModule.create(app: app, proposal: proposal) + + func present(proposal: Session.Proposal, context: VerifyContext?) { + SessionProposalModule.create(app: app, proposal: proposal, context: context) .presentFullScreen(from: viewController, transparentBackground: true) } @@ -22,6 +24,11 @@ final class MainRouter { .wrapToNavigationController() } + func web3InboxViewController() -> UIViewController { + return Web3InboxModule.create(app: app) + .wrapToNavigationController() + } + func present(pushRequest: PushRequest) { PushRequestModule.create(app: app, pushRequest: pushRequest) .presentFullScreen(from: viewController, transparentBackground: true) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift index 0416f515c..d195c90ae 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift @@ -2,14 +2,17 @@ import UIKit enum TabPage: CaseIterable { case wallet - case notifications +// case notifications + case web3Inbox var title: String { switch self { case .wallet: return "Apps" - case .notifications: - return "Notifications" +// case .notifications: +// return "Notifications" + case .web3Inbox: + return "w3i" } } @@ -17,7 +20,9 @@ enum TabPage: CaseIterable { switch self { case .wallet: return UIImage(systemName: "house.fill")! - case .notifications: +// case .notifications: +// return UIImage(systemName: "bell.fill")! + case .web3Inbox: return UIImage(systemName: "bell.fill")! } } @@ -27,6 +32,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.wallet, .notifications] + return [.wallet, .web3Inbox] } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift index 987c7920c..2afda2ff4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift @@ -3,17 +3,10 @@ import WalletConnectPush final class PushRequestInteractor { func approve(pushRequest: PushRequest) async throws { - try await Push.wallet.approve(id: pushRequest.id, onSign: onSing(_:)) + try await Push.wallet.approve(id: pushRequest.id, onSign: Web3InboxSigner.onSing) } func reject(pushRequest: PushRequest) async throws { try await Push.wallet.reject(id: pushRequest.id) } - - func onSing(_ message: String) async -> SigningResult { - let privateKey = EthKeyStore.shared.privateKeyRaw - let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() - let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) - return .signed(signature) - } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalModule.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalModule.swift index 4f0736d0a..b0edc7232 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalModule.swift @@ -4,13 +4,14 @@ import Web3Wallet final class SessionProposalModule { @discardableResult - static func create(app: Application, proposal: Session.Proposal) -> UIViewController { + static func create(app: Application, proposal: Session.Proposal, context: VerifyContext?) -> UIViewController { let router = SessionProposalRouter(app: app) let interactor = SessionProposalInteractor() let presenter = SessionProposalPresenter( interactor: interactor, router: router, - proposal: proposal + proposal: proposal, + context: context ) let view = SessionProposalView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 3c5eca87f..a13d932c2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -8,18 +8,21 @@ final class SessionProposalPresenter: ObservableObject { private let router: SessionProposalRouter let sessionProposal: Session.Proposal + let verified: Bool? private var disposeBag = Set() init( interactor: SessionProposalInteractor, router: SessionProposalRouter, - proposal: Session.Proposal + proposal: Session.Proposal, + context: VerifyContext? ) { defer { setupInitialState() } self.interactor = interactor self.router = router self.sessionProposal = proposal + self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index 9c69a7043..97717d9b5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -18,10 +18,29 @@ struct SessionProposalView: View { .resizable() .scaledToFit() - Text(presenter.sessionProposal.proposer.name) - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) - .padding(.top, 10) + HStack { + Text(presenter.sessionProposal.proposer.name) + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + + if let verified = presenter.verified { + if verified { + Image(systemName: "checkmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .green) + } else { + Image(systemName: "xmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .red) + } + } else { + Image(systemName: "exclamationmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .orange) + } + } + + .padding(.top, 10) Text("would like to connect") .foregroundColor(.grey8) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift index 7c1aca962..8a6b51fe2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestModule.swift @@ -1,12 +1,13 @@ import SwiftUI + import Web3Wallet final class SessionRequestModule { @discardableResult - static func create(app: Application, sessionRequest: Request) -> UIViewController { + static func create(app: Application, sessionRequest: Request, sessionContext: VerifyContext?) -> UIViewController { let router = SessionRequestRouter(app: app) let interactor = SessionRequestInteractor() - let presenter = SessionRequestPresenter(interactor: interactor, router: router, sessionRequest: sessionRequest) + let presenter = SessionRequestPresenter(interactor: interactor, router: router, sessionRequest: sessionRequest, context: sessionContext) let view = SessionRequestView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index 2402c0e29..910a5288e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -8,6 +8,7 @@ final class SessionRequestPresenter: ObservableObject { private let router: SessionRequestRouter let sessionRequest: Request + let verified: Bool? var message: String { return String(describing: sessionRequest.params.value) @@ -21,12 +22,14 @@ final class SessionRequestPresenter: ObservableObject { init( interactor: SessionRequestInteractor, router: SessionRequestRouter, - sessionRequest: Request + sessionRequest: Request, + context: VerifyContext? ) { defer { setupInitialState() } self.interactor = interactor self.router = router self.sessionRequest = sessionRequest + self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index e6a719606..dc56b2e24 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -17,10 +17,28 @@ struct SessionRequestView: View { .resizable() .scaledToFit() - Text(presenter.sessionRequest.method) - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) - .padding(.top, 10) + HStack { + Text(presenter.sessionRequest.method) + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + + if let verified = presenter.verified { + if verified { + Image(systemName: "checkmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .green) + } else { + Image(systemName: "xmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .red) + } + } else { + Image(systemName: "exclamationmark.shield.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .orange) + } + } + .padding(.top, 10) if presenter.message != "[:]" { authRequestView() diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift index e709edeae..d6eabca65 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift @@ -4,11 +4,11 @@ import Web3Wallet import WalletConnectPush final class WalletInteractor { - var requestPublisher: AnyPublisher { + var requestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { return Web3Wallet.instance.authRequestPublisher } - var sessionRequestPublisher: AnyPublisher { + var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { return Web3Wallet.instance.sessionRequestPublisher } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index b58d9cdae..930b3472e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -75,15 +75,15 @@ extension WalletPresenter { private func setupInitialState() { interactor.requestPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] request in - self?.router.present(request: request) + .sink { [weak self] result in + self?.router.present(request: result.request, context: result.context) } .store(in: &disposeBag) interactor.sessionRequestPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] sessionRequest in - self?.router.present(sessionRequest: sessionRequest) + .sink { [weak self] request, context in + self?.router.present(sessionRequest: request, sessionContext: context) }.store(in: &disposeBag) interactor.sessionsPublisher diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift index 2eb2fcfd4..9d6342d79 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift @@ -1,4 +1,5 @@ import UIKit + import Web3Wallet final class WalletRouter { @@ -10,13 +11,13 @@ final class WalletRouter { self.app = app } - func present(request: AuthRequest) { - AuthRequestModule.create(app: app, request: request) + func present(request: AuthRequest, context: VerifyContext?) { + AuthRequestModule.create(app: app, request: request, context: context) .presentFullScreen(from: viewController, transparentBackground: true) } - func present(sessionRequest: Request) { - SessionRequestModule.create(app: app, sessionRequest: sessionRequest) + func present(sessionRequest: Request, sessionContext: VerifyContext?) { + SessionRequestModule.create(app: app, sessionRequest: sessionRequest, sessionContext: sessionContext) .presentFullScreen(from: viewController, transparentBackground: true) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxModule.swift b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxModule.swift new file mode 100644 index 000000000..ad0633441 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxModule.swift @@ -0,0 +1,13 @@ +import SwiftUI + +final class Web3InboxModule { + + @discardableResult + static func create(app: Application) -> UIViewController { + let router = Web3InboxRouter(app: app) + let viewController = Web3InboxViewController() + router.viewController = viewController + return viewController + } + +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxRouter.swift new file mode 100644 index 000000000..3631c35be --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxRouter.swift @@ -0,0 +1,12 @@ +import UIKit + +final class Web3InboxRouter { + + weak var viewController: UIViewController! + + private let app: Application + + init(app: Application) { + self.app = app + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift new file mode 100644 index 000000000..e4043cca5 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift @@ -0,0 +1,27 @@ +import UIKit +import WebKit +import Web3Inbox + +final class Web3InboxViewController: UIViewController { + + + init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + edgesForExtendedLayout = [] + navigationItem.title = "Web3Inbox SDK" + navigationItem.largeTitleDisplayMode = .never + view = Web3Inbox.instance.getWebView() + } +} + + + diff --git a/Package.swift b/Package.swift index d65fe6fe8..72dc4fdcd 100644 --- a/Package.swift +++ b/Package.swift @@ -43,12 +43,18 @@ let package = Package( .library( name: "Web3Inbox", targets: ["Web3Inbox"]), + .library( + name: "Web3Modal", + targets: ["Web3Modal"]), + + ], + dependencies: [ + .package(url: "https://github.com/WalletConnect/QRCode", branch: "main") ], - dependencies: [], targets: [ .target( name: "WalletConnectSign", - dependencies: ["WalletConnectPairing"], + dependencies: ["WalletConnectPairing", "WalletConnectVerify"], path: "Sources/WalletConnectSign"), .target( name: "WalletConnectChat", @@ -60,11 +66,11 @@ let package = Package( path: "Sources/Auth"), .target( name: "Web3Wallet", - dependencies: ["Auth", "WalletConnectSign", "WalletConnectEcho"], + dependencies: ["Auth", "WalletConnectSign", "WalletConnectEcho", "WalletConnectVerify"], path: "Sources/Web3Wallet"), .target( name: "WalletConnectPush", - dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking", "WalletConnectIdentity"], + dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking", "WalletConnectIdentity", "WalletConnectSigner"], path: "Sources/WalletConnectPush"), .target( name: "WalletConnectEcho", @@ -84,7 +90,7 @@ let package = Package( dependencies: ["WalletConnectNetworking"]), .target( name: "Web3Inbox", - dependencies: ["WalletConnectChat"]), + dependencies: ["WalletConnectChat", "WalletConnectPush"]), .target( name: "WalletConnectSigner", dependencies: ["WalletConnectNetworking"]), @@ -111,10 +117,13 @@ let package = Package( dependencies: []), .target( name: "WalletConnectVerify", - dependencies: ["WalletConnectUtils"]), + dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]), + .target( + name: "Web3Modal", + dependencies: ["QRCode"]), .testTarget( name: "WalletConnectSignTests", - dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils"]), + dependencies: ["WalletConnectSign", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]), .testTarget( name: "Web3WalletTests", dependencies: ["Web3Wallet", "TestingUtils"]), @@ -126,13 +135,13 @@ let package = Package( dependencies: ["WalletConnectChat", "WalletConnectUtils", "TestingUtils"]), .testTarget( name: "AuthTests", - dependencies: ["Auth", "WalletConnectUtils", "TestingUtils"]), + dependencies: ["Auth", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]), .testTarget( name: "RelayerTests", dependencies: ["WalletConnectRelay", "WalletConnectUtils", "TestingUtils"]), .testTarget( name: "VerifyTests", - dependencies: ["WalletConnectVerify", "TestingUtils"]), + dependencies: ["WalletConnectVerify", "TestingUtils", "WalletConnectSign"]), .testTarget( name: "WalletConnectKMSTests", dependencies: ["WalletConnectKMS", "WalletConnectUtils", "TestingUtils"]), diff --git a/Sources/Auth/Auth.swift b/Sources/Auth/Auth.swift index c3261ad34..8240dff20 100644 --- a/Sources/Auth/Auth.swift +++ b/Sources/Auth/Auth.swift @@ -1,6 +1,9 @@ import Foundation import Combine +import WalletConnectVerify + +public typealias VerifyContext = WalletConnectVerify.VerifyContext /// Auth instatnce wrapper /// /// ```Swift diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 2ab5e242a..26f3797be 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -13,7 +13,7 @@ public class AuthClient: AuthClientProtocol { /// Publisher that sends authentication requests /// /// Wallet should subscribe on events in order to receive auth requests. - public var authRequestPublisher: AnyPublisher { + public var authRequestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { authRequestPublisherSubject.eraseToAnyPublisher() } @@ -37,7 +37,7 @@ public class AuthClient: AuthClientProtocol { private let pairingRegisterer: PairingRegisterer private var authResponsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>() - private var authRequestPublisherSubject = PassthroughSubject() + private var authRequestPublisherSubject = PassthroughSubject<(request: AuthRequest, context: VerifyContext?), Never>() private let appRequestService: AppRequestService private let appRespondSubscriber: AppRespondSubscriber private let walletRequestSubscriber: WalletRequestSubscriber diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index 51b973a37..3b4a430be 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -1,5 +1,7 @@ import Foundation +import WalletConnectVerify + public struct AuthClientFactory { public static func create( @@ -44,11 +46,12 @@ public struct AuthClientFactory { let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let messageFormatter = SIWECacaoFormatter() let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider) + let verifyClient = try? VerifyClientFactory.create(verifyHost: metadata.verifyUrl) let messageVerifierFactory = MessageVerifierFactory(crypto: crypto) let signatureVerifier = messageVerifierFactory.create(projectId: projectId) let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: signatureVerifier, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter) let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history) - let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer) + let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient) let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, walletErrorResponder: walletErrorResponder) let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history) diff --git a/Sources/Auth/AuthClientProtocol.swift b/Sources/Auth/AuthClientProtocol.swift index 31413cb76..d79048ca8 100644 --- a/Sources/Auth/AuthClientProtocol.swift +++ b/Sources/Auth/AuthClientProtocol.swift @@ -2,7 +2,7 @@ import Foundation import Combine public protocol AuthClientProtocol { - var authRequestPublisher: AnyPublisher { get } + var authRequestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { get } func formatMessage(payload: AuthPayload, address: String) throws -> String func respond(requestId: RPCID, signature: CacaoSignature, from account: Account) async throws diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index 67b541fd2..84640faf9 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -1,6 +1,8 @@ import Foundation import Combine +import WalletConnectVerify + class WalletRequestSubscriber { private let networkingInteractor: NetworkInteracting private let logger: ConsoleLogging @@ -8,34 +10,51 @@ class WalletRequestSubscriber { private var publishers = [AnyCancellable]() private let walletErrorResponder: WalletErrorResponder private let pairingRegisterer: PairingRegisterer - var onRequest: ((AuthRequest) -> Void)? - + private let verifyClient: VerifyClient? + var onRequest: (((request: AuthRequest, context: VerifyContext?)) -> Void)? + init( networkingInteractor: NetworkInteracting, logger: ConsoleLogging, kms: KeyManagementServiceProtocol, walletErrorResponder: WalletErrorResponder, - pairingRegisterer: PairingRegisterer) { + pairingRegisterer: PairingRegisterer, + verifyClient: VerifyClient? + ) { self.networkingInteractor = networkingInteractor self.logger = logger self.kms = kms self.walletErrorResponder = walletErrorResponder self.pairingRegisterer = pairingRegisterer + self.verifyClient = verifyClient subscribeForRequest() } - + private func subscribeForRequest() { pairingRegisterer.register(method: AuthRequestProtocolMethod()) .sink { [unowned self] (payload: RequestSubscriptionPayload) in logger.debug("WalletRequestSubscriber: Received request") - + pairingRegisterer.activate( pairingTopic: payload.topic, peerMetadata: payload.request.requester.metadata ) - + let request = AuthRequest(id: payload.id, payload: payload.request.payloadParams) - onRequest?(request) + + guard let verifyClient else { + onRequest?((request, nil)) + return + } + Task(priority: .high) { + let assertionId = payload.decryptedPayload.sha256().toHexString() + let origin = try? await verifyClient.verifyOrigin(assertionId: assertionId) + let verifyContext = await verifyClient.createVerifyContext( + origin: origin, + domain: payload.request.payloadParams.domain + ) + onRequest?((request, verifyContext)) + } }.store(in: &publishers) } } diff --git a/Sources/WalletConnectJWT/JWTEncoder.swift b/Sources/WalletConnectJWT/JWTEncoder.swift index f9a2d5425..fd2ec386f 100644 --- a/Sources/WalletConnectJWT/JWTEncoder.swift +++ b/Sources/WalletConnectJWT/JWTEncoder.swift @@ -13,17 +13,8 @@ struct JWTEncoder { } public static func base64urlDecodedData(string: String) throws -> Data { - var base64 = string - .replacingOccurrences(of: "-", with: "+") - .replacingOccurrences(of: "_", with: "/") - - if base64.count % 4 != 0 { - base64.append(String(repeating: "=", count: 4 - base64.count % 4)) - } - - guard let result = Data(base64Encoded: base64) + guard let result = Data(base64url: string) else { throw JWTError.notBase64String } - return result } } diff --git a/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/AgreementCryptoKit.swift b/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/AgreementCryptoKit.swift index 01f0ed2a2..f5e0f906e 100644 --- a/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/AgreementCryptoKit.swift +++ b/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/AgreementCryptoKit.swift @@ -18,6 +18,9 @@ extension Curve25519.KeyAgreement.PrivateKey: Equatable { // MARK: - Public Key public struct AgreementPublicKey: GenericPasswordConvertible, Equatable { + enum Errors: Error { + case invalidBase64urlString + } fileprivate let key: Curve25519.KeyAgreement.PublicKey @@ -34,6 +37,11 @@ public struct AgreementPublicKey: GenericPasswordConvertible, Equatable { try self.init(rawRepresentation: data) } + public init(base64url: String) throws { + guard let raw = Data(base64url: base64url) else { throw Errors.invalidBase64urlString } + try self.init(rawRepresentation: raw) + } + public var rawRepresentation: Data { key.rawRepresentation } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index 88abeda39..dcd8f984e 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -40,38 +40,38 @@ public class Serializer: Serializing { /// - topic: Topic that is associated with a symetric key for decrypting particular codable object /// - encodedEnvelope: Envelope to deserialize and decrypt /// - Returns: Deserialized object - public func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?) { + public func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?, decryptedPayload: Data) { let envelope = try Envelope(encodedEnvelope) switch envelope.type { case .type0: - let deserialisedType: T = try handleType0Envelope(topic, envelope) - return (deserialisedType, nil) + let deserialisedType: (object: T, data: Data) = try handleType0Envelope(topic, envelope) + return (deserialisedType.object, nil, deserialisedType.data) case .type1(let peerPubKey): return try handleType1Envelope(topic, peerPubKey: peerPubKey, sealbox: envelope.sealbox) } } - private func handleType0Envelope(_ topic: String, _ envelope: Envelope) throws -> T { + private func handleType0Envelope(_ topic: String, _ envelope: Envelope) throws -> (T, Data) { if let symmetricKey = kms.getSymmetricKeyRepresentable(for: topic) { return try decode(sealbox: envelope.sealbox, symmetricKey: symmetricKey) } else { throw Errors.symmetricKeyForTopicNotFound } } - - private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> (T, String) { + + private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> (T, String, Data) { guard let selfPubKey = kms.getPublicKey(for: topic) else { throw Errors.publicKeyForTopicNotFound } let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.toHexString()) - let decodedType: T = try decode(sealbox: sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation) + let decodedType: (object: T, data: Data) = try decode(sealbox: sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation) let derivedTopic = agreementKeys.derivedTopic() try kms.setAgreementSecret(agreementKeys, topic: derivedTopic) - return (decodedType, derivedTopic) + return (decodedType.object, derivedTopic, decodedType.data) } - private func decode(sealbox: Data, symmetricKey: Data) throws -> T { + private func decode(sealbox: Data, symmetricKey: Data) throws -> (T, Data) { let decryptedData = try codec.decode(sealbox: sealbox, symmetricKey: symmetricKey) - return try JSONDecoder().decode(T.self, from: decryptedData) + return (try JSONDecoder().decode(T.self, from: decryptedData), decryptedData) } } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializing.swift b/Sources/WalletConnectKMS/Serialiser/Serializing.swift index 81d37229f..5982ce660 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializing.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializing.swift @@ -3,12 +3,12 @@ import Foundation public protocol Serializing { func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String /// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key - func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?) + func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?, decryptedPayload: Data) } public extension Serializing { /// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key - func tryDeserialize(topic: String, encodedEnvelope: String) -> (T, derivedTopic: String?)? { + func tryDeserialize(topic: String, encodedEnvelope: String) -> (T, derivedTopic: String?, decryptedPayload: Data)? { return try? deserialize(topic: topic, encodedEnvelope: encodedEnvelope) } diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 8f47c6cc9..ca15160a8 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -3,7 +3,7 @@ import Combine public protocol NetworkInteracting { var socketConnectionStatusPublisher: AnyPublisher { get } - var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { get } + var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) func batchSubscribe(topics: [String]) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 7c092867b..1f8a7cebf 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -9,10 +9,10 @@ public class NetworkingInteractor: NetworkInteracting { private let rpcHistory: RPCHistory private let logger: ConsoleLogging - private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never>() + private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never>() private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -71,9 +71,9 @@ public class NetworkingInteractor: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, publishedAt, derivedTopic in + .compactMap { topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(RequestParams.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt, derivedTopic: derivedTopic) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } @@ -131,29 +131,29 @@ public class NetworkingInteractor: NetworkInteracting { } private func manageSubscription(_ topic: String, _ encodedEnvelope: String, _ publishedAt: Date) { - if let (deserializedJsonRpcRequest, derivedTopic): (RPCRequest, String?) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleRequest(topic: topic, request: deserializedJsonRpcRequest, publishedAt: publishedAt, derivedTopic: derivedTopic) - } else if let (response, derivedTopic): (RPCResponse, String?) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleResponse(response: response, publishedAt: publishedAt, derivedTopic: derivedTopic) + if let (deserializedJsonRpcRequest, derivedTopic, decryptedPayload): (RPCRequest, String?, Data) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { + handleRequest(topic: topic, request: deserializedJsonRpcRequest, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic) + } else if let (response, derivedTopic, _): (RPCResponse, String?, Data) = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { + handleResponse(topic: topic, response: response, publishedAt: publishedAt, derivedTopic: derivedTopic) } else { logger.debug("Networking Interactor - Received unknown object type from networking relay") } } - private func handleRequest(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?) { + private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) { do { try rpcHistory.set(request, forTopic: topic, emmitedBy: .remote) - requestPublisherSubject.send((topic, request, publishedAt, derivedTopic)) + requestPublisherSubject.send((topic, request, decryptedPayload, publishedAt, derivedTopic)) } catch { logger.debug(error) } } - private func handleResponse(response: RPCResponse, publishedAt: Date, derivedTopic: String?) { + private func handleResponse(topic: String, response: RPCResponse, publishedAt: Date, derivedTopic: String?) { do { try rpcHistory.resolve(response) let record = rpcHistory.get(recordId: response.id!)! - responsePublisherSubject.send((record.topic, record.request, response, publishedAt, derivedTopic)) + responsePublisherSubject.send((topic, record.request, response, publishedAt, derivedTopic)) } catch { logger.debug("Handle json rpc response error: \(error)") } diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift index 1f34ceec0..135378a99 100644 --- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift @@ -4,13 +4,15 @@ public struct RequestSubscriptionPayload: Codable, Subscriptio public let id: RPCID public let topic: String public let request: Request + public let decryptedPayload: Data public let publishedAt: Date public let derivedTopic: String? - public init(id: RPCID, topic: String, request: Request, publishedAt: Date, derivedTopic: String?) { + public init(id: RPCID, topic: String, request: Request, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) { self.id = id self.topic = topic self.request = request + self.decryptedPayload = decryptedPayload self.publishedAt = publishedAt self.derivedTopic = derivedTopic } diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift index 6291cd1a4..c3327e482 100644 --- a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift +++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift @@ -28,7 +28,7 @@ public class PairingRequestsSubscriber { .filter { [unowned self] in !pairingProtocolMethods.contains($0.request.method)} .filter { [unowned self] in pairingStorage.hasPairing(forTopic: $0.topic)} .filter { [unowned self] in !registeredProtocolMethods.contains($0.request.method)} - .sink { [unowned self] topic, request, _, _ in + .sink { [unowned self] topic, request, _, _, _ in Task(priority: .high) { let protocolMethod = UnsupportedProtocolMethod(method: request.method) logger.debug("PairingRequestsSubscriber: responding unregistered request method") diff --git a/Sources/WalletConnectPairing/Types/AppMetadata.swift b/Sources/WalletConnectPairing/Types/AppMetadata.swift index 5a039740d..e30e8c71c 100644 --- a/Sources/WalletConnectPairing/Types/AppMetadata.swift +++ b/Sources/WalletConnectPairing/Types/AppMetadata.swift @@ -42,8 +42,11 @@ public struct AppMetadata: Codable, Equatable { /// An array of URL strings pointing to the icon assets on the web. public let icons: [String] + + /// The URL which used by VerifyClient. + public let verifyUrl: String? - /// Redirect links which could be manually used on wallet side + /// Redirect links which could be manually used on wallet side. public let redirect: Redirect? /** @@ -54,12 +57,22 @@ public struct AppMetadata: Codable, Equatable { - description: A brief textual description of the app that can be displayed to peers. - url: The URL string that identifies the official domain of the app. - icons: An array of URL strings pointing to the icon assets on the web. + - verifyUrl: The URL which used by VerifyClient. + - redirect: Redirect links which could be manually used on wallet side. */ - public init(name: String, description: String, url: String, icons: [String], redirect: Redirect? = nil) { + public init( + name: String, + description: String, + url: String, + icons: [String], + verifyUrl: String? = nil, + redirect: Redirect? = nil + ) { self.name = name self.description = description self.url = url self.icons = icons + self.verifyUrl = verifyUrl self.redirect = redirect } } diff --git a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift index 91248a150..e2179fc95 100644 --- a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift +++ b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift @@ -1,3 +1,5 @@ +import Foundation + import WalletConnectKMS public class PushDecryptionService { @@ -17,7 +19,7 @@ public class PushDecryptionService { } public func decryptMessage(topic: String, ciphertext: String) throws -> PushMessage { - let (rpcRequest, _): (RPCRequest, String?) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) + let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) guard let params = rpcRequest.params else { throw Errors.malformedPushMessage } return try params.get(PushMessage.self) } diff --git a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift index 603b81bbb..e131f075b 100644 --- a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift @@ -35,7 +35,7 @@ class ProposalResponseSubscriber { private func subscribeForProposalResponse() { let protocolMethod = PushRequestProtocolMethod() networkingInteractor.responseSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in logger.debug("Received Push Proposal response") Task(priority: .userInitiated) { do { @@ -49,15 +49,16 @@ class ProposalResponseSubscriber { }.store(in: &publishers) } - private func handleResponse(payload: ResponseSubscriptionPayload) async throws -> (PushSubscription, String) { + private func handleResponse(payload: ResponseSubscriptionPayload) async throws -> (PushSubscription, String) { let jwt = payload.response.jwtString - _ = try AcceptSubscriptionJWTPayload.decodeAndVerify(from: payload.response) + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response) logger.debug("subscriptionAuth JWT validated") guard let subscriptionTopic = payload.derivedTopic else { throw Errors.subscriptionTopicNotDerived } + let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata) + let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) logger.debug("Subscribing to Push Subscription topic: \(subscriptionTopic)") subscriptionsStore.set(pushSubscription, forKey: subscriptionTopic) try await networkingInteractor.subscribe(topic: subscriptionTopic) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift new file mode 100644 index 000000000..402d8084c --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -0,0 +1,51 @@ + +import Foundation + +class NotifyUpdateRequester { + enum Errors: Error { + case noSubscriptionForGivenTopic + } + + private let keyserverURL: URL + private let identityClient: IdentityClient + private let networkingInteractor: NetworkInteracting + private let logger: ConsoleLogging + private let subscriptionsStore: CodableStore + + init(keyserverURL: URL, + identityClient: IdentityClient, + networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + subscriptionsStore: CodableStore + ) { + self.keyserverURL = keyserverURL + self.identityClient = identityClient + self.networkingInteractor = networkingInteractor + self.logger = logger + self.subscriptionsStore = subscriptionsStore + } + + func update(topic: String, scope: Set) async throws { + + logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") + guard let subscription = try subscriptionsStore.get(key: topic) else { throw Errors.noSubscriptionForGivenTopic } + + let request = try createJWTRequest(subscriptionAccount: subscription.account, dappUrl: subscription.metadata.url, scope: scope) + + let protocolMethod = NotifyUpdateProtocolMethod() + + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + } + + private func createJWTRequest(subscriptionAccount: Account, dappUrl: String, scope: Set) throws -> RPCRequest { + let protocolMethod = NotifyUpdateProtocolMethod().method + let scopeClaim = scope.map {$0.rawValue}.joined(separator: " ") + let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scopeClaim) + let wrapper = try identityClient.signAndCreateWrapper( + payload: jwtPayload, + account: subscriptionAccount + ) + print(wrapper.subscriptionAuth) + return RPCRequest(method: protocolMethod, params: wrapper) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift new file mode 100644 index 000000000..5847d1185 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -0,0 +1,72 @@ + +import Foundation +import Combine + +class NotifyUpdateResponseSubscriber { + enum Errors: Error { + case subscriptionDoesNotExist + } + + private let networkingInteractor: NetworkInteracting + private var publishers = [AnyCancellable]() + private let logger: ConsoleLogging + private let subscriptionsStore: CodableStore + private let subscriptionScopeProvider: SubscriptionScopeProvider + private var subscriptionPublisherSubject = PassthroughSubject, Never>() + var updateSubscriptionPublisher: AnyPublisher, Never> { + return subscriptionPublisherSubject.eraseToAnyPublisher() + } + + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + subscriptionScopeProvider: SubscriptionScopeProvider, + subscriptionsStore: CodableStore + ) { + self.networkingInteractor = networkingInteractor + self.logger = logger + self.subscriptionsStore = subscriptionsStore + self.subscriptionScopeProvider = subscriptionScopeProvider + subscribeForUpdateResponse() + } + + private func subscribeForUpdateResponse() { + let protocolMethod = NotifyUpdateProtocolMethod() + networkingInteractor.responseSubscription(on: protocolMethod) + .sink {[unowned self] (payload: ResponseSubscriptionPayload) in + Task(priority: .high) { + logger.debug("Received Push Update response") + + let subscriptionTopic = payload.topic + + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) + let scope = try await buildScope(selected: claims.scp, dappUrl: claims.aud) + + guard let oldSubscription = try? subscriptionsStore.get(key: subscriptionTopic) else { + logger.debug("NotifyUpdateResponseSubscriber Subscription does not exist") + subscriptionPublisherSubject.send(.failure(Errors.subscriptionDoesNotExist)) + return + } + let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) + + let updatedSubscription = PushSubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry) + + subscriptionsStore.set(updatedSubscription, forKey: subscriptionTopic) + + subscriptionPublisherSubject.send(.success(updatedSubscription)) + + logger.debug("Updated Subscription") + } + }.store(in: &publishers) + } + + private func buildScope(selected: String, dappUrl: String) async throws -> [NotificationScope: ScopeValue] { + let selectedScope = selected + .components(separatedBy: " ") + .compactMap { NotificationScope(rawValue: $0) } + + let availableScope = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl) + return availableScope.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: selectedScope.contains($1.name)) } + } + + // TODO: handle error response +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushMessageSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift similarity index 100% rename from Sources/WalletConnectPush/Client/Wallet/PushMessageSubscriber.swift rename to Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift diff --git a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift similarity index 87% rename from Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift rename to Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift index ae7b83357..cfebe88a3 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift @@ -1,5 +1,6 @@ import WalletConnectNetworking import WalletConnectIdentity +import Combine import Foundation class PushRequestResponder { @@ -17,6 +18,10 @@ class PushRequestResponder { // Keychain shared with UNNotificationServiceExtension in order to decrypt PNs private let groupKeychainStorage: KeychainStorageProtocol + private var subscriptionPublisherSubject = PassthroughSubject, Never>() + var subscriptionPublisher: AnyPublisher, Never> { + return subscriptionPublisherSubject.eraseToAnyPublisher() + } init(keyserverURL: URL, networkingInteractor: NetworkInteracting, @@ -65,13 +70,16 @@ class PushRequestResponder { let response = try createJWTResponse(requestId: requestId, subscriptionAccount: requestParams.account, dappUrl: requestParams.metadata.url) - let pushSubscription = PushSubscription(topic: pushTopic, account: requestParams.account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata) + // will be changed in stage 2 refactor, this method will depricate + let expiry = Date() + let pushSubscription = PushSubscription(topic: pushTopic, account: requestParams.account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata, scope: [:], expiry: expiry) subscriptionsStore.set(pushSubscription, forKey: pushTopic) try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: PushRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) kms.deletePrivateKey(for: keys.publicKey.hexRepresentation) + subscriptionPublisherSubject.send(.success(pushSubscription)) } func respondError(requestId: RPCID) async throws { @@ -83,7 +91,7 @@ class PushRequestResponder { } private func createJWTResponse(requestId: RPCID, subscriptionAccount: Account, dappUrl: String) throws -> RPCResponse { - let jwtPayload = AcceptSubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl) + let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "v1") let wrapper = try identityClient.signAndCreateWrapper( payload: jwtPayload, account: subscriptionAccount diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift new file mode 100644 index 000000000..49217c554 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift @@ -0,0 +1,98 @@ + +import Foundation + +class PushSubscribeRequester { + + enum Errors: Error { + case didDocDoesNotContainKeyAgreement + case noVerificationMethodForKey + case unsupportedCurve + } + + private let keyserverURL: URL + private let identityClient: IdentityClient + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementService + private let logger: ConsoleLogging + private let webDidResolver: WebDidResolver + private let dappsMetadataStore: CodableStore + + init(keyserverURL: URL, + networkingInteractor: NetworkInteracting, + identityClient: IdentityClient, + logger: ConsoleLogging, + kms: KeyManagementService, + webDidResolver: WebDidResolver = WebDidResolver(), + dappsMetadataStore: CodableStore + ) { + self.keyserverURL = keyserverURL + self.identityClient = identityClient + self.networkingInteractor = networkingInteractor + self.logger = logger + self.kms = kms + self.webDidResolver = webDidResolver + self.dappsMetadataStore = dappsMetadataStore + } + + func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { + + let dappUrl = metadata.url + + logger.debug("Subscribing for Push") + + let peerPublicKey = try await resolvePublicKey(dappUrl: metadata.url) + let subscribeTopic = peerPublicKey.rawRepresentation.sha256().toHexString() + + let keysY = try generateAgreementKeys(peerPublicKey: peerPublicKey) + + let responseTopic = keysY.derivedTopic() + + dappsMetadataStore.set(metadata, forKey: responseTopic) + + try kms.setSymmetricKey(keysY.sharedKey, for: subscribeTopic) + + _ = try await identityClient.register(account: account, onSign: onSign) + + try kms.setAgreementSecret(keysY, topic: responseTopic) + + logger.debug("setting symm key for response topic \(responseTopic)") + + let request = try createJWTRequest(subscriptionAccount: account, dappUrl: dappUrl) + + let protocolMethod = PushSubscribeProtocolMethod() + + logger.debug("PushSubscribeRequester: subscribing to response topic: \(responseTopic)") + + try await networkingInteractor.subscribe(topic: responseTopic) + + try await networkingInteractor.request(request, topic: subscribeTopic, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keysY.publicKey.rawRepresentation)) + } + + private func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey { + logger.debug("PushSubscribeRequester: Resolving DIDDoc for: \(dappUrl)") + let didDoc = try await webDidResolver.resolveDidDoc(domainUrl: dappUrl) + guard let keyAgreement = didDoc.keyAgreement.first else { throw Errors.didDocDoesNotContainKeyAgreement } + guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyAgreement }) else { throw Errors.noVerificationMethodForKey } + guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve} + let pubKeyBase64Url = verificationMethod.publicKeyJwk.x + return try AgreementPublicKey(base64url: pubKeyBase64Url) + } + + + private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { + let selfPubKey = try kms.createX25519KeyPair() + + let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) + return keys + } + + private func createJWTRequest(subscriptionAccount: Account, dappUrl: String) throws -> RPCRequest { + let protocolMethod = PushSubscribeProtocolMethod().method + let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "") + let wrapper = try identityClient.signAndCreateWrapper( + payload: jwtPayload, + account: subscriptionAccount + ) + return RPCRequest(method: protocolMethod, params: wrapper) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift new file mode 100644 index 000000000..63e8e0fa3 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift @@ -0,0 +1,100 @@ + +import Foundation +import Combine + +class PushSubscribeResponseSubscriber { + enum Errors: Error { + case couldNotCreateSubscription + } + + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + private let logger: ConsoleLogging + private let subscriptionsStore: CodableStore + private let groupKeychainStorage: KeychainStorageProtocol + private let dappsMetadataStore: CodableStore + private let subscriptionScopeProvider: SubscriptionScopeProvider + private var subscriptionPublisherSubject = PassthroughSubject, Never>() + var subscriptionPublisher: AnyPublisher, Never> { + return subscriptionPublisherSubject.eraseToAnyPublisher() + } + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + groupKeychainStorage: KeychainStorageProtocol, + subscriptionsStore: CodableStore, + dappsMetadataStore: CodableStore, + subscriptionScopeProvider: SubscriptionScopeProvider + ) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.groupKeychainStorage = groupKeychainStorage + self.subscriptionsStore = subscriptionsStore + self.dappsMetadataStore = dappsMetadataStore + self.subscriptionScopeProvider = subscriptionScopeProvider + subscribeForSubscriptionResponse() + } + + private func subscribeForSubscriptionResponse() { + let protocolMethod = PushSubscribeProtocolMethod() + networkingInteractor.responseSubscription(on: protocolMethod) + .sink {[unowned self] (payload: ResponseSubscriptionPayload) in + Task(priority: .high) { + logger.debug("Received Push Subscribe response") + + guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else { + logger.debug("PushSubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") + subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) + return + } + + // get keypair Y + let pubKeyY = responseKeys.publicKey + let peerPubKeyZ = payload.response.publicKey + + var account: Account! + var metadata: AppMetadata! + var pushSubscriptionTopic: String! + var availableScope: Set! + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) + do { + // generate symm key P + let agreementKeysP = try kms.performKeyAgreement(selfPublicKey: pubKeyY, peerPublicKey: peerPubKeyZ) + pushSubscriptionTopic = agreementKeysP.derivedTopic() + try kms.setAgreementSecret(agreementKeysP, topic: pushSubscriptionTopic) + try groupKeychainStorage.add(agreementKeysP, forKey: pushSubscriptionTopic) + account = try Account(DIDPKHString: claims.sub) + metadata = try dappsMetadataStore.get(key: payload.topic) + availableScope = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: metadata!.url) + logger.debug("PushSubscribeResponseSubscriber: subscribing push subscription topic: \(pushSubscriptionTopic!)") + try await networkingInteractor.subscribe(topic: pushSubscriptionTopic) + } catch { + logger.debug("PushSubscribeResponseSubscriber: error: \(error)") + subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) + return + } + + guard let metadata = metadata else { + logger.debug("PushSubscribeResponseSubscriber: no metadata for topic: \(pushSubscriptionTopic)") + return + } + dappsMetadataStore.delete(forKey: payload.topic) + let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) + let scope: [NotificationScope: ScopeValue] = availableScope.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: true) } + let pushSubscription = PushSubscription(topic: pushSubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry) + + subscriptionsStore.set(pushSubscription, forKey: pushSubscriptionTopic) + + logger.debug("PushSubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") + networkingInteractor.unsubscribe(topic: payload.topic) + subscriptionPublisherSubject.send(.success(pushSubscription)) + } + }.store(in: &publishers) + } + + // TODO: handle error response + +} diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift b/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift new file mode 100644 index 000000000..a2140addd --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift @@ -0,0 +1,22 @@ + +import Foundation + +class SubscriptionScopeProvider { + enum Errors: Error { + case invalidUrl + } + + private var cache = [String: Set]() + + func getSubscriptionScope(dappUrl: String) async throws -> Set { + if let availableScope = cache[dappUrl] { + return availableScope + } + guard let scopeUrl = URL(string: "\(dappUrl)/.well-known/wc-push-config.json") else { throw Errors.invalidUrl } + let (data, _) = try await URLSession.shared.data(from: scopeUrl) + let config = try JSONDecoder().decode(NotificationConfig.self, from: data) + let availableScope = Set(config.types) + cache[dappUrl] = availableScope + return availableScope + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index c7848a79e..2740cb252 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -9,6 +9,13 @@ public class WalletPushClient { private var publishers = Set() + /// publishes new subscriptions + public var subscriptionPublisher: AnyPublisher, Never> { + return subscriptionPublisherSubject.eraseToAnyPublisher() + } + + public var subscriptionPublisherSubject = PassthroughSubject, Never>() + public var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { return pushSubscriptionsObserver.subscriptionsPublisher } @@ -33,8 +40,13 @@ public class WalletPushClient { deleteSubscriptionPublisherSubject.eraseToAnyPublisher() } + public var updateSubscriptionPublisher: AnyPublisher, Never> { + return notifyUpdateResponseSubscriber.updateSubscriptionPublisher + } + private let deletePushSubscriptionService: DeletePushSubscriptionService private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber + private let pushSubscribeRequester: PushSubscribeRequester public let logger: ConsoleLogging @@ -45,6 +57,9 @@ public class WalletPushClient { private let subscriptionsProvider: SubscriptionsProvider private let pushMessagesDatabase: PushMessagesDatabase private let resubscribeService: PushResubscribeService + private let pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber + private let notifyUpdateRequester: NotifyUpdateRequester + private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, @@ -57,7 +72,12 @@ public class WalletPushClient { deletePushSubscriptionService: DeletePushSubscriptionService, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, resubscribeService: PushResubscribeService, - pushSubscriptionsObserver: PushSubscriptionsObserver) { + pushSubscriptionsObserver: PushSubscriptionsObserver, + pushSubscribeRequester: PushSubscribeRequester, + pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber, + notifyUpdateRequester: NotifyUpdateRequester, + notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber + ) { self.logger = logger self.pairingRegisterer = pairingRegisterer self.proposeResponder = proposeResponder @@ -69,9 +89,17 @@ public class WalletPushClient { self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService self.pushSubscriptionsObserver = pushSubscriptionsObserver + self.pushSubscribeRequester = pushSubscribeRequester + self.pushSubscribeResponseSubscriber = pushSubscribeResponseSubscriber + self.notifyUpdateRequester = notifyUpdateRequester + self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber setupSubscriptions() } + public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { + try await pushSubscribeRequester.subscribe(metadata: metadata, account: account, onSign: onSign) + } + public func approve(id: RPCID, onSign: @escaping SigningCallback) async throws { try await proposeResponder.respond(requestId: id, onSign: onSign) } @@ -80,6 +108,10 @@ public class WalletPushClient { try await proposeResponder.respondError(requestId: id) } + public func update(topic: String, scope: Set) async throws { + try await notifyUpdateRequester.update(topic: topic, scope: scope) + } + public func getActiveSubscriptions() -> [PushSubscription] { subscriptionsProvider.getActiveSubscriptions() } @@ -114,9 +146,18 @@ private extension WalletPushClient { pushMessageSubscriber.onPushMessage = { [unowned self] pushMessageRecord in pushMessagePublisherSubject.send(pushMessageRecord) } + deletePushSubscriptionSubscriber.onDelete = {[unowned self] topic in deleteSubscriptionPublisherSubject.send(topic) } + + pushSubscribeResponseSubscriber.subscriptionPublisher.sink { [unowned self] result in + subscriptionPublisherSubject.send(result) + }.store(in: &publishers) + + proposeResponder.subscriptionPublisher.sink { [unowned self] result in + subscriptionPublisherSubject.send(result) + }.store(in: &publishers) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index ee88eb8de..adec22410 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -6,7 +6,7 @@ import WalletConnectIdentity public struct WalletPushClientFactory { public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient) -> WalletPushClient { - let logger = ConsoleLogger(loggingLevel: .debug) + let logger = ConsoleLogger(suffix: "🔔",loggingLevel: .debug) let keyValueStorage = UserDefaults.standard let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") @@ -52,6 +52,19 @@ public struct WalletPushClientFactory { let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) let pushSubscriptionsObserver = PushSubscriptionsObserver(store: subscriptionStore) + + + let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.dappsMetadataStore) + + let pushSubscribeRequester = PushSubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, dappsMetadataStore: dappsMetadataStore) + + let subscriptionScopeProvider = SubscriptionScopeProvider() + let pushSubscribeResponseSubscriber = PushSubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, subscriptionsStore: subscriptionStore, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider) + + let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, subscriptionsStore: subscriptionStore) + + let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, subscriptionsStore: subscriptionStore) + return WalletPushClient( logger: logger, kms: kms, @@ -64,7 +77,11 @@ public struct WalletPushClientFactory { deletePushSubscriptionService: deletePushSubscriptionService, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, resubscribeService: resubscribeService, - pushSubscriptionsObserver: pushSubscriptionsObserver + pushSubscriptionsObserver: pushSubscriptionsObserver, + pushSubscribeRequester: pushSubscribeRequester, + pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber, + notifyUpdateRequester: notifyUpdateRequester, + notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber ) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift b/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift new file mode 100644 index 000000000..f7e6617ad --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift @@ -0,0 +1,15 @@ + +import Foundation + +class WebDidResolver { + + enum Errors: Error { + case invalidUrl + } + + func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc { + guard let didDocUrl = URL(string: "\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl } + let (data, _) = try await URLSession.shared.data(from: didDocUrl) + return try JSONDecoder().decode(WebDidDoc.self, from: data) + } +} diff --git a/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift new file mode 100644 index 000000000..198ec859f --- /dev/null +++ b/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift @@ -0,0 +1,11 @@ + +import Foundation + +struct NotifyUpdateProtocolMethod: ProtocolMethod { + let method: String = "wc_pushUpdate" + + let requestConfig: RelayConfig = RelayConfig(tag: 4008, prompt: true, ttl: 86400) + + let responseConfig: RelayConfig = RelayConfig(tag: 4009, prompt: true, ttl: 86400) +} + diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift index 287b2a3bb..808817290 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift +++ b/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift @@ -4,7 +4,7 @@ import WalletConnectPairing struct PushMessageProtocolMethod: ProtocolMethod { let method: String = "wc_pushMessage" - let requestConfig: RelayConfig = RelayConfig(tag: 4002, prompt: true, ttl: 86400) + let requestConfig: RelayConfig = RelayConfig(tag: 4002, prompt: true, ttl: 2592000) - let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: true, ttl: 86400) + let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: true, ttl: 2592000) } diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift new file mode 100644 index 000000000..a101dc0e5 --- /dev/null +++ b/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift @@ -0,0 +1,10 @@ + +import Foundation + +struct PushSubscribeProtocolMethod: ProtocolMethod { + let method: String = "wc_pushSubscribe" + + let requestConfig: RelayConfig = RelayConfig(tag: 4006, prompt: true, ttl: 86400) + + let responseConfig: RelayConfig = RelayConfig(tag: 4007, prompt: true, ttl: 86400) +} diff --git a/Sources/WalletConnectPush/PushStorageIdntifiers.swift b/Sources/WalletConnectPush/PushStorageIdntifiers.swift index 5dd0f0a53..57ac3611c 100644 --- a/Sources/WalletConnectPush/PushStorageIdntifiers.swift +++ b/Sources/WalletConnectPush/PushStorageIdntifiers.swift @@ -3,4 +3,5 @@ import Foundation enum PushStorageIdntifiers { static let pushSubscription = "com.walletconnect.sdk.pushSbscription" static let pushMessagesRecords = "com.walletconnect.sdk.pushMessagesRecords" + static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore" } diff --git a/Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift b/Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift new file mode 100644 index 000000000..51e75148f --- /dev/null +++ b/Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift @@ -0,0 +1,6 @@ + +import Foundation + +struct SubscribeResponseParams: Codable { + let publicKey: String +} diff --git a/Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift b/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift similarity index 83% rename from Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift rename to Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift index 5594aec83..d45ac558b 100644 --- a/Sources/WalletConnectPush/RPCRequests/AcceptSubscriptionJWTPayload.swift +++ b/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift @@ -1,6 +1,6 @@ import Foundation -struct AcceptSubscriptionJWTPayload: JWTClaimsCodable { +struct SubscriptionJWTPayload: JWTClaimsCodable { struct Claims: JWTClaims { /// timestamp when jwt was issued @@ -17,6 +17,8 @@ struct AcceptSubscriptionJWTPayload: JWTClaimsCodable { let sub: String /// description of action intent. Must be equal to "push_subscription" let act: String + + let scp: String } struct Wrapper: JWTWrapper { @@ -34,28 +36,33 @@ struct AcceptSubscriptionJWTPayload: JWTClaimsCodable { let keyserver: URL let subscriptionAccount: Account let dappUrl: String + let scope: String - init(keyserver: URL, subscriptionAccount: Account, dappUrl: String) { + init(keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) { self.keyserver = keyserver self.subscriptionAccount = subscriptionAccount self.dappUrl = dappUrl + self.scope = scope } init(claims: Claims) throws { self.keyserver = try claims.ksu.asURL() self.subscriptionAccount = try Account(DIDPKHString: claims.sub) self.dappUrl = claims.aud + self.scope = claims.scp } func encode(iss: String) throws -> Claims { return Claims( - iat: expiry(days: 1), - exp: defaultIatMilliseconds(), + iat: defaultIatMilliseconds(), + exp: expiry(days: 30), iss: iss, ksu: keyserver.absoluteString, aud: dappUrl, sub: subscriptionAccount.did, - act: "push_subscription" + act: "push_subscription", + scp: scope ) } } + diff --git a/Sources/WalletConnectPush/Types/NotificationConfig.swift b/Sources/WalletConnectPush/Types/NotificationConfig.swift new file mode 100644 index 000000000..c53e9b674 --- /dev/null +++ b/Sources/WalletConnectPush/Types/NotificationConfig.swift @@ -0,0 +1,9 @@ + +import Foundation + +struct NotificationConfig: Codable { + let version: Int + let lastModified: TimeInterval + let types: [NotificationType] + +} diff --git a/Sources/WalletConnectPush/Types/NotificationScope.swift b/Sources/WalletConnectPush/Types/NotificationScope.swift new file mode 100644 index 000000000..b4c74e4be --- /dev/null +++ b/Sources/WalletConnectPush/Types/NotificationScope.swift @@ -0,0 +1,9 @@ + +import Foundation + +public enum NotificationScope: String, Hashable, Codable, CodingKeyRepresentable, CaseIterable { + case promotional + case transactional + case `private` + case alerts +} diff --git a/Sources/WalletConnectPush/Types/NotificationType.swift b/Sources/WalletConnectPush/Types/NotificationType.swift new file mode 100644 index 000000000..0e6b2df35 --- /dev/null +++ b/Sources/WalletConnectPush/Types/NotificationType.swift @@ -0,0 +1,7 @@ + +import Foundation + +public struct NotificationType: Codable, Hashable { + let name: NotificationScope + let description: String +} diff --git a/Sources/WalletConnectPush/Types/PushMessage.swift b/Sources/WalletConnectPush/Types/PushMessage.swift index eb32df560..d48604937 100644 --- a/Sources/WalletConnectPush/Types/PushMessage.swift +++ b/Sources/WalletConnectPush/Types/PushMessage.swift @@ -5,11 +5,13 @@ public struct PushMessage: Codable, Equatable { public let body: String public let icon: String public let url: String + public let type: String? - public init(title: String, body: String, icon: String, url: String) { + public init(title: String, body: String, icon: String, url: String, type: String? = nil) { self.title = title self.body = body self.icon = icon self.url = url + self.type = type } } diff --git a/Sources/WalletConnectPush/Types/PushSubscription.swift b/Sources/WalletConnectPush/Types/PushSubscription.swift index 783ec3dd1..57522f2d8 100644 --- a/Sources/WalletConnectPush/Types/PushSubscription.swift +++ b/Sources/WalletConnectPush/Types/PushSubscription.swift @@ -7,4 +7,11 @@ public struct PushSubscription: Codable, Equatable { public let account: Account public let relay: RelayProtocolOptions public let metadata: AppMetadata + public let scope: [NotificationScope: ScopeValue] + public let expiry: Date +} + +public struct ScopeValue: Codable, Equatable { + let description: String + let enabled: Bool } diff --git a/Sources/WalletConnectPush/Types/WebDidDoc.swift b/Sources/WalletConnectPush/Types/WebDidDoc.swift new file mode 100644 index 000000000..28f301773 --- /dev/null +++ b/Sources/WalletConnectPush/Types/WebDidDoc.swift @@ -0,0 +1,36 @@ + +import Foundation + +// MARK: - WebDidDoc +struct WebDidDoc: Codable { + let context: [String] + let id: String + let verificationMethod: [VerificationMethod] + let authentication: [String]? + let keyAgreement: [String] + + enum CodingKeys: String, CodingKey { + case context = "@context" + case id, verificationMethod, authentication, keyAgreement + } +} +extension WebDidDoc { + + struct VerificationMethod: Codable { + let id: String + let type: String + let controller: String + let publicKeyJwk: PublicKeyJwk + } + + struct PublicKeyJwk: Codable { + enum Curve: String, Codable { + case X25519 = "X25519" + } + let kty: String + + let crv: Curve + /// The x member contains the x coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation. + let x: String + } +} diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 19e7a1501..90a323c7b 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.5.15"} +{"version": "1.6.0"} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 59846a852..6831041bb 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -77,7 +77,7 @@ public final class RelayClient { keychainStorage: KeychainStorageProtocol = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk"), socketFactory: WebSocketFactory, socketConnectionType: SocketConnectionType = .automatic, - logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off) + logger: ConsoleLogging = ConsoleLogger(loggingLevel: .debug) ) { let clientIdStorage = ClientIdStorage(keychain: keychainStorage) let socketAuthenticator = SocketAuthenticator( diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index a00b62c00..f56ffd908 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -1,6 +1,8 @@ import Foundation import Combine +import WalletConnectVerify + final class ApproveEngine { enum Errors: Error { case wrongRequestParams @@ -11,13 +13,14 @@ final class ApproveEngine { case agreementMissingOrInvalid } - var onSessionProposal: ((Session.Proposal) -> Void)? + var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)? var onSessionRejected: ((Session.Proposal, Reason) -> Void)? var onSessionSettle: ((Session) -> Void)? private let networkingInteractor: NetworkInteracting private let pairingStore: WCPairingStorage private let sessionStore: WCSessionStorage + private let verifyClient: VerifyClient? private let proposalPayloadsStore: CodableStore> private let sessionTopicToProposal: CodableStore private let pairingRegisterer: PairingRegisterer @@ -36,7 +39,8 @@ final class ApproveEngine { kms: KeyManagementServiceProtocol, logger: ConsoleLogging, pairingStore: WCPairingStorage, - sessionStore: WCSessionStorage + sessionStore: WCSessionStorage, + verifyClient: VerifyClient? ) { self.networkingInteractor = networkingInteractor self.proposalPayloadsStore = proposalPayloadsStore @@ -47,6 +51,7 @@ final class ApproveEngine { self.logger = logger self.pairingStore = pairingStore self.sessionStore = sessionStore + self.verifyClient = verifyClient setupRequestSubscriptions() setupResponseSubscriptions() @@ -277,7 +282,20 @@ private extension ApproveEngine { return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: SessionProposeProtocolMethod()) } proposalPayloadsStore.set(payload, forKey: proposal.proposer.publicKey) - onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic)) + + guard let verifyClient else { + onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), nil) + return + } + Task(priority: .high) { + let assertionId = payload.decryptedPayload.sha256().toHexString() + let origin = try? await verifyClient.verifyOrigin(assertionId: assertionId) + let verifyContext = await verifyClient.createVerifyContext( + origin: origin, + domain: payload.request.proposer.metadata.url + ) + onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) + } } // MARK: SessionSettleRequest diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 420c1a418..27aae0941 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -1,6 +1,8 @@ import Foundation import Combine +import WalletConnectVerify + final class SessionEngine { enum Errors: Error { case sessionNotFound(topic: String) @@ -8,7 +10,7 @@ final class SessionEngine { } var onSessionsUpdate: (([Session]) -> Void)? - var onSessionRequest: ((Request) -> Void)? + var onSessionRequest: ((Request, VerifyContext?) -> Void)? var onSessionResponse: ((Response) -> Void)? var onSessionRejected: ((String, SessionType.Reason) -> Void)? var onSessionDelete: ((String, SessionType.Reason) -> Void)? @@ -17,6 +19,7 @@ final class SessionEngine { private let sessionStore: WCSessionStorage private let networkingInteractor: NetworkInteracting private let historyService: HistoryService + private let verifyClient: VerifyClient? private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging @@ -24,12 +27,14 @@ final class SessionEngine { init( networkingInteractor: NetworkInteracting, historyService: HistoryService, + verifyClient: VerifyClient?, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, logger: ConsoleLogging ) { self.networkingInteractor = networkingInteractor self.historyService = historyService + self.verifyClient = verifyClient self.kms = kms self.sessionStore = sessionStore self.logger = logger @@ -127,7 +132,9 @@ private extension SessionEngine { networkingInteractor.requestSubscription(on: SessionRequestProtocolMethod()) .sink { [unowned self] (payload: RequestSubscriptionPayload) in - onSessionRequest(payload: payload) + Task(priority: .high) { + onSessionRequest(payload: payload) + } }.store(in: &publishers) networkingInteractor.requestSubscription(on: SessionPingProtocolMethod()) @@ -221,7 +228,6 @@ private extension SessionEngine { chainId: payload.request.chainId, expiry: payload.request.request.expiry ) - guard let session = sessionStore.getSession(forTopic: topic) else { return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod) } @@ -231,12 +237,22 @@ private extension SessionEngine { guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else { return respondError(payload: payload, reason: .unauthorizedMethod(request.method), protocolMethod: protocolMethod) } - guard !request.isExpired() else { return respondError(payload: payload, reason: .sessionRequestExpired, protocolMethod: protocolMethod) } - - onSessionRequest?(request) + guard let verifyClient else { + onSessionRequest?(request, nil) + return + } + Task(priority: .high) { + let assertionId = payload.decryptedPayload.sha256().toHexString() + let origin = try? await verifyClient.verifyOrigin(assertionId: assertionId) + let verifyContext = await verifyClient.createVerifyContext( + origin: origin, + domain: session.peerParticipant.metadata.url + ) + onSessionRequest?(request, verifyContext) + } } func onSessionPing(payload: SubscriptionPayload) { diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index 5e398757b..43ba9518a 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -1,4 +1,4 @@ -enum AutoNamespacesError: Error { +public enum AutoNamespacesError: Error { case requiredChainsNotSatisfied case requiredAccountsNotSatisfied case requiredMethodsNotSatisfied diff --git a/Sources/WalletConnectSign/Session.swift b/Sources/WalletConnectSign/Session.swift index a274c8e2b..c880f6cd0 100644 --- a/Sources/WalletConnectSign/Session.swift +++ b/Sources/WalletConnectSign/Session.swift @@ -41,5 +41,4 @@ extension Session { SessionType.EventParams.Event(name: name, data: data) } } - } diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift index 923b6fe82..40ba5b16a 100644 --- a/Sources/WalletConnectSign/Sign/Sign.swift +++ b/Sources/WalletConnectSign/Sign/Sign.swift @@ -1,6 +1,10 @@ import Foundation import Combine +import WalletConnectVerify + +public typealias VerifyContext = WalletConnectVerify.VerifyContext + /// Sign instatnce wrapper /// /// ```swift diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index abf6cad91..7d18d08ed 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -16,14 +16,14 @@ public final class SignClient: SignClientProtocol { /// Publisher that sends session proposal /// /// event is emited on responder client only - public var sessionProposalPublisher: AnyPublisher { + public var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> { sessionProposalPublisherSubject.eraseToAnyPublisher() } /// Publisher that sends session request /// /// In most cases event will be emited on wallet - public var sessionRequestPublisher: AnyPublisher { + public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { sessionRequestPublisherSubject.eraseToAnyPublisher() } @@ -113,8 +113,8 @@ public final class SignClient: SignClientProtocol { private let historyService: HistoryService private let cleanupService: SignCleanupService - private let sessionProposalPublisherSubject = PassthroughSubject() - private let sessionRequestPublisherSubject = PassthroughSubject() + private let sessionProposalPublisherSubject = PassthroughSubject<(proposal: Session.Proposal, context: VerifyContext?), Never>() + private let sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() private let socketConnectionStatusPublisherSubject = PassthroughSubject() private let sessionSettlePublisherSubject = PassthroughSubject() private let sessionDeletePublisherSubject = PassthroughSubject<(String, Reason), Never>() @@ -371,8 +371,8 @@ public final class SignClient: SignClientProtocol { // MARK: - Private private func setUpEnginesCallbacks() { - approveEngine.onSessionProposal = { [unowned self] proposal in - sessionProposalPublisherSubject.send(proposal) + approveEngine.onSessionProposal = { [unowned self] (proposal, context) in + sessionProposalPublisherSubject.send((proposal, context)) } approveEngine.onSessionRejected = { [unowned self] proposal, reason in sessionRejectionPublisherSubject.send((proposal, reason)) @@ -380,8 +380,8 @@ public final class SignClient: SignClientProtocol { approveEngine.onSessionSettle = { [unowned self] settledSession in sessionSettlePublisherSubject.send(settledSession) } - sessionEngine.onSessionRequest = { [unowned self] sessionRequest in - sessionRequestPublisherSubject.send(sessionRequest) + sessionEngine.onSessionRequest = { [unowned self] (sessionRequest, context) in + sessionRequestPublisherSubject.send((sessionRequest, context)) } sessionEngine.onSessionDelete = { [unowned self] topic, reason in sessionDeletePublisherSubject.send((topic, reason)) diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 16ff60a7c..ecacb5e6e 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -1,5 +1,7 @@ import Foundation +import WalletConnectVerify + public struct SignClientFactory { /// Initializes and returns newly created WalletConnect Client Instance @@ -25,11 +27,12 @@ public struct SignClientFactory { let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.sessions.rawValue))) let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.proposals.rawValue) let historyService = HistoryService(history: rpcHistory) - let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, kms: kms, sessionStore: sessionStore, logger: logger) + let verifyClient = try? VerifyClientFactory.create(verifyHost: metadata.verifyUrl) + let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.sessionTopicToProposal.rawValue) - let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionTopicToProposal: sessionTopicToProposal, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore) + let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionTopicToProposal: sessionTopicToProposal, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore, verifyClient: verifyClient) let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore) diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 342063400..a1dcfc3f3 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -2,8 +2,8 @@ import Foundation import Combine public protocol SignClientProtocol { - var sessionProposalPublisher: AnyPublisher { get } - var sessionRequestPublisher: AnyPublisher { get } + var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> { get } + var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { get } var sessionsPublisher: AnyPublisher<[Session], Never> { get } var socketConnectionStatusPublisher: AnyPublisher { get } var sessionSettlePublisher: AnyPublisher { get } diff --git a/Sources/WalletConnectUtils/Extensions/Data.swift b/Sources/WalletConnectUtils/Extensions/Data.swift index 88624109f..cb2ffb6c8 100644 --- a/Sources/WalletConnectUtils/Extensions/Data.swift +++ b/Sources/WalletConnectUtils/Extensions/Data.swift @@ -53,4 +53,15 @@ extension Data { public func toHexString() -> String { return map({ String(format: "%02x", $0) }).joined() } + + public init?(base64url: String) { + var base64 = base64url + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + + if base64.count % 4 != 0 { + base64.append(String(repeating: "=", count: 4 - base64.count % 4)) + } + self.init(base64Encoded: base64) + } } diff --git a/Sources/WalletConnectVerify/AppAttestationRegistrer.swift b/Sources/WalletConnectVerify/AppAttestationRegistrer.swift index 8f5b918c5..84b5d5dd1 100644 --- a/Sources/WalletConnectVerify/AppAttestationRegistrer.swift +++ b/Sources/WalletConnectVerify/AppAttestationRegistrer.swift @@ -3,8 +3,6 @@ import DeviceCheck import WalletConnectUtils import CryptoKit -@available(iOS 14.0, *) -@available(macOS 11.0, *) class AppAttestationRegistrer { private let logger: ConsoleLogging private let keyIdStorage: CodableStore diff --git a/Sources/WalletConnectVerify/AssertionRegistrer.swift b/Sources/WalletConnectVerify/AssertionRegistrer.swift index 67640c1fa..616368bd7 100644 --- a/Sources/WalletConnectVerify/AssertionRegistrer.swift +++ b/Sources/WalletConnectVerify/AssertionRegistrer.swift @@ -1,5 +1,3 @@ -import Foundation - class AssertionRegistrer { func registerAssertion() async throws {} } diff --git a/Sources/WalletConnectVerify/AttestKeyGenerator.swift b/Sources/WalletConnectVerify/AttestKeyGenerator.swift index 1826526e0..a53b15c6d 100644 --- a/Sources/WalletConnectVerify/AttestKeyGenerator.swift +++ b/Sources/WalletConnectVerify/AttestKeyGenerator.swift @@ -6,10 +6,9 @@ protocol AttestKeyGenerating { func generateKeys() async throws -> String } -@available(iOS 14.0, *) -@available(macOS 11.0, *) class AttestKeyGenerator: AttestKeyGenerating { - private let service = DCAppAttestService.shared + // TODO - Add DCAppAttestService handling for iOS 14.0 + // private let service = DCAppAttestService.shared private let logger: ConsoleLogging private let keyIdStorage: CodableStore @@ -21,8 +20,9 @@ class AttestKeyGenerator: AttestKeyGenerating { } func generateKeys() async throws -> String { - let keyId = try await service.generateKey() - keyIdStorage.set(keyId, forKey: Constants.keyIdStorageKey) - return keyId + // TODO - Add DCAppAttestService handling for iOS 14.0 + // let keyId = try await service.generateKey() + keyIdStorage.set("keyId", forKey: Constants.keyIdStorageKey) + return "keyId" } } diff --git a/Sources/WalletConnectVerify/KeyAttestationService.swift b/Sources/WalletConnectVerify/KeyAttestationService.swift index 54109ef01..d57bfbc4f 100644 --- a/Sources/WalletConnectVerify/KeyAttestationService.swift +++ b/Sources/WalletConnectVerify/KeyAttestationService.swift @@ -5,17 +5,17 @@ protocol KeyAttesting { func attestKey(keyId: String, clientDataHash: Data) async throws } -@available(iOS 14.0, *) -@available(macOS 11.0, *) class KeyAttestationService: KeyAttesting { - private let service = DCAppAttestService.shared + // TODO - Add DCAppAttestService handling for iOS 14.0 + // private let service = DCAppAttestService.shared + // If the method, which accesses a remote Apple server, returns the serverUnavailable error, // try attestation again later with the same key. For any other error, // discard the key identifier and create a new key when you want to try again. // Otherwise, send the completion handler’s attestation object and the keyId to your server for processing. - func attestKey(keyId: String, clientDataHash: Data) async throws { + func attestKey(keyId: String, clientDataHash: Data) { - try await service.attestKey(keyId, clientDataHash: clientDataHash) + // try await service.attestKey(keyId, clientDataHash: clientDataHash) // TODO - Send the attestation object to your server for verification. handle errors } diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift index 7ad8c2d33..424a45ac0 100644 --- a/Sources/WalletConnectVerify/OriginVerifier.swift +++ b/Sources/WalletConnectVerify/OriginVerifier.swift @@ -1,5 +1,27 @@ import Foundation +import WalletConnectNetworking -class OriginVerifier { - func verifyOrigin() async throws {} +public final class OriginVerifier { + enum Errors: Error { + case registrationFailed + } + + private let verifyHost: String + + init(verifyHost: String) { + self.verifyHost = verifyHost + } + + func verifyOrigin(assertionId: String) async throws -> String { + let httpClient = HTTPNetworkClient(host: verifyHost) + let response = try await httpClient.request( + VerifyResponse.self, + at: VerifyAPI.resolve(assertionId: assertionId) + ) + guard let origin = response.origin else { + throw Errors.registrationFailed + } + return origin + } } + diff --git a/Sources/WalletConnectVerify/Register/VerifyResponse.swift b/Sources/WalletConnectVerify/Register/VerifyResponse.swift new file mode 100644 index 000000000..ed2e59460 --- /dev/null +++ b/Sources/WalletConnectVerify/Register/VerifyResponse.swift @@ -0,0 +1,5 @@ +import Foundation + +struct VerifyResponse: Decodable { + let origin: String? +} diff --git a/Sources/WalletConnectVerify/Register/VerifyService.swift b/Sources/WalletConnectVerify/Register/VerifyService.swift new file mode 100644 index 000000000..cec111182 --- /dev/null +++ b/Sources/WalletConnectVerify/Register/VerifyService.swift @@ -0,0 +1,34 @@ +import Foundation +import WalletConnectNetworking + +enum VerifyAPI: HTTPService { + case resolve(assertionId: String) + + var path: String { + switch self { + case .resolve(let assertionId): return "/attestation/\(assertionId)" + } + } + + var method: HTTPMethod { + switch self { + case .resolve: return .get + } + } + + var body: Data? { + nil + } + + var queryParameters: [String: String]? { + return nil + } + + var scheme: String { + return "https" + } + + var additionalHeaderFields: [String : String]? { + nil + } +} diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index aa6919af4..74d01217b 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -1,23 +1,27 @@ import DeviceCheck import Foundation + import WalletConnectUtils +import WalletConnectNetworking -@available(iOS 14.0, *) -@available(macOS 11.0, *) public actor VerifyClient { enum Errors: Error { case attestationNotSupported } + let originVerifier: OriginVerifier let assertionRegistrer: AssertionRegistrer let appAttestationRegistrer: AppAttestationRegistrer + + private let verifyHost: String - init(originVerifier: OriginVerifier, - assertionRegistrer: AssertionRegistrer, - appAttestationRegistrer: AppAttestationRegistrer) throws { - if !DCAppAttestService.shared.isSupported { - throw Errors.attestationNotSupported - } + init( + verifyHost: String, + originVerifier: OriginVerifier, + assertionRegistrer: AssertionRegistrer, + appAttestationRegistrer: AppAttestationRegistrer + ) throws { + self.verifyHost = verifyHost self.originVerifier = originVerifier self.assertionRegistrer = assertionRegistrer self.appAttestationRegistrer = appAttestationRegistrer @@ -27,12 +31,19 @@ public actor VerifyClient { try await appAttestationRegistrer.registerAttestationIfNeeded() } - public func verifyOrigin() async throws { - try await originVerifier.verifyOrigin() + public func verifyOrigin(assertionId: String) async throws -> String { + return try await originVerifier.verifyOrigin(assertionId: assertionId) + } + + public func createVerifyContext(origin: String?, domain: String) -> VerifyContext { + return VerifyContext( + origin: origin, + validation: (origin == domain) ? .valid : (origin == nil ? .unknown : .invalid), + verifyUrl: verifyHost + ) } public func registerAssertion() async throws { try await assertionRegistrer.registerAssertion() } - } diff --git a/Sources/WalletConnectVerify/VerifyClientFactory.swift b/Sources/WalletConnectVerify/VerifyClientFactory.swift index 7554a3189..b07648532 100644 --- a/Sources/WalletConnectVerify/VerifyClientFactory.swift +++ b/Sources/WalletConnectVerify/VerifyClientFactory.swift @@ -1,12 +1,12 @@ import Foundation import WalletConnectUtils -@available(iOS 14.0, *) -@available(macOS 11.0, *) public class VerifyClientFactory { - - public static func create() throws -> VerifyClient { - let originVerifier = OriginVerifier() + public static func create(verifyHost: String?) throws -> VerifyClient? { + guard let verifyHost else { + return nil + } + let originVerifier = OriginVerifier(verifyHost: verifyHost) let assertionRegistrer = AssertionRegistrer() let logger = ConsoleLogger(loggingLevel: .off) let keyValueStorage = UserDefaults.standard @@ -14,11 +14,18 @@ public class VerifyClientFactory { let attestKeyGenerator = AttestKeyGenerator(logger: logger, keyIdStorage: keyIdStorage) let attestChallengeProvider = AttestChallengeProvider() let keyAttestationService = KeyAttestationService() - let appAttestationRegistrer = AppAttestationRegistrer(logger: logger, - keyIdStorage: keyIdStorage, - attestKeyGenerator: attestKeyGenerator, - attestChallengeProvider: attestChallengeProvider, - keyAttestationService: keyAttestationService) - return try VerifyClient(originVerifier: originVerifier, assertionRegistrer: assertionRegistrer, appAttestationRegistrer: appAttestationRegistrer) + let appAttestationRegistrer = AppAttestationRegistrer( + logger: logger, + keyIdStorage: keyIdStorage, + attestKeyGenerator: attestKeyGenerator, + attestChallengeProvider: attestChallengeProvider, + keyAttestationService: keyAttestationService + ) + return try VerifyClient( + verifyHost: verifyHost, + originVerifier: originVerifier, + assertionRegistrer: assertionRegistrer, + appAttestationRegistrer: appAttestationRegistrer + ) } } diff --git a/Sources/WalletConnectVerify/VerifyContext.swift b/Sources/WalletConnectVerify/VerifyContext.swift new file mode 100644 index 000000000..094ea95ab --- /dev/null +++ b/Sources/WalletConnectVerify/VerifyContext.swift @@ -0,0 +1,17 @@ +public struct VerifyContext: Equatable, Hashable { + public enum ValidationStatus { + case unknown + case valid + case invalid + } + + public let origin: String? + public let validation: ValidationStatus + public let verifyUrl: String + + public init(origin: String?, validation: ValidationStatus, verifyUrl: String) { + self.origin = origin + self.validation = validation + self.verifyUrl = verifyUrl + } +} diff --git a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift index 1363b0c6a..ef51bd3b4 100644 --- a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift +++ b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift @@ -13,7 +13,7 @@ final class ChatClientProxy { } func request(_ request: RPCRequest) async throws { - guard let event = WebViewEvent(rawValue: request.method) + guard let event = ChatWebViewEvent(rawValue: request.method) else { throw Errors.unregisteredMethod } switch event { diff --git a/Sources/Web3Inbox/ConfigParam.swift b/Sources/Web3Inbox/ConfigParam.swift new file mode 100644 index 000000000..429be268d --- /dev/null +++ b/Sources/Web3Inbox/ConfigParam.swift @@ -0,0 +1,8 @@ + +import Foundation + +public enum ConfigParam { + case chatEnabled + case pushEnabled + case settingsEnabled +} diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift b/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift new file mode 100644 index 000000000..c64b22f0d --- /dev/null +++ b/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift @@ -0,0 +1,104 @@ +import Foundation + +final class PushClientProxy { + + private let client: WalletPushClient + + var onSign: SigningCallback + var onResponse: ((RPCResponse) async throws -> Void)? + + init(client: WalletPushClient, onSign: @escaping SigningCallback) { + self.client = client + self.onSign = onSign + } + + func request(_ request: RPCRequest) async throws { + guard let event = PushWebViewEvent(rawValue: request.method) + else { throw Errors.unregisteredMethod } + + switch event { + case .approve: + let params = try parse(ApproveRequest.self, params: request.params) + try await client.approve(id: params.id, onSign: onSign) + try await respond(request: request) + case .update: + let params = try parse(UpdateRequest.self, params: request.params) + try await client.update(topic: params.topic, scope: params.scope) + try await respond(request: request) + case .reject: + let params = try parse(RejectRequest.self, params: request.params) + try await client.reject(id: params.id) + try await respond(request: request) + case .subscribe: + let params = try parse(SubscribeRequest.self, params: request.params) + try await client.subscribe(metadata: params.metadata, account: params.account, onSign: onSign) + try await respond(request: request) + case .getActiveSubscriptions: + let subscriptions = client.getActiveSubscriptions() + try await respond(with: subscriptions, request: request) + case .getMessageHistory: + let params = try parse(GetMessageHistoryRequest.self, params: request.params) + let messages = client.getMessageHistory(topic: params.topic) + try await respond(with: messages, request: request) + case .deleteSubscription: + let params = try parse(DeleteSubscriptionRequest.self, params: request.params) + try await client.deleteSubscription(topic: params.topic) + try await respond(request: request) + case .deletePushMessage: + let params = try parse(DeletePushMessageRequest.self, params: request.params) + client.deletePushMessage(id: params.id) + try await respond(request: request) + } + } +} + +private extension PushClientProxy { + + private typealias Blob = Dictionary + + enum Errors: Error { + case unregisteredMethod + case unregisteredParams + } + + struct ApproveRequest: Codable { + let id: RPCID + } + + struct UpdateRequest: Codable { + let topic: String + let scope: Set + } + + struct RejectRequest: Codable { + let id: RPCID + } + + struct SubscribeRequest: Codable { + let metadata: AppMetadata + let account: Account + } + + struct GetMessageHistoryRequest: Codable { + let topic: String + } + + struct DeleteSubscriptionRequest: Codable { + let topic: String + } + + struct DeletePushMessageRequest: Codable { + let id: String + } + + func parse(_ type: Request.Type, params: AnyCodable?) throws -> Request { + guard let params = try params?.get(Request.self) + else { throw Errors.unregisteredParams } + return params + } + + func respond(with object: Object = Blob(), request: RPCRequest) async throws { + let response = RPCResponse(matchingRequest: request, result: object) + try await onResponse?(response) + } +} diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift new file mode 100644 index 000000000..e03e3378c --- /dev/null +++ b/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift @@ -0,0 +1,13 @@ +import Foundation + +enum PushClientRequest: String { + case pushRequest = "push_request" + case pushMessage = "push_message" + case pushUpdate = "push_update" + case pushDelete = "push_delete" + case pushSubscription = "push_subscription" + + var method: String { + return rawValue + } +} diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift new file mode 100644 index 000000000..7cb07c45a --- /dev/null +++ b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift @@ -0,0 +1,78 @@ +import Foundation +import Combine + +final class PushClientRequestSubscriber { + + private var publishers: Set = [] + + private let client: WalletPushClient + private let logger: ConsoleLogging + + var onRequest: ((RPCRequest) async throws -> Void)? + + init(client: WalletPushClient, logger: ConsoleLogging) { + self.client = client + self.logger = logger + + setupSubscriptions() + } + + func setupSubscriptions() { + client.requestPublisher.sink { [unowned self] id, account, metadata in + let params = RequestPayload(id: id, account: account, metadata: metadata) + handle(event: .pushRequest, params: params) + }.store(in: &publishers) + + client.pushMessagePublisher.sink { [unowned self] record in + handle(event: .pushMessage, params: record) + }.store(in: &publishers) + + client.deleteSubscriptionPublisher.sink { [unowned self] record in + handle(event: .pushDelete, params: record) + }.store(in: &publishers) + + client.subscriptionPublisher.sink { [unowned self] record in + switch record { + case .success(let subscription): + handle(event: .pushSubscription, params: subscription) + case .failure: + //TODO - handle error + break + + } + }.store(in: &publishers) + + client.updateSubscriptionPublisher.sink { [unowned self] record in + switch record { + case .success(let subscription): + handle(event: .pushUpdate, params: subscription) + case .failure: + //TODO - handle error + break + } + }.store(in: &publishers) + } +} + +private extension PushClientRequestSubscriber { + + struct RequestPayload: Codable { + let id: RPCID + let account: Account + let metadata: AppMetadata + } + + func handle(event: PushClientRequest, params: Codable) { + Task { + do { + let request = RPCRequest( + method: event.method, + params: params + ) + try await onRequest?(request) + } catch { + logger.error("Client Request error: \(error.localizedDescription)") + } + } + } +} diff --git a/Sources/Web3Inbox/Web3Inbox.swift b/Sources/Web3Inbox/Web3Inbox.swift index 1582c1ed1..32a1a87fb 100644 --- a/Sources/Web3Inbox/Web3Inbox.swift +++ b/Sources/Web3Inbox/Web3Inbox.swift @@ -4,13 +4,14 @@ public final class Web3Inbox { /// Web3Inbox client instance public static var instance: Web3InboxClient = { - guard let account, let onSign else { + guard let account, let config = config, let onSign else { fatalError("Error - you must call Web3Inbox.configure(_:) before accessing the shared instance.") } - return Web3InboxClientFactory.create(chatClient: Chat.instance, account: account, onSign: onSign) + return Web3InboxClientFactory.create(chatClient: Chat.instance, pushClient: Push.wallet, account: account, config: config, onSign: onSign) }() private static var account: Account? + private static var config: [ConfigParam: Bool]? private static var onSign: SigningCallback? private init() { } @@ -18,9 +19,11 @@ public final class Web3Inbox { /// Web3Inbox instance config method /// - Parameters: /// - account: Web3Inbox initial account - static public func configure(account: Account, onSign: @escaping SigningCallback) { + static public func configure(account: Account, config: [ConfigParam: Bool] = [:], onSign: @escaping SigningCallback, environment: APNSEnvironment) { Web3Inbox.account = account + Web3Inbox.config = config Web3Inbox.onSign = onSign Chat.configure() + Push.configure(environment: environment) } } diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift index 1fbb15bd4..730180633 100644 --- a/Sources/Web3Inbox/Web3InboxClient.swift +++ b/Sources/Web3Inbox/Web3InboxClient.swift @@ -7,28 +7,42 @@ public final class Web3InboxClient { private var account: Account private let logger: ConsoleLogging - private let clientProxy: ChatClientProxy - private let clientSubscriber: ChatClientRequestSubscriber + private let chatClientProxy: ChatClientProxy + private let chatClientSubscriber: ChatClientRequestSubscriber - private let webviewProxy: WebViewProxy - private let webviewSubscriber: WebViewRequestSubscriber + private let pushClientProxy: PushClientProxy + private let pushClientSubscriber: PushClientRequestSubscriber - init( + private let chatWebviewProxy: WebViewProxy + private let pushWebviewProxy: WebViewProxy + + private let chatWebviewSubscriber: WebViewRequestSubscriber + private let pushWebviewSubscriber: WebViewRequestSubscriber + +init( webView: WKWebView, account: Account, logger: ConsoleLogging, - clientProxy: ChatClientProxy, + chatClientProxy: ChatClientProxy, clientSubscriber: ChatClientRequestSubscriber, - webviewProxy: WebViewProxy, - webviewSubscriber: WebViewRequestSubscriber + chatWebviewProxy: WebViewProxy, + pushWebviewProxy: WebViewProxy, + chatWebviewSubscriber: WebViewRequestSubscriber, + pushWebviewSubscriber: WebViewRequestSubscriber, + pushClientProxy: PushClientProxy, + pushClientSubscriber: PushClientRequestSubscriber ) { self.webView = webView self.account = account self.logger = logger - self.clientProxy = clientProxy - self.clientSubscriber = clientSubscriber - self.webviewProxy = webviewProxy - self.webviewSubscriber = webviewSubscriber + self.chatClientProxy = chatClientProxy + self.chatClientSubscriber = clientSubscriber + self.chatWebviewProxy = chatWebviewProxy + self.pushWebviewProxy = pushWebviewProxy + self.chatWebviewSubscriber = chatWebviewSubscriber + self.pushWebviewSubscriber = pushWebviewSubscriber + self.pushClientProxy = pushClientProxy + self.pushClientSubscriber = pushClientSubscriber setupSubscriptions() } @@ -41,7 +55,7 @@ public final class Web3InboxClient { _ account: Account, onSign: @escaping SigningCallback ) async throws { - clientProxy.onSign = onSign + chatClientProxy.onSign = onSign try await authorize(account: account) } } @@ -51,14 +65,35 @@ public final class Web3InboxClient { private extension Web3InboxClient { func setupSubscriptions() { - webviewSubscriber.onRequest = { [unowned self] request in - try await self.clientProxy.request(request) + + // Chat + + chatClientProxy.onResponse = { [unowned self] response in + try await self.chatWebviewProxy.respond(response) + } + + chatClientSubscriber.onRequest = { [unowned self] request in + try await self.chatWebviewProxy.request(request) + } + + chatWebviewSubscriber.onRequest = { [unowned self] request in + logger.debug("w3i: chat method \(request.method) requested") + try await self.chatClientProxy.request(request) + } + + // Push + + pushClientProxy.onResponse = { [unowned self] response in + try await self.pushWebviewProxy.respond(response) } - clientProxy.onResponse = { [unowned self] response in - try await self.webviewProxy.respond(response) + + pushClientSubscriber.onRequest = { [unowned self] request in + try await self.pushWebviewProxy.request(request) } - clientSubscriber.onRequest = { [unowned self] request in - try await self.webviewProxy.request(request) + + pushWebviewSubscriber.onRequest = { [unowned self] request in + logger.debug("w3i: push method \(request.method) requested") + try await self.pushClientProxy.request(request) } } @@ -69,6 +104,6 @@ private extension Web3InboxClient { method: ChatClientRequest.setAccount.method, params: ["account": account.address] ) - try await webviewProxy.request(request) + try await chatWebviewProxy.request(request) } } diff --git a/Sources/Web3Inbox/Web3InboxClientFactory.swift b/Sources/Web3Inbox/Web3InboxClientFactory.swift index 76c01863c..50d255e29 100644 --- a/Sources/Web3Inbox/Web3InboxClientFactory.swift +++ b/Sources/Web3Inbox/Web3InboxClientFactory.swift @@ -5,29 +5,48 @@ final class Web3InboxClientFactory { static func create( chatClient: ChatClient, + pushClient: WalletPushClient, account: Account, + config: [ConfigParam: Bool], onSign: @escaping SigningCallback ) -> Web3InboxClient { - let host = hostUrlString(account: account) - let logger = ConsoleLogger(suffix: "📬") - let webviewSubscriber = WebViewRequestSubscriber(logger: logger) - let webView = WebViewFactory(host: host, webviewSubscriber: webviewSubscriber).create() - let webViewProxy = WebViewProxy(webView: webView) + let url = buildUrl(account: account, config: config) + let logger = ConsoleLogger(suffix: "📬", loggingLevel: .debug) + let chatWebviewSubscriber = WebViewRequestSubscriber(logger: logger) + let pushWebviewSubscriber = WebViewRequestSubscriber(logger: logger) + let webView = WebViewFactory(url: url, chatWebviewSubscriber: chatWebviewSubscriber, pushWebviewSubscriber: pushWebviewSubscriber).create() + let chatWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: ChatWebViewScriptFormatter(), logger: logger) + let pushWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: PushWebViewScriptFormatter(), logger: logger) + let clientProxy = ChatClientProxy(client: chatClient, onSign: onSign) let clientSubscriber = ChatClientRequestSubscriber(chatClient: chatClient, logger: logger) + let pushClientProxy = PushClientProxy(client: pushClient, onSign: onSign) + let pushClientSubscriber = PushClientRequestSubscriber(client: pushClient, logger: logger) + return Web3InboxClient( webView: webView, account: account, - logger: ConsoleLogger(), - clientProxy: clientProxy, + logger: logger, + chatClientProxy: clientProxy, clientSubscriber: clientSubscriber, - webviewProxy: webViewProxy, - webviewSubscriber: webviewSubscriber + chatWebviewProxy: chatWebViewProxy, + pushWebviewProxy: pushWebViewProxy, + chatWebviewSubscriber: chatWebviewSubscriber, + pushWebviewSubscriber: pushWebviewSubscriber, + pushClientProxy: pushClientProxy, + pushClientSubscriber: pushClientSubscriber ) } - private static func hostUrlString(account: Account) -> String { - return "https://web3inbox-dev-hidden.vercel.app/?chatProvider=ios&account=\(account.address)" + private static func buildUrl(account: Account, config: [ConfigParam: Bool]) -> URL { + var urlComponents = URLComponents(string: "https://web3inbox-dev-hidden.vercel.app/")! + var queryItems = [URLQueryItem(name: "chatProvider", value: "ios"), URLQueryItem(name: "pushProvider", value: "ios"), URLQueryItem(name: "account", value: account.address)] + + for param in config.filter({ $0.value == false}) { + queryItems.append(URLQueryItem(name: "\(param.key)", value: "false")) + } + urlComponents.queryItems = queryItems + return urlComponents.url! } } diff --git a/Sources/Web3Inbox/Web3InboxImports.swift b/Sources/Web3Inbox/Web3InboxImports.swift index 54b421e88..fd78f4977 100644 --- a/Sources/Web3Inbox/Web3InboxImports.swift +++ b/Sources/Web3Inbox/Web3InboxImports.swift @@ -1,3 +1,4 @@ #if !CocoaPods @_exported import WalletConnectChat +@_exported import WalletConnectPush #endif diff --git a/Sources/Web3Inbox/WebView/WebViewEvent.swift b/Sources/Web3Inbox/WebView/ChatWebViewEvent.swift similarity index 86% rename from Sources/Web3Inbox/WebView/WebViewEvent.swift rename to Sources/Web3Inbox/WebView/ChatWebViewEvent.swift index 3556bf3e8..eaf58ea47 100644 --- a/Sources/Web3Inbox/WebView/WebViewEvent.swift +++ b/Sources/Web3Inbox/WebView/ChatWebViewEvent.swift @@ -1,6 +1,6 @@ import Foundation -enum WebViewEvent: String { +enum ChatWebViewEvent: String { case getReceivedInvites case getSentInvites case getThreads diff --git a/Sources/Web3Inbox/WebView/PushWebViewEvent.swift b/Sources/Web3Inbox/WebView/PushWebViewEvent.swift new file mode 100644 index 000000000..5f898fa98 --- /dev/null +++ b/Sources/Web3Inbox/WebView/PushWebViewEvent.swift @@ -0,0 +1,12 @@ +import Foundation + +enum PushWebViewEvent: String { + case approve + case update + case reject + case subscribe + case getActiveSubscriptions + case getMessageHistory + case deleteSubscription + case deletePushMessage +} diff --git a/Sources/Web3Inbox/WebView/WebViewFactory.swift b/Sources/Web3Inbox/WebView/WebViewFactory.swift index bcddd0c9b..70a784c64 100644 --- a/Sources/Web3Inbox/WebView/WebViewFactory.swift +++ b/Sources/Web3Inbox/WebView/WebViewFactory.swift @@ -3,25 +3,36 @@ import WebKit final class WebViewFactory { - private let host: String - private let webviewSubscriber: WebViewRequestSubscriber + private let url: URL + private let chatWebviewSubscriber: WebViewRequestSubscriber + private let pushWebviewSubscriber: WebViewRequestSubscriber - init(host: String, webviewSubscriber: WebViewRequestSubscriber) { - self.host = host - self.webviewSubscriber = webviewSubscriber + init( + url: URL, + chatWebviewSubscriber: WebViewRequestSubscriber, + pushWebviewSubscriber: WebViewRequestSubscriber + ) { + self.url = url + self.chatWebviewSubscriber = chatWebviewSubscriber + self.pushWebviewSubscriber = pushWebviewSubscriber } func create() -> WKWebView { let configuration = WKWebViewConfiguration() configuration.allowsInlineMediaPlayback = true configuration.userContentController.add( - webviewSubscriber, - name: WebViewRequestSubscriber.name + chatWebviewSubscriber, + name: WebViewRequestSubscriber.chat + ) + configuration.userContentController.add( + pushWebviewSubscriber, + name: WebViewRequestSubscriber.push ) let webview = WKWebView(frame: .zero, configuration: configuration) - let request = URLRequest(url: URL(string: host)!) + + let request = URLRequest(url: url) webview.load(request) - webview.uiDelegate = webviewSubscriber + webview.uiDelegate = chatWebviewSubscriber return webview } } diff --git a/Sources/Web3Inbox/WebView/WebViewProxy.swift b/Sources/Web3Inbox/WebView/WebViewProxy.swift index 73130fede..5948b6660 100644 --- a/Sources/Web3Inbox/WebView/WebViewProxy.swift +++ b/Sources/Web3Inbox/WebView/WebViewProxy.swift @@ -4,29 +4,47 @@ import WebKit actor WebViewProxy { private let webView: WKWebView + private let scriptFormatter: WebViewScriptFormatter + private let logger: ConsoleLogging - init(webView: WKWebView) { + init(webView: WKWebView, + scriptFormatter: WebViewScriptFormatter, + logger: ConsoleLogging) { self.webView = webView + self.scriptFormatter = scriptFormatter + self.logger = logger } @MainActor func respond(_ response: RPCResponse) async throws { let body = try response.json() - let script = await formatScript(body: body) + logger.debug("resonding to w3i with \(body)") + let script = scriptFormatter.formatScript(body: body) webView.evaluateJavaScript(script, completionHandler: nil) } @MainActor func request(_ request: RPCRequest) async throws { let body = try request.json() - let script = await formatScript(body: body) + logger.debug("requesting w3i with \(body)") + let script = scriptFormatter.formatScript(body: body) webView.evaluateJavaScript(script, completionHandler: nil) } } -private extension WebViewProxy { +protocol WebViewScriptFormatter { + func formatScript(body: String) -> String +} + +class ChatWebViewScriptFormatter: WebViewScriptFormatter { + func formatScript(body: String) -> String { + return "window.web3inbox.chat.postMessage(\(body))" + } +} + +class PushWebViewScriptFormatter: WebViewScriptFormatter { func formatScript(body: String) -> String { - return "window.\(WebViewRequestSubscriber.name).chat.postMessage(\(body))" + return "window.web3inbox.push.postMessage(\(body))" } } diff --git a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift index 9b43a57c7..7df7480c4 100644 --- a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift +++ b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift @@ -3,7 +3,8 @@ import WebKit final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { - static let name = "web3inboxChat" + static let chat = "web3inboxChat" + static let push = "web3inboxPush" var onRequest: ((RPCRequest) async throws -> Void)? @@ -17,13 +18,12 @@ final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { - guard message.name == WebViewRequestSubscriber.name else { return } - + logger.debug("WebViewRequestSubscriber: received request from w3i") guard let body = message.body as? String, let data = body.data(using: .utf8), let request = try? JSONDecoder().decode(RPCRequest.self, from: data) else { return } - + logger.debug("request method: \(request.method)") Task { do { try await onRequest?(request) diff --git a/Sources/Web3Modal/Extensions/View+Backport.swift b/Sources/Web3Modal/Extensions/View+Backport.swift new file mode 100644 index 000000000..1946cd60c --- /dev/null +++ b/Sources/Web3Modal/Extensions/View+Backport.swift @@ -0,0 +1,15 @@ +import SwiftUI +import Combine + +extension View { + /// A backwards compatible wrapper for iOS 14 `onChange` + @ViewBuilder func onChangeBackported(of value: T, perform: @escaping (T) -> Void) -> some View { + if #available(iOS 14.0, *) { + self.onChange(of: value, perform: perform) + } else { + self.onReceive(Just(value)) { (value) in + perform(value) + } + } + } +} diff --git a/Sources/Web3Modal/QRCodeView.swift b/Sources/Web3Modal/QRCodeView.swift new file mode 100644 index 000000000..82b78386c --- /dev/null +++ b/Sources/Web3Modal/QRCodeView.swift @@ -0,0 +1,44 @@ +import SwiftUI +import QRCode + +struct QRCodeView: View { + + @State var doc: QRCode.Document! + + @Environment(\.colorScheme) var colorScheme: ColorScheme + + var body: some View { + + QRCodeViewUI( + content: Array(repeating: ["a", "b", "c", "1", "2", "3"], count: 50).flatMap({ $0 }).shuffled().joined(), + foregroundColor: colorScheme == .light ? UIColor.black.cgColor : UIColor.white.cgColor, + backgroundColor: colorScheme == .light ? UIColor.white.cgColor : UIColor.black.cgColor, + pixelStyle: QRCode.PixelShape.Vertical( + insetFraction: 0.2, + cornerRadiusFraction: 1 + ), + eyeStyle: QRCode.EyeShape.Squircle(), + logoTemplate: QRCode.LogoTemplate( + image: (UIImage(named: "wc_logo", in: .module, with: .none)?.cgImage)!, + path: CGPath( + rect: CGRect(x: 0.35, y: 0.3875, width: 0.30, height: 0.225), + transform: nil + ) + ) + ) + .padding(.bottom, 40) + } +} + + +struct QRCodeView_Previews: PreviewProvider { + static var previews: some View { + QRCodeView() + .previewLayout(.sizeThatFits) + .preferredColorScheme(.dark) + + QRCodeView() + .previewLayout(.sizeThatFits) + .preferredColorScheme(.light) + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/accent.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/accent.colorset/Contents.json new file mode 100644 index 000000000..7157df412 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/accent.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "255", + "green" : "150", + "red" : "51" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xA1", + "red" : "0x47" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/background1.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background1.colorset/Contents.json new file mode 100644 index 000000000..855c60979 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background1.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/background2.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background2.colorset/Contents.json new file mode 100644 index 000000000..c32b826d0 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background2.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF3", + "green" : "0xF3", + "red" : "0xF1" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2A", + "green" : "0x2A", + "red" : "0x27" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/background3.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background3.colorset/Contents.json new file mode 100644 index 000000000..bd44d87a7 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/background3.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE7", + "green" : "0xE7", + "red" : "0xE4" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x40", + "green" : "0x40", + "red" : "0x3B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground1.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground1.colorset/Contents.json new file mode 100644 index 000000000..3142df2de --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground1.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE7", + "green" : "0xE7", + "red" : "0xE4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground2.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground2.colorset/Contents.json new file mode 100644 index 000000000..530bc2ce5 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground2.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x86", + "green" : "0x86", + "red" : "0x79" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9E", + "green" : "0x9E", + "red" : "0x94" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground3.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground3.colorset/Contents.json new file mode 100644 index 000000000..93ad845ea --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foreground3.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xA9", + "green" : "0xA9", + "red" : "0x9E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x77", + "green" : "0x77", + "red" : "0x6E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/foregroundInverse.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foregroundInverse.colorset/Contents.json new file mode 100644 index 000000000..2536dc2d1 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/foregroundInverse.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/negative.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/negative.colorset/Contents.json new file mode 100644 index 000000000..01cebdc26 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/negative.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x42", + "green" : "0x51", + "red" : "0xF0" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x67", + "green" : "0x5A", + "red" : "0xF2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/thickOverlay.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/thickOverlay.colorset/Contents.json new file mode 100644 index 000000000..dd489ec2c --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/thickOverlay.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.300", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.300", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/colors/thinOverlay.colorset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/colors/thinOverlay.colorset/Contents.json new file mode 100644 index 000000000..bac5e9b1a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/colors/thinOverlay.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.100", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.100", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Browser.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Browser.pdf new file mode 100644 index 000000000..b7944ba52 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Browser.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Contents.json new file mode 100644 index 000000000..c45b00a1d --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Browser.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Browser.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/Contents.json new file mode 100644 index 000000000..3406b5cf5 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "DAO.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/DAO.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/DAO.pdf new file mode 100644 index 000000000..53cd61496 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/DAO.imageset/DAO.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/Contents.json new file mode 100644 index 000000000..b7797a066 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "DeFi.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/DeFi.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/DeFi.pdf new file mode 100644 index 000000000..64e3298c0 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/DeFi.imageset/DeFi.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/Contents.json new file mode 100644 index 000000000..a6132faf7 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ETH.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/ETH.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/ETH.pdf new file mode 100644 index 000000000..484421e4e Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/ETH.imageset/ETH.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Contents.json new file mode 100644 index 000000000..0c147bee4 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Layers.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Layers.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Layers.pdf new file mode 100644 index 000000000..0edfb7734 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Layers.imageset/Layers.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Contents.json new file mode 100644 index 000000000..4b52ef2bc --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Lock.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Lock.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Lock.pdf new file mode 100644 index 000000000..59e30d6e5 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Lock.imageset/Lock.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Contents.json new file mode 100644 index 000000000..509ec01f3 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Login.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Login.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Login.pdf new file mode 100644 index 000000000..f1d3aaf66 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Login.imageset/Login.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/Contents.json new file mode 100644 index 000000000..762deb793 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "NFT.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/NFT.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/NFT.pdf new file mode 100644 index 000000000..31794831e Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/NFT.imageset/NFT.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Contents.json new file mode 100644 index 000000000..30b0cf9c7 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Network.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Network.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Network.pdf new file mode 100644 index 000000000..ad24230ca Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Network.imageset/Network.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Contents.json new file mode 100644 index 000000000..f934274a6 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Noun.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Noun.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Noun.pdf new file mode 100644 index 000000000..20e469623 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Noun.imageset/Noun.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Contents.json new file mode 100644 index 000000000..3b3fd8aee --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Profile.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Profile.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Profile.pdf new file mode 100644 index 000000000..fafa0706e Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/Profile.imageset/Profile.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/Contents.json new file mode 100644 index 000000000..7c2d02756 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "System.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/System.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/System.pdf new file mode 100644 index 000000000..8d17755bd Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/help/System.imageset/System.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Close.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Close.pdf new file mode 100644 index 000000000..f29dd2ed6 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Close.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Contents.json new file mode 100644 index 000000000..544a5d9ea --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/close.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Close.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/Contents.json new file mode 100644 index 000000000..be6a2f0ba --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "External Link.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/External Link.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/External Link.pdf new file mode 100644 index 000000000..58f827c20 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/external_link.imageset/External Link.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Contents.json new file mode 100644 index 000000000..4fe36f6b1 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Help.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Help.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Help.pdf new file mode 100644 index 000000000..4f8e4d227 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/help.imageset/Help.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Contents.json new file mode 100644 index 000000000..7cd8eaf7e --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Copy.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Copy.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Copy.pdf new file mode 100644 index 000000000..ada796931 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/copy_large.imageset/Copy.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/Contents.json new file mode 100644 index 000000000..e2a5a8e19 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "QRCode.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/QRCode.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/QRCode.pdf new file mode 100644 index 000000000..c598193e7 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/large/qr_large.imageset/QRCode.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Contents.json new file mode 100644 index 000000000..5e974e19c --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Wallet.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Wallet.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Wallet.pdf new file mode 100644 index 000000000..c7c0ebf9f Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/icons/wallet.imageset/Wallet.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/Contents.json new file mode 100644 index 000000000..cfe764035 --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "LogoLockup.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/LogoLockup.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/LogoLockup.pdf new file mode 100644 index 000000000..1f0698299 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/walletconnect_logo.imageset/LogoLockup.pdf differ diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/Contents.json b/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/Contents.json new file mode 100644 index 000000000..9797adf2f --- /dev/null +++ b/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "WCLOGO.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/WCLOGO.pdf b/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/WCLOGO.pdf new file mode 100644 index 000000000..551f66c99 Binary files /dev/null and b/Sources/Web3Modal/Resources/Assets.xcassets/wc_logo.imageset/WCLOGO.pdf differ diff --git a/Sources/Web3Modal/UIKitSupport/Web3ModalSheetController.swift b/Sources/Web3Modal/UIKitSupport/Web3ModalSheetController.swift new file mode 100644 index 000000000..61b9cb237 --- /dev/null +++ b/Sources/Web3Modal/UIKitSupport/Web3ModalSheetController.swift @@ -0,0 +1,19 @@ +import SwiftUI + +public class Web3ModalSheetController: UIHostingController { + + @MainActor required dynamic init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init() { + super.init(rootView: Web3ModalContainerView()) + self.modalTransitionStyle = .crossDissolve + self.modalPresentationStyle = .overFullScreen + } + + public override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .clear + } +} diff --git a/Sources/Web3Modal/Web3ModalContainerView.swift b/Sources/Web3Modal/Web3ModalContainerView.swift new file mode 100644 index 000000000..8d77ee4d3 --- /dev/null +++ b/Sources/Web3Modal/Web3ModalContainerView.swift @@ -0,0 +1,51 @@ +import SwiftUI + +public struct Web3ModalContainerView: View { + + @Environment(\.presentationMode) var presentationMode + + @State var showModal: Bool = false + + public init() { + + } + + public var body: some View { + + VStack(spacing: 0) { + + Color.black.opacity(0.3) + .onTapGesture { + withAnimation { + showModal = false + } + } + + if showModal { + Web3ModalSheet(destination: .welcome, isShown: $showModal) + .transition(.move(edge: .bottom)) + .animation(.spring(), value: showModal) + } + } + .edgesIgnoringSafeArea(.all) + .onChangeBackported(of: showModal, perform: { newValue in + if newValue == false { + dismiss() + } + }) + .onAppear { + withAnimation { + showModal = true + } + } + } + + private func dismiss() { + // Small delay so the sliding transition can happen before cross disolve starts + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + presentationMode.wrappedValue.dismiss() + } + } +} + + diff --git a/Sources/Web3Modal/Web3ModalSheet.swift b/Sources/Web3Modal/Web3ModalSheet.swift new file mode 100644 index 000000000..d4039f3d8 --- /dev/null +++ b/Sources/Web3Modal/Web3ModalSheet.swift @@ -0,0 +1,91 @@ +import SwiftUI + +public struct Web3ModalSheet: View { + + @State public var destination: Destination + @Binding public var isShown: Bool + + public enum Destination: String { + case welcome + case help + case qr + } + + public var body: some View { + ZStack(alignment: .top) { + Color.white + + VStack { + Color.blue + .frame(height: 40) + .overlay( + HStack() { + backButton() + .disabled(destination == .welcome) + + Spacer() + + closeButton() + }, + alignment: .topTrailing + ) + + switch destination { + case .welcome: + + Button("Help") { + withAnimation { + destination = .help + } + } + + Button("QR") { + withAnimation { + destination = .qr + } + } + case .help: + WhatIsWalletView() + case .qr: + QRCodeView() + } + } + } + } + + func backButton() -> some View { + Button(action: { + withAnimation { + destination = .welcome + } + }, label: { + Image(systemName: "chevron.backward") + .foregroundColor(.black) + }) + .padding() + } + + func closeButton() -> some View { + Button(action: { + withAnimation { + isShown = false + } + }, label: { + Image(systemName: "x.circle") + .foregroundColor(.black) + }) + .padding() + } +} + +struct Web3ModalSheet_Previews: PreviewProvider { + static var previews: some View { + Web3ModalSheet(destination: .welcome, isShown: .constant(true)) + .previewLayout(.sizeThatFits) + .preferredColorScheme(.light) + + Web3ModalSheet(destination: .qr, isShown: .constant(true)) + .previewLayout(.sizeThatFits) + .preferredColorScheme(.dark) + } +} diff --git a/Sources/Web3Modal/WhatIsWalletView.swift b/Sources/Web3Modal/WhatIsWalletView.swift new file mode 100644 index 000000000..847d9b9e7 --- /dev/null +++ b/Sources/Web3Modal/WhatIsWalletView.swift @@ -0,0 +1,97 @@ +import SwiftUI + +struct WhatIsWalletView: View { + + var body: some View { + + VStack(spacing: 10) { + HelpSection( + title: "A home for your digital assets", + description: "A wallet lets you store, send and receive digital assets like cryptocurrencies and NFTs.", + assets: ["DeFi", "NFT", "ETH"] + ) + HelpSection( + title: "One login for all of web3", + description: "Log in to any app by connecting your wallet. Say goodbye to countless passwords!", + assets: ["Login", "Profile", "Lock"] + ) + HelpSection( + title: "Your gateway to a new web", + description: "With your wallet, you can explore and interact with DeFi, NFTs, DAOs, and much more.", + assets: ["Browser", "Noun", "DAO"] + ) + + HStack { + Button(action: {}) { + HStack { + Image("wallet", bundle: .module) + Text("Get a Wallet") + } + } + Button(action: {}) { + HStack { + Text("Learn More") + Image("external_link", bundle: .module) + } + } + } + .buttonStyle(W3MButtonStyle()) + } + .padding(34) + } +} + +extension Color { + static let foreground1 = Color(red: 20/255, green: 20/255, blue: 20/255) + static let foreground2 = Color(red: 121/255, green: 134/255, blue: 134/255) + + static let blueDark = Color(red: 71/255, green: 161/255, blue: 255/255) +} + +struct HelpSection: View { + + let title: String + let description: String + let assets: [String] + + var body: some View { + VStack { + HStack { + ForEach(assets, id: \.self) { asset in + Image(asset, bundle: .module) + } + } + + Text(title) + .font(.system(size: 16)) + .foregroundColor(.foreground1) + .fontWeight(.medium) + .multilineTextAlignment(.center) + Text(description) + .font(.system(size: 14)) + .foregroundColor(.foreground2) + .multilineTextAlignment(.center) + } + .padding(.top, 10) + .padding(.bottom, 5) + } +} + +struct WhatIsWalletView_Previews: PreviewProvider { + + static var previews: some View { + + WhatIsWalletView() + } +} + +struct W3MButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(Color.blueDark) + .foregroundColor(.white) + .clipShape(Capsule()) + } +} diff --git a/Sources/Web3Wallet/Web3Wallet.swift b/Sources/Web3Wallet/Web3Wallet.swift index c016d47d1..ffdf09f16 100644 --- a/Sources/Web3Wallet/Web3Wallet.swift +++ b/Sources/Web3Wallet/Web3Wallet.swift @@ -1,6 +1,9 @@ import Foundation import Combine +import WalletConnectVerify + +public typealias VerifyContext = WalletConnectVerify.VerifyContext /// Web3Wallet instance wrapper /// /// ```Swift diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 58cb53ce6..0f117f0a8 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -8,25 +8,25 @@ import Combine /// 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 { + public var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> { signClient.sessionProposalPublisher.eraseToAnyPublisher() } /// Publisher that sends session request /// /// In most cases event will be emited on wallet - public var sessionRequestPublisher: AnyPublisher { + public var sessionRequestPublisher: AnyPublisher<(request: Request, context: VerifyContext?), Never> { signClient.sessionRequestPublisher.eraseToAnyPublisher() } /// Publisher that sends authentication requests /// /// Wallet should subscribe on events in order to receive auth requests. - public var authRequestPublisher: AnyPublisher { + public var authRequestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { authClient.authRequestPublisher.eraseToAnyPublisher() } diff --git a/Tests/AuthTests/WalletRequestSubscriberTests.swift b/Tests/AuthTests/WalletRequestSubscriberTests.swift index 4cb6e3c68..8690a69da 100644 --- a/Tests/AuthTests/WalletRequestSubscriberTests.swift +++ b/Tests/AuthTests/WalletRequestSubscriberTests.swift @@ -24,7 +24,8 @@ class WalletRequestSubscriberTests: XCTestCase { logger: ConsoleLoggerMock(), kms: KeyManagementServiceMock(), walletErrorResponder: walletErrorResponder, - pairingRegisterer: pairingRegisterer) + pairingRegisterer: pairingRegisterer, + verifyClient: nil) } func testSubscribeRequest() { @@ -35,13 +36,13 @@ class WalletRequestSubscriberTests: XCTestCase { var requestId: RPCID! var requestPayload: AuthPayload! - sut.onRequest = { request in - requestId = request.id - requestPayload = request.payload + sut.onRequest = { result in + requestId = result.request.id + requestPayload = result.request.payload messageExpectation.fulfill() } - let payload = RequestSubscriptionPayload(id: expectedRequestId, topic: "123", request: AuthRequestParams.stub(id: expectedRequestId, iat: iat), publishedAt: Date(), derivedTopic: nil) + let payload = RequestSubscriptionPayload(id: expectedRequestId, topic: "123", request: AuthRequestParams.stub(id: expectedRequestId, iat: iat), decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil) pairingRegisterer.subject.send(payload) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index ed6edbd63..14f7f9d24 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -6,7 +6,6 @@ import WalletConnectKMS import WalletConnectNetworking public class NetworkingInteractorMock: NetworkInteracting { - private(set) var subscriptions: [String] = [] private(set) var unsubscriptions: [String] = [] @@ -30,10 +29,10 @@ public class NetworkingInteractorMock: NetworkInteracting { socketConnectionStatusPublisherSubject.eraseToAnyPublisher() } - public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, publishedAt: Date, derivedTopic: String?), Never>() + public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never>() public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: JSONRPC.RPCRequest, publishedAt: Date, derivedTopic: String?), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: JSONRPC.RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -47,9 +46,9 @@ public class NetworkingInteractorMock: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, publishedAt, derivedTopic in + .compactMap { topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, publishedAt: publishedAt, derivedTopic: derivedTopic) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic) } .eraseToAnyPublisher() } diff --git a/Tests/VerifyTests/AppAttestationRegistrerTests.swift b/Tests/VerifyTests/AppAttestationRegistrerTests.swift index ceab96b01..d50edd182 100644 --- a/Tests/VerifyTests/AppAttestationRegistrerTests.swift +++ b/Tests/VerifyTests/AppAttestationRegistrerTests.swift @@ -3,6 +3,7 @@ import XCTest import WalletConnectUtils import TestingUtils @testable import WalletConnectVerify +@testable import WalletConnectSign @available(iOS 14.0, *) @available(macOS 11.0, *) @@ -29,6 +30,16 @@ class AppAttestationRegistrerTests: XCTestCase { keyAttestationService: keyAttestationService) } + func testHash() { + let string = """ + {"id":1681835052048874,"jsonrpc":"2.0","method":"wc_sessionPropose","params":{"requiredNamespaces":{"eip155":{"methods":["eth_sendTransaction","eth_signTransaction","eth_sign","personal_sign","eth_signTypedData"],"chains":["eip155:1"],"events":["chainChanged","accountsChanged"]}},"optionalNamespaces":{},"relays":[{"protocol":"irn"}],"proposer":{"publicKey":"9644bb921f5628ec3325b4027229976172a4ab71043fe0e1174acfa237f0592b","metadata":{"description":"React App for WalletConnect","url":"http://localhost:3000","icons":["https://avatars.githubusercontent.com/u/37784886"],"name":"React App"}}}} + """ + let sha256 = string.rawRepresentation.sha256().toHexString() + print(sha256) + XCTAssertEqual("c52ef2f630a172c4a3ae7ef5750b7662a904273fc81d1e892c5dd0c508c09583", sha256) + } + + func testAttestation() async { try! await sut.registerAttestationIfNeeded() XCTAssertTrue(attestKeyGenerator.keysGenerated) diff --git a/Tests/WalletConnectKMSTests/SerialiserTests.swift b/Tests/WalletConnectKMSTests/SerialiserTests.swift index 02822ff25..627171b5a 100644 --- a/Tests/WalletConnectKMSTests/SerialiserTests.swift +++ b/Tests/WalletConnectKMSTests/SerialiserTests.swift @@ -23,7 +23,7 @@ final class SerializerTests: XCTestCase { _ = try! myKms.createSymmetricKey(topic) let messageToSerialize = "todo - change for request object" let serializedMessage = try! mySerializer.serialize(topic: topic, encodable: messageToSerialize, envelopeType: .type0) - let (deserializedMessage, _): (String, String?) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! + let (deserializedMessage, _, _): (String, String?, Data) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! XCTAssertEqual(messageToSerialize, deserializedMessage) } @@ -39,7 +39,7 @@ final class SerializerTests: XCTestCase { let serializedMessage = try! peerSerializer.serialize(topic: topic, encodable: messageToSerialize, envelopeType: .type1(pubKey: peerPubKey.rawRepresentation)) print(agreementKeys.sharedKey.hexRepresentation) // -----------Me Deserialising ------------------- - let (deserializedMessage, _): (String, String?) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! + let (deserializedMessage, _, _): (String, String?, Data) = mySerializer.tryDeserialize(topic: topic, encodedEnvelope: serializedMessage)! XCTAssertEqual(messageToSerialize, deserializedMessage) } } diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift index 27a48066d..670b253e4 100644 --- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift +++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift @@ -68,7 +68,8 @@ final class AppProposalServiceTests: XCTestCase { kms: cryptoMock, logger: logger, pairingStore: storageMock, - sessionStore: WCSessionStorageMock() + sessionStore: WCSessionStorageMock(), + verifyClient: nil ) } diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 3bcc34e01..a41b1cd46 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -40,7 +40,8 @@ final class ApproveEngineTests: XCTestCase { kms: cryptoMock, logger: ConsoleLoggerMock(), pairingStore: pairingStorageMock, - sessionStore: sessionStorageMock + sessionStore: sessionStorageMock, + verifyClient: nil ) } @@ -60,8 +61,8 @@ final class ApproveEngineTests: XCTestCase { pairingStorageMock.setPairing(pairing) let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date(), derivedTopic: nil)) - + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) + try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary()) let topicB = networkingInteractor.subscriptions.last! @@ -80,11 +81,11 @@ final class ApproveEngineTests: XCTestCase { let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - engine.onSessionProposal = { _ in + engine.onSessionProposal = { _, _ in sessionProposed = true } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, publishedAt: Date(), derivedTopic: nil)) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) XCTAssertNotNil(try! proposalPayloadsStore.get(key: proposal.proposer.publicKey), "Proposer must store proposal payload") XCTAssertTrue(sessionProposed) } @@ -108,7 +109,7 @@ final class ApproveEngineTests: XCTestCase { didCallBackOnSessionApproved = true } sessionTopicToProposal.set(SessionProposal.stub().publicRepresentation(pairingTopic: ""), forKey: sessionTopic) - networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Data(), Date(), "")) usleep(100) diff --git a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift index ceca38ac3..aa4c917ca 100644 --- a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift +++ b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift @@ -35,7 +35,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { didCallbackUpdatMethods = true XCTAssertEqual(topic, session.topic) } - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) XCTAssertTrue(didCallbackUpdatMethods) usleep(100) XCTAssertTrue(networkingInteractor.didRespondSuccess) @@ -51,7 +51,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { // } func testUpdateMethodPeerErrorSessionNotFound() { - networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 7001) @@ -60,7 +60,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { func testUpdateMethodPeerErrorUnauthorized() { let session = WCSession.stub(isSelfController: true) // Peer is not a controller storageMock.setSession(session) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 3003) @@ -74,7 +74,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Data(), Date(), nil)) let extendedSession = storageMock.getAll().first {$0.topic == session.topic}! print(extendedSession.expiryDate) @@ -87,7 +87,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Data(), Date(), nil)) let potentiallyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentiallyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended for peer non controller request ") @@ -98,7 +98,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow) storageMock.setSession(session) let tenDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Data(), Date(), nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to high") @@ -110,7 +110,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let oneDayFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Data(), Date(), nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to low") } diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index c5a2d36e6..4bd18ff2b 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -22,6 +22,7 @@ final class SessionEngineTests: XCTestCase { ) ) ), + verifyClient: nil, kms: KeyManagementServiceMock(), sessionStore: sessionStorage, logger: ConsoleLoggerMock() @@ -47,7 +48,7 @@ final class SessionEngineTests: XCTestCase { expiry: UInt64(Date().timeIntervalSince1970) ) - networkingInteractor.requestPublisherSubject.send(("topic", request, Date(), nil)) + networkingInteractor.requestPublisherSubject.send(("topic", request, Data(), Date(), "")) wait(for: [expectation], timeout: 0.5) } diff --git a/Tests/Web3WalletTests/Mocks/AuthClientMock.swift b/Tests/Web3WalletTests/Mocks/AuthClientMock.swift index 4f3e50c39..535ace60a 100644 --- a/Tests/Web3WalletTests/Mocks/AuthClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/AuthClientMock.swift @@ -1,7 +1,7 @@ import Foundation import Combine -@testable import Auth +@testable import Auth final class AuthClientMock: AuthClientProtocol { var respondCalled = false @@ -26,8 +26,8 @@ final class AuthClientMock: AuthClientProtocol { ) } - var authRequestPublisher: AnyPublisher { - return Result.Publisher(authRequest).eraseToAnyPublisher() + var authRequestPublisher: AnyPublisher<(request: AuthRequest, context: VerifyContext?), Never> { + return Result.Publisher((authRequest, nil)).eraseToAnyPublisher() } func formatMessage(payload: AuthPayload, address: String) throws -> String { diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index bf0fd1f03..3a9b6336d 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -3,7 +3,6 @@ import Combine @testable import WalletConnectSign - final class SignClientMock: SignClientProtocol { var approveCalled = false var rejectCalled = false @@ -19,7 +18,7 @@ final class SignClientMock: SignClientProtocol { private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: "", chainId: Blockchain("eip155:1")!, expiry: nil) private let response = WalletConnectSign.Response(id: RPCID(1234567890123456789), topic: "", chainId: "", result: .response(AnyCodable(any: ""))) - var sessionProposalPublisher: AnyPublisher { + var sessionProposalPublisher: AnyPublisher<(proposal: WalletConnectSign.Session.Proposal, context: VerifyContext?), Never> { let proposer = Participant(publicKey: "", metadata: metadata) let sessionProposal = WalletConnectSign.SessionProposal( relays: [], @@ -29,12 +28,12 @@ final class SignClientMock: SignClientProtocol { sessionProperties: nil ).publicRepresentation(pairingTopic: "") - return Result.Publisher(sessionProposal) + return Result.Publisher((sessionProposal, nil)) .eraseToAnyPublisher() } - var sessionRequestPublisher: AnyPublisher { - return Result.Publisher(request) + var sessionRequestPublisher: AnyPublisher<(request: WalletConnectSign.Request, context: VerifyContext?), Never> { + return Result.Publisher((request, nil)) .eraseToAnyPublisher() } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 76a63d5f8..b7e71fc34 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -86,7 +86,9 @@ platform :ios do git_url: "https://github.com/WalletConnect/match-swift.git", git_basic_authorization: options[:token], api_key: api_key, - include_all_certificates: true + include_all_certificates: true, + force_for_new_devices: true, + force_for_new_certificates: true ) number = latest_testflight_build_number( app_identifier: ENV["APP_IDENTIFIER"],