diff --git a/.env.Showcase b/.env.Showcase new file mode 100644 index 000000000..c08277921 --- /dev/null +++ b/.env.Showcase @@ -0,0 +1,3 @@ +SCHEME = "Showcase" +APP_IDENTIFIER = "com.walletconnect.chat" +APPLE_ID = "1634760092" \ No newline at end of file diff --git a/.env.WalletApp b/.env.WalletApp new file mode 100644 index 000000000..5ac0d15e7 --- /dev/null +++ b/.env.WalletApp @@ -0,0 +1,3 @@ +SCHEME = "WalletApp" +APP_IDENTIFIER = "com.walletconnect.walletapp" +APPLE_ID = "1667351690" \ No newline at end of file diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index e3f1b5e5f..1c105f783 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -19,13 +19,7 @@ runs: - name: Run tests if: inputs.type == 'unit-tests' shell: bash - run: "xcodebuild \ - -project Example/ExampleApp.xcodeproj \ - -scheme WalletConnect \ - -clonedSourcePackagesDirPath SourcePackagesCache \ - -destination 'platform=iOS Simulator,name=iPhone 13' \ - -derivedDataPath DerivedDataCache \ - test" + run: make unit_tests # Integration tests - name: Run integration tests @@ -34,46 +28,23 @@ runs: env: RELAY_ENDPOINT: ${{ inputs.relay-endpoint }} PROJECT_ID: ${{ inputs.project-id }} - run: "xcodebuild \ - -project Example/ExampleApp.xcodeproj \ - -scheme IntegrationTests \ - -clonedSourcePackagesDirPath SourcePackagesCache \ - -destination 'platform=iOS Simulator,name=iPhone 13' \ - -derivedDataPath DerivedDataCache \ - RELAY_HOST='$RELAY_ENDPOINT' \ - PROJECT_ID='$PROJECT_ID' \ - test" + run: make integration_tests RELAY_HOST=$RELAY_ENDPOINT PROJECT_ID=$PROJECT_ID # Wallet build - name: Build Example Wallet if: inputs.type == 'build-example-wallet' shell: bash - run: "xcodebuild \ - -project Example/ExampleApp.xcodeproj \ - -scheme Wallet \ - -clonedSourcePackagesDirPath SourcePackagesCache \ - -destination 'platform=iOS Simulator,name=iPhone 13' \ - -derivedDataPath DerivedDataCache" + run: make build_wallet # DApp build - name: Build Example Dapp if: inputs.type == 'build-example-dapp' shell: bash - run: "xcodebuild \ - -project Example/ExampleApp.xcodeproj \ - -scheme DApp \ - -clonedSourcePackagesDirPath SourcePackagesCache \ - -destination 'platform=iOS Simulator,name=iPhone 13' \ - -derivedDataPath DerivedDataCache" + run: make build_dapp # UI tests - name: UI Tests if: inputs.type == 'ui-tests' shell: bash - run: "xcodebuild \ - -project Example/ExampleApp.xcodeproj \ - -scheme UITests \ - -derivedDataPath DerivedDataCache \ - -clonedSourcePackagesDirPath SourcePackagesCache \ - -destination 'platform=iOS Simulator,name=iPhone 13' test" + run: make ui_tests continue-on-error: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a71b4ce7e..e1846f591 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,37 +42,9 @@ jobs: - name: Resolve Dependencies shell: bash - run: " - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache -destination 'platform=iOS Simulator,name=iPhone 13'; \ - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache -destination 'platform=iOS Simulator,name=iPhone 13'" + run: make resolve_packages - uses: ./.github/actions/ci with: type: ${{ matrix.test-type }} project-id: ${{ secrets.PROJECT_ID }} - - test-ui: - if: github.ref == 'refs/heads/main' - runs-on: macos-latest - strategy: - matrix: - test-type: [ui-tests] - - steps: - - uses: actions/checkout@v2 - - - name: Setup Xcode Version - uses: maxim-lobanov/setup-xcode@v1 - - - uses: actions/cache@v2 - with: - path: | - .build - SourcePackagesCache - key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-spm- - - - uses: ./.github/actions/ci - with: - type: ${{ matrix.test-type }} diff --git a/.gitignore b/.gitignore index dad29299f..64e06f6b1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,18 @@ Package.resolved /*.xcodeproj # Fastlane -*/fastlane/report.xml -*/fastlane/Preview.html -*/fastlane/screenshots/**/*.png -*/fastlane/test_output -*/fastlane/README.md +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output +fastlane/README.md # Configuration Configuration.xcconfig +# Cache +SourcePackagesCache +DerivedDataCache + +# Artifacts +*.ipa \ No newline at end of file diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme deleted file mode 100644 index 3cf5ef95b..000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectUtilsTests.xcscheme +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/DApp/Info.plist b/Example/DApp/Info.plist index 07ba0e705..ec622dae1 100644 --- a/Example/DApp/Info.plist +++ b/Example/DApp/Info.plist @@ -2,10 +2,14 @@ - PROJECT_ID - $(PROJECT_ID) + CFBundleVersion + 7 + CFBundleShortVersionString + $(MARKETING_VERSION) ITSAppUsesNonExemptEncryption + PROJECT_ID + $(PROJECT_ID) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift index 8a7d256fa..d08e5287c 100644 --- a/Example/DApp/Sign/Accounts/AccountsViewController.swift +++ b/Example/DApp/Sign/Accounts/AccountsViewController.swift @@ -1,5 +1,7 @@ import UIKit import WalletConnectSign +import WalletConnectPush +import Combine struct AccountDetails { let chain: String @@ -12,7 +14,9 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT let session: Session var accountsDetails: [AccountDetails] = [] var onDisconnect: (() -> Void)? + var pushSubscription: PushSubscription? + private var publishers = [AnyCancellable]() private let accountsView: AccountsView = { AccountsView() }() @@ -34,12 +38,21 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT super.viewDidLoad() navigationItem.title = "Accounts" + navigationItem.rightBarButtonItem = UIBarButtonItem( title: "Disconnect", style: .plain, target: self, action: #selector(disconnect) ) + + navigationItem.leftBarButtonItem = UIBarButtonItem( + title: "Push Test", + style: .plain, + target: self, + action: #selector(pushTest) + ) + accountsView.tableView.dataSource = self accountsView.tableView.delegate = self session.namespaces.values.forEach { namespace in @@ -49,6 +62,27 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT } } + func proposePushSubscription() { + let account = session.namespaces.values.first!.accounts.first! + + Task(priority: .high){ try! await Push.dapp.request(account: account, topic: session.pairingTopic)} + Push.dapp.responsePublisher.sink { (id: RPCID, result: Result) in + switch result { + case .success(let subscription): + self.pushSubscription = subscription + case .failure(let error): + print(error) + } + }.store(in: &publishers) + } + + @objc + private func pushTest() { + guard let pushTopic = pushSubscription?.topic else {return} + let message = PushMessage(title: "Push Message", body: "He,y this is a message from the swift client", icon: "", url: "") + Task(priority: .userInitiated) { try! await Push.dapp.notify(topic: pushTopic, message: message) } + } + @objc private func disconnect() { Task { diff --git a/Example/DApp/Sign/Connect/ConnectViewController.swift b/Example/DApp/Sign/Connect/ConnectViewController.swift index 3e3da5422..c7fc8bcbe 100644 --- a/Example/DApp/Sign/Connect/ConnectViewController.swift +++ b/Example/DApp/Sign/Connect/ConnectViewController.swift @@ -91,7 +91,7 @@ class ConnectViewController: UIViewController, UITableViewDataSource, UITableVie "eth_sendTransaction", "personal_sign", "eth_signTypedData" - ], events: [], extensions: nil + ], events: [] ), "solana": ProposalNamespace( chains: [ @@ -100,7 +100,7 @@ class ConnectViewController: UIViewController, UITableViewDataSource, UITableVie methods: [ "solana_signMessage", "solana_signTransaction" - ], events: [], extensions: nil + ], events: [] ) ] Task { diff --git a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift index be310e2f9..83a73f0a8 100644 --- a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift +++ b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift @@ -46,7 +46,7 @@ class SelectChainViewController: UIViewController, UITableViewDataSource { "eth_sendTransaction", "personal_sign", "eth_signTypedData" - ], events: [], extensions: nil + ], events: [] ), "solana": ProposalNamespace( chains: [ @@ -55,7 +55,7 @@ class SelectChainViewController: UIViewController, UITableViewDataSource { methods: [ "solana_signMessage", "solana_signTransaction" - ], events: [], extensions: nil + ], events: [] ) ] Task { diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift index 51e70078e..55344a902 100644 --- a/Example/DApp/Sign/SignCoordinator.swift +++ b/Example/DApp/Sign/SignCoordinator.swift @@ -48,11 +48,12 @@ final class SignCoordinator { Sign.instance.sessionSettlePublisher .receive(on: DispatchQueue.main) .sink { [unowned self] session in - showAccountsScreen(session) + let vc = showAccountsScreen(session) + vc.proposePushSubscription() }.store(in: &publishers) if let session = Sign.instance.getSessions().first { - showAccountsScreen(session) + _ = showAccountsScreen(session) } else { showSelectChainScreen() } @@ -63,13 +64,14 @@ final class SignCoordinator { navigationController.viewControllers = [controller] } - private func showAccountsScreen(_ session: Session) { + private func showAccountsScreen(_ session: Session) -> AccountsViewController { let controller = AccountsViewController(session: session) controller.onDisconnect = { [unowned self] in showSelectChainScreen() } navigationController.presentedViewController?.dismiss(animated: false) navigationController.viewControllers = [controller] + return controller } private func presentResponse(for response: Response) { diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index d5d883e65..dd044d0bc 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -57,12 +57,26 @@ 84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; }; 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; }; 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; }; + 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */; }; 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; }; + 84E6B84A29787A8000428BAF /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B84929787A8000428BAF /* NotificationService.swift */; }; + 84E6B84E29787A8000428BAF /* PNDecryptionService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B85329787AAE00428BAF /* WalletConnectPush */; }; + 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8572981624F00428BAF /* PushRequestModule.swift */; }; + 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */; }; + 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85C298162F700428BAF /* PushRequestRouter.swift */; }; + 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */; }; + 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8602981630C00428BAF /* PushRequestView.swift */; }; + 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B86229816A7900428BAF /* WalletConnectPush */; }; + 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B8642981720400428BAF /* WalletConnectPush */; }; 84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; 84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; }; 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; }; + A518A98729683FB60035247E /* Web3InboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518A98429683FB60035247E /* Web3InboxViewController.swift */; }; + A518A98829683FB60035247E /* Web3InboxModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518A98529683FB60035247E /* Web3InboxModule.swift */; }; + A518A98929683FB60035247E /* Web3InboxRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518A98629683FB60035247E /* Web3InboxRouter.swift */; }; A518B31428E33A6500A2CE93 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A518B31328E33A6500A2CE93 /* InputConfig.swift */; }; A51AC0D928E436A3001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0D828E436A3001BACF9 /* InputConfig.swift */; }; A51AC0DD28E43727001BACF9 /* InputConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51AC0DB28E436E6001BACF9 /* InputConfig.swift */; }; @@ -93,6 +107,7 @@ A5629AE828772A0100094373 /* InviteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AE728772A0100094373 /* InviteViewModel.swift */; }; A5629AEA2877F2D600094373 /* WalletConnectChat in Frameworks */ = {isa = PBXBuildFile; productRef = A5629AE92877F2D600094373 /* WalletConnectChat */; }; A5629AF22877F75100094373 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = A5629AF12877F75100094373 /* Starscream */; }; + A574B3582964560C00C2BB91 /* Web3Inbox in Frameworks */ = {isa = PBXBuildFile; productRef = A574B3572964560C00C2BB91 /* Web3Inbox */; }; A578FA322873036400AA7720 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA312873036400AA7720 /* InputView.swift */; }; A578FA35287304A300AA7720 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA34287304A300AA7720 /* Color.swift */; }; A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA362873D8EE00AA7720 /* UIColor.swift */; }; @@ -269,6 +284,13 @@ remoteGlobalIDString = 84B767752954554A00E92316; remoteInfo = PushDecryptionService; }; + 84E6B84C29787A8000428BAF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84E6B84629787A8000428BAF; + remoteInfo = PNDecryptionService; + }; A5A4FC7D2840C5D400BBEC1E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; @@ -297,6 +319,17 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + 84E6B85229787A8000428BAF /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 84E6B84E29787A8000428BAF /* PNDecryptionService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -332,6 +365,8 @@ 845B8D8B2934B36C0084A966 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; 8460DCFB274F98A10081F94C /* RequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = ""; }; 8485617E295307C20064877B /* PushNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationTests.swift; sourceTree = ""; }; + 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; + 849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionServiceRelease.entitlements; sourceTree = ""; }; 849D7A92292E2169006A2BD4 /* PushTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = ""; }; 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; }; 84B767762954554A00E92316 /* PushDecryptionService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PushDecryptionService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -353,11 +388,25 @@ 84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = ""; }; 84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; }; 84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = ""; }; + 84DB38F029828A7C00BFEE37 /* WalletApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletApp.entitlements; sourceTree = ""; }; + 84DB38F129828A7F00BFEE37 /* PNDecryptionService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionService.entitlements; sourceTree = ""; }; + 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRegisterer.swift; sourceTree = ""; }; + 84E6B84729787A8000428BAF /* PNDecryptionService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PNDecryptionService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 84E6B84929787A8000428BAF /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 84E6B84B29787A8000428BAF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 84E6B8572981624F00428BAF /* PushRequestModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestModule.swift; sourceTree = ""; }; + 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestPresenter.swift; sourceTree = ""; }; + 84E6B85C298162F700428BAF /* PushRequestRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestRouter.swift; sourceTree = ""; }; + 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestInteractor.swift; sourceTree = ""; }; + 84E6B8602981630C00428BAF /* PushRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestView.swift; sourceTree = ""; }; 84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; A50F3945288005B200064555 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; + A518A98429683FB60035247E /* Web3InboxViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3InboxViewController.swift; sourceTree = ""; }; + A518A98529683FB60035247E /* Web3InboxModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3InboxModule.swift; sourceTree = ""; }; + A518A98629683FB60035247E /* Web3InboxRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Web3InboxRouter.swift; sourceTree = ""; }; A518B31328E33A6500A2CE93 /* InputConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; A51AC0D828E436A3001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; A51AC0DB28E436E6001BACF9 /* InputConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConfig.swift; sourceTree = ""; }; @@ -559,11 +608,20 @@ files = ( 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */, A54195A52934E83F0035AD19 /* Web3 in Frameworks */, + 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */, A5D85228286333E300DAF5C3 /* Starscream in Frameworks */, A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; + 84E6B84429787A8000428BAF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A58E7CE528729F550082D443 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -571,6 +629,7 @@ A5629AEA2877F2D600094373 /* WalletConnectChat in Frameworks */, A59FAEC928B7B93A002BB66F /* Web3 in Frameworks */, A5629AF22877F75100094373 /* Starscream in Frameworks */, + A574B3582964560C00C2BB91 /* Web3Inbox in Frameworks */, A59F877628B5462900A9CD80 /* WalletConnectAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -604,6 +663,7 @@ C5133A78294125CC00A8314C /* Web3 in Frameworks */, C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */, C55D349929630D440004314A /* Web3Wallet in Frameworks */, + 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */, C56EE255293F569A004840D1 /* Starscream in Frameworks */, C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */, ); @@ -662,6 +722,7 @@ A58E7CE928729F550082D443 /* Showcase */, 84B767772954554A00E92316 /* PushDecryptionService */, C56EE21C293F55ED004840D1 /* WalletApp */, + 84E6B84829787A8000428BAF /* PNDecryptionService */, 764E1D3D26F8D3FC00A1FB15 /* Products */, 764E1D5326F8DAC800A1FB15 /* Frameworks */, 764E1D5626F8DB6000A1FB15 /* WalletConnectSwiftV2 */, @@ -678,6 +739,7 @@ A58E7CE828729F550082D443 /* Showcase.app */, 84B767762954554A00E92316 /* PushDecryptionService.appex */, C56EE21B293F55ED004840D1 /* WalletApp.app */, + 84E6B84729787A8000428BAF /* PNDecryptionService.appex */, ); name = Products; sourceTree = ""; @@ -825,6 +887,29 @@ path = Auth; sourceTree = ""; }; + 84E6B84829787A8000428BAF /* PNDecryptionService */ = { + isa = PBXGroup; + children = ( + 84DB38F129828A7F00BFEE37 /* PNDecryptionService.entitlements */, + 849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */, + 84E6B84929787A8000428BAF /* NotificationService.swift */, + 84E6B84B29787A8000428BAF /* Info.plist */, + ); + path = PNDecryptionService; + sourceTree = ""; + }; + 84E6B8592981625A00428BAF /* PushRequest */ = { + isa = PBXGroup; + children = ( + 84E6B8572981624F00428BAF /* PushRequestModule.swift */, + 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */, + 84E6B85C298162F700428BAF /* PushRequestRouter.swift */, + 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */, + 84E6B8602981630C00428BAF /* PushRequestView.swift */, + ); + path = PushRequest; + sourceTree = ""; + }; A50F3944288005A700064555 /* Types */ = { isa = PBXGroup; children = ( @@ -925,6 +1010,16 @@ path = Chat; sourceTree = ""; }; + A574B3592964570000C2BB91 /* Web3Inbox */ = { + isa = PBXGroup; + children = ( + A518A98529683FB60035247E /* Web3InboxModule.swift */, + A518A98629683FB60035247E /* Web3InboxRouter.swift */, + A518A98429683FB60035247E /* Web3InboxViewController.swift */, + ); + path = Web3Inbox; + sourceTree = ""; + }; A578FA332873049400AA7720 /* Style */ = { isa = PBXGroup; children = ( @@ -1024,6 +1119,7 @@ A58E7D062872A4390082D443 /* PresentationLayer */ = { isa = PBXGroup; children = ( + A574B3592964570000C2BB91 /* Web3Inbox */, A59F876828B53E6400A9CD80 /* Chat */, ); path = PresentationLayer; @@ -1354,6 +1450,8 @@ C56EE21C293F55ED004840D1 /* WalletApp */ = { isa = PBXGroup; children = ( + 84DB38F029828A7C00BFEE37 /* WalletApp.entitlements */, + 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */, C56EE25C293F56D6004840D1 /* Common */, C56EE27E293F5756004840D1 /* ApplicationLayer */, C56EE29E293F577B004840D1 /* PresentationLayer */, @@ -1373,6 +1471,7 @@ C5F32A2A2954812900A6476E /* ConnectionDetails */, C56EE236293F566A004840D1 /* Scan */, C56EE22A293F5668004840D1 /* Wallet */, + 84E6B8592981625A00428BAF /* PushRequest */, ); path = Wallet; sourceTree = ""; @@ -1474,6 +1573,7 @@ C56EE280293F5757004840D1 /* Application.swift */, C56EE27F293F5757004840D1 /* AppDelegate.swift */, C56EE281293F5757004840D1 /* SceneDelegate.swift */, + 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */, ); path = ApplicationLayer; sourceTree = ""; @@ -1611,11 +1711,32 @@ A5D85227286333E300DAF5C3 /* Starscream */, A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */, A54195A42934E83F0035AD19 /* Web3 */, + 84E6B8642981720400428BAF /* WalletConnectPush */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; productType = "com.apple.product-type.application"; }; + 84E6B84629787A8000428BAF /* PNDecryptionService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 84E6B84F29787A8000428BAF /* Build configuration list for PBXNativeTarget "PNDecryptionService" */; + buildPhases = ( + 84E6B84329787A8000428BAF /* Sources */, + 84E6B84429787A8000428BAF /* Frameworks */, + 84E6B84529787A8000428BAF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PNDecryptionService; + packageProductDependencies = ( + 84E6B86229816A7900428BAF /* WalletConnectPush */, + ); + productName = PNDecryptionService; + productReference = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; + productType = "com.apple.product-type.app-extension"; + }; A58E7CE728729F550082D443 /* Showcase */ = { isa = PBXNativeTarget; buildConfigurationList = A58E7CFB28729F550082D443 /* Build configuration list for PBXNativeTarget "Showcase" */; @@ -1634,6 +1755,7 @@ A5629AF12877F75100094373 /* Starscream */, A59F877528B5462900A9CD80 /* WalletConnectAuth */, A59FAEC828B7B93A002BB66F /* Web3 */, + A574B3572964560C00C2BB91 /* Web3Inbox */, ); productName = Showcase; productReference = A58E7CE828729F550082D443 /* Showcase.app */; @@ -1691,10 +1813,12 @@ C56EE217293F55ED004840D1 /* Sources */, C56EE218293F55ED004840D1 /* Frameworks */, C56EE219293F55ED004840D1 /* Resources */, + 84E6B85229787A8000428BAF /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 84E6B84D29787A8000428BAF /* PBXTargetDependency */, ); name = WalletApp; packageProductDependencies = ( @@ -1704,6 +1828,7 @@ C5133A77294125CC00A8314C /* Web3 */, C55D349829630D440004314A /* Web3Wallet */, C5B2F7042970573D000DBA0E /* SolanaSwift */, + 84E6B85329787AAE00428BAF /* WalletConnectPush */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -1727,6 +1852,9 @@ 84CE641B27981DED00142511 = { CreatedOnToolsVersion = 13.2; }; + 84E6B84629787A8000428BAF = { + CreatedOnToolsVersion = 14.2; + }; A58E7CE728729F550082D443 = { CreatedOnToolsVersion = 13.3; }; @@ -1766,6 +1894,7 @@ A58E7CE728729F550082D443 /* Showcase */, 84B767752954554A00E92316 /* PushDecryptionService */, C56EE21A293F55ED004840D1 /* WalletApp */, + 84E6B84629787A8000428BAF /* PNDecryptionService */, ); }; /* End PBXProject section */ @@ -1796,6 +1925,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84E6B84529787A8000428BAF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A58E7CE628729F550082D443 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1900,6 +2036,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84E6B84329787A8000428BAF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84E6B84A29787A8000428BAF /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A58E7CE428729F550082D443 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1938,14 +2082,17 @@ A58E7D0C2872A45B0082D443 /* MainModule.swift in Sources */, A5C2021C287E1FD8007E3188 /* ImportInteractor.swift in Sources */, A58E7D0D2872A45B0082D443 /* MainPresenter.swift in Sources */, + A518A98829683FB60035247E /* Web3InboxModule.swift in Sources */, A5C20219287E1FD8007E3188 /* ImportModule.swift in Sources */, A5629AD62876CC5700094373 /* InviteInteractor.swift in Sources */, A5629AE12876CC6E00094373 /* InviteListInteractor.swift in Sources */, A58E7CED28729F550082D443 /* SceneDelegate.swift in Sources */, A5C2020F287D9DEE007E3188 /* WelcomeView.swift in Sources */, + A518A98929683FB60035247E /* Web3InboxRouter.swift in Sources */, A5C20226287EB099007E3188 /* AccountNameResolver.swift in Sources */, A5C2020D287D9DEE007E3188 /* WelcomeRouter.swift in Sources */, A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */, + A518A98729683FB60035247E /* Web3InboxViewController.swift in Sources */, A5C2021D287E1FD8007E3188 /* ImportView.swift in Sources */, A5C2021A287E1FD8007E3188 /* ImportPresenter.swift in Sources */, A5629AE828772A0100094373 /* InviteViewModel.swift in Sources */, @@ -2024,7 +2171,9 @@ files = ( C5D4603A29687A5700302C7E /* DefaultSocketFactory.swift in Sources */, C53AA4362941251C008EA57C /* DefaultSignerFactory.swift in Sources */, + 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */, C55D3480295DD7140004314A /* AuthRequestPresenter.swift in Sources */, + 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */, C5F32A2E2954814A00A6476E /* ConnectionDetailsRouter.swift in Sources */, C55D3482295DD7140004314A /* AuthRequestInteractor.swift in Sources */, C55D34B12965FB750004314A /* SessionProposalInteractor.swift in Sources */, @@ -2054,6 +2203,8 @@ C55D3493295DFA750004314A /* WelcomeModule.swift in Sources */, C56EE270293F56D7004840D1 /* String.swift in Sources */, C56EE279293F56D7004840D1 /* Color.swift in Sources */, + 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */, + 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */, C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */, C56EE243293F566D004840D1 /* ScanView.swift in Sources */, C56EE288293F5757004840D1 /* ThirdPartyConfigurator.swift in Sources */, @@ -2077,9 +2228,11 @@ C55D3489295DD8CA0004314A /* PasteUriModule.swift in Sources */, C55D3494295DFA750004314A /* WelcomePresenter.swift in Sources */, C5B2F6F929705293000DBA0E /* SessionRequestPresenter.swift in Sources */, + 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */, C5B2F6FB297055B0000DBA0E /* ETHSigner.swift in Sources */, C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, + 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, C55D348D295DD8CA0004314A /* PasteUriView.swift in Sources */, C5F32A2C2954814200A6476E /* ConnectionDetailsModule.swift in Sources */, @@ -2101,6 +2254,11 @@ target = 84B767752954554A00E92316 /* PushDecryptionService */; targetProxy = 84B7677B2954554A00E92316 /* PBXContainerItemProxy */; }; + 84E6B84D29787A8000428BAF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84E6B84629787A8000428BAF /* PNDecryptionService */; + targetProxy = 84E6B84C29787A8000428BAF /* PBXContainerItemProxy */; + }; A5A4FC7E2840C5D400BBEC1E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 84CE641B27981DED00142511 /* DApp */; @@ -2176,6 +2334,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 7; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2200,6 +2359,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -2238,6 +2398,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 7; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2257,6 +2418,7 @@ SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -2269,7 +2431,7 @@ CODE_SIGN_ENTITLEMENTS = Wallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; INFOPLIST_FILE = ExampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2277,7 +2439,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.example; PRODUCT_NAME = "WalletConnect Wallet"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2295,7 +2457,7 @@ CODE_SIGN_ENTITLEMENTS = Wallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; INFOPLIST_FILE = ExampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2303,7 +2465,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.example; PRODUCT_NAME = "WalletConnect Wallet"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2319,7 +2481,7 @@ CODE_SIGN_ENTITLEMENTS = PushDecryptionService/PushDecryptionService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PushDecryptionService/Info.plist; @@ -2348,7 +2510,7 @@ CODE_SIGN_ENTITLEMENTS = PushDecryptionService/PushDecryptionService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PushDecryptionService/Info.plist; @@ -2378,7 +2540,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; @@ -2394,7 +2556,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.dapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2412,7 +2574,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; @@ -2428,7 +2590,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.dapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2438,6 +2600,64 @@ }; name = Release; }; + 84E6B85029787A8000428BAF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = PNDecryptionService/PNDecryptionService.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = W5R8AG9K22; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PNDecryptionService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PNDecryptionService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp.PNDecryptionService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 84E6B85129787A8000428BAF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = PNDecryptionService/PNDecryptionServiceRelease.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = W5R8AG9K22; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PNDecryptionService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PNDecryptionService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp.PNDecryptionService; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; A58E7CF928729F550082D443 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2445,7 +2665,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Showcase/Other/Info.plist; @@ -2475,7 +2695,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Showcase/Other/Info.plist; @@ -2503,11 +2723,10 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -2522,11 +2741,10 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -2541,7 +2759,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2550,7 +2768,6 @@ ); GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -2564,11 +2781,10 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -2580,12 +2796,14 @@ C56EE226293F55EE004840D1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = WalletApp/WalletApp.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = RQ4NX29V75; + CURRENT_PROJECT_VERSION = 7; + DEVELOPMENT_TEAM = W5R8AG9K22; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; @@ -2610,12 +2828,15 @@ C56EE227293F55EE004840D1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = WalletApp/WalletAppRelease.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = RQ4NX29V75; + CURRENT_PROJECT_VERSION = 7; + DEVELOPMENT_TEAM = W5R8AG9K22; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; @@ -2631,6 +2852,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2676,6 +2898,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 84E6B84F29787A8000428BAF /* Build configuration list for PBXNativeTarget "PNDecryptionService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84E6B85029787A8000428BAF /* Debug */, + 84E6B85129787A8000428BAF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A58E7CFB28729F550082D443 /* Build configuration list for PBXNativeTarget "Showcase" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2774,6 +3005,18 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; }; + 84E6B85329787AAE00428BAF /* WalletConnectPush */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectPush; + }; + 84E6B86229816A7900428BAF /* WalletConnectPush */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectPush; + }; + 84E6B8642981720400428BAF /* WalletConnectPush */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectPush; + }; A54195A42934E83F0035AD19 /* Web3 */ = { isa = XCSwiftPackageProductDependency; package = A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */; @@ -2793,6 +3036,10 @@ package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; productName = Starscream; }; + A574B3572964560C00C2BB91 /* Web3Inbox */ = { + isa = XCSwiftPackageProductDependency; + productName = Web3Inbox; + }; A59F877528B5462900A9CD80 /* WalletConnectAuth */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 4dc474e71..000000000 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,88 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "BigInt", - "repositoryURL": "https://github.com/attaswift/BigInt.git", - "state": { - "branch": null, - "revision": "0ed110f7555c34ff468e72e1686e59721f2b0da6", - "version": "5.3.0" - } - }, - { - "package": "CryptoSwift", - "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", - "state": { - "branch": null, - "revision": "19b3c3ceed117c5cc883517c4e658548315ba70b", - "version": "1.6.0" - } - }, - { - "package": "PromiseKit", - "repositoryURL": "https://github.com/mxcl/PromiseKit.git", - "state": { - "branch": null, - "revision": "43772616c46a44a9977e41924ae01d0e55f2f9ca", - "version": "6.18.1" - } - }, - { - "package": "secp256k1", - "repositoryURL": "https://github.com/Boilertalk/secp256k1.swift.git", - "state": { - "branch": null, - "revision": "cd187c632fb812fd93711a9f7e644adb7e5f97f0", - "version": "0.1.7" - } - }, - { - "package": "SolanaSwift", - "repositoryURL": "https://github.com/flypaper0/solana-swift", - "state": { - "branch": "feature/available-13", - "revision": "a98811518e0a90c2dfc60c30cfd3ec85c33b6790", - "version": null - } - }, - { - "package": "Starscream", - "repositoryURL": "https://github.com/daltoniam/Starscream", - "state": { - "branch": null, - "revision": "a063fda2b8145a231953c20e7a646be254365396", - "version": "3.1.2" - } - }, - { - "package": "Task_retrying", - "repositoryURL": "https://github.com/bigearsenal/task-retrying-swift.git", - "state": { - "branch": null, - "revision": "645eaaf207a6f39ab4b469558d916ae23df199b5", - "version": "1.0.3" - } - }, - { - "package": "TweetNacl", - "repositoryURL": "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git", - "state": { - "branch": null, - "revision": "f8fd111642bf2336b11ef9ea828510693106e954", - "version": "1.1.0" - } - }, - { - "package": "Web3", - "repositoryURL": "https://github.com/WalletConnect/Web3.swift", - "state": { - "branch": null, - "revision": "569255adcfff0b37e4cb8004aea29d0e2d6266df", - "version": "1.0.2" - } - } - ] - }, - "version": 1 -} diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme index 6c42dec52..177cd0cec 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme @@ -5,6 +5,22 @@ + + + + + + + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme new file mode 100644 index 000000000..59e2134d6 --- /dev/null +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/PushDecryptionService.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/UITests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/UITests.xcscheme index a8ccba84c..e454d658f 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/UITests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/UITests.xcscheme @@ -5,6 +5,22 @@ + + + + + + + ReferencedContainer = "container:.."> + BlueprintIdentifier = "Web3Inbox" + BuildableName = "Web3Inbox" + BlueprintName = "Web3Inbox" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectPush" + BuildableName = "WalletConnectPush" + BlueprintName = "WalletConnectPush" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectVerify" + BuildableName = "WalletConnectVerify" + BlueprintName = "WalletConnectVerify" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectPairing" + BuildableName = "WalletConnectPairing" + BlueprintName = "WalletConnectPairing" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectAuth" + BuildableName = "WalletConnectAuth" + BlueprintName = "WalletConnectAuth" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "Web3Wallet" + BuildableName = "Web3Wallet" + BlueprintName = "Web3Wallet" + ReferencedContainer = "container:.."> - - - - + BlueprintIdentifier = "WalletConnectRouter" + BuildableName = "WalletConnectRouter" + BlueprintName = "WalletConnectRouter" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectEcho" + BuildableName = "WalletConnectEcho" + BlueprintName = "WalletConnectEcho" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectNetworking" + BuildableName = "WalletConnectNetworking" + BlueprintName = "WalletConnectNetworking" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectChat" + BuildableName = "WalletConnectChat" + BlueprintName = "WalletConnectChat" + ReferencedContainer = "container:.."> @@ -188,7 +174,7 @@ BlueprintIdentifier = "WalletConnect" BuildableName = "WalletConnect" BlueprintName = "WalletConnect" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -196,30 +182,20 @@ skipped = "NO"> - - - - + BlueprintIdentifier = "AuthTests" + BuildableName = "AuthTests" + BlueprintName = "AuthTests" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "ChatTests" + BuildableName = "ChatTests" + BlueprintName = "ChatTests" + ReferencedContainer = "container:.."> + ReferencedContainer = "container:.."> + BlueprintIdentifier = "JSONRPCTests" + BuildableName = "JSONRPCTests" + BlueprintName = "JSONRPCTests" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "RelayerTests" + BuildableName = "RelayerTests" + BlueprintName = "RelayerTests" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "VerifyTests" + BuildableName = "VerifyTests" + BlueprintName = "VerifyTests" + ReferencedContainer = "container:.."> + BlueprintIdentifier = "WalletConnectKMSTests" + BuildableName = "WalletConnectKMSTests" + BlueprintName = "WalletConnectKMSTests" + ReferencedContainer = "container:.."> + ReferencedContainer = "container:.."> + + + + + + + + + + + + @@ -307,7 +313,7 @@ BlueprintIdentifier = "WalletConnect" BuildableName = "WalletConnect" BlueprintName = "WalletConnect" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectAuth.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectAuth.xcscheme similarity index 94% rename from .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectAuth.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectAuth.xcscheme index 10b74b10a..3a5aff949 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectAuth.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectAuth.xcscheme @@ -17,7 +17,7 @@ BlueprintIdentifier = "WalletConnectAuth" BuildableName = "WalletConnectAuth" BlueprintName = "WalletConnectAuth" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -35,7 +35,7 @@ BlueprintIdentifier = "AuthTests" BuildableName = "AuthTests" BlueprintName = "AuthTests" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -63,7 +63,7 @@ BlueprintIdentifier = "WalletConnectAuth" BuildableName = "WalletConnectAuth" BlueprintName = "WalletConnectAuth" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectChat.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectChat.xcscheme similarity index 94% rename from .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectChat.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectChat.xcscheme index c04003b78..ae2c82e99 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectChat.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectChat.xcscheme @@ -17,7 +17,7 @@ BlueprintIdentifier = "WalletConnectChat" BuildableName = "WalletConnectChat" BlueprintName = "WalletConnectChat" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -35,7 +35,7 @@ BlueprintIdentifier = "ChatTests" BuildableName = "ChatTests" BlueprintName = "ChatTests" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -63,7 +63,7 @@ BlueprintIdentifier = "WalletConnectChat" BuildableName = "WalletConnectChat" BlueprintName = "WalletConnectChat" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ChatTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme similarity index 62% rename from .swiftpm/xcode/xcshareddata/xcschemes/ChatTests.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme index a7cb31007..341890963 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ChatTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme @@ -1,10 +1,26 @@ + + + + + + - - - - + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectNetworking.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectNetworking.xcscheme new file mode 100644 index 000000000..4d9ac47b7 --- /dev/null +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectNetworking.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme new file mode 100644 index 000000000..dc1c7488b --- /dev/null +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPairing.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/RelayerTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme similarity index 62% rename from .swiftpm/xcode/xcshareddata/xcschemes/RelayerTests.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme index a887ff162..48c7aad62 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/RelayerTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme @@ -1,10 +1,26 @@ + + + + + + - - - - + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectKMSTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectRouter.xcscheme similarity index 61% rename from .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectKMSTests.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectRouter.xcscheme index d88c50634..a7c2791a6 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectKMSTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectRouter.xcscheme @@ -1,10 +1,26 @@ + + + + + + - - - - + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectVerify.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme similarity index 95% rename from .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectVerify.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme index b5ef0e1c9..5b4aa4030 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectVerify.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme @@ -17,7 +17,7 @@ BlueprintIdentifier = "WalletConnectVerify" BuildableName = "WalletConnectVerify" BlueprintName = "WalletConnectVerify" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> @@ -53,7 +53,7 @@ BlueprintIdentifier = "WalletConnectVerify" BuildableName = "WalletConnectVerify" BlueprintName = "WalletConnectVerify" - ReferencedContainer = "container:"> + ReferencedContainer = "container:.."> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Inbox.xcscheme similarity index 63% rename from .swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Inbox.xcscheme index b6a508a85..ae55238ff 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/AuthTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Inbox.xcscheme @@ -1,10 +1,26 @@ + + + + + + - - - - + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Wallet.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Wallet.xcscheme new file mode 100644 index 000000000..8776c0962 --- /dev/null +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/Web3Wallet.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ExampleApp/Info.plist b/Example/ExampleApp/Info.plist index 97535e087..c1f94161e 100644 --- a/Example/ExampleApp/Info.plist +++ b/Example/ExampleApp/Info.plist @@ -2,10 +2,6 @@ - PROJECT_ID - $(PROJECT_ID) - SIMULATOR_IDENTIFIER - $(SIMULATOR_IDENTIFIER) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -36,13 +32,17 @@ CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 7 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS NSCameraUsageDescription Allow the app to scan for QR codes + PROJECT_ID + $(PROJECT_ID) + SIMULATOR_IDENTIFIER + $(SIMULATOR_IDENTIFIER) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift index d43a5ce3d..6e047d692 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift @@ -175,8 +175,7 @@ private extension SessionNamespace { self.init( accounts: accounts ?? namespace.accounts, methods: methods ?? namespace.methods, - events: events ?? namespace.events, - extensions: namespace.extensions + events: events ?? namespace.events ) } } diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index da1d92cad..0eaef4cac 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -223,11 +223,7 @@ extension WalletViewController: ProposalViewControllerDelegate { let proposalNamespace = $0.value let accounts = Set(proposalNamespace.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) - let extensions: [SessionNamespace.Extension]? = proposalNamespace.extensions?.map { element in - let accounts = Set(element.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) - return SessionNamespace.Extension(accounts: accounts, methods: element.methods, events: element.events) - } - let sessionNamespace = SessionNamespace(accounts: accounts, methods: proposalNamespace.methods, events: proposalNamespace.events, extensions: extensions) + let sessionNamespace = SessionNamespace(accounts: accounts, methods: proposalNamespace.methods, events: proposalNamespace.events) sessionNamespaces[caip2Namespace] = sessionNamespace } approve(proposalId: proposal.id, namespaces: sessionNamespaces) diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 0f7d770ce..a10fb95e2 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -12,25 +12,26 @@ final class ChatTests: XCTestCase { var registry: KeyValueRegistry! private var publishers = [AnyCancellable]() + let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")! + let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")! + override func setUp() { registry = KeyValueRegistry() - invitee = makeClient(prefix: "🦖 Registered") - inviter = makeClient(prefix: "🍄 Inviter") + invitee = makeClient(prefix: "🦖 Registered", account: inviteeAccount) + inviter = makeClient(prefix: "🍄 Inviter", account: inviterAccount) } - func makeClient(prefix: String) -> ChatClient { + func makeClient(prefix: String, account: Account) -> ChatClient { let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) let keychain = KeychainStorageMock() let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: logger) - return ChatClientFactory.create(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage()) + return ChatClientFactory.create(account: account, registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage()) } func testInvite() async { let inviteExpectation = expectation(description: "invitation expectation") - let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")! - let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")! try! await invitee.register(account: inviteeAccount) - try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "", account: inviterAccount) + try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "") invitee.invitePublisher.sink { _ in inviteExpectation.fulfill() }.store(in: &publishers) @@ -40,12 +41,10 @@ final class ChatTests: XCTestCase { func testAcceptAndCreateNewThread() { let newThreadInviterExpectation = expectation(description: "new thread on inviting client expectation") let newThreadinviteeExpectation = expectation(description: "new thread on invitee client expectation") - let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")! - let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")! Task(priority: .high) { try! await invitee.register(account: inviteeAccount) - try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) + try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "opening message") } invitee.invitePublisher.sink { [unowned self] invite in @@ -67,12 +66,9 @@ final class ChatTests: XCTestCase { let messageExpectation = expectation(description: "message received") messageExpectation.expectedFulfillmentCount = 4 // expectedFulfillmentCount 4 because onMessage() called on send too - let inviteeAccount = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")! - let inviterAccount = Account(chainIdentifier: "eip155:1", address: "0x36275231673672234423f")! - Task(priority: .high) { try! await invitee.register(account: inviteeAccount) - try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) + try! await inviter.invite(peerAccount: inviteeAccount, openingMessage: "opening message") } invitee.invitePublisher.sink { [unowned self] invite in diff --git a/Example/IntegrationTests/Chat/RegistryTests.swift b/Example/IntegrationTests/Chat/RegistryTests.swift index ab665f828..ea21c9375 100644 --- a/Example/IntegrationTests/Chat/RegistryTests.swift +++ b/Example/IntegrationTests/Chat/RegistryTests.swift @@ -6,13 +6,4 @@ import WalletConnectUtils final class RegistryTests: XCTestCase { - func testRegistry() async throws { - let client = HTTPNetworkClient(host: "keys.walletconnect.com") - let registry = KeyserverRegistryProvider(client: client) - let account = Account("eip155:1:" + Data.randomBytes(count: 16).toHexString())! - let pubKey = SigningPrivateKey().publicKey.hexRepresentation - try await registry.register(account: account, pubKey: pubKey) - let resolvedKey = try await registry.resolve(account: account) - XCTAssertEqual(resolvedKey, pubKey) - } } diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 8390e4e38..b0f51314a 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -69,7 +69,7 @@ final class PairingTests: XCTestCase { let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient - let echoClient = EchoClientFactory.create(projectId: "", clientId: "") + let echoClient = EchoClientFactory.create(projectId: "", clientId: "", echoHost: "echo.walletconnect.com") walletPushClient = WalletPushClientFactory.create(logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 123549b51..a8619ba30 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -69,7 +69,7 @@ final class PushTests: XCTestCase { let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient - let echoClient = EchoClientFactory.create(projectId: "", clientId: "") + let echoClient = EchoClientFactory.create(projectId: "", clientId: "", echoHost: "echo.walletconnect.com") walletPushClient = WalletPushClientFactory.create(logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, @@ -147,6 +147,7 @@ final class PushTests: XCTestCase { func testDappSendsPushMessage() async { let expectation = expectation(description: "expects wallet to receive push message") let pushMessage = PushMessage.stub() + var pushSubscription: PushSubscription! let uri = try! await dappPairingClient.create() try! await walletPairingClient.pair(uri: uri) try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) @@ -160,14 +161,18 @@ final class PushTests: XCTestCase { XCTFail() return } + pushSubscription = subscription Task(priority: .userInitiated) { try! await dappPushClient.notify(topic: subscription.topic, message: pushMessage) } }.store(in: &publishers) - walletPushClient.pushMessagePublisher.sink { receivedPushMessage in + walletPushClient.pushMessagePublisher.sink { [unowned self] receivedPushMessage in + let messageHistory = walletPushClient.getMessageHistory(topic: pushSubscription.topic) XCTAssertEqual(pushMessage, receivedPushMessage) + XCTAssertTrue(messageHistory.contains(receivedPushMessage)) expectation.fulfill() }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) } diff --git a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift index 60ab35252..79e91d5f2 100644 --- a/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift +++ b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift @@ -33,7 +33,7 @@ final class RelayClientEndToEndTests: XCTestCase { subscribeExpectation.assertForOverFulfill = true relayClient.socketConnectionStatusPublisher.sink { status in if status == .connected { - relayClient.subscribe(topic: "qwerty") { error in + relayClient.subscribe(topic: "ecb78f2df880c43d3418ddbf871092b847801932e21765b250cc50b9e96a9131") { error in XCTAssertNil(error) subscribeExpectation.fulfill() } diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index a429d0884..c5a7fcab1 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -184,7 +184,7 @@ final class SignClientTests: XCTestCase { } dapp.onSessionSettled = { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) + let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil) try await dapp.client.request(params: request) } } @@ -230,7 +230,7 @@ final class SignClientTests: XCTestCase { } dapp.onSessionSettled = { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) + let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil) try await dapp.client.request(params: request) } } diff --git a/Example/PNDecryptionService/Info.plist b/Example/PNDecryptionService/Info.plist new file mode 100644 index 000000000..57421ebf9 --- /dev/null +++ b/Example/PNDecryptionService/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift new file mode 100644 index 000000000..26d9c8960 --- /dev/null +++ b/Example/PNDecryptionService/NotificationService.swift @@ -0,0 +1,41 @@ + +import UserNotifications +import WalletConnectPush + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + if let bestAttemptContent = bestAttemptContent, + let topic = bestAttemptContent.userInfo["topic"] as? String, + let ciphertext = bestAttemptContent.userInfo["blob"] as? String { + do { + let service = PushDecryptionService() + let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext) + bestAttemptContent.title = pushMessage.title + bestAttemptContent.body = pushMessage.body + contentHandler(bestAttemptContent) + return + } + catch { + print(error) + } + bestAttemptContent.title = "" + bestAttemptContent.body = "content not set" + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/Example/PNDecryptionService/PNDecryptionService.entitlements b/Example/PNDecryptionService/PNDecryptionService.entitlements new file mode 100644 index 000000000..0bd7a7310 --- /dev/null +++ b/Example/PNDecryptionService/PNDecryptionService.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.walletconnect.sdk + + + diff --git a/Example/PNDecryptionService/PNDecryptionServiceRelease.entitlements b/Example/PNDecryptionService/PNDecryptionServiceRelease.entitlements new file mode 100644 index 000000000..0bd7a7310 --- /dev/null +++ b/Example/PNDecryptionService/PNDecryptionServiceRelease.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.walletconnect.sdk + + + diff --git a/Example/PushDecryptionService/Info.plist b/Example/PushDecryptionService/Info.plist index 57421ebf9..38ada2364 100644 --- a/Example/PushDecryptionService/Info.plist +++ b/Example/PushDecryptionService/Info.plist @@ -2,6 +2,10 @@ + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 7 NSExtension NSExtensionPointIdentifier diff --git a/Example/PushDecryptionService/NotificationService.swift b/Example/PushDecryptionService/NotificationService.swift index 737026677..f8da6c818 100644 --- a/Example/PushDecryptionService/NotificationService.swift +++ b/Example/PushDecryptionService/NotificationService.swift @@ -1,4 +1,3 @@ -// import UserNotifications import WalletConnectPush @@ -13,7 +12,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { let topic = bestAttemptContent.userInfo["topic"] as! String - let ciphertext = bestAttemptContent.userInfo["ciphertext"] as! String + let ciphertext = bestAttemptContent.userInfo["blob"] as! String do { let service = PushDecryptionService() let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext) diff --git a/Example/Showcase/Classes/ApplicationLayer/Application.swift b/Example/Showcase/Classes/ApplicationLayer/Application.swift index 75d88c640..46b30ef87 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Application.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Application.swift @@ -4,7 +4,7 @@ import WalletConnectChat final class Application { lazy var chatService: ChatService = { - return ChatService(client: Chat.instance) + return ChatService(accountStorage: accountStorage) }() lazy var accountStorage: AccountStorage = { diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift index 991c06c66..839ef53a6 100644 --- a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift @@ -7,14 +7,26 @@ typealias Stream = AsyncPublisher> final class ChatService { - private let client: ChatClient - - init(client: ChatClient) { - self.client = client + private lazy var client: ChatClient = { + guard let account = accountStorage.account else { + fatalError("Error - you must call Chat.configure(_:) before accessing the shared instance.") + } + Chat.configure(account: account) + return Chat.instance + }() + + private lazy var networking: NetworkingClient = { + return Networking.instance + }() + + private let accountStorage: AccountStorage + + init(accountStorage: AccountStorage) { + self.accountStorage = accountStorage } var connectionPublisher: Stream { - return client.socketConnectionStatusPublisher.values + return networking.socketConnectionStatusPublisher.values } var messagePublisher: Stream { @@ -29,16 +41,16 @@ final class ChatService { return client.invitePublisher.values } - func getMessages(thread: WalletConnectChat.Thread) async -> [WalletConnectChat.Message] { - await client.getMessages(topic: thread.topic) + func getMessages(thread: WalletConnectChat.Thread) -> [WalletConnectChat.Message] { + client.getMessages(topic: thread.topic) } - func getThreads() async -> [WalletConnectChat.Thread] { - await client.getThreads() + func getThreads() -> [WalletConnectChat.Thread] { + client.getThreads() } - func getInvites(account: Account) async -> [WalletConnectChat.Invite] { - client.getInvites(account: account) + func getInvites() -> [WalletConnectChat.Invite] { + client.getInvites() } func sendMessage(topic: String, message: String) async throws { @@ -53,8 +65,8 @@ final class ChatService { try await client.reject(inviteId: invite.id) } - func invite(peerAccount: Account, message: String, selfAccount: Account) async throws { - try await client.invite(peerAccount: peerAccount, openingMessage: message, account: selfAccount) + func invite(peerAccount: Account, message: String) async throws { + try await client.invite(peerAccount: peerAccount, openingMessage: message) } func register(account: Account) async throws { diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListInteractor.swift index 87e40a7af..09a0c976a 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListInteractor.swift @@ -10,16 +10,16 @@ final class ChatListInteractor { self.accountStorage = accountStorage } - func getThreads() async -> [WalletConnectChat.Thread] { - return await chatService.getThreads() + func getThreads() -> [WalletConnectChat.Thread] { + return chatService.getThreads() } func threadsSubscription() -> Stream { return chatService.threadPublisher } - func getInvites(account: Account) async -> [Invite] { - return await chatService.getInvites(account: account) + func getInvites() -> [Invite] { + return chatService.getInvites() } func invitesSubscription() -> Stream { diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListPresenter.swift index a839d8dd7..0f7a0b412 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListPresenter.swift @@ -44,7 +44,7 @@ final class ChatListPresenter: ObservableObject { func didLogoutPress() { interactor.logout() - router.presentMain() + router.presentWelcome() } func didPressNewChat() { @@ -88,26 +88,24 @@ private extension ChatListPresenter { @MainActor func setupInvites() async { - await loadInvites() + loadInvites() for await _ in interactor.invitesSubscription() { - await loadInvites() + loadInvites() } } @MainActor func loadThreads() async { - let threads = await interactor.getThreads() - self.threads = threads - .filter { $0.selfAccount == account } + self.threads = interactor.getThreads() .sorted(by: { $0.topic < $1.topic }) .map { ThreadViewModel(thread: $0) } } @MainActor - func loadInvites() async { - let invites = await interactor.getInvites(account: account) - self.invites = invites.sorted(by: { $0.publicKey < $1.publicKey }) + func loadInvites() { + self.invites = interactor.getInvites() + .sorted(by: { $0.publicKey < $1.publicKey }) .map { InviteViewModel(invite: $0) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift index eb0564d37..94210189e 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListRouter.swift @@ -25,7 +25,7 @@ final class ChatListRouter { ChatModule.create(thread: thread, app: app).push(from: viewController) } - func presentMain() { - MainModule.create(app: app).present() + func presentWelcome() { + WelcomeModule.create(app: app).present() } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListView.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListView.swift index 11a6173de..72b5f62f7 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatList/ChatListView.swift @@ -47,6 +47,7 @@ struct ChatListView: View { presenter.didLogoutPress() } .foregroundColor(.red) + .padding(.bottom, 16) } .onAppear { presenter.setupInitialState() diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportPresenter.swift index 9768456f4..09428a950 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportPresenter.swift @@ -15,11 +15,12 @@ final class ImportPresenter: ObservableObject { self.router = router } - func didPressImport() { + @MainActor + func didPressImport() async { guard let account = AccountNameResolver.resolveAccount(input) else { return input = .empty } interactor.save(account: account) - register(account: account) + await interactor.register(account: account) router.presentChat(account: account) } } @@ -44,10 +45,4 @@ private extension ImportPresenter { func setupInitialState() { } - - func register(account: Account) { - Task(priority: .high) { - await interactor.register(account: account) - } - } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportRouter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportRouter.swift index 0902e7251..9b8b2ac8f 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportRouter.swift @@ -11,8 +11,6 @@ final class ImportRouter { } func presentChat(account: Account) { - ChatListModule.create(app: app, account: account) - .wrapToNavigationController() - .present() + MainModule.create(app: app, account: account).present() } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift index 6f83f4a4b..028156aaf 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift @@ -15,9 +15,9 @@ struct ImportView: View { Spacer() - BrandButton(title: "Ok, done", action: { - presenter.didPressImport() - }) + BrandButton(title: "Ok, done" ) { Task(priority: .userInitiated) { + await presenter.didPressImport() + }} .padding(16.0) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Invite/InviteInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Invite/InviteInteractor.swift index 26af501c6..e707b2a1a 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Invite/InviteInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Invite/InviteInteractor.swift @@ -6,6 +6,6 @@ final class InviteInteractor { } func invite(peerAccount: Account, message: String, selfAccount: Account) async { - try! await chatService.invite(peerAccount: peerAccount, message: message, selfAccount: selfAccount) + try! await chatService.invite(peerAccount: peerAccount, message: message) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListInteractor.swift index 58c404ab9..1880de742 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListInteractor.swift @@ -7,8 +7,8 @@ final class InviteListInteractor { self.chatService = chatService } - func getInvites(account: Account) async -> [Invite] { - return await chatService.getInvites(account: account) + func getInvites() -> [Invite] { + return chatService.getInvites() } func invitesSubscription() -> Stream { diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListPresenter.swift index 385a0d37d..915f08ed4 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/InviteList/InviteListPresenter.swift @@ -19,10 +19,10 @@ final class InviteListPresenter: ObservableObject { @MainActor func setupInitialState() async { - await loadInvites(account: account) + loadInvites() for await _ in interactor.invitesSubscription() { - await loadInvites(account: account) + loadInvites() } } @@ -58,10 +58,9 @@ extension InviteListPresenter: SceneViewModel { private extension InviteListPresenter { - @MainActor - func loadInvites(account: Account) async { - let invites = await interactor.getInvites(account: account) - self.invites = invites.sorted(by: { $0.publicKey < $1.publicKey }) + func loadInvites() { + invites = interactor.getInvites() + .sorted(by: { $0.publicKey < $1.publicKey }) .map { InviteViewModel(invite: $0) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainModule.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainModule.swift index f60ff5d77..fd6309823 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainModule.swift @@ -3,9 +3,9 @@ import SwiftUI final class MainModule { @discardableResult - static func create(app: Application) -> UIViewController { + static func create(app: Application, account: Account) -> UIViewController { let router = MainRouter(app: app) - let presenter = MainPresenter(router: router) + let presenter = MainPresenter(router: router, account: account) let viewController = MainViewController(presenter: presenter) router.viewController = viewController diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainPresenter.swift index fde2e49fc..10d1de36f 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainPresenter.swift @@ -3,6 +3,7 @@ import Combine final class MainPresenter { + private let account: Account private let router: MainRouter var tabs: [TabPage] { @@ -11,11 +12,13 @@ final class MainPresenter { var viewControllers: [UIViewController] { return [ - router.chatViewController + router.chatViewController(account: account), + router.web3InboxViewController(account: account), ] } - init(router: MainRouter) { + init(router: MainRouter, account: Account) { + self.account = account self.router = router } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainRouter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainRouter.swift index fdd32686d..9072fcdf1 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Main/MainRouter.swift @@ -6,8 +6,12 @@ final class MainRouter { private let app: Application - var chatViewController: UIViewController { - return WelcomeModule.create(app: app) + func chatViewController(account: Account) -> UIViewController { + return ChatListModule.create(app: app, account: account).wrapToNavigationController() + } + + func web3InboxViewController(account: Account) -> UIViewController { + return Web3InboxModule.create(app: app, account: account).wrapToNavigationController() } init(app: Application) { diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Main/Model/TabPage.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Main/Model/TabPage.swift index 2613f6568..2e6e3b43b 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Main/Model/TabPage.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Main/Model/TabPage.swift @@ -2,14 +2,14 @@ import UIKit enum TabPage: CaseIterable { case chat - case wallet + case web3Inbox var title: String { switch self { case .chat: return "Chat" - case .wallet: - return "Wallet" + case .web3Inbox: + return "Web3Inbox" } } @@ -17,8 +17,8 @@ enum TabPage: CaseIterable { switch self { case .chat: return UIImage(systemName: "message.fill")! - case .wallet: - return UIImage(systemName: "signature")! + case .web3Inbox: + return UIImage(systemName: "safari.fill")! } } @@ -27,6 +27,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.chat, .wallet] + return [.chat, .web3Inbox] } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomePresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomePresenter.swift index 0ad6296ee..dd9d00f1f 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomePresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomePresenter.swift @@ -26,33 +26,11 @@ final class WelcomePresenter: ObservableObject { return interactor.isAuthorized() ? "Start Messaging" : "Connect wallet" } - func didPressImport() async { + func didPressImport() { if let account = interactor.account { - router.presentChats(account: account) + router.presentMain(account: account) } else { - await authWithWallet() - } - } - - private func authWithWallet() async { - let uri = await interactor.generateUri() - try? await Auth.instance.request( - RequestParams( - domain: "example.wallet", - chainId: "eip155:1", - nonce: "32891756", - aud: "https://example.wallet/login", - nbf: nil, - exp: nil, - statement: "I accept the ServiceOrg Terms of Service: https://service.invalid/tos", - requestId: nil, - resources: ["ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/", "https://example.com/my-web2-claim.json"] - ), - topic: uri.topic - ) - - DispatchQueue.main.async { - self.router.openWallet(uri: uri.absoluteString) + router.presentImport() } } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeRouter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeRouter.swift index e69faac65..b6ff4209d 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeRouter.swift @@ -16,9 +16,8 @@ final class WelcomeRouter { .present() } - func presentChats(account: Account) { - ChatListModule.create(app: app, account: account) - .wrapToNavigationController() + func presentMain(account: Account) { + MainModule.create(app: app, account: account) .present() } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeView.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeView.swift index b56ef64e0..2d9ba4129 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Welcome/WelcomeView.swift @@ -32,9 +32,7 @@ struct WelcomeView: View { .multilineTextAlignment(.center) BrandButton(title: presenter.buttonTitle, action: { - Task { - await presenter.didPressImport() - } + presenter.didPressImport() }) Text("By connecting your wallet you agree with our\nTerms of Service") diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxModule.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxModule.swift new file mode 100644 index 000000000..fe2242603 --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxModule.swift @@ -0,0 +1,13 @@ +import SwiftUI + +final class Web3InboxModule { + + @discardableResult + static func create(app: Application, account: Account) -> UIViewController { + let router = Web3InboxRouter(app: app) + let viewController = Web3InboxViewController(account: account) + router.viewController = viewController + return viewController + } + +} diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxRouter.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxRouter.swift new file mode 100644 index 000000000..3631c35be --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/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/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift new file mode 100644 index 000000000..7f5d62ab3 --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift @@ -0,0 +1,26 @@ +import UIKit +import Web3Inbox +import WebKit + +final class Web3InboxViewController: UIViewController { + + private let account: Account + + init(account: Account) { + self.account = account + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + Web3Inbox.configure(account: account) + view = Web3Inbox.instance.getWebView() + + navigationItem.title = "Web3Inbox SDK" + } +} diff --git a/Example/Showcase/Other/Info.plist b/Example/Showcase/Other/Info.plist index 136371e1c..f4245dd27 100644 --- a/Example/Showcase/Other/Info.plist +++ b/Example/Showcase/Other/Info.plist @@ -2,6 +2,10 @@ + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 7 PROJECT_ID $(PROJECT_ID) RELAY_HOST diff --git a/Example/WalletApp/ApplicationLayer/AppDelegate.swift b/Example/WalletApp/ApplicationLayer/AppDelegate.swift index 821a8f719..25fa75d10 100644 --- a/Example/WalletApp/ApplicationLayer/AppDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/AppDelegate.swift @@ -1,7 +1,11 @@ import UIKit +import WalletConnectPush +import Combine @main final class AppDelegate: UIResponder, UIApplicationDelegate { + private var publishers = [AnyCancellable]() + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true @@ -15,4 +19,26 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {} + + func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + Task(priority: .high) { + // Use pasteboard for testing purposes + let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } + let token = tokenParts.joined() + let pasteboard = UIPasteboard.general + pasteboard.string = token + try await Push.wallet.register(deviceToken: deviceToken) + } + } + + func application( + _ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error + ) { + print("Failed to register: \(error)") + } + } diff --git a/Example/WalletApp/ApplicationLayer/Application.swift b/Example/WalletApp/ApplicationLayer/Application.swift index 97fc2aaf6..91cd85b90 100644 --- a/Example/WalletApp/ApplicationLayer/Application.swift +++ b/Example/WalletApp/ApplicationLayer/Application.swift @@ -3,4 +3,5 @@ import WalletConnectChat final class Application { var uri: String? + let pushRegisterer = PushRegisterer() } diff --git a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift index 25279daaa..298207e2b 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift @@ -1,7 +1,9 @@ import WalletConnectNetworking import Web3Wallet +import WalletConnectPush struct ThirdPartyConfigurator: Configurator { + func configure() { Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory()) @@ -13,5 +15,8 @@ struct ThirdPartyConfigurator: Configurator { ) Web3Wallet.configure(metadata: metadata, signerFactory: DefaultSignerFactory()) + Push.configure() + } + } diff --git a/Example/WalletApp/ApplicationLayer/PushRegisterer.swift b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift new file mode 100644 index 000000000..1f5f19162 --- /dev/null +++ b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift @@ -0,0 +1,40 @@ + +import WalletConnectPush +import Combine +import UIKit + +class PushRegisterer { + + private var publishers = [AnyCancellable]() + + func getNotificationSettings() { + UNUserNotificationCenter.current().getNotificationSettings { settings in + print("Notification settings: \(settings)") + guard settings.authorizationStatus == .authorized else { return } + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } + + func registerForPushNotifications() { + UNUserNotificationCenter.current() + .requestAuthorization( + options: [.alert, .sound, .badge]) { [weak self] granted, _ in + print("Permission granted: \(granted)") + guard granted else { return } + self?.getNotificationSettings() +#if targetEnvironment(simulator) + Networking.interactor.socketConnectionStatusPublisher + .first {$0 == .connected} + .sink{ status in + let deviceToken = InputConfig.simulatorIdentifier + assert(deviceToken != "SIMULATOR_IDENTIFIER", "Please set your Simulator identifier") + Task(priority: .high) { + try await Push.wallet.register(deviceToken: deviceToken) + } + }.store(in: &self!.publishers) +#endif + } + } +} diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index 1fa5738e2..999164f85 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -2,9 +2,11 @@ import UIKit import Auth import WalletConnectPairing + final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + private let app = Application() private var configurators: [Configurator] { @@ -31,6 +33,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { app.uri = connectionOptions.urlContexts.first?.url.absoluteString.replacingOccurrences(of: "walletapp://wc?uri=", with: "") configurators.configure() + app.pushRegisterer.registerForPushNotifications() } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { @@ -40,5 +43,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { Task { try await Pair.instance.pair(uri: WalletConnectURI(string: uri)!) } + } } diff --git a/Example/WalletApp/Common/InputConfig.swift b/Example/WalletApp/Common/InputConfig.swift index 1a4e505cf..4cdd5ee73 100644 --- a/Example/WalletApp/Common/InputConfig.swift +++ b/Example/WalletApp/Common/InputConfig.swift @@ -5,7 +5,14 @@ struct InputConfig { return config(for: "PROJECT_ID")! } +#if targetEnvironment(simulator) + static var simulatorIdentifier: String { + return config(for: "SIMULATOR_IDENTIFIER")! + } +#endif + private static func config(for key: String) -> String? { return Bundle.main.object(forInfoDictionaryKey: key) as? String } + } diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist index ef9e6d46f..08395f3e9 100644 --- a/Example/WalletApp/Other/Info.plist +++ b/Example/WalletApp/Other/Info.plist @@ -2,6 +2,10 @@ + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 7 CFBundleIconName AppIcon CFBundleURLTypes @@ -43,5 +47,7 @@ + SIMULATOR_IDENTIFIER + $(SIMULATOR_IDENTIFIER) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift new file mode 100644 index 000000000..eb6d6f329 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift @@ -0,0 +1,12 @@ +import Foundation +import WalletConnectPush + +final class PushRequestInteractor { + func approve(pushRequest: PushRequest) async throws { + try await Push.wallet.approve(id: pushRequest.id) + } + + func reject(pushRequest: PushRequest) async throws { + try await Push.wallet.reject(id: pushRequest.id) + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift new file mode 100644 index 000000000..3062207e9 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift @@ -0,0 +1,17 @@ +import SwiftUI +import WalletConnectPush + +final class PushRequestModule { + @discardableResult + static func create(app: Application, pushRequest: PushRequest) -> UIViewController { + let router = PushRequestRouter(app: app) + let interactor = PushRequestInteractor() + let presenter = PushRequestPresenter(interactor: interactor, router: router, pushRequest: pushRequest) + let view = PushRequestView().environmentObject(presenter) + let viewController = SceneViewController(viewModel: presenter, content: view) + + router.viewController = viewController + + return viewController + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift new file mode 100644 index 000000000..194b5e8e3 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift @@ -0,0 +1,51 @@ +import UIKit +import Combine +import WalletConnectPush + +final class PushRequestPresenter: ObservableObject { + private let interactor: PushRequestInteractor + private let router: PushRequestRouter + + let pushRequest: PushRequest + + var message: String { + return String(describing: pushRequest.account) + } + + private var disposeBag = Set() + + init( + interactor: PushRequestInteractor, + router: PushRequestRouter, + pushRequest: PushRequest + ) { + defer { setupInitialState() } + self.interactor = interactor + self.router = router + self.pushRequest = pushRequest + } + + @MainActor + func onApprove() async throws { + try await interactor.approve(pushRequest: pushRequest) + router.dismiss() + } + + @MainActor + func onReject() async throws { + try await interactor.reject(pushRequest: pushRequest) + router.dismiss() + } +} + +// MARK: - Private functions +private extension PushRequestPresenter { + func setupInitialState() { + + } +} + +// MARK: - SceneViewModel +extension PushRequestPresenter: SceneViewModel { + +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift new file mode 100644 index 000000000..6ac5f730c --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift @@ -0,0 +1,15 @@ +import UIKit + +final class PushRequestRouter { + weak var viewController: UIViewController! + + private let app: Application + + init(app: Application) { + self.app = app + } + + func dismiss() { + viewController.dismiss() + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift new file mode 100644 index 000000000..62a21e17f --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift @@ -0,0 +1,127 @@ +import SwiftUI + +struct PushRequestView: View { + @EnvironmentObject var presenter: PushRequestPresenter + + @State var text = "" + + var body: some View { + ZStack { + Color.black.opacity(0.6) + + VStack { + Spacer() + + VStack(spacing: 0) { + Image("header") + .resizable() + .scaledToFit() + + Text("would you like to send notifications") + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + .padding(.top, 10) + + pushRequestView() + + HStack(spacing: 20) { + Button { + Task(priority: .userInitiated) { try await + presenter.onReject() + } + } label: { + Text("Decline") + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .padding(.vertical, 11) + .background( + LinearGradient( + gradient: Gradient(colors: [ + .foregroundNegative, + .lightForegroundNegative + ]), + startPoint: .top, endPoint: .bottom) + ) + .cornerRadius(20) + } + .shadow(color: .white.opacity(0.25), radius: 8, y: 2) + + Button { + Task(priority: .userInitiated) { try await + presenter.onApprove() + } + } label: { + Text("Allow") + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .font(.system(size: 20, weight: .semibold, design: .rounded)) + .padding(.vertical, 11) + .background( + LinearGradient( + gradient: Gradient(colors: [ + .foregroundPositive, + .lightForegroundPositive + ]), + startPoint: .top, endPoint: .bottom) + ) + .cornerRadius(20) + } + .shadow(color: .white.opacity(0.25), radius: 8, y: 2) + } + .padding(.top, 25) + } + .padding(20) + .background(.ultraThinMaterial) + .cornerRadius(34) + .padding(.horizontal, 10) + + Spacer() + } + } + .edgesIgnoringSafeArea(.all) + } + + private func pushRequestView() -> some View { + VStack { + VStack(alignment: .leading) { + Text("Notifications") + .font(.system(size: 15, weight: .semibold, design: .rounded)) + .foregroundColor(.whiteBackground) + .padding(.horizontal, 8) + .padding(.vertical, 5) + .background(Color.grey70) + .cornerRadius(28, corners: .allCorners) + .padding(.leading, 15) + .padding(.top, 9) + + VStack(spacing: 0) { + ScrollView { + Text(presenter.message) + .foregroundColor(.grey50) + .font(.system(size: 13, weight: .semibold, design: .rounded)) + } + .padding(.horizontal, 18) + .padding(.vertical, 10) + .frame(height: 250) + } + .background(Color.whiteBackground) + .cornerRadius(20, corners: .allCorners) + .padding(.horizontal, 5) + .padding(.bottom, 5) + + } + .background(.thinMaterial) + .cornerRadius(25, corners: .allCorners) + } + .padding(.top, 30) + } +} + +#if DEBUG +struct PushRequestView_Previews: PreviewProvider { + static var previews: some View { + PushRequestView() + } +} +#endif diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index c88d43aef..fcbd47201 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift @@ -15,11 +15,7 @@ final class SessionProposalInteractor { let proposalNamespace = $0.value let accounts = Set(proposalNamespace.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) - let extensions: [SessionNamespace.Extension]? = proposalNamespace.extensions?.map { element in - let accounts = Set(element.chains.compactMap { Account($0.absoluteString + ":\(self.accounts[$0.namespace]!)") }) - return SessionNamespace.Extension(accounts: accounts, methods: element.methods, events: element.events) - } - let sessionNamespace = SessionNamespace(accounts: accounts, methods: proposalNamespace.methods, events: proposalNamespace.events, extensions: extensions) + let sessionNamespace = SessionNamespace(accounts: accounts, methods: proposalNamespace.methods, events: proposalNamespace.events) sessionNamespaces[caip2Namespace] = sessionNamespace } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift index b79c6b255..3a4e3601e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift @@ -1,6 +1,7 @@ import Combine import Web3Wallet +import WalletConnectPush final class WalletInteractor { var requestPublisher: AnyPublisher { @@ -15,10 +16,14 @@ final class WalletInteractor { return Web3Wallet.instance.sessionRequestPublisher } + var pushRequestPublisher: AnyPublisher<(id: RPCID, account: Account, metadata: AppMetadata), Never> { + return Push.wallet.requestPublisher + } + var sessionsPublisher: AnyPublisher<[Session], Never> { return Web3Wallet.instance.sessionsPublisher } - + func getSessions() -> [Session] { return Web3Wallet.instance.getSessions() } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index e6e677d8e..1e129ae89 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -71,7 +71,13 @@ extension WalletPresenter { .sink { [weak self] sessionRequest in self?.router.present(sessionRequest: sessionRequest) }.store(in: &disposeBag) - + + interactor.pushRequestPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] request in + self?.router.present(pushRequest: request) + }.store(in: &disposeBag) + interactor.sessionProposalPublisher .receive(on: DispatchQueue.main) .sink { [weak self] proposal in diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift index c32bfd8d3..8cc1c31ed 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletRouter.swift @@ -1,6 +1,7 @@ import UIKit import Web3Wallet +import WalletConnectPush final class WalletRouter { weak var viewController: UIViewController! @@ -25,6 +26,11 @@ final class WalletRouter { SessionRequestModule.create(app: app, sessionRequest: sessionRequest) .presentFullScreen(from: viewController, transparentBackground: true) } + + func present(pushRequest: PushRequest) { + PushRequestModule.create(app: app, pushRequest: pushRequest) + .presentFullScreen(from: viewController, transparentBackground: true) + } func presentPaste(onValue: @escaping (String) -> Void, onError: @escaping (Error) -> Void) { PasteUriModule.create(app: app, onValue: onValue, onError: onError) diff --git a/Example/WalletApp/WalletApp.entitlements b/Example/WalletApp/WalletApp.entitlements new file mode 100644 index 000000000..2dccdcca9 --- /dev/null +++ b/Example/WalletApp/WalletApp.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.com.walletconnect.sdk + + + diff --git a/Example/WalletApp/WalletAppRelease.entitlements b/Example/WalletApp/WalletAppRelease.entitlements new file mode 100644 index 000000000..315a4dbfc --- /dev/null +++ b/Example/WalletApp/WalletAppRelease.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + production + com.apple.security.application-groups + + group.com.walletconnect.sdk + + + diff --git a/Makefile b/Makefile index 3ba7ee113..218b88441 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,40 @@ XCODE_USER_TEMPLATES_DIR=/Applications/Xcode.app/Contents/Developer/Library/Xcod TEMPLATE_NAME=VIPER TEMPLATES_DIR=Example/Templates/VIPER +EXISTS_FASTLANE = $(shell command -v fastlane 2> /dev/null) + install_templates: mkdir -p $(XCODE_USER_TEMPLATES_DIR) rm -fR $(XCODE_USER_TEMPLATES_DIR)/$(TEMPLATE_NAME) - cp -R $(TEMPLATES_DIR) $(XCODE_USER_TEMPLATES_DIR) \ No newline at end of file + cp -R $(TEMPLATES_DIR) $(XCODE_USER_TEMPLATES_DIR) + +install_env: +ifeq "${EXISTS_FASTLANE}" "" + @echo Installing fastlane + sudo gem install fastlane --no-document +endif + @echo "All dependencies was installed" + +build_dapp: + fastlane build scheme:DApp + +build_wallet: + fastlane build scheme:WalletApp + +ui_tests: + echo "UI Tests disabled" + +unit_tests: + fastlane tests scheme:WalletConnect + +integration_tests: + fastlane tests scheme:IntegrationTests relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) + +resolve_packages: + fastlane resolve scheme:WalletApp + +release_wallet: + fastlane release_testflight username:$(APPLE_ID) --env WalletApp + +release_showcase: + fastlane release_testflight username:$(APPLE_ID) --env Showcase \ No newline at end of file diff --git a/Package.swift b/Package.swift index 7110440bc..c633746d6 100644 --- a/Package.swift +++ b/Package.swift @@ -39,7 +39,10 @@ let package = Package( targets: ["WalletConnectNetworking"]), .library( name: "WalletConnectVerify", - targets: ["WalletConnectVerify"]) + targets: ["WalletConnectVerify"]), + .library( + name: "Web3Inbox", + targets: ["Web3Inbox"]), ], dependencies: [], targets: [ @@ -79,6 +82,9 @@ let package = Package( .target( name: "WalletConnectPairing", dependencies: ["WalletConnectNetworking"]), + .target( + name: "Web3Inbox", + dependencies: ["WalletConnectChat"]), .target( name: "WalletConnectUtils", dependencies: ["JSONRPC"]), diff --git a/README.md b/README.md index d7724421b..9d0543d5f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ open `Example/ExampleApp.xcodeproj` ## Web3Wallet Web3Wallet SDK introduces a new interface for all wallets that wraps the Sign and Auth clients internally. -- [Migration guide from Sign and Auth to Web3Wallet](https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/swift/guides/web3wallet-migration.md) +- [Migration guide from Sign and Auth to Web3Wallet](https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/swift/web3wallet/upgrade-guide.md) ## License diff --git a/Sources/Chat/Chat.swift b/Sources/Chat/Chat.swift index 9c10e0492..31430fe0b 100644 --- a/Sources/Chat/Chat.swift +++ b/Sources/Chat/Chat.swift @@ -5,8 +5,20 @@ public class Chat { /// Chat client instance public static var instance: ChatClient = { - return ChatClientFactory.create() + guard let account = account else { + fatalError("Error - you must call Chat.configure(_:) before accessing the shared instance.") + } + return ChatClientFactory.create(account: account) }() + private static var account: Account? + private init() { } + + /// Chat instance config method + /// - Parameters: + /// - account: Chat initial account + static public func configure(account: Account) { + Chat.account = account + } } diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift index b4c529076..57cdf66bf 100644 --- a/Sources/Chat/ChatClient.swift +++ b/Sources/Chat/ChatClient.swift @@ -6,14 +6,12 @@ public class ChatClient { private let registry: Registry private let registryService: RegistryService private let messagingService: MessagingService + private let accountService: AccountService private let invitationHandlingService: InvitationHandlingService private let inviteService: InviteService private let leaveService: LeaveService - private let resubscriptionService: ResubscriptionService private let kms: KeyManagementService - private let threadStore: Database - private let messagesStore: Database - private let invitePayloadStore: CodableStore> + private let chatStorage: ChatStorage public let socketConnectionStatusPublisher: AnyPublisher @@ -37,27 +35,23 @@ public class ChatClient { init(registry: Registry, registryService: RegistryService, messagingService: MessagingService, + accountService: AccountService, invitationHandlingService: InvitationHandlingService, inviteService: InviteService, leaveService: LeaveService, - resubscriptionService: ResubscriptionService, kms: KeyManagementService, - threadStore: Database, - messagesStore: Database, - invitePayloadStore: CodableStore>, + chatStorage: ChatStorage, socketConnectionStatusPublisher: AnyPublisher ) { self.registry = registry self.registryService = registryService self.messagingService = messagingService + self.accountService = accountService self.invitationHandlingService = invitationHandlingService self.inviteService = inviteService self.leaveService = leaveService - self.resubscriptionService = resubscriptionService self.kms = kms - self.threadStore = threadStore - self.messagesStore = messagesStore - self.invitePayloadStore = invitePayloadStore + self.chatStorage = chatStorage self.socketConnectionStatusPublisher = socketConnectionStatusPublisher setUpEnginesCallbacks() @@ -86,15 +80,15 @@ public class ChatClient { /// - publicKey: publicKey associated with a peer /// - openingMessage: oppening message for a chat invite /// TODO - peerAccount should be derived - public func invite(peerAccount: Account, openingMessage: String, account: Account) async throws { - try await inviteService.invite(peerAccount: peerAccount, openingMessage: openingMessage, account: account) + public func invite(peerAccount: Account, openingMessage: String) async throws { + try await inviteService.invite(peerAccount: peerAccount, openingMessage: openingMessage) } - public func accept(inviteId: String) async throws { + public func accept(inviteId: Int64) async throws { try await invitationHandlingService.accept(inviteId: inviteId) } - public func reject(inviteId: String) async throws { + public func reject(inviteId: Int64) async throws { try await invitationHandlingService.reject(inviteId: inviteId) } @@ -116,16 +110,16 @@ public class ChatClient { try await leaveService.leave(topic: topic) } - public func getInvites(account: Account) -> [Invite] { - return invitePayloadStore.getAll().map { $0.request } + public func getInvites() -> [Invite] { + return chatStorage.getInvites(account: accountService.currentAccount) } - public func getThreads() async -> [Thread] { - await threadStore.getAll() + public func getThreads() -> [Thread] { + return chatStorage.getThreads(account: accountService.currentAccount) } - public func getMessages(topic: String) async -> [Message] { - await messagesStore.filter {$0.topic == topic} ?? [] + public func getMessages(topic: String) -> [Message] { + return chatStorage.getMessages(topic: topic, account: accountService.currentAccount) } private func setUpEnginesCallbacks() { diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift index 84b79bc4c..526261241 100644 --- a/Sources/Chat/ChatClientFactory.swift +++ b/Sources/Chat/ChatClientFactory.swift @@ -2,11 +2,12 @@ import Foundation public struct ChatClientFactory { - static func create() -> ChatClient { + static func create(account: Account) -> ChatClient { let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.showcase") let client = HTTPNetworkClient(host: "keys.walletconnect.com") let registry = KeyserverRegistryProvider(client: client) return ChatClientFactory.create( + account: account, registry: registry, relayClient: Relay.instance, kms: KeyManagementService(keychain: keychain), @@ -16,6 +17,7 @@ public struct ChatClientFactory { } public static func create( + account: Account, registry: Registry, relayClient: RelayClient, kms: KeyManagementService, @@ -26,28 +28,28 @@ public struct ChatClientFactory { let serialiser = Serializer(kms: kms) let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serialiser, logger: logger, rpcHistory: rpcHistory) - let invitePayloadStore = CodableStore>(defaults: keyValueStorage, identifier: ChatStorageIdentifiers.invite.rawValue) - let registryService = RegistryService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore) - let threadStore = Database(keyValueStorage: keyValueStorage, identifier: ChatStorageIdentifiers.threads.rawValue) - let resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, threadStore: threadStore, logger: logger) - let invitationHandlingService = InvitationHandlingService(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore, invitePayloadStore: invitePayloadStore, threadsStore: threadStore) - let inviteService = InviteService(networkingInteractor: networkingInteractor, kms: kms, threadStore: threadStore, rpcHistory: rpcHistory, logger: logger, registry: registry) + let messageStore = KeyedDatabase(storage: keyValueStorage, identifier: ChatStorageIdentifiers.messages.rawValue) + let inviteStore = KeyedDatabase(storage: keyValueStorage, identifier: ChatStorageIdentifiers.invites.rawValue) + let threadStore = KeyedDatabase(storage: keyValueStorage, identifier: ChatStorageIdentifiers.threads.rawValue) + let accountService = AccountService(currentAccount: account) + let chatStorage = ChatStorage(messageStore: messageStore, inviteStore: inviteStore, threadStore: threadStore) + let resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, accountService: accountService, chatStorage: chatStorage, logger: logger) + let registryService = RegistryService(registry: registry, accountService: accountService, resubscriptionService: resubscriptionService, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore) + let invitationHandlingService = InvitationHandlingService(registry: registry, networkingInteractor: networkingInteractor, accountService: accountService, kms: kms, logger: logger, topicToRegistryRecordStore: topicToRegistryRecordStore, chatStorage: chatStorage) + let inviteService = InviteService(networkingInteractor: networkingInteractor, accountService: accountService, kms: kms, chatStorage: chatStorage, logger: logger, registry: registry) let leaveService = LeaveService() - let messagesStore = Database(keyValueStorage: keyValueStorage, identifier: ChatStorageIdentifiers.messages.rawValue) - let messagingService = MessagingService(networkingInteractor: networkingInteractor, messagesStore: messagesStore, threadStore: threadStore, logger: logger) + let messagingService = MessagingService(networkingInteractor: networkingInteractor, accountService: accountService, chatStorage: chatStorage, logger: logger) let client = ChatClient( registry: registry, registryService: registryService, messagingService: messagingService, + accountService: accountService, invitationHandlingService: invitationHandlingService, inviteService: inviteService, leaveService: leaveService, - resubscriptionService: resubscriptionService, kms: kms, - threadStore: threadStore, - messagesStore: messagesStore, - invitePayloadStore: invitePayloadStore, + chatStorage: chatStorage, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher ) diff --git a/Sources/Chat/ChatStorage.swift b/Sources/Chat/ChatStorage.swift new file mode 100644 index 000000000..f6f0c0a72 --- /dev/null +++ b/Sources/Chat/ChatStorage.swift @@ -0,0 +1,65 @@ +import Foundation + +struct ChatStorage { + + private let messageStore: KeyedDatabase + private let inviteStore: KeyedDatabase + private let threadStore: KeyedDatabase + + init( + messageStore: KeyedDatabase, + inviteStore: KeyedDatabase, + threadStore: KeyedDatabase + ) { + self.messageStore = messageStore + self.inviteStore = inviteStore + self.threadStore = threadStore + } + + // MARK: - Invites + + func getInvite(id: Int64, account: Account) -> Invite? { + return inviteStore.getElements(for: account.absoluteString) + .first(where: { $0.id == id }) + } + + func set(invite: Invite, account: Account) { + inviteStore.set(invite, for: account.absoluteString) + } + + func getInviteTopic(id: Int64, account: Account) -> String? { + return getInvites(account: account).first(where: { $0.id == id })?.topic + } + + func getInvites(account: Account) -> [Invite] { + return inviteStore.getElements(for: account.absoluteString) + } + + func delete(invite: Invite, account: Account) { + inviteStore.delete(invite, for: account.absoluteString) + } + + // MARK: - Threads + + func getThreads(account: Account) -> [Thread] { + return threadStore.getElements(for: account.absoluteString) + } + + func set(thread: Thread, account: Account) { + threadStore.set(thread, for: account.absoluteString) + } + + // MARK: - Messages + + func set(message: Message, account: Account) { + messageStore.set(message, for: account.absoluteString) + } + + func getMessages(account: Account) -> [Message] { + return messageStore.getElements(for: account.absoluteString) + } + + func getMessages(topic: String, account: Account) -> [Message] { + return messageStore.getElements(for: account.absoluteString).filter { $0.topic == topic } + } +} diff --git a/Sources/Chat/ChatStorageIdentifiers.swift b/Sources/Chat/ChatStorageIdentifiers.swift index bcf560ed3..e9eda634d 100644 --- a/Sources/Chat/ChatStorageIdentifiers.swift +++ b/Sources/Chat/ChatStorageIdentifiers.swift @@ -2,8 +2,7 @@ import Foundation enum ChatStorageIdentifiers: String { case topicToInvitationPubKey = "com.walletconnect.chat.topicToInvitationPubKey" - case invite = "com.walletconnect.chat.invite" - case jsonRpcHistory = "com.walletconnect.chat.jsonRpcHistory" - case threads = "com.walletconnect.chat.threads" case messages = "com.walletconnect.chat.messages" + case threads = "com.walletconnect.chat.threads" + case invites = "com.walletconnect.chat.invites" } diff --git a/Sources/Chat/Database.swift b/Sources/Chat/Database.swift deleted file mode 100644 index 08e6f1360..000000000 --- a/Sources/Chat/Database.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation - -class Database where Element: Codable { - - private var array = [Element]() - private let keyValueStorage: KeyValueStorage - private let identifier: String - - init(keyValueStorage: KeyValueStorage, - identifier: String) { - self.keyValueStorage = keyValueStorage - self.identifier = identifier - if let data = keyValueStorage.object(forKey: identifier) as? Data, - let decoded = try? JSONDecoder().decode([Element].self, from: data) { - array = decoded - } - } - - func filter(_ isIncluded: (Element) -> Bool) async -> [Element]? { - return Array(self.array.filter(isIncluded)) - } - - func getAll() async -> [Element] { - array - } - - func add(_ element: Element) async { - self.array.append(element) - if let encoded = try? JSONEncoder().encode(array) { - keyValueStorage.set(encoded, forKey: identifier) - } - } - - func first(where predicate: (Element) -> Bool) async -> Element? { - self.array.first(where: predicate) - } -} diff --git a/Sources/Chat/Extensions/Dictionary.swift b/Sources/Chat/Extensions/Dictionary.swift new file mode 100644 index 000000000..90fb7ffc6 --- /dev/null +++ b/Sources/Chat/Extensions/Dictionary.swift @@ -0,0 +1,19 @@ +import Foundation + +extension Dictionary where Value: RangeReplaceableCollection, Value.Element: Equatable { + + mutating func append(_ element: Value.Iterator.Element, for key: Key) { + var value: Value = self[key] ?? Value() + value.append(element) + self[key] = value + } + + mutating func delete(_ element: Value.Iterator.Element, for key: Key) { + guard + let value: Value = self[key], + value.contains(where: { $0 == element }) + else { return } + + self[key] = value.filter { $0 != element } + } +} diff --git a/Sources/Chat/KeyedDatabase.swift b/Sources/Chat/KeyedDatabase.swift new file mode 100644 index 000000000..bc110e572 --- /dev/null +++ b/Sources/Chat/KeyedDatabase.swift @@ -0,0 +1,47 @@ +import Foundation + +class KeyedDatabase where Element: Codable & Equatable { + + private var index: [String: [Element]] = [:] { + didSet { self.set(index, for: identifier) } + } + + private let storage: KeyValueStorage + private let identifier: String + + init(storage: KeyValueStorage, identifier: String) { + self.storage = storage + self.identifier = identifier + + initializeIndex() + } + + func getElements(for key: String) -> [Element] { + return index[key] ?? [] + } + + func set(_ element: Element, for key: String) { + index.append(element, for: key) + } + + func delete(_ element: Element, for key: String) { + index.delete(element, for: key) + } +} + +private extension KeyedDatabase { + + func initializeIndex() { + guard + let data = storage.object(forKey: identifier) as? Data, + let decoded = try? JSONDecoder().decode([String: [Element]].self, from: data) + else { return } + + index = decoded + } + + func set(_ value: [String: [Element]], for key: String) { + let data = try! JSONEncoder().encode(value) + storage.set(data, forKey: key) + } +} diff --git a/Sources/Chat/ProtocolServices/Accounts/AccountService.swift b/Sources/Chat/ProtocolServices/Accounts/AccountService.swift new file mode 100644 index 000000000..e0343aa0d --- /dev/null +++ b/Sources/Chat/ProtocolServices/Accounts/AccountService.swift @@ -0,0 +1,14 @@ +import Foundation + +final class AccountService { + + private(set) var currentAccount: Account + + init(currentAccount: Account) { + self.currentAccount = currentAccount + } + + func setAccount(_ account: Account) { + currentAccount = account + } +} diff --git a/Sources/Chat/ProtocolServices/Common/MessagingService.swift b/Sources/Chat/ProtocolServices/Common/MessagingService.swift index b9de3db59..323fe66ba 100644 --- a/Sources/Chat/ProtocolServices/Common/MessagingService.swift +++ b/Sources/Chat/ProtocolServices/Common/MessagingService.swift @@ -5,38 +5,42 @@ class MessagingService { enum Errors: Error { case threadDoNotExist } - let networkingInteractor: NetworkInteracting - var messagesStore: Database - let logger: ConsoleLogging + var onMessage: ((Message) -> Void)? - var threadStore: Database + + private let networkingInteractor: NetworkInteracting + private let accountService: AccountService + private let chatStorage: ChatStorage + private let logger: ConsoleLogging + private var publishers = [AnyCancellable]() + private var currentAccount: Account { + return accountService.currentAccount + } + init(networkingInteractor: NetworkInteracting, - messagesStore: Database, - threadStore: Database, + accountService: AccountService, + chatStorage: ChatStorage, logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor - self.messagesStore = messagesStore + self.accountService = accountService + self.chatStorage = chatStorage self.logger = logger - self.threadStore = threadStore setUpResponseHandling() setUpRequestHandling() } func send(topic: String, messageString: String) async throws { - // TODO - manage author account - let protocolMethod = ChatMessageProtocolMethod() - let thread = await threadStore.first {$0.topic == topic} - guard let authorAccount = thread?.selfAccount else { throw Errors.threadDoNotExist} let timestamp = Int64(Date().timeIntervalSince1970 * 1000) - let message = Message(topic: topic, message: messageString, authorAccount: authorAccount, timestamp: timestamp) + let message = Message(topic: topic, message: messageString, authorAccount: currentAccount, timestamp: timestamp) + + let protocolMethod = ChatMessageProtocolMethod() let request = RPCRequest(method: protocolMethod.method, params: message) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - Task(priority: .background) { - await messagesStore.add(message) - onMessage?(message) - } + + chatStorage.set(message: message, account: currentAccount) + onMessage?(message) } private func setUpResponseHandling() { @@ -48,18 +52,21 @@ class MessagingService { private func setUpRequestHandling() { networkingInteractor.requestSubscription(on: ChatMessageProtocolMethod()) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - var message = payload.request - message.topic = payload.topic + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + let message = Message(topic: payload.topic, payload: payload.request) handleMessage(message, topic: payload.topic, requestId: payload.id) }.store(in: &publishers) } private func handleMessage(_ message: Message, topic: String, requestId: RPCID) { - Task(priority: .background) { - try await networkingInteractor.respondSuccess(topic: topic, requestId: requestId, protocolMethod: ChatMessageProtocolMethod()) - await messagesStore.add(message) + Task(priority: .high) { + try await networkingInteractor.respondSuccess( + topic: topic, + requestId: requestId, + protocolMethod: ChatMessageProtocolMethod() + ) logger.debug("Received message") + chatStorage.set(message: message, account: currentAccount) onMessage?(message) } } diff --git a/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift b/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift index b01b901ae..754ceedfc 100644 --- a/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift +++ b/Sources/Chat/ProtocolServices/Common/ResubscriptionService.swift @@ -3,16 +3,19 @@ import Combine class ResubscriptionService { private let networkingInteractor: NetworkInteracting + private let accountService: AccountService private let logger: ConsoleLogging - private var threadStore: Database + private var chatStorage: ChatStorage private var publishers = [AnyCancellable]() init(networkingInteractor: NetworkInteracting, - threadStore: Database, + accountService: AccountService, + chatStorage: ChatStorage, logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor + self.accountService = accountService self.logger = logger - self.threadStore = threadStore + self.chatStorage = chatStorage setUpResubscription() } @@ -20,10 +23,20 @@ class ResubscriptionService { networkingInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in guard status == .connected else { return } - Task(priority: .background) { - let topics = await threadStore.getAll().map {$0.topic} - topics.forEach { topic in Task(priority: .background) { try? await networkingInteractor.subscribe(topic: topic) } } + + Task(priority: .high) { + try await resubscribe(account: accountService.currentAccount) } }.store(in: &publishers) } + + func resubscribe(account: Account) async throws { + let topics = chatStorage.getThreads(account: account).map { $0.topic } + try await networkingInteractor.batchSubscribe(topics: topics) + } + + func unsubscribe(account: Account) async throws { + let topics = chatStorage.getThreads(account: account).map { $0.topic } + try await networkingInteractor.batchUnsubscribe(topics: topics) + } } diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift index db0c13599..27f7b14c0 100644 --- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift @@ -8,45 +8,59 @@ class InvitationHandlingService { var onInvite: ((Invite) -> Void)? var onNewThread: ((Thread) -> Void)? private let networkingInteractor: NetworkInteracting - private let invitePayloadStore: CodableStore> + private let chatStorage: ChatStorage + private let accountService: AccountService private let topicToRegistryRecordStore: CodableStore private let registry: Registry private let logger: ConsoleLogging private let kms: KeyManagementService - private let threadsStore: Database private var publishers = [AnyCancellable]() + private var currentAccount: Account { + return accountService.currentAccount + } + init(registry: Registry, networkingInteractor: NetworkInteracting, + accountService: AccountService, kms: KeyManagementService, logger: ConsoleLogging, topicToRegistryRecordStore: CodableStore, - invitePayloadStore: CodableStore>, - threadsStore: Database) { + chatStorage: ChatStorage) { self.registry = registry self.kms = kms self.networkingInteractor = networkingInteractor + self.accountService = accountService self.logger = logger self.topicToRegistryRecordStore = topicToRegistryRecordStore - self.invitePayloadStore = invitePayloadStore - self.threadsStore = threadsStore + self.chatStorage = chatStorage setUpRequestHandling() } - func accept(inviteId: String) async throws { - let protocolMethod = ChatInviteProtocolMethod() - - guard let payload = try invitePayloadStore.get(key: inviteId) else { throw Error.inviteForIdNotFound } + func accept(inviteId: Int64) async throws { + guard + let invite = chatStorage.getInvite(id: inviteId, account: currentAccount), + let inviteTopic = chatStorage.getInviteTopic(id: inviteId, account: currentAccount) + else { throw Error.inviteForIdNotFound } let selfThreadPubKey = try kms.createX25519KeyPair() let inviteResponse = InviteResponse(publicKey: selfThreadPubKey.hexRepresentation) - let response = RPCResponse(id: payload.id, result: inviteResponse) - let responseTopic = try getInviteResponseTopic(requestTopic: payload.topic, invite: payload.request) - try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod) - - let threadAgreementKeys = try kms.performKeyAgreement(selfPublicKey: selfThreadPubKey, peerPublicKey: payload.request.publicKey) + let responseTopic = try getInviteResponseTopic( + requestTopic: inviteTopic, + invite: invite + ) + try await networkingInteractor.respond( + topic: responseTopic, + response: RPCResponse(id: inviteId, result: inviteResponse), + protocolMethod: ChatInviteProtocolMethod() + ) + + let threadAgreementKeys = try kms.performKeyAgreement( + selfPublicKey: selfThreadPubKey, + peerPublicKey: invite.publicKey + ) let threadTopic = threadAgreementKeys.derivedTopic() try kms.setSymmetricKey(threadAgreementKeys.sharedKey, for: threadTopic) try await networkingInteractor.subscribe(topic: threadTopic) @@ -54,31 +68,49 @@ class InvitationHandlingService { logger.debug("Accepting an invite on topic: \(threadTopic)") // TODO - derive account - let selfAccount = try! topicToRegistryRecordStore.get(key: payload.topic)!.account - let thread = Thread(topic: threadTopic, selfAccount: selfAccount, peerAccount: payload.request.account) - await threadsStore.add(thread) + let selfAccount = try! topicToRegistryRecordStore.get(key: inviteTopic)!.account + + let thread = Thread( + topic: threadTopic, + selfAccount: selfAccount, + peerAccount: invite.account + ) - invitePayloadStore.delete(forKey: inviteId) + chatStorage.set(thread: thread, account: currentAccount) + chatStorage.delete(invite: invite, account: currentAccount) onNewThread?(thread) } - func reject(inviteId: String) async throws { - guard let payload = try invitePayloadStore.get(key: inviteId) else { throw Error.inviteForIdNotFound } + func reject(inviteId: Int64) async throws { + guard + let invite = chatStorage.getInvite(id: inviteId, account: currentAccount), + let inviteTopic = chatStorage.getInviteTopic(id: inviteId, account: currentAccount) + else { throw Error.inviteForIdNotFound } - let responseTopic = try getInviteResponseTopic(requestTopic: payload.topic, invite: payload.request) + let responseTopic = try getInviteResponseTopic(requestTopic: inviteTopic, invite: invite) - try await networkingInteractor.respondError(topic: responseTopic, requestId: payload.id, protocolMethod: ChatInviteProtocolMethod(), reason: ChatError.userRejected) + try await networkingInteractor.respondError( + topic: responseTopic, + requestId: RPCID(inviteId), + protocolMethod: ChatInviteProtocolMethod(), + reason: ChatError.userRejected + ) - invitePayloadStore.delete(forKey: inviteId) + chatStorage.delete(invite: invite, account: currentAccount) } private func setUpRequestHandling() { networkingInteractor.requestSubscription(on: ChatInviteProtocolMethod()) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in + .sink { [unowned self] (payload: RequestSubscriptionPayload) in logger.debug("did receive an invite") - invitePayloadStore.set(payload, forKey: payload.request.publicKey) - onInvite?(payload.request) + let invite = Invite( + id: payload.id.integer, + topic: payload.topic, + payload: payload.request + ) + chatStorage.set(invite: invite, account: currentAccount) + onInvite?(invite) }.store(in: &publishers) } diff --git a/Sources/Chat/ProtocolServices/Invitee/RegistryService.swift b/Sources/Chat/ProtocolServices/Invitee/RegistryService.swift index 002102fb3..47f34e155 100644 --- a/Sources/Chat/ProtocolServices/Invitee/RegistryService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/RegistryService.swift @@ -1,19 +1,25 @@ import Foundation actor RegistryService { - let networkingInteractor: NetworkInteracting - let topicToRegistryRecordStore: CodableStore - let registry: Registry - let logger: ConsoleLogging - let kms: KeyManagementServiceProtocol + private let networkingInteractor: NetworkInteracting + private let accountService: AccountService + private let resubscriptionService: ResubscriptionService + private let topicToRegistryRecordStore: CodableStore + private let registry: Registry + private let logger: ConsoleLogging + private let kms: KeyManagementServiceProtocol init(registry: Registry, + accountService: AccountService, + resubscriptionService: ResubscriptionService, networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, topicToRegistryRecordStore: CodableStore) { self.registry = registry self.kms = kms + self.accountService = accountService + self.resubscriptionService = resubscriptionService self.networkingInteractor = networkingInteractor self.logger = logger self.topicToRegistryRecordStore = topicToRegistryRecordStore @@ -23,12 +29,22 @@ actor RegistryService { let pubKey = try kms.createX25519KeyPair() let pubKeyHex = pubKey.hexRepresentation try await registry.register(account: account, pubKey: pubKeyHex) + let topic = pubKey.rawRepresentation.sha256().toHexString() try kms.setPublicKey(publicKey: pubKey, for: topic) + let record = RegistryRecord(account: account, pubKey: pubKeyHex) topicToRegistryRecordStore.set(record, forKey: topic) + try await networkingInteractor.subscribe(topic: topic) + + let oldAccount = accountService.currentAccount + try await resubscriptionService.unsubscribe(account: oldAccount) + accountService.setAccount(account) + try await resubscriptionService.resubscribe(account: account) + logger.debug("Did register an account: \(account) and is subscribing on topic: \(topic)") + return pubKeyHex } } diff --git a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift index f8b2339c3..8a357fba6 100644 --- a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift +++ b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift @@ -4,10 +4,10 @@ import Combine class InviteService { private var publishers = [AnyCancellable]() private let networkingInteractor: NetworkInteracting + private let accountService: AccountService private let logger: ConsoleLogging private let kms: KeyManagementService - private let threadStore: Database - private let rpcHistory: RPCHistory + private let chatStorage: ChatStorage private let registry: Registry var onNewThread: ((Thread) -> Void)? @@ -15,29 +15,28 @@ class InviteService { init( networkingInteractor: NetworkInteracting, + accountService: AccountService, kms: KeyManagementService, - threadStore: Database, - rpcHistory: RPCHistory, + chatStorage: ChatStorage, logger: ConsoleLogging, registry: Registry ) { self.kms = kms self.networkingInteractor = networkingInteractor + self.accountService = accountService self.logger = logger - self.threadStore = threadStore - self.rpcHistory = rpcHistory + self.chatStorage = chatStorage self.registry = registry setUpResponseHandling() } var peerAccount: Account! - func invite(peerAccount: Account, openingMessage: String, account: Account) async throws { + func invite(peerAccount: Account, openingMessage: String) async throws { // TODO ad storage let protocolMethod = ChatInviteProtocolMethod() self.peerAccount = peerAccount let selfPubKeyY = try kms.createX25519KeyPair() - let invite = Invite(message: openingMessage, account: account, publicKey: selfPubKeyY.hexRepresentation) let peerPubKey = try await registry.resolve(account: peerAccount) let symKeyI = try kms.performKeyAgreement(selfPublicKey: selfPubKeyY, peerPublicKey: peerPubKey) let inviteTopic = try AgreementPublicKey(hex: peerPubKey).rawRepresentation.sha256().toHexString() @@ -45,6 +44,7 @@ class InviteService { // overrides on invite toipic try kms.setSymmetricKey(symKeyI.sharedKey, for: inviteTopic) + let invite = InvitePayload(message: openingMessage, account: accountService.currentAccount, publicKey: selfPubKeyY.hexRepresentation) let request = RPCRequest(method: protocolMethod.method, params: invite) // 2. Proposer subscribes to topic R which is the hash of the derived symKey @@ -60,10 +60,10 @@ class InviteService { private func setUpResponseHandling() { networkingInteractor.responseSubscription(on: ChatInviteProtocolMethod()) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in logger.debug("Invite has been accepted") - Task(priority: .background) { + Task(priority: .high) { try await createThread( selfPubKeyHex: payload.request.publicKey, peerPubKey: payload.response.publicKey, @@ -79,9 +79,17 @@ class InviteService { let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey) let threadTopic = agreementKeys.derivedTopic() try kms.setSymmetricKey(agreementKeys.sharedKey, for: threadTopic) + try await networkingInteractor.subscribe(topic: threadTopic) - let thread = Thread(topic: threadTopic, selfAccount: account, peerAccount: peerAccount) - await threadStore.add(thread) + + let thread = Thread( + topic: threadTopic, + selfAccount: account, + peerAccount: peerAccount + ) + + chatStorage.set(thread: thread, account: accountService.currentAccount) + onNewThread?(thread) // TODO - remove symKeyI } diff --git a/Sources/Chat/Types/Invite.swift b/Sources/Chat/Types/Invite.swift index dc3dcb490..4815731d7 100644 --- a/Sources/Chat/Types/Invite.swift +++ b/Sources/Chat/Types/Invite.swift @@ -1,22 +1,27 @@ import Foundation -struct InviteResponse: Codable { - let publicKey: String -} - public struct Invite: Codable, Equatable { - public var id: String { - return publicKey - } + public let id: Int64 + public let topic: String public let message: String public let account: Account public let publicKey: String - static var tag: Int { - return 2000 + init(id: Int64, topic: String, payload: InvitePayload) { + self.id = id + self.topic = topic + self.message = payload.message + self.account = payload.account + self.publicKey = payload.publicKey } +} - static var method: String { - return "wc_chatInvite" - } +struct InviteResponse: Codable { + let publicKey: String +} + +struct InvitePayload: Codable { + let message: String + let account: Account + let publicKey: String } diff --git a/Sources/Chat/Types/Message.swift b/Sources/Chat/Types/Message.swift index ac4e1b33e..ba6e3ac43 100644 --- a/Sources/Chat/Types/Message.swift +++ b/Sources/Chat/Types/Message.swift @@ -1,22 +1,28 @@ import Foundation public struct Message: Codable, Equatable { - public var topic: String? + public let topic: String public let message: String public let authorAccount: Account public let timestamp: Int64 - enum CodingKeys: String, CodingKey { - case topic - case message - case authorAccount - case timestamp - } - - init(topic: String? = nil, message: String, authorAccount: Account, timestamp: Int64) { + init(topic: String, message: String, authorAccount: Account, timestamp: Int64) { self.topic = topic self.message = message self.authorAccount = authorAccount self.timestamp = timestamp } + + init(topic: String, payload: MessagePayload) { + self.topic = topic + self.message = payload.message + self.authorAccount = payload.authorAccount + self.timestamp = payload.timestamp + } +} + +public struct MessagePayload: Codable { + public let message: String + public let authorAccount: Account + public let timestamp: Int64 } diff --git a/Sources/Chat/Types/Thread.swift b/Sources/Chat/Types/Thread.swift index 8768c20a6..ed5ef5ca0 100644 --- a/Sources/Chat/Types/Thread.swift +++ b/Sources/Chat/Types/Thread.swift @@ -1,6 +1,6 @@ import Foundation -public struct Thread: Codable { +public struct Thread: Codable, Equatable { public let topic: String public let selfAccount: Account public let peerAccount: Account diff --git a/Sources/Commons/AnyCodable.swift b/Sources/Commons/AnyCodable.swift index 52552e344..895c7cfc2 100644 --- a/Sources/Commons/AnyCodable.swift +++ b/Sources/Commons/AnyCodable.swift @@ -40,7 +40,16 @@ public struct AnyCodable { genericEncoding = { encoder in try codable.encode(to: encoder) } + } + + /** + Creates a type-erased codable value that wraps the given instance. + - parameters: + - any: Any value which supposed to be codable + */ + public init(any value: Any) { + self.init(AnyCodable(value)) } /** diff --git a/Sources/JSONRPC/RPCID.swift b/Sources/JSONRPC/RPCID.swift index 4bf915fbc..c067b67ae 100644 --- a/Sources/JSONRPC/RPCID.swift +++ b/Sources/JSONRPC/RPCID.swift @@ -17,6 +17,24 @@ struct IntIdentifierGenerator: IdentifierGenerator { extension RPCID { + public var string: String { + switch self { + case .right(let int): + return int.description + case .left(let string): + return string + } + } + + public var integer: Int64 { + switch self { + case .right(let int): + return int + case .left(let string): + return Int64(string) ?? 0 + } + } + public var timestamp: Date { guard let id = self.right else { return .distantPast } let interval = TimeInterval(id / 1000 / 1000) diff --git a/Sources/WalletConnectEcho/Echo.swift b/Sources/WalletConnectEcho/Echo.swift index 3921ffebc..6ebc721e2 100644 --- a/Sources/WalletConnectEcho/Echo.swift +++ b/Sources/WalletConnectEcho/Echo.swift @@ -2,7 +2,7 @@ import Foundation import WalletConnectNetworking public class Echo { - + static public let echoHost = "echo.walletconnect.com" public static var instance: EchoClient = { guard let config = Echo.config else { fatalError("Error - you must call Echo.configure(_:) before accessing the shared instance.") @@ -10,7 +10,8 @@ public class Echo { return EchoClientFactory.create( projectId: Networking.projectId, - clientId: config.clientId) + clientId: config.clientId, + echoHost: config.echoHost) }() private static var config: Config? @@ -19,7 +20,10 @@ public class Echo { /// Echo instance config method /// - Parameter clientId: https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/core/relay/relay-client-auth.md#overview - static public func configure(clientId: String) { - Echo.config = Echo.Config(clientId: clientId) + static public func configure( + clientId: String, + echoHost: String = echoHost + ) { + Echo.config = Echo.Config(clientId: clientId, echoHost: echoHost) } } diff --git a/Sources/WalletConnectEcho/EchoClientFactory.swift b/Sources/WalletConnectEcho/EchoClientFactory.swift index 859b0d37f..ff7b8cbb6 100644 --- a/Sources/WalletConnectEcho/EchoClientFactory.swift +++ b/Sources/WalletConnectEcho/EchoClientFactory.swift @@ -2,9 +2,9 @@ import Foundation import WalletConnectNetworking public struct EchoClientFactory { - public static func create(projectId: String, clientId: String) -> EchoClient { + public static func create(projectId: String, clientId: String, echoHost: String) -> EchoClient { - let httpClient = HTTPNetworkClient(host: "echo.walletconnect.com") + let httpClient = HTTPNetworkClient(host: echoHost) return EchoClientFactory.create( projectId: projectId, @@ -16,7 +16,7 @@ public struct EchoClientFactory { clientId: String, httpClient: HTTPClient) -> EchoClient { - let logger = ConsoleLogger(loggingLevel: .debug) + let logger = ConsoleLogger(loggingLevel: .off) let registerService = EchoRegisterService(httpClient: httpClient, projectId: projectId, clientId: clientId, logger: logger) return EchoClient( diff --git a/Sources/WalletConnectEcho/EchoConfig.swift b/Sources/WalletConnectEcho/EchoConfig.swift index acd6ade9f..d1eab366f 100644 --- a/Sources/WalletConnectEcho/EchoConfig.swift +++ b/Sources/WalletConnectEcho/EchoConfig.swift @@ -3,5 +3,6 @@ import Foundation extension Echo { struct Config { let clientId: String + let echoHost: String } } diff --git a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift index c3e507af2..51fbd7560 100644 --- a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift +++ b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift @@ -33,9 +33,10 @@ actor EchoRegisterService { EchoResponse.self, at: EchoAPI.register(clientId: clientIdMutlibase, token: token, projectId: projectId) ) - guard response.status == .ok else { + guard response.status == .success else { throw Errors.registrationFailed } + logger.debug("Successfully registered at Echo Server") } #if DEBUG @@ -44,9 +45,10 @@ actor EchoRegisterService { EchoResponse.self, at: EchoAPI.register(clientId: clientIdMutlibase, token: deviceToken, projectId: projectId) ) - guard response.status == .ok else { + guard response.status == .success else { throw Errors.registrationFailed } + logger.debug("Successfully registered at Echo Server") } #endif } diff --git a/Sources/WalletConnectEcho/Register/EcoResponse.swift b/Sources/WalletConnectEcho/Register/EcoResponse.swift index 4c50053a0..f480c510c 100644 --- a/Sources/WalletConnectEcho/Register/EcoResponse.swift +++ b/Sources/WalletConnectEcho/Register/EcoResponse.swift @@ -2,7 +2,7 @@ import Foundation struct EchoResponse: Codable { enum Status: String, Codable { - case ok = "OK" + case success = "SUCCESS" case failed = "FAILED" } diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index ea759adfe..109415ad6 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -6,6 +6,7 @@ public protocol NetworkInteracting { var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) + func batchSubscribe(topics: [String]) async throws func batchUnsubscribe(topics: [String]) async throws func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 0b0e0f37e..634f350d2 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -57,6 +57,10 @@ public class NetworkingInteractor: NetworkInteracting { } } + public func batchSubscribe(topics: [String]) async throws { + try await relayClient.batchSubscribe(topics: topics) + } + public func batchUnsubscribe(topics: [String]) async throws { try await relayClient.batchUnsubscribe(topics: topics) rpcHistory.deleteAll(forTopics: topics) diff --git a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift index 4afa24002..5e4b85ebe 100644 --- a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift @@ -46,7 +46,7 @@ class ProposalResponseSubscriber { let selfpublicKeyHex = payload.request.publicKey let (topic, _) = try generateAgreementKeys(peerPublicKeyHex: peerPublicKeyHex, selfpublicKeyHex: selfpublicKeyHex) - let pushSubscription = PushSubscription(topic: topic, relay: relay, metadata: metadata) + let pushSubscription = PushSubscription(topic: topic, account: payload.request.account, relay: relay, metadata: metadata) subscriptionsStore.set(pushSubscription, forKey: topic) kms.deletePrivateKey(for: selfpublicKeyHex) try await networkingInteractor.subscribe(topic: topic) diff --git a/Sources/WalletConnectPush/Client/Wallet/PushMessagesProvider.swift b/Sources/WalletConnectPush/Client/Wallet/PushMessagesProvider.swift new file mode 100644 index 000000000..236fb0879 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/PushMessagesProvider.swift @@ -0,0 +1,15 @@ + +import Foundation +import WalletConnectUtils + +class PushMessagesProvider { + private let history: RPCHistory + + init(history: RPCHistory) { + self.history = history + } + + public func getMessageHistory(topic: String) -> [PushMessage] { + history.getAll(of: PushMessage.self, topic: topic) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift b/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift index 840e85cfa..c674d63a0 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/PushRequestResponder.swift @@ -51,7 +51,7 @@ class PushRequestResponder { let response = RPCResponse(id: requestId, result: responseParams) let requestParams = try requestRecord.request.params!.get(PushRequestParams.self) - let pushSubscription = PushSubscription(topic: pushTopic, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata) + let pushSubscription = PushSubscription(topic: pushTopic, account: requestParams.account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata) subscriptionsStore.set(pushSubscription, forKey: pushTopic) try await networkingInteractor.respond(topic: pairingTopic, response: response, protocolMethod: PushRequestProtocolMethod()) diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 18c7c30b3..65ef69e89 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -4,13 +4,14 @@ import WalletConnectNetworking import WalletConnectPairing import WalletConnectEcho + public class WalletPushClient { private var publishers = Set() - private let requestPublisherSubject = PassthroughSubject<(id: RPCID, account: Account, metadata: AppMetadata), Never>() + private let requestPublisherSubject = PassthroughSubject() - public var requestPublisher: AnyPublisher<(id: RPCID, account: Account, metadata: AppMetadata), Never> { + public var requestPublisher: AnyPublisher { requestPublisherSubject.eraseToAnyPublisher() } @@ -36,6 +37,7 @@ public class WalletPushClient { private let proposeResponder: PushRequestResponder private let pushMessageSubscriber: PushMessageSubscriber private let subscriptionsProvider: SubscriptionsProvider + private let pushMessagesProvider: PushMessagesProvider private let resubscribeService: PushResubscribeService init(logger: ConsoleLogging, @@ -45,6 +47,7 @@ public class WalletPushClient { proposeResponder: PushRequestResponder, pushMessageSubscriber: PushMessageSubscriber, subscriptionsProvider: SubscriptionsProvider, + pushMessagesProvider: PushMessagesProvider, deletePushSubscriptionService: DeletePushSubscriptionService, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, resubscribeService: PushResubscribeService) { @@ -54,6 +57,7 @@ public class WalletPushClient { self.echoClient = echoClient self.pushMessageSubscriber = pushMessageSubscriber self.subscriptionsProvider = subscriptionsProvider + self.pushMessagesProvider = pushMessagesProvider self.deletePushSubscriptionService = deletePushSubscriptionService self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService @@ -72,6 +76,10 @@ public class WalletPushClient { subscriptionsProvider.getActiveSubscriptions() } + public func getMessageHistory(topic: String) -> [PushMessage] { + pushMessagesProvider.getMessageHistory(topic: topic) + } + public func delete(topic: String) async throws { try await deletePushSubscriptionService.delete(topic: topic) } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index 7d11f4ade..2ed976c16 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -36,6 +36,7 @@ public struct WalletPushClientFactory { let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) + let pushMessagesProvider = PushMessagesProvider(history: history) return WalletPushClient( logger: logger, kms: kms, @@ -44,6 +45,7 @@ public struct WalletPushClientFactory { proposeResponder: proposeResponder, pushMessageSubscriber: pushMessageSubscriber, subscriptionsProvider: subscriptionProvider, + pushMessagesProvider: pushMessagesProvider, deletePushSubscriptionService: deletePushSubscriptionService, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, resubscribeService: resubscribeService diff --git a/Sources/WalletConnectPush/Push.swift b/Sources/WalletConnectPush/Push.swift index eea9a582c..5b159bf58 100644 --- a/Sources/WalletConnectPush/Push.swift +++ b/Sources/WalletConnectPush/Push.swift @@ -16,7 +16,7 @@ public class Push { guard let config = Push.config else { fatalError("Error - you must call Push.configure(_:) before accessing the shared wallet instance.") } - Echo.configure(clientId: config.clientId) + Echo.configure(clientId: config.clientId, echoHost: config.echoHost) return WalletPushClientFactory.create( networkInteractor: Networking.interactor, pairingRegisterer: Pair.registerer, @@ -29,9 +29,9 @@ public class Push { private init() { } /// Wallet's configuration method - static public func configure() { + static public func configure(echoHost: String = "echo.walletconnect.com") { let clientId = try! Networking.interactor.getClientId() - Push.config = Push.Config(clientId: clientId) + Push.config = Push.Config(clientId: clientId, echoHost: echoHost) } } diff --git a/Sources/WalletConnectPush/PushConfig.swift b/Sources/WalletConnectPush/PushConfig.swift index bf0f083d1..8c7445dcc 100644 --- a/Sources/WalletConnectPush/PushConfig.swift +++ b/Sources/WalletConnectPush/PushConfig.swift @@ -3,5 +3,6 @@ import Foundation extension Push { struct Config { let clientId: String + let echoHost: String } } diff --git a/Sources/WalletConnectPush/Types/PushRequest.swift b/Sources/WalletConnectPush/Types/PushRequest.swift new file mode 100644 index 000000000..a27b87a9b --- /dev/null +++ b/Sources/WalletConnectPush/Types/PushRequest.swift @@ -0,0 +1,5 @@ + +import Foundation +import WalletConnectPairing + +public typealias PushRequest = (id: RPCID, account: Account, metadata: AppMetadata) diff --git a/Sources/WalletConnectPush/Types/PushSubscription.swift b/Sources/WalletConnectPush/Types/PushSubscription.swift index 0dc61526b..783ec3dd1 100644 --- a/Sources/WalletConnectPush/Types/PushSubscription.swift +++ b/Sources/WalletConnectPush/Types/PushSubscription.swift @@ -3,7 +3,8 @@ import WalletConnectUtils import WalletConnectPairing public struct PushSubscription: Codable, Equatable { - let topic: String - let relay: RelayProtocolOptions - let metadata: AppMetadata + public let topic: String + public let account: Account + public let relay: RelayProtocolOptions + public let metadata: AppMetadata } diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index c712e32f1..1663b3678 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.3.0"} +{"version": "1.3.1"} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index ca9f9b649..104c92d69 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -213,6 +213,16 @@ public final class RelayClient { } } + public func batchSubscribe(topics: [String]) async throws { + await withThrowingTaskGroup(of: Void.self) { group in + for topic in topics { + group.addTask { + try await self.subscribe(topic: topic) + } + } + } + } + public func batchUnsubscribe(topics: [String]) async throws { await withThrowingTaskGroup(of: Void.self) { group in for topic in topics { @@ -251,7 +261,6 @@ public final class RelayClient { self?.concurrentQueue.async(flags: .barrier) { self?.subscriptions[topic] = nil } - completion(nil) } } } diff --git a/Sources/WalletConnectRouter/Router.m b/Sources/WalletConnectRouter/Router.m index 9010aa7e6..0648d6eb1 100644 --- a/Sources/WalletConnectRouter/Router.m +++ b/Sources/WalletConnectRouter/Router.m @@ -1,6 +1,8 @@ #import #import "Router.h" +#if __has_include() + @import UIKit; @import ObjectiveC.runtime; @@ -24,3 +26,5 @@ + (void)goBack { @end +#endif + diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index a3624466a..a00b62c00 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -15,13 +15,11 @@ final class ApproveEngine { var onSessionRejected: ((Session.Proposal, Reason) -> Void)? var onSessionSettle: ((Session) -> Void)? - var settlingProposal: SessionProposal? - private let networkingInteractor: NetworkInteracting private let pairingStore: WCPairingStorage private let sessionStore: WCSessionStorage private let proposalPayloadsStore: CodableStore> - private let sessionToPairingTopic: CodableStore + private let sessionTopicToProposal: CodableStore private let pairingRegisterer: PairingRegisterer private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol @@ -32,7 +30,7 @@ final class ApproveEngine { init( networkingInteractor: NetworkInteracting, proposalPayloadsStore: CodableStore>, - sessionToPairingTopic: CodableStore, + sessionTopicToProposal: CodableStore, pairingRegisterer: PairingRegisterer, metadata: AppMetadata, kms: KeyManagementServiceProtocol, @@ -42,7 +40,7 @@ final class ApproveEngine { ) { self.networkingInteractor = networkingInteractor self.proposalPayloadsStore = proposalPayloadsStore - self.sessionToPairingTopic = sessionToPairingTopic + self.sessionTopicToProposal = sessionTopicToProposal self.pairingRegisterer = pairingRegisterer self.metadata = metadata self.kms = kms @@ -62,6 +60,7 @@ final class ApproveEngine { } let proposal = payload.request + let pairingTopic = payload.topic proposalPayloadsStore.delete(forKey: proposerPubKey) @@ -77,7 +76,6 @@ final class ApproveEngine { let sessionTopic = agreementKey.derivedTopic() try kms.setAgreementSecret(agreementKey, topic: sessionTopic) - sessionToPairingTopic.set(payload.topic, forKey: sessionTopic) guard let relay = proposal.relays.first else { throw Errors.relayNotFound @@ -88,7 +86,7 @@ final class ApproveEngine { async let proposeResponse: () = networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod()) - async let settleRequest: () = settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces) + async let settleRequest: () = settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces, pairingTopic: pairingTopic) _ = try await [proposeResponse, settleRequest] @@ -107,7 +105,7 @@ final class ApproveEngine { // TODO: Delete pairing if inactive } - func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace]) async throws { + func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], pairingTopic: String) async throws { guard let agreementKeys = kms.getAgreementSecret(for: topic) else { throw Errors.agreementMissingOrInvalid } @@ -132,6 +130,7 @@ final class ApproveEngine { let session = WCSession( topic: topic, + pairingTopic: pairingTopic, timestamp: Date(), selfParticipant: selfParticipant, peerParticipant: proposal.proposer, @@ -150,7 +149,6 @@ final class ApproveEngine { async let settleRequest: () = networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) _ = try await [settleRequest, subscription] - onSessionSettle?(session.publicRepresentation()) } } @@ -216,10 +214,9 @@ private extension ApproveEngine { logger.debug("Received Session Proposal response") try kms.setAgreementSecret(agreementKeys, topic: sessionTopic) - sessionToPairingTopic.set(payload.topic, forKey: sessionTopic) - - settlingProposal = payload.request + let proposal = payload.request.publicRepresentation(pairingTopic: payload.topic) + sessionTopicToProposal.set(proposal, forKey: sessionTopic) Task(priority: .high) { try await networkingInteractor.subscribe(topic: sessionTopic) } @@ -290,11 +287,13 @@ private extension ApproveEngine { let protocolMethod = SessionSettleProtocolMethod() - guard let proposedNamespaces = settlingProposal?.requiredNamespaces else { - return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod) - } + let sessionTopic = payload.topic - settlingProposal = nil + guard let proposal = try? sessionTopicToProposal.get(key: sessionTopic) else { + return respondError(payload: payload, reason: .sessionSettlementFailed, protocolMethod: protocolMethod) + } + let pairingTopic = proposal.pairingTopic + let proposedNamespaces = proposal.requiredNamespaces let params = payload.request let sessionNamespaces = params.namespaces @@ -308,22 +307,20 @@ private extension ApproveEngine { return respondError(payload: payload, reason: .invalidUpdateRequest, protocolMethod: protocolMethod) } - let topic = payload.topic - let agreementKeys = kms.getAgreementSecret(for: topic)! + let agreementKeys = kms.getAgreementSecret(for: sessionTopic)! let selfParticipant = Participant( publicKey: agreementKeys.publicKey.hexRepresentation, metadata: metadata ) - if let pairingTopic = try? sessionToPairingTopic.get(key: topic) { - pairingRegisterer.activate( - pairingTopic: pairingTopic, - peerMetadata: params.controller.metadata - ) - } + pairingRegisterer.activate( + pairingTopic: pairingTopic, + peerMetadata: params.controller.metadata + ) let session = WCSession( - topic: topic, + topic: sessionTopic, + pairingTopic: pairingTopic, timestamp: Date(), selfParticipant: selfParticipant, peerParticipant: params.controller, diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 7f552ca32..562726920 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -4,6 +4,7 @@ import Combine final class SessionEngine { enum Errors: Error { case sessionNotFound(topic: String) + case sessionRequestExpired } var onSessionsUpdate: (([Session]) -> Void)? @@ -15,17 +16,20 @@ final class SessionEngine { private let sessionStore: WCSessionStorage private let networkingInteractor: NetworkInteracting + private let historyService: HistoryService private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging init( networkingInteractor: NetworkInteracting, + historyService: HistoryService, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, logger: ConsoleLogging ) { self.networkingInteractor = networkingInteractor + self.historyService = historyService self.kms = kms self.sessionStore = sessionStore self.logger = logger @@ -54,9 +58,9 @@ final class SessionEngine { guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else { throw WalletConnectError.invalidPermissions } - let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params) + let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiry) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) - let protocolMethod = SessionRequestProtocolMethod() + let protocolMethod = SessionRequestProtocolMethod(ttl: request.calculateTtl()) let rpcRequest = RPCRequest(method: protocolMethod.method, params: sessionRequestParams) try await networkingInteractor.request(rpcRequest, topic: request.topic, protocolMethod: SessionRequestProtocolMethod()) } @@ -65,8 +69,24 @@ final class SessionEngine { guard sessionStore.hasSession(forTopic: topic) else { throw Errors.sessionNotFound(topic: topic) } - let response = RPCResponse(id: requestId, outcome: response) - try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: SessionRequestProtocolMethod()) + + let protocolMethod = SessionRequestProtocolMethod() + + guard sessionRequestNotExpired(requestId: requestId) else { + try await networkingInteractor.respondError( + topic: topic, + requestId: requestId, + protocolMethod: protocolMethod, + reason: SignReasonCode.sessionRequestExpired + ) + throw Errors.sessionRequestExpired + } + + try await networkingInteractor.respond( + topic: topic, + response: RPCResponse(id: requestId, outcome: response), + protocolMethod: protocolMethod + ) } func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws { @@ -159,6 +179,13 @@ private extension SessionEngine { } } + func sessionRequestNotExpired(requestId: RPCID) -> Bool { + guard let request = historyService.getSessionRequest(id: requestId) + else { return false } + + return !request.isExpired() + } + func respondError(payload: SubscriptionPayload, reason: SignReasonCode, protocolMethod: ProtocolMethod) { Task(priority: .high) { do { @@ -191,7 +218,9 @@ private extension SessionEngine { topic: payload.topic, method: payload.request.request.method, params: payload.request.request.params, - chainId: payload.request.chainId) + 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) @@ -202,6 +231,11 @@ 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) } diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index 931ae2d4b..f9d2f8e16 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -3,25 +3,11 @@ public struct ProposalNamespace: Equatable, Codable { public let chains: Set public let methods: Set public let events: Set - public let extensions: [Extension]? - public struct Extension: Equatable, Codable { - public let chains: Set - public let methods: Set - public let events: Set - - public init(chains: Set, methods: Set, events: Set) { - self.chains = chains - self.methods = methods - self.events = events - } - } - - public init(chains: Set, methods: Set, events: Set, extensions: [ProposalNamespace.Extension]? = nil) { + public init(chains: Set, methods: Set, events: Set) { self.chains = chains self.methods = methods self.events = events - self.extensions = extensions } } @@ -30,36 +16,11 @@ public struct SessionNamespace: Equatable, Codable { public let accounts: Set public let methods: Set public let events: Set - public let extensions: [Extension]? - - public struct Extension: Equatable, Codable { - public let accounts: Set - public let methods: Set - public let events: Set - - public init(accounts: Set, methods: Set, events: Set) { - self.accounts = accounts - self.methods = methods - self.events = events - } - - func isCompliant(to required: ProposalNamespace.Extension) -> Bool { - guard - SessionNamespace.accountsAreCompliant(accounts, toChains: required.chains), - methods.isSuperset(of: required.methods), - events.isSuperset(of: required.events) - else { - return false - } - return true - } - } - public init(accounts: Set, methods: Set, events: Set, extensions: [SessionNamespace.Extension]? = nil) { + public init(accounts: Set, methods: Set, events: Set) { self.accounts = accounts self.methods = methods self.events = events - self.extensions = extensions } static func accountsAreCompliant(_ accounts: Set, toChains chains: Set) -> Bool { @@ -84,13 +45,6 @@ enum Namespace { throw WalletConnectError.unsupportedNamespace(.unsupportedChains) } } - if let extensions = namespace.extensions { - for ext in extensions { - if ext.chains.isEmpty { - throw WalletConnectError.unsupportedNamespace(.unsupportedChains) - } - } - } } } @@ -104,13 +58,6 @@ enum Namespace { throw WalletConnectError.unsupportedNamespace(.unsupportedAccounts) } } - if let extensions = namespace.extensions { - for ext in extensions { - if ext.accounts.isEmpty { - throw WalletConnectError.unsupportedNamespace(.unsupportedAccounts) - } - } - } } } diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index 4820b8fc0..1cae4e0cd 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -6,20 +6,57 @@ public struct Request: Codable, Equatable { public let method: String public let params: AnyCodable public let chainId: Blockchain + public let expiry: UInt64? - internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain) { + internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64?) { self.id = id self.topic = topic self.method = method self.params = params self.chainId = chainId + self.expiry = expiry } - public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) { - self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId) + public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64? = nil) { + self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: expiry) } - internal init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable { - self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId) + init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, expiry: UInt64?) where C: Codable { + self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiry: expiry) + } + + func isExpired(currentDate: Date = Date()) -> Bool { + guard let expiry = expiry else { return false } + + let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) + + guard + abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry, + abs(currentDate.distance(to: expiryDate)) > Constants.minExpiry + else { return true } + + return expiryDate < currentDate + } + + func calculateTtl(currentDate: Date = Date()) -> Int { + guard let expiry = expiry else { return SessionRequestProtocolMethod.defaultTtl } + + let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) + let diff = expiryDate - currentDate.timeIntervalSince1970 + + guard + diff.timeIntervalSince1970 < Constants.maxExpiry, + diff.timeIntervalSince1970 > Constants.minExpiry + else { return SessionRequestProtocolMethod.defaultTtl } + + return Int(diff.timeIntervalSince1970) + } +} + +private extension Request { + + struct Constants { + static let minExpiry: TimeInterval = 300 // 5 minutes + static let maxExpiry: TimeInterval = 604800 // 7 days } } diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift new file mode 100644 index 000000000..394ff0c61 --- /dev/null +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -0,0 +1,42 @@ +import Foundation + +final class HistoryService { + + private let history: RPCHistory + + init(history: RPCHistory) { + self.history = history + } + + func getPendingRequests() -> [Request] { + return history.getPending() + .compactMap { mapRequestRecord($0) } + .filter { !$0.isExpired() } + } + + func getPendingRequests(topic: String) -> [Request] { + return getPendingRequests().filter { $0.topic == topic } + } + + public func getSessionRequest(id: RPCID) -> Request? { + guard let record = history.get(recordId: id) else { return nil } + return mapRequestRecord(record) + } +} + +private extension HistoryService { + + func mapRequestRecord(_ record: RPCHistory.Record) -> Request? { + guard let request = try? record.request.params?.get(SessionType.RequestParams.self) + else { return nil } + + return Request( + id: record.id, + topic: record.topic, + method: request.request.method, + params: request.request.params, + chainId: request.chainId, + expiry: request.request.expiry + ) + } +} diff --git a/Sources/WalletConnectSign/Services/SignCleanupService.swift b/Sources/WalletConnectSign/Services/SignCleanupService.swift index 1da2aee3e..abee34063 100644 --- a/Sources/WalletConnectSign/Services/SignCleanupService.swift +++ b/Sources/WalletConnectSign/Services/SignCleanupService.swift @@ -5,13 +5,13 @@ final class SignCleanupService { private let pairingStore: WCPairingStorage private let sessionStore: WCSessionStorage private let kms: KeyManagementServiceProtocol - private let sessionToPairingTopic: CodableStore + private let sessionTopicToProposal: CodableStore private let networkInteractor: NetworkInteracting - init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionToPairingTopic: CodableStore, networkInteractor: NetworkInteracting) { + init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionTopicToProposal: CodableStore, networkInteractor: NetworkInteracting) { self.pairingStore = pairingStore self.sessionStore = sessionStore - self.sessionToPairingTopic = sessionToPairingTopic + self.sessionTopicToProposal = sessionTopicToProposal self.networkInteractor = networkInteractor self.kms = kms } @@ -38,7 +38,7 @@ private extension SignCleanupService { func cleanupStorages() throws { pairingStore.deleteAll() sessionStore.deleteAll() - sessionToPairingTopic.deleteAll() + sessionTopicToProposal.deleteAll() try kms.deleteAll() } } diff --git a/Sources/WalletConnectSign/Session.swift b/Sources/WalletConnectSign/Session.swift index 99e981591..8c85f4d6a 100644 --- a/Sources/WalletConnectSign/Session.swift +++ b/Sources/WalletConnectSign/Session.swift @@ -5,6 +5,7 @@ import Foundation */ public struct Session { public let topic: String + public let pairingTopic: String public let peer: AppMetadata public let namespaces: [String: SessionNamespace] public let expiryDate: Date @@ -15,7 +16,7 @@ public struct Session { extension Session { - public struct Proposal: Equatable { + public struct Proposal: Equatable, Codable { public var id: String public let pairingTopic: String public let proposer: AppMetadata diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 6d015414e..dd0614324 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -110,7 +110,7 @@ public final class SignClient: SignClientProtocol { private let nonControllerSessionStateMachine: NonControllerSessionStateMachine private let controllerSessionStateMachine: ControllerSessionStateMachine private let appProposeService: AppProposeService - private let history: RPCHistory + private let historyService: HistoryService private let cleanupService: SignCleanupService private let sessionProposalPublisherSubject = PassthroughSubject() @@ -140,7 +140,7 @@ public final class SignClient: SignClientProtocol { controllerSessionStateMachine: ControllerSessionStateMachine, appProposeService: AppProposeService, disconnectService: DisconnectService, - history: RPCHistory, + historyService: HistoryService, cleanupService: SignCleanupService, pairingClient: PairingClient ) { @@ -153,7 +153,7 @@ public final class SignClient: SignClientProtocol { self.nonControllerSessionStateMachine = nonControllerSessionStateMachine self.controllerSessionStateMachine = controllerSessionStateMachine self.appProposeService = appProposeService - self.history = history + self.historyService = historyService self.cleanupService = cleanupService self.disconnectService = disconnectService self.pairingClient = pairingClient @@ -323,27 +323,17 @@ public final class SignClient: SignClientProtocol { /// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method /// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions. public func getPendingRequests(topic: String? = nil) -> [Request] { - let pendingRequests: [Request] = history.getPending() - .compactMap { - guard let request = try? $0.request.params?.get(SessionType.RequestParams.self) else { return nil } - return Request(id: $0.id, topic: $0.topic, method: request.request.method, params: request.request.params, chainId: request.chainId) - } if let topic = topic { - return pendingRequests.filter {$0.topic == topic} + return historyService.getPendingRequests(topic: topic) } else { - return pendingRequests + return historyService.getPendingRequests() } } /// - Parameter id: id of a wc_sessionRequest jsonrpc request /// - Returns: json rpc record object for given id or nil if record for give id does not exits public func getSessionRequestRecord(id: RPCID) -> Request? { - guard - let record = history.get(recordId: id), - let request = try? record.request.params?.get(SessionType.RequestParams.self) - else { return nil } - - return Request(id: record.id, topic: record.topic, method: record.request.method, params: request, chainId: request.chainId) + return historyService.getSessionRequest(id: id) } /// Delete all stored data such as: pairings, sessions, keys diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index ce27c1e4c..ef68dd39e 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -23,13 +23,14 @@ public struct SignClientFactory { let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.pairings.rawValue))) let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.sessions.rawValue))) - let sessionToPairingTopic = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.sessionToPairingTopic.rawValue) let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.proposals.rawValue) - let sessionEngine = SessionEngine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) + let historyService = HistoryService(history: rpcHistory) + let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, 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 approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore) - let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic, networkInteractor: networkingClient) + 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 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) let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger) @@ -47,7 +48,7 @@ public struct SignClientFactory { controllerSessionStateMachine: controllerSessionStateMachine, appProposeService: appProposerService, disconnectService: disconnectService, - history: rpcHistory, + historyService: historyService, cleanupService: cleanupService, pairingClient: pairingClient ) diff --git a/Sources/WalletConnectSign/SignStorageIdentifiers.swift b/Sources/WalletConnectSign/SignStorageIdentifiers.swift index 4da79ec0e..8854dd4e8 100644 --- a/Sources/WalletConnectSign/SignStorageIdentifiers.swift +++ b/Sources/WalletConnectSign/SignStorageIdentifiers.swift @@ -4,5 +4,5 @@ enum SignStorageIdentifiers: String { case pairings = "com.walletconnect.sdk.pairingSequences" case sessions = "com.walletconnect.sdk.sessionSequences" case proposals = "com.walletconnect.sdk.sessionProposals" - case sessionToPairingTopic = "com.walletconnect.sdk.sessionToPairingTopic" + case sessionTopicToProposal = "com.walletconnect.sdk.sessionTopicToProposal" } diff --git a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift index 13d77a32e..28ee6d446 100644 --- a/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift +++ b/Sources/WalletConnectSign/Types/ProtocolMethods/SessionRequestProtocolMethod.swift @@ -1,9 +1,22 @@ import Foundation struct SessionRequestProtocolMethod: ProtocolMethod { + + static let defaultTtl: Int = 300 + let method: String = "wc_sessionRequest" - let requestConfig = RelayConfig(tag: 1108, prompt: true, ttl: 300) + private let ttl: Int + + var requestConfig: RelayConfig { + RelayConfig(tag: 1108, prompt: true, ttl: ttl) + } + + var responseConfig: RelayConfig { + RelayConfig(tag: 1109, prompt: false, ttl: ttl) + } - let responseConfig = RelayConfig(tag: 1109, prompt: false, ttl: 300) + init(ttl: Int = Self.defaultTtl) { + self.ttl = ttl + } } diff --git a/Sources/WalletConnectSign/Types/Session/SessionType.swift b/Sources/WalletConnectSign/Types/Session/SessionType.swift index d6c7c87d5..2731e448c 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionType.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionType.swift @@ -42,6 +42,7 @@ internal enum SessionType { struct Request: Codable, Equatable { let method: String let params: AnyCodable + let expiry: UInt64? } } diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift index e4e4d2b14..7cbcf82c0 100644 --- a/Sources/WalletConnectSign/Types/Session/WCSession.swift +++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift @@ -7,6 +7,7 @@ struct WCSession: SequenceObject, Equatable { } let topic: String + let pairingTopic: String let relay: RelayProtocolOptions let selfParticipant: Participant let peerParticipant: Participant @@ -27,6 +28,7 @@ struct WCSession: SequenceObject, Equatable { } init(topic: String, + pairingTopic: String, timestamp: Date, selfParticipant: Participant, peerParticipant: Participant, @@ -34,6 +36,7 @@ struct WCSession: SequenceObject, Equatable { requiredNamespaces: [String: ProposalNamespace], acknowledged: Bool) { self.topic = topic + self.pairingTopic = pairingTopic self.timestamp = timestamp self.relay = settleParams.relay self.controller = AgreementPeer(publicKey: settleParams.controller.publicKey) @@ -48,6 +51,7 @@ struct WCSession: SequenceObject, Equatable { #if DEBUG internal init( topic: String, + pairingTopic: String, timestamp: Date, relay: RelayProtocolOptions, controller: AgreementPeer, @@ -61,6 +65,7 @@ struct WCSession: SequenceObject, Equatable { expiry: Int64 ) { self.topic = topic + self.pairingTopic = pairingTopic self.timestamp = timestamp self.relay = relay self.controller = controller @@ -95,15 +100,6 @@ struct WCSession: SequenceObject, Equatable { if namespace.methods.contains(method) { return true } - if let extensions = namespace.extensions { - for extended in extensions { - if extended.accounts.contains(where: { $0.blockchain == chain }) { - if extended.methods.contains(method) { - return true - } - } - } - } } } return false @@ -115,15 +111,6 @@ struct WCSession: SequenceObject, Equatable { if namespace.events.contains(event) { return true } - if let extensions = namespace.extensions { - for extended in extensions { - if extended.accounts.contains(where: { $0.blockchain == chain }) { - if extended.events.contains(event) { - return true - } - } - } - } } } return false @@ -139,16 +126,6 @@ struct WCSession: SequenceObject, Equatable { else { throw Error.unsatisfiedUpdateNamespaceRequirement } - if let extensions = item.value.extensions { - guard let compliantExtensions = compliantNamespace.extensions else { - throw Error.unsatisfiedUpdateNamespaceRequirement - } - for existingExtension in extensions { - guard compliantExtensions.contains(where: { $0.isCompliant(to: existingExtension) }) else { - throw Error.unsatisfiedUpdateNamespaceRequirement - } - } - } } self.namespaces = namespaces self.timestamp = timestamp @@ -179,6 +156,7 @@ struct WCSession: SequenceObject, Equatable { func publicRepresentation() -> Session { return Session( topic: topic, + pairingTopic: pairingTopic, peer: peerParticipant.metadata, namespaces: namespaces, expiryDate: expiryDate) @@ -190,7 +168,7 @@ struct WCSession: SequenceObject, Equatable { extension WCSession { enum CodingKeys: String, CodingKey { - case topic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces + case topic, pairingTopic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces } init(from decoder: Decoder) throws { @@ -203,15 +181,15 @@ extension WCSession { self.namespaces = try container.decode([String: SessionNamespace].self, forKey: .namespaces) self.acknowledged = try container.decode(Bool.self, forKey: .acknowledged) self.expiryDate = try container.decode(Date.self, forKey: .expiryDate) - - // Migration beta.102 - self.timestamp = try container.decodeIfPresent(Date.self, forKey: .timestamp) ?? .distantPast - self.requiredNamespaces = try container.decodeIfPresent([String: ProposalNamespace].self, forKey: .requiredNamespaces) ?? [:] + self.timestamp = try container.decode(Date.self, forKey: .timestamp) + self.requiredNamespaces = try container.decode([String: ProposalNamespace].self, forKey: .requiredNamespaces) + self.pairingTopic = try container.decode(String.self, forKey: .pairingTopic) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(topic, forKey: .topic) + try container.encode(pairingTopic, forKey: .pairingTopic) try container.encode(relay, forKey: .relay) try container.encode(controller, forKey: .controller) try container.encode(selfParticipant, forKey: .selfParticipant) diff --git a/Sources/WalletConnectSign/Types/SignReasonCode.swift b/Sources/WalletConnectSign/Types/SignReasonCode.swift index 522cbfb4f..5d4471b63 100644 --- a/Sources/WalletConnectSign/Types/SignReasonCode.swift +++ b/Sources/WalletConnectSign/Types/SignReasonCode.swift @@ -37,6 +37,11 @@ enum SignReasonCode: Reason, Codable, Equatable { // 6000 case userDisconnected + // 7000 + case sessionSettlementFailed + // 8000 + case sessionRequestExpired + var code: Int { switch self { case .invalidMethod: return 1001 @@ -65,7 +70,9 @@ enum SignReasonCode: Reason, Codable, Equatable { case .userDisconnected: return 6000 + case .sessionSettlementFailed: return 7000 case .noSessionForTopic: return 7001 + case .sessionRequestExpired: return 8000 } } @@ -79,9 +86,6 @@ enum SignReasonCode: Reason, Codable, Equatable { return "Invalid update namespace request" case .invalidExtendRequest: return "Invalid update expiry request" - case .noSessionForTopic: - return "No matching session matching topic" - case .unauthorizedMethod(let method): return "Unauthorized JSON-RPC method requested: \(method)" case .unauthorizedEvent(let type): @@ -117,6 +121,12 @@ enum SignReasonCode: Reason, Codable, Equatable { return "Unsupported namespace key" case .userDisconnected: return "User discconnected" + case .sessionSettlementFailed: + return "Session Settlement Failed" + case .noSessionForTopic: + return "No matching session matching topic" + case .sessionRequestExpired: + return "Session request expired or expiry param validation failed (MIN_INTERVAL: 300, MAX_INTERVAL: 604800)" } } } diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index 8e130c106..4fc00aebe 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -27,7 +27,7 @@ public final class RPCHistory { } public func get(recordId: RPCID) -> Record? { - try? storage.get(key: "\(recordId)") + try? storage.get(key: recordId.string) } public func set(_ request: RPCRequest, forTopic topic: String, emmitedBy origin: Record.Origin) throws { @@ -65,11 +65,33 @@ public final class RPCHistory { } } + public func getAll(of type: Object.Type, topic: String) -> [Object] { + return storage.getAll() + .filter{$0.topic == topic} + .compactMap { try? $0.request.params?.get(Object.self) } + } + + public func getAll(of type: Object.Type) -> [Object] { + return getAllWithIDs(of: type).map { $0.value } + } + + public func getAllWithIDs(of type: Object.Type) -> [(id: RPCID, value: Object)] { + return storage.getAll().compactMap { record in + guard let object = try? record.request.params?.get(Object.self) + else { return nil } + return (record.id, object) + } + } + + public func delete(id: RPCID) { + storage.delete(forKey: id.string) + } + public func deleteAll(forTopic topic: String) { deleteAll(forTopics: [topic]) } public func getPending() -> [Record] { - storage.getAll().filter {$0.response == nil} + storage.getAll().filter { $0.response == nil } } } diff --git a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift new file mode 100644 index 000000000..291d0b1a3 --- /dev/null +++ b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift @@ -0,0 +1,108 @@ +import Foundation + +final class ChatClientProxy { + + private let client: ChatClient + + var onResponse: ((RPCResponse) async throws -> Void)? + + init(client: ChatClient) { + self.client = client + } + + func request(_ request: RPCRequest) async throws { + guard let event = WebViewEvent(rawValue: request.method) + else { throw Errors.unregisteredMethod } + + switch event { + case .getInvites: + let invites = client.getInvites() + try await respond(with: invites, request: request) + + case .getThreads: + let threads = client.getThreads() + try await respond(with: threads, request: request) + + case .register: + let params = try parse(RegisterRequest.self, params: request.params) + try await client.register(account: params.account) + try await respond(request: request) + + case .getMessages: + let params = try parse(GetMessagesRequest.self, params: request.params) + let messages = client.getMessages(topic: params.topic) + try await respond(with: messages, request: request) + + case .message: + let params = try parse(MessageRequest.self, params: request.params) + try await client.message(topic: params.topic, message: params.payload.message) + try await respond(with: params.payload, request: request) + + case .accept: + let params = try parse(AcceptRequest.self, params: request.params) + try await client.accept(inviteId: params.id) + try await respond(request: request) + + case .reject: + let params = try parse(RejectRequest.self, params: request.params) + try await client.reject(inviteId: params.id) + try await respond(request: request) + + case .invite: + let params = try parse(InviteRequest.self, params: request.params) + try await client.invite(peerAccount: params.invite.account, openingMessage: params.invite.message) + try await respond(request: request) + } + } +} + +private extension ChatClientProxy { + + private typealias Blob = Dictionary + + enum Errors: Error { + case unregisteredMethod + case unregisteredParams + } + + struct RegisterRequest: Codable { + let account: Account + } + + struct GetMessagesRequest: Codable { + let topic: String + } + + struct MessageRequest: Codable { + let topic: String + let payload: MessagePayload + } + + struct AcceptRequest: Codable { + let id: Int64 + } + + struct RejectRequest: Codable { + let id: Int64 + } + + struct InviteRequest: Codable { + struct Invite: Codable { + let account: Account + let message: String + } + let account: Account + let invite: Invite + } + + func parse(_ type: Request.Type, params: AnyCodable?) throws -> Request { + guard let params = try params?.get([Request].self).first + 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/ChatClient/ChatClientRequest.swift b/Sources/Web3Inbox/ChatClient/ChatClientRequest.swift new file mode 100644 index 000000000..c5dcb307d --- /dev/null +++ b/Sources/Web3Inbox/ChatClient/ChatClientRequest.swift @@ -0,0 +1,12 @@ +import Foundation + +enum ChatClientRequest: String { + case chatInvite = "chat_invite" + case chatThread = "chat_joined" + case chatMessage = "chat_message" + case setAccount = "setAccount" + + var method: String { + return rawValue + } +} diff --git a/Sources/Web3Inbox/ChatClient/ChatClientRequestSubscriber.swift b/Sources/Web3Inbox/ChatClient/ChatClientRequestSubscriber.swift new file mode 100644 index 000000000..12d3c24b1 --- /dev/null +++ b/Sources/Web3Inbox/ChatClient/ChatClientRequestSubscriber.swift @@ -0,0 +1,53 @@ +import Foundation +import Combine + +final class ChatClientRequestSubscriber { + + private var publishers: Set = [] + + private let chatClient: ChatClient + private let logger: ConsoleLogging + + var onRequest: ((RPCRequest) async throws -> Void)? + + init(chatClient: ChatClient, logger: ConsoleLogging) { + self.chatClient = chatClient + self.logger = logger + + setupSubscriptions() + } + + func setupSubscriptions() { + chatClient.invitePublisher + .sink { [unowned self] invite in + handle(event: .chatInvite, params: invite) + }.store(in: &publishers) + + chatClient.newThreadPublisher + .sink { [unowned self] thread in + handle(event: .chatThread, params: thread) + }.store(in: &publishers) + + chatClient.messagePublisher + .sink { [unowned self] message in + handle(event: .chatMessage, params: message) + }.store(in: &publishers) + } +} + +private extension ChatClientRequestSubscriber { + + func handle(event: ChatClientRequest, 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 new file mode 100644 index 000000000..c6fdb4a69 --- /dev/null +++ b/Sources/Web3Inbox/Web3Inbox.swift @@ -0,0 +1,24 @@ +import Foundation + +public final class Web3Inbox { + + /// Web3Inbox client instance + public static var instance: Web3InboxClient = { + guard let account = account else { + fatalError("Error - you must call Web3Inbox.configure(_:) before accessing the shared instance.") + } + return Web3InboxClientFactory.create(chatClient: Chat.instance, account: account) + }() + + private static var account: Account? + + private init() { } + + /// Web3Inbox instance config method + /// - Parameters: + /// - account: Web3Inbox initial account + static public func configure(account: Account) { + Chat.configure(account: account) + Web3Inbox.account = account + } +} diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift new file mode 100644 index 000000000..a02bb6d08 --- /dev/null +++ b/Sources/Web3Inbox/Web3InboxClient.swift @@ -0,0 +1,73 @@ +import Foundation +import WebKit + +public final class Web3InboxClient { + + private let webView: WKWebView + private var account: Account + private let logger: ConsoleLogging + + private let clientProxy: ChatClientProxy + private let clientSubscriber: ChatClientRequestSubscriber + + private let webviewProxy: WebViewProxy + private let webviewSubscriber: WebViewRequestSubscriber + + init( + webView: WKWebView, + account: Account, + logger: ConsoleLogging, + clientProxy: ChatClientProxy, + clientSubscriber: ChatClientRequestSubscriber, + webviewProxy: WebViewProxy, + webviewSubscriber: WebViewRequestSubscriber + ) { + self.webView = webView + self.account = account + self.logger = logger + self.clientProxy = clientProxy + self.clientSubscriber = clientSubscriber + self.webviewProxy = webviewProxy + self.webviewSubscriber = webviewSubscriber + + setupSubscriptions() + } + + public func getWebView() -> WKWebView { + return webView + } + + public func setAccount(_ account: Account) async throws { + try await authorize(account: account) + } +} + +// MARK: - Privates + +private extension Web3InboxClient { + + func setupSubscriptions() { + webviewSubscriber.onLogin = { [unowned self] in + try await self.authorize(account: self.account) + } + webviewSubscriber.onRequest = { [unowned self] request in + try await self.clientProxy.request(request) + } + clientProxy.onResponse = { [unowned self] response in + try await self.webviewProxy.respond(response) + } + clientSubscriber.onRequest = { [unowned self] request in + try await self.webviewProxy.request(request) + } + } + + func authorize(account: Account) async throws { + self.account = account + + let request = RPCRequest( + method: ChatClientRequest.setAccount.method, + params: ["account": account.address] + ) + try await webviewProxy.request(request) + } +} diff --git a/Sources/Web3Inbox/Web3InboxClientFactory.swift b/Sources/Web3Inbox/Web3InboxClientFactory.swift new file mode 100644 index 000000000..5a54dc042 --- /dev/null +++ b/Sources/Web3Inbox/Web3InboxClientFactory.swift @@ -0,0 +1,25 @@ +import Foundation +import WebKit + +final class Web3InboxClientFactory { + + static func create(chatClient: ChatClient, account: Account) -> Web3InboxClient { + let host = "https://web3inbox-dev-hidden.vercel.app/?chatProvider=ios" + let logger = ConsoleLogger(suffix: "📬") + let webviewSubscriber = WebViewRequestSubscriber(logger: logger) + let webView = WebViewFactory(host: host, webviewSubscriber: webviewSubscriber).create() + let webViewProxy = WebViewProxy(webView: webView) + let clientProxy = ChatClientProxy(client: chatClient) + let clientSubscriber = ChatClientRequestSubscriber(chatClient: chatClient, logger: logger) + + return Web3InboxClient( + webView: webView, + account: account, + logger: ConsoleLogger(), + clientProxy: clientProxy, + clientSubscriber: clientSubscriber, + webviewProxy: webViewProxy, + webviewSubscriber: webviewSubscriber + ) + } +} diff --git a/Sources/Web3Inbox/Web3InboxImports.swift b/Sources/Web3Inbox/Web3InboxImports.swift new file mode 100644 index 000000000..54b421e88 --- /dev/null +++ b/Sources/Web3Inbox/Web3InboxImports.swift @@ -0,0 +1,3 @@ +#if !CocoaPods +@_exported import WalletConnectChat +#endif diff --git a/Sources/Web3Inbox/WebView/WebViewEvent.swift b/Sources/Web3Inbox/WebView/WebViewEvent.swift new file mode 100644 index 000000000..b83a3249b --- /dev/null +++ b/Sources/Web3Inbox/WebView/WebViewEvent.swift @@ -0,0 +1,12 @@ +import Foundation + +enum WebViewEvent: String { + case getInvites + case getThreads + case register + case getMessages + case message + case accept + case reject + case invite +} diff --git a/Sources/Web3Inbox/WebView/WebViewFactory.swift b/Sources/Web3Inbox/WebView/WebViewFactory.swift new file mode 100644 index 000000000..8b1cf0256 --- /dev/null +++ b/Sources/Web3Inbox/WebView/WebViewFactory.swift @@ -0,0 +1,26 @@ +import Foundation +import WebKit + +final class WebViewFactory { + + private let host: String + private let webviewSubscriber: WebViewRequestSubscriber + + init(host: String, webviewSubscriber: WebViewRequestSubscriber) { + self.host = host + self.webviewSubscriber = webviewSubscriber + } + + func create() -> WKWebView { + let configuration = WKWebViewConfiguration() + configuration.userContentController.add( + webviewSubscriber, + name: WebViewRequestSubscriber.name + ) + let webview = WKWebView(frame: .zero, configuration: configuration) + webview.navigationDelegate = webviewSubscriber + let request = URLRequest(url: URL(string: host)!) + webview.load(request) + return webview + } +} diff --git a/Sources/Web3Inbox/WebView/WebViewProxy.swift b/Sources/Web3Inbox/WebView/WebViewProxy.swift new file mode 100644 index 000000000..73130fede --- /dev/null +++ b/Sources/Web3Inbox/WebView/WebViewProxy.swift @@ -0,0 +1,32 @@ +import Foundation +import WebKit + +actor WebViewProxy { + + private let webView: WKWebView + + init(webView: WKWebView) { + self.webView = webView + } + + @MainActor + func respond(_ response: RPCResponse) async throws { + let body = try response.json() + let script = await 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) + webView.evaluateJavaScript(script, completionHandler: nil) + } +} + +private extension WebViewProxy { + + func formatScript(body: String) -> String { + return "window.\(WebViewRequestSubscriber.name).chat.postMessage(\(body))" + } +} diff --git a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift new file mode 100644 index 000000000..fea0dc95b --- /dev/null +++ b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift @@ -0,0 +1,50 @@ +import Foundation +import WebKit + +final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { + + static let name = "web3inbox" + + var onRequest: ((RPCRequest) async throws -> Void)? + var onLogin: (() async throws -> Void)? + + private let logger: ConsoleLogging + + init(logger: ConsoleLogging) { + self.logger = logger + } + + func userContentController( + _ userContentController: WKUserContentController, + didReceive message: WKScriptMessage + ) { + guard message.name == WebViewRequestSubscriber.name else { return } + + guard + let dict = message.body as? [String: Any], + let data = try? JSONSerialization.data(withJSONObject: dict), + let request = try? JSONDecoder().decode(RPCRequest.self, from: data) + else { return } + + Task { + do { + try await onRequest?(request) + } catch { + logger.error("WebView Request error: \(error.localizedDescription)") + } + } + } +} + +// MARK: - WKNavigationDelegate + +extension WebViewRequestSubscriber: WKNavigationDelegate { + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + guard webView.url?.path == "/login" else { return } + + Task { + try await onLogin?() + } + } +} diff --git a/Tests/ChatTests/RegistryManagerTests.swift b/Tests/ChatTests/RegistryManagerTests.swift index 1b87397c6..ec9308be5 100644 --- a/Tests/ChatTests/RegistryManagerTests.swift +++ b/Tests/ChatTests/RegistryManagerTests.swift @@ -11,27 +11,64 @@ final class RegistryManagerTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! var topicToRegistryRecordStore: CodableStore! var registry: Registry! + var accountService: AccountService! var kms: KeyManagementServiceMock! + var resubscriptionService: ResubscriptionService! + var chatStorage: ChatStorage! + var threadStore: KeyedDatabase! + + let initialAccount = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + let newAccount = Account("eip155:2:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + let mockAccount = Account("eip155:3:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! override func setUp() { registry = KeyValueRegistry() networkingInteractor = NetworkingInteractorMock() kms = KeyManagementServiceMock() topicToRegistryRecordStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") + accountService = AccountService(currentAccount: initialAccount) + threadStore = KeyedDatabase(storage: RuntimeKeyValueStorage(), identifier: "") + chatStorage = ChatStorage( + messageStore: .init(storage: RuntimeKeyValueStorage(), identifier: ""), + inviteStore: .init(storage: RuntimeKeyValueStorage(), identifier: ""), + threadStore: threadStore + ) + resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, accountService: accountService, chatStorage: chatStorage, logger: ConsoleLoggerMock()) registryManager = RegistryService( registry: registry, + accountService: accountService, + resubscriptionService: resubscriptionService, networkingInteractor: networkingInteractor, kms: kms, logger: ConsoleLoggerMock(), topicToRegistryRecordStore: topicToRegistryRecordStore) } - func testRegister() async { - let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! - _ = try! await registryManager.register(account: account) + func testRegister() async throws { + threadStore.set(Thread(topic: "topic1", selfAccount: mockAccount, peerAccount: mockAccount), for: newAccount.absoluteString) + threadStore.set(Thread(topic: "topic2", selfAccount: mockAccount, peerAccount: mockAccount), for: newAccount.absoluteString) + + // Test accountService initial state + XCTAssertEqual(accountService.currentAccount, initialAccount) + + let pubKey = try await registryManager.register(account: newAccount) + + // Test subscription for invite topic XCTAssert(!networkingInteractor.subscriptions.isEmpty, "networkingInteractors subscribes to new topic") - let resolved = try! await registry.resolve(account: account) - XCTAssertNotNil(resolved, "register account is resolvable") + + // Test resubscription for threads + XCTAssertTrue(networkingInteractor.subscriptions.contains("topic1")) + XCTAssertTrue(networkingInteractor.subscriptions.contains("topic2")) + + let resolved = try await registry.resolve(account: newAccount) + + // Test resolved account pubKey + XCTAssertEqual(pubKey, resolved) + + // Test topicToRegistryRecordStore filled XCTAssertFalse(topicToRegistryRecordStore.getAll().isEmpty, "stores topic to invitation") + + // Test current account changed + XCTAssertEqual(accountService.currentAccount, newAccount) } } diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 323c52279..9fdd2eb54 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -23,6 +23,7 @@ public class NetworkingInteractorMock: NetworkInteracting { var didCallRequest: Bool { requestCallCount > 0 } var onSubscribeCalled: (() -> Void)? + var onRespondError: ((Int) -> Void)? public let socketConnectionStatusPublisherSubject = PassthroughSubject() public var socketConnectionStatusPublisher: AnyPublisher { @@ -105,6 +106,12 @@ public class NetworkingInteractorMock: NetworkInteracting { } } + public func batchSubscribe(topics: [String]) async throws { + for topic in topics { + try await subscribe(topic: topic) + } + } + public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { requestCallCount += 1 requests.append((topic, request)) @@ -121,6 +128,7 @@ public class NetworkingInteractorMock: NetworkInteracting { public func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws { lastErrorCode = reason.code didRespondError = true + onRespondError?(reason.code) } public func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws { diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift index f733358ea..d10924417 100644 --- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift +++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift @@ -62,7 +62,7 @@ final class AppProposalServiceTests: XCTestCase { approveEngine = ApproveEngine( networkingInteractor: networkingInteractor, proposalPayloadsStore: .init(defaults: RuntimeKeyValueStorage(), identifier: ""), - sessionToPairingTopic: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""), + sessionTopicToProposal: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""), pairingRegisterer: pairingRegisterer, metadata: meta, kms: cryptoMock, @@ -117,7 +117,7 @@ final class AppProposalServiceTests: XCTestCase { networkingInteractor.responsePublisherSubject.send((topicA, request, response)) let privateKey = try! cryptoMock.getPrivateKey(for: proposal.proposer.publicKey)! let topicB = deriveTopic(publicKey: responder.publicKey, privateKey: privateKey) - let storedPairing = storageMock.getPairing(forTopic: topicA)! + _ = storageMock.getPairing(forTopic: topicA)! wait(for: [exp], timeout: 5) diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index dfbf0523e..8db9dd112 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -18,6 +18,7 @@ final class ApproveEngineTests: XCTestCase { var sessionStorageMock: WCSessionStorageMock! var pairingRegisterer: PairingRegistererMock! var proposalPayloadsStore: CodableStore>! + var sessionTopicToProposal: CodableStore! var publishers = Set() @@ -29,10 +30,11 @@ final class ApproveEngineTests: XCTestCase { sessionStorageMock = WCSessionStorageMock() pairingRegisterer = PairingRegistererMock() proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "") + sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") engine = ApproveEngine( networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, - sessionToPairingTopic: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""), + sessionTopicToProposal: sessionTopicToProposal, pairingRegisterer: pairingRegisterer, metadata: metadata, kms: cryptoMock, @@ -92,7 +94,7 @@ final class ApproveEngineTests: XCTestCase { let topicB = String.generateTopic() cryptoMock.setAgreementSecret(agreementKeys, topic: topicB) let proposal = SessionProposal.stub(proposerPubKey: AgreementPrivateKey().publicKey.hexRepresentation) - try await engine.settle(topic: topicB, proposal: proposal, namespaces: SessionNamespace.stubDictionary()) + try await engine.settle(topic: topicB, proposal: proposal, namespaces: SessionNamespace.stubDictionary(), pairingTopic: "") XCTAssertTrue(sessionStorageMock.hasSession(forTopic: topicB), "Responder must persist session on topic B") XCTAssert(networkingInteractor.didSubscribe(to: topicB), "Responder must subscribe for topic B") XCTAssertTrue(networkingInteractor.didCallRequest, "Responder must send session settle payload on topic B") @@ -105,8 +107,7 @@ final class ApproveEngineTests: XCTestCase { engine.onSessionSettle = { _ in didCallBackOnSessionApproved = true } - - engine.settlingProposal = SessionProposal.stub() + sessionTopicToProposal.set(SessionProposal.stub().publicRepresentation(pairingTopic: ""), forKey: sessionTopic) networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle())) usleep(100) diff --git a/Tests/WalletConnectSignTests/NamespaceValidationTests.swift b/Tests/WalletConnectSignTests/NamespaceValidationTests.swift index 599b40c70..6fea012f9 100644 --- a/Tests/WalletConnectSignTests/NamespaceValidationTests.swift +++ b/Tests/WalletConnectSignTests/NamespaceValidationTests.swift @@ -18,16 +18,12 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["method"], - events: ["event"], - extensions: [ - ProposalNamespace.Extension(chains: [Blockchain("eip155:137")!], methods: ["otherMethod"], events: ["otherEvent"]) - ] + events: ["event"] ), "cosmos": ProposalNamespace( chains: [cosmosChain], methods: ["someMethod"], - events: ["someEvent"], - extensions: nil + events: ["someEvent"] ) ] XCTAssertNoThrow(try Namespace.validate(namespace)) @@ -38,8 +34,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertThrowsError(try Namespace.validate(namespace)) } @@ -49,8 +44,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: [], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertNoThrow(try Namespace.validate(namespace)) } @@ -60,8 +54,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["method"], - events: [], - extensions: nil) + events: []) ] XCTAssertNoThrow(try Namespace.validate(namespace)) } @@ -71,43 +64,26 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain, Blockchain("eip155:137")!, Blockchain("eip155:10")!], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] let invalidNamespace = [ "eip155": ProposalNamespace( chains: [ethChain, Blockchain("cosmos:cosmoshub-4")!], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertNoThrow(try Namespace.validate(validNamespace)) XCTAssertThrowsError(try Namespace.validate(invalidNamespace)) } - func testExtensionChainsMustNotBeEmpty() { - let namespace = [ - "eip155": ProposalNamespace( - chains: [ethChain], - methods: ["method"], - events: ["event"], - extensions: [ - ProposalNamespace.Extension(chains: [], methods: ["otherMethod"], events: ["otherEvent"]) - ] - ) - ] - XCTAssertThrowsError(try Namespace.validate(namespace)) - } - func testValidateAllProposalNamespaces() { let namespace = [ "eip155": ProposalNamespace( chains: [ethChain], methods: ["method"], - events: ["event"], - extensions: nil), + events: ["event"]), "cosmos": ProposalNamespace( - chains: [], methods: [], events: [], extensions: nil) + chains: [], methods: [], events: []) ] XCTAssertThrowsError(try Namespace.validate(namespace)) } @@ -119,10 +95,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method"], - events: ["event"], - extensions: [ - SessionNamespace.Extension(accounts: [polyAccount], methods: ["otherMethod"], events: ["otherEvent"]) - ] + events: ["event"] ) ] XCTAssertNoThrow(try Namespace.validate(namespace)) @@ -133,8 +106,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": SessionNamespace( accounts: [], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertThrowsError(try Namespace.validate(namespace)) } @@ -144,8 +116,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertNoThrow(try Namespace.validate(namespace)) } @@ -155,8 +126,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method"], - events: [], - extensions: nil) + events: []) ] XCTAssertNoThrow(try Namespace.validate(namespace)) } @@ -166,43 +136,26 @@ final class NamespaceValidationTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] let invalidNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount, cosmosAccount], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] XCTAssertNoThrow(try Namespace.validate(validNamespace)) XCTAssertThrowsError(try Namespace.validate(invalidNamespace)) } - func testExtensionAccountsMustNotBeEmpty() { - let namespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount], - methods: ["method"], - events: ["event"], - extensions: [ - SessionNamespace.Extension(accounts: [], methods: ["otherMethod"], events: ["otherEvent"]) - ] - ) - ] - XCTAssertThrowsError(try Namespace.validate(namespace)) - } - func testValidateAllSessionNamespaces() { let namespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method"], - events: ["event"], - extensions: nil), + events: ["event"]), "cosmos": SessionNamespace( - accounts: [], methods: [], events: [], extensions: nil) + accounts: [], methods: [], events: []) ] XCTAssertThrowsError(try Namespace.validate(namespace)) } @@ -214,22 +167,19 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["eth_sign"], - events: [], - extensions: nil) + events: []) ] let validSessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["eth_sign"], - events: [], - extensions: nil) + events: []) ] let invalidSessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: [], - extensions: nil) + events: []) ] XCTAssertNoThrow(try Namespace.validateApproved(validSessionNamespace, against: proposalNamespace)) XCTAssertThrowsError(try Namespace.validateApproved(invalidSessionNamespace, against: proposalNamespace)) @@ -240,22 +190,19 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain, polyChain], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] let validSessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] let invalidSessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] XCTAssertNoThrow(try Namespace.validateApproved(validSessionNamespace, against: proposalNamespace)) XCTAssertThrowsError(try Namespace.validateApproved(invalidSessionNamespace, against: proposalNamespace)) @@ -266,8 +213,7 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] let sessionNamespace = [ "eip155": SessionNamespace( @@ -277,8 +223,7 @@ final class NamespaceValidationTests: XCTestCase { Account("eip155:1:0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8")!, Account("eip155:1:0xEB2F31B0224222D774541BfF89A221e7eb15a17E")!], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) } @@ -288,15 +233,13 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] let sessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["eth_sign", "personalSign"], - events: ["accountsChanged", "someEvent"], - extensions: nil) + events: ["accountsChanged", "someEvent"]) ] XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) } @@ -306,15 +249,13 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] let sessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) + events: ["accountsChanged"]) ] XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) } @@ -324,154 +265,45 @@ final class NamespaceValidationTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethChain], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil), + events: ["accountsChanged"]), "cosmos": ProposalNamespace( chains: [cosmosChain], methods: ["cosmos_signDirect"], - events: ["someEvent"], - extensions: nil) + events: ["someEvent"]) ] let validNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil), + events: ["accountsChanged"]), "cosmos": SessionNamespace( accounts: [cosmosAccount], methods: ["cosmos_signDirect"], - events: ["someEvent"], - extensions: nil) + events: ["someEvent"]) ] let invalidNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: ["eth_sign", "cosmos_signDirect"], - events: ["accountsChanged", "someEvent"], - extensions: nil) + events: ["accountsChanged", "someEvent"]) ] XCTAssertNoThrow(try Namespace.validateApproved(validNamespace, against: proposalNamespace)) XCTAssertThrowsError(try Namespace.validateApproved(invalidNamespace, against: proposalNamespace)) } - func testExtensionsMayBeMerged() { - let proposalNamespace = [ - "eip155": ProposalNamespace( - chains: [ethChain, polyChain], - methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: [ - ProposalNamespace.Extension(chains: [polyChain], methods: ["personalSign"], events: []) - ] - ) - ] - let sessionNamespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["eth_sign"], - events: ["accountsChanged", "personalSign"], - extensions: nil) - ] - XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) - } - func testApprovalMustContainAllEvents() { let proposalNamespace = [ "eip155": ProposalNamespace( chains: [ethChain], methods: [], - events: ["chainChanged"], - extensions: nil) + events: ["chainChanged"]) ] let sessionNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: [], - extensions: nil) + events: []) ] XCTAssertThrowsError(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) } - - func testApprovalMayExtendoMethodsAndEventsInExtensions() { - let proposalNamespace = [ - "eip155": ProposalNamespace( - chains: [ethChain, polyChain], - methods: [], - events: ["chainChanged"], - extensions: [ - ProposalNamespace.Extension(chains: [polyChain], methods: ["eth_sign"], events: []) - ] - ) - ] - let sessionNamespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: [], - events: ["chainChanged"], - extensions: [ - SessionNamespace.Extension( - accounts: [polyAccount], - methods: ["eth_sign", "personalSign"], - events: ["accountsChanged"] - ) - ] - ) - ] - XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) - } - - func testApprovalExtensionsMayContainAccountsNotDefinedInProposal() { - let proposalNamespace = [ - "eip155": ProposalNamespace( - chains: [ethChain, polyChain], - methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: [ - ProposalNamespace.Extension(chains: [polyChain], methods: ["personalSign"], events: []) - ] - ) - ] - let sessionNamespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: [ - SessionNamespace.Extension( - accounts: [polyAccount, Account("eip155:42:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!], - methods: ["personalSign"], - events: [] - ) - ] - ) - ] - XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) - } - - func testApprovalMayAddExtensionsNotDefinedInProposal() { - let proposalNamespace = [ - "eip155": ProposalNamespace( - chains: [ethChain, polyChain], - methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: nil) - ] - let sessionNamespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["eth_sign"], - events: ["accountsChanged"], - extensions: [ - SessionNamespace.Extension( - accounts: [polyAccount], - methods: ["personalSign"], - events: ["accountsChanged"] - ) - ] - ) - ] - XCTAssertNoThrow(try Namespace.validateApproved(sessionNamespace, against: proposalNamespace)) - } } diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift new file mode 100644 index 000000000..051329ffe --- /dev/null +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -0,0 +1,54 @@ +import XCTest +@testable import WalletConnectSign +@testable import WalletConnectUtils +@testable import TestingUtils + +final class SessionEngineTests: XCTestCase { + + var networkingInteractor: NetworkingInteractorMock! + var sessionStorage: WCSessionStorageMock! + var engine: SessionEngine! + + override func setUp() { + networkingInteractor = NetworkingInteractorMock() + sessionStorage = WCSessionStorageMock() + engine = SessionEngine( + networkingInteractor: networkingInteractor, + historyService: HistoryService( + history: RPCHistory( + keyValueStore: .init( + defaults: RuntimeKeyValueStorage(), + identifier: "" + ) + ) + ), + kms: KeyManagementServiceMock(), + sessionStore: sessionStorage, + logger: ConsoleLoggerMock() + ) + } + + func testErrorOnRequestExpiry() { + let expectation = expectation(description: "TestErrorOnRequestExpiry") + + sessionStorage.setSession(WCSession.stub( + topic: "topic", + namespaces: SessionNamespace.stubDictionary() + )) + + networkingInteractor.onRespondError = { code in + XCTAssertEqual(code, 8000) + expectation.fulfill() + } + + let request = RPCRequest.stubRequest( + method: "method", + chainId: Blockchain("eip155:1")!, + expiry: UInt64(Date().timeIntervalSince1970) + ) + + networkingInteractor.requestPublisherSubject.send(("topic", request)) + + wait(for: [expectation], timeout: 0.5) + } +} diff --git a/Tests/WalletConnectSignTests/SessionRequestTests.swift b/Tests/WalletConnectSignTests/SessionRequestTests.swift new file mode 100644 index 000000000..e89ff347d --- /dev/null +++ b/Tests/WalletConnectSignTests/SessionRequestTests.swift @@ -0,0 +1,83 @@ +import XCTest +@testable import WalletConnectSign + +final class SessionRequestTests: XCTestCase { + + func testRequestTtlDefault() { + let request = Request.stub() + + XCTAssertEqual(request.calculateTtl(), SessionRequestProtocolMethod.defaultTtl) + } + + func testRequestTtlExtended() { + let currentDate = Date(timeIntervalSince1970: 0) + let expiry = currentDate.advanced(by: 500) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + + XCTAssertEqual(request.calculateTtl(currentDate: currentDate), 500) + } + + func testRequestTtlNotExtendedMinValidation() { + let currentDate = Date(timeIntervalSince1970: 0) + let expiry = currentDate.advanced(by: 200) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + + XCTAssertEqual(request.calculateTtl(currentDate: currentDate), SessionRequestProtocolMethod.defaultTtl) + } + + func testRequestTtlNotExtendedMaxValidation() { + let currentDate = Date(timeIntervalSince1970: 0) + let expiry = currentDate.advanced(by: 700000) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + + XCTAssertEqual(request.calculateTtl(currentDate: currentDate), SessionRequestProtocolMethod.defaultTtl) + } + + func testIsExpiredDefault() { + let request = Request.stub() + + XCTAssertFalse(request.isExpired()) + } + + func testIsExpiredTrue() { + let currentDate = Date(timeIntervalSince1970: 500) + let expiry = Date(timeIntervalSince1970: 0) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + XCTAssertTrue(request.isExpired(currentDate: currentDate)) + } + + func testIsExpiredTrueMinValidation() { + let currentDate = Date(timeIntervalSince1970: 500) + let expiry = Date(timeIntervalSince1970: 600) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + XCTAssertTrue(request.isExpired(currentDate: currentDate)) + } + + func testIsExpiredTrueMaxValidation() { + let currentDate = Date(timeIntervalSince1970: 500) + let expiry = Date(timeIntervalSince1970: 700000) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + XCTAssertTrue(request.isExpired(currentDate: currentDate)) + } + + func testIsExpiredFalse() { + let currentDate = Date(timeIntervalSince1970: 0) + let expiry = Date(timeIntervalSince1970: 500) + let request = Request.stub(expiry: UInt64(expiry.timeIntervalSince1970)) + + XCTAssertFalse(request.isExpired(currentDate: currentDate)) + } +} + +private extension Request { + + static func stub(expiry: UInt64? = nil) -> Request { + return Request( + topic: "topic", + method: "method", + params: AnyCodable("params"), + chainId: Blockchain("eip155:1")!, + expiry: expiry + ) + } +} diff --git a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift index bbef34c9b..e60b778ec 100644 --- a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift +++ b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift @@ -18,6 +18,7 @@ extension WCSession { let controllerKey = isSelfController ? selfKey : peerKey return WCSession( topic: topic, + pairingTopic: "", timestamp: timestamp, relay: RelayProtocolOptions.stub(), controller: AgreementPeer(publicKey: controllerKey), diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index c83298a20..d34075b0c 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -24,8 +24,7 @@ extension ProposalNamespace { "eip155": ProposalNamespace( chains: [Blockchain("eip155:1")!], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] } } @@ -36,8 +35,7 @@ extension SessionNamespace { "eip155": SessionNamespace( accounts: [Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!], methods: ["method"], - events: ["event"], - extensions: nil) + events: ["event"]) ] } } @@ -68,9 +66,9 @@ extension RPCRequest { return RPCRequest(method: SessionSettleProtocolMethod().method, params: SessionType.SettleParams.stub()) } - static func stubRequest(method: String, chainId: Blockchain) -> RPCRequest { + static func stubRequest(method: String, chainId: Blockchain, expiry: UInt64? = nil) -> RPCRequest { let params = SessionType.RequestParams( - request: SessionType.RequestParams.Request(method: method, params: AnyCodable(EmptyCodable())), + request: SessionType.RequestParams.Request(method: method, params: AnyCodable(EmptyCodable()), expiry: expiry), chainId: chainId) return RPCRequest(method: SessionRequestProtocolMethod().method, params: params) } diff --git a/Tests/WalletConnectSignTests/WCSessionTests.swift b/Tests/WalletConnectSignTests/WCSessionTests.swift index 3a0d567b4..097dd8883 100644 --- a/Tests/WalletConnectSignTests/WCSessionTests.swift +++ b/Tests/WalletConnectSignTests/WCSessionTests.swift @@ -14,25 +14,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method"], - events: [], - extensions: nil) - ] - var session = WCSession.stub() - XCTAssertNoThrow(try session.updateNamespaces(namespace)) - XCTAssertTrue(session.hasPermission(forMethod: "method", onChain: ethAccount.blockchain)) - } - - func testHasPermissionForMethodInExtension() { - let namespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount], - methods: [], - events: [], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount], - methods: ["method"], - events: [])]) + events: []) ] var session = WCSession.stub() XCTAssertNoThrow(try session.updateNamespaces(namespace)) @@ -44,30 +26,11 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: [], - extensions: nil), + events: []), "cosmos": SessionNamespace( accounts: [cosmosAccount], methods: ["method"], - events: [], - extensions: nil) - ] - var session = WCSession.stub() - XCTAssertNoThrow(try session.updateNamespaces(namespace)) - XCTAssertFalse(session.hasPermission(forMethod: "method", onChain: ethAccount.blockchain)) - } - - func testDenyPermissionForMethodInOtherChainExtension() { - let namespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: [], - events: [], - extensions: [ - SessionNamespace.Extension( - accounts: [polyAccount], - methods: ["method"], - events: [])]) + events: []) ] var session = WCSession.stub() XCTAssertNoThrow(try session.updateNamespaces(namespace)) @@ -79,25 +42,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: ["event"], - extensions: nil) - ] - var session = WCSession.stub() - XCTAssertNoThrow(try session.updateNamespaces(namespace)) - XCTAssertTrue(session.hasPermission(forEvent: "event", onChain: ethAccount.blockchain)) - } - - func testHasPermissionForEventInExtension() { - let namespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount], - methods: [], - events: [], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount], - methods: [], - events: ["event"])]) + events: ["event"]) ] var session = WCSession.stub() XCTAssertNoThrow(try session.updateNamespaces(namespace)) @@ -109,30 +54,11 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: [], - events: [], - extensions: nil), + events: []), "cosmos": SessionNamespace( accounts: [cosmosAccount], methods: [], - events: ["event"], - extensions: nil) - ] - var session = WCSession.stub() - XCTAssertNoThrow(try session.updateNamespaces(namespace)) - XCTAssertFalse(session.hasPermission(forEvent: "event", onChain: ethAccount.blockchain)) - } - - func testDenyPermissionForEventInOtherChainExtension() { - let namespace = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: [], - events: [], - extensions: [ - SessionNamespace.Extension( - accounts: [polyAccount], - methods: [], - events: ["event"])]) + events: ["event"]) ] var session = WCSession.stub() XCTAssertNoThrow(try session.updateNamespaces(namespace)) @@ -146,8 +72,7 @@ final class WCSessionTests: XCTestCase { "eip155": ProposalNamespace( chains: [ethAccount.blockchain, polyAccount.blockchain], methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: nil)] + events: ["event", "event-2"])] } private func stubCompliantNamespaces() -> [String: SessionNamespace] { @@ -155,21 +80,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: nil)] - } - - private func stubRequiredNamespacesWithExtension() -> [String: ProposalNamespace] { - return [ - "eip155": ProposalNamespace( - chains: [ethAccount.blockchain, polyAccount.blockchain], - methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: [ - ProposalNamespace.Extension( - chains: [ethAccount.blockchain, polyAccount.blockchain], - methods: ["method-2", "newMethod-2"], - events: ["event-2", "newEvent-2"])])] + events: ["event", "event-2"])] } func testUpdateEqualNamespaces() { @@ -182,22 +93,12 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method"], - events: ["event"], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount], - methods: ["method-2"], - events: ["event-2"])])] + events: ["event"])] let newNamespace = [ "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["method", "newMethod"], - events: ["event", "newEvent"], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount, polyAccount], - methods: ["method-2", "newMethod-2"], - events: ["event-2", "newEvent-2"])])] + events: ["event", "newEvent"])] var session = WCSession.stub(namespaces: namespace) XCTAssertNoThrow(try session.updateNamespaces(newNamespace)) } @@ -213,8 +114,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [newEthAccount, polyAccount], methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: nil)] + events: ["event", "event-2"])] var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces()) XCTAssertNoThrow(try session.updateNamespaces(valid)) } @@ -224,8 +124,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount], methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: nil)] + events: ["event", "event-2"])] var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces()) XCTAssertThrowsError(try session.updateNamespaces(invalid)) } @@ -235,8 +134,7 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["method"], - events: ["event", "event-2"], - extensions: nil)] + events: ["event", "event-2"])] var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces()) XCTAssertThrowsError(try session.updateNamespaces(invalid)) } @@ -246,65 +144,8 @@ final class WCSessionTests: XCTestCase { "eip155": SessionNamespace( accounts: [ethAccount, polyAccount], methods: ["method", "method-2"], - events: ["event"], - extensions: nil)] + events: ["event"])] var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces()) XCTAssertThrowsError(try session.updateNamespaces(invalid)) } - - func testUpdateLessThanRequiredExtension() { - let invalid = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: nil)] - var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension()) - XCTAssertThrowsError(try session.updateNamespaces(invalid)) - } - - func testUpdateLessThanRequiredExtensionAccounts() { - let invalid = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount], - methods: ["method-2", "newMethod-2"], - events: ["event-2", "newEvent-2"])])] - var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension()) - XCTAssertThrowsError(try session.updateNamespaces(invalid)) - } - - func testUpdateLessThanRequiredExtensionMethods() { - let invalid = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount, polyAccount], - methods: ["method-2"], - events: ["event-2", "newEvent-2"])])] - var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension()) - XCTAssertThrowsError(try session.updateNamespaces(invalid)) - } - - func testUpdateLessThanRequiredExtensionEvents() { - let invalid = [ - "eip155": SessionNamespace( - accounts: [ethAccount, polyAccount], - methods: ["method", "method-2"], - events: ["event", "event-2"], - extensions: [ - SessionNamespace.Extension( - accounts: [ethAccount, polyAccount], - methods: ["method-2", "newMethod-2"], - events: ["event-2"])])] - var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension()) - XCTAssertThrowsError(try session.updateNamespaces(invalid)) - } } diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index 72ad8f006..ab03aca18 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -16,7 +16,7 @@ final class SignClientMock: SignClientProtocol { var disconnectCalled = false private let metadata = AppMetadata(name: "", description: "", url: "", icons: []) - private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: "", chainId: Blockchain("eip155:1")!) + private let request = WalletConnectSign.Request(id: .left(""), topic: "", method: "", params: "", chainId: Blockchain("eip155:1")!, expiry: nil) var sessionProposalPublisher: AnyPublisher { let proposer = Participant(publicKey: "", metadata: metadata) @@ -24,8 +24,7 @@ final class SignClientMock: SignClientProtocol { relays: [], proposer: proposer, requiredNamespaces: [:] - ) - .publicRepresentation() + ).publicRepresentation(pairingTopic: "") return Result.Publisher(sessionProposal) .eraseToAnyPublisher() @@ -37,7 +36,7 @@ final class SignClientMock: SignClientProtocol { } var sessionsPublisher: AnyPublisher<[WalletConnectSign.Session], Never> { - return Result.Publisher([WalletConnectSign.Session(topic: "", peer: metadata, namespaces: [:], expiryDate: Date())]) + return Result.Publisher([WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, namespaces: [:], expiryDate: Date())]) .eraseToAnyPublisher() } @@ -74,7 +73,7 @@ final class SignClientMock: SignClientProtocol { } func getSessions() -> [WalletConnectSign.Session] { - return [WalletConnectSign.Session(topic: "", peer: metadata, namespaces: [:], expiryDate: Date())] + return [WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, namespaces: [:], expiryDate: Date())] } func getPendingRequests(topic: String?) -> [WalletConnectSign.Request] { diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 000000000..9fae697da --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,3 @@ +itc_team_id("123564616") +team_id("W5R8AG9K22") +git_url("https://github.com/WalletConnect/match-swift") diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 000000000..ba33be662 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,82 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120" + +default_platform(:ios) + +platform :ios do + + lane :tests do |options| + run_tests( + project: 'Example/ExampleApp.xcodeproj', + scheme: options[:scheme], + cloned_source_packages_path: 'SourcePackagesCache', + destination: 'platform=iOS Simulator,name=iPhone 13', + derived_data_path: 'DerivedDataCache', + skip_package_dependencies_resolution: true, + skip_build: true, + xcargs: "RELAY_HOST='#{options[:relay_host]}' PROJECT_ID='#{options[:project_id]}'" + ) + end + + lane :build do |options| + xcodebuild( + project: 'Example/ExampleApp.xcodeproj', + scheme: options[:scheme], + destination: 'platform=iOS Simulator,name=iPhone 13', + xcargs: "-clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache" + ) + end + + lane :resolve do |options| + xcodebuild( + project: 'Example/ExampleApp.xcodeproj', + scheme: options[:scheme], + destination: 'platform=iOS Simulator,name=iPhone 13', + xcargs: "-resolvePackageDependencies -clonedSourcePackagesDirPath SourcePackagesCache -derivedDataPath DerivedDataCache" + ) + end + + lane :release_testflight do |options| + match( + readonly: false, + type: "appstore", + app_identifier: ENV["APP_IDENTIFIER"], + git_url: "https://github.com/WalletConnect/match-swift.git", + username: options[:username], + ) + number = latest_testflight_build_number( + app_identifier: ENV["APP_IDENTIFIER"], + username: options[:username], + ) + increment_build_number( + build_number: number + 1, + xcodeproj: "Example/ExampleApp.xcodeproj" + ) + build_app( + project: "Example/ExampleApp.xcodeproj", + scheme: ENV["SCHEME"] + ) + upload_to_testflight( + app_identifier: ENV["APP_IDENTIFIER"], + username: options[:username], + apple_id: ENV["APPLE_ID"], + skip_waiting_for_build_processing: true, + ) + clean_build_artifacts() + end + +end \ No newline at end of file