diff --git a/Example/DApp/DApp.entitlements b/Example/DApp/DApp.entitlements new file mode 100644 index 000000000..3396b33e2 --- /dev/null +++ b/Example/DApp/DApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.walletconnect.dapp + + + diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index b9b49d56c..b52f47555 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -12,7 +12,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { Networking.configure( - groupIdentifier: "group.com.walletconnect.sdk", + groupIdentifier: "group.com.walletconnect.dapp", projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory() ) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 90639185b..f90232086 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -444,6 +444,7 @@ 84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; 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 = ""; }; + 84D72FC62B4692770057EAF3 /* DApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DApp.entitlements; 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 = ""; }; @@ -972,6 +973,7 @@ 84CE641D27981DED00142511 /* DApp */ = { isa = PBXGroup; children = ( + 84D72FC62B4692770057EAF3 /* DApp.entitlements */, C5BE01E02AF692F80064FC88 /* ApplicationLayer */, C5BE02202AF7DDE70064FC88 /* Modules */, A5BB7FAB28B6AA7100707FC6 /* Common */, @@ -2860,6 +2862,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = DApp/DApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 18; @@ -2895,6 +2898,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CODE_SIGN_ENTITLEMENTS = DApp/DApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 18; diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme index 5b4aa4030..0f35eb712 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectVerify.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + String { + public static func replace(table: String, rows: [SqliteRow]) -> String? { var values: [String] = [] for row in rows { @@ -12,7 +12,7 @@ public struct SqliteQuery { } guard let first = rows.first else { - throw Errors.rowsNotFound + return nil } let formattedArguments = first.encode().values @@ -37,6 +37,10 @@ public struct SqliteQuery { return "SELECT * FROM \(table) WHERE \(argument) = '\(value)';" } + public static func delete(table: String) -> String { + return "DELETE FROM \(table);" + } + public static func delete(table: String, where argument: String, equals value: String) -> String { return "DELETE FROM \(table) WHERE \(argument) = '\(value)';" } diff --git a/Sources/WalletConnectHistory/HistoryNetworkService.swift b/Sources/WalletConnectHistory/HistoryNetworkService.swift index f4d01ddb8..906959577 100644 --- a/Sources/WalletConnectHistory/HistoryNetworkService.swift +++ b/Sources/WalletConnectHistory/HistoryNetworkService.swift @@ -28,8 +28,8 @@ private extension HistoryNetworkService { } func getJwt(historyUrl: String) throws -> String { - let authenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage, url: historyUrl) - return try authenticator.createAuthToken() + let authenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage) + return try authenticator.createAuthToken(url: historyUrl) } func host(from url: String) throws -> String { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 99cff45c5..3a08a5727 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -7,7 +7,7 @@ public struct NotifyClientFactory { let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier) let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier) - let databasePath = databasePath(appGroup: groupIdentifier, database: "notify.db") + let databasePath = databasePath(appGroup: groupIdentifier, database: "notify_v\(version).db") let sqlite = DiskSqlite(path: databasePath) return NotifyClientFactory.create( @@ -102,4 +102,8 @@ public struct NotifyClientFactory { return path.absoluteString } + + static var version: String { + return "1" + } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyConfig.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyConfig.swift index f7b3a1dae..19c4333ac 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyConfig.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyConfig.swift @@ -5,18 +5,14 @@ struct NotifyConfig: Codable { let id: String let name: String let description: String - } - struct ImageUrl: Codable { - let sm: String? - let md: String? - let lg: String? + let imageUrls: NotifyImageUrls? } let id: String let name: String let homepage: String? let description: String let dapp_url: String - let image_url: ImageUrl? + let image_url: NotifyImageUrls? let notificationTypes: [NotificationType] var appDomain: String { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift index 9e6110e2b..874e86237 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -29,11 +29,19 @@ final class NotifyDatabase { } func save(subscriptions: [NotifySubscription]) throws { - let sql = try SqliteQuery.replace(table: Table.subscriptions, rows: subscriptions) + guard let sql = SqliteQuery.replace(table: Table.subscriptions, rows: subscriptions) else { return } try execute(sql: sql) try onSubscriptionsUpdate?() } + func replace(subscriptions: [NotifySubscription]) throws { + try execute(sql: SqliteQuery.delete(table: Table.subscriptions)) + if let sql = SqliteQuery.replace(table: Table.subscriptions, rows: subscriptions) { + try execute(sql: sql) + } + try onSubscriptionsUpdate?() + } + func getSubscription(topic: String) -> NotifySubscription? { let sql = SqliteQuery.select(table: Table.subscriptions, where: "topic", equals: topic) let subscriptions: [NotifySubscription]? = try? query(sql: sql) @@ -95,7 +103,7 @@ final class NotifyDatabase { } func save(messages: [NotifyMessageRecord]) throws { - let sql = try SqliteQuery.replace(table: Table.messages, rows: messages) + guard let sql = SqliteQuery.replace(table: Table.messages, rows: messages) else { return } try execute(sql: sql) try onMessagesUpdate?() } @@ -110,14 +118,15 @@ private extension NotifyDatabase { try sqlite.execute(sql: """ CREATE TABLE IF NOT EXISTS \(Table.subscriptions) ( - topic TEXT PRIMARY KEY, + topic TEXT NOT NULL, account TEXT NOT NULL, relay TEXT NOT NULL, metadata TEXT NOT NULL, scope TEXT NOT NULL, expiry TEXT NOT NULL, symKey TEXT NOT NULL, - appAuthenticationKey TEXT NOT NULL + appAuthenticationKey TEXT NOT NULL, + id TEXT PRIMARY KEY ); """) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift new file mode 100644 index 000000000..124dc1def --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift @@ -0,0 +1,7 @@ +import Foundation + +public struct NotifyImageUrls: Codable, Equatable { + public let sm: String? + public let md: String? + public let lg: String? +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index 1e9cf905f..cca2c3e6a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -71,7 +71,7 @@ final class NotifyStorage: NotifyStoring { } func replaceAllSubscriptions(_ subscriptions: [NotifySubscription]) throws { - try database.save(subscriptions: subscriptions) + try database.replace(subscriptions: subscriptions) } func deleteSubscription(topic: String) throws { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift index 0426ce460..0e0d21810 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift @@ -40,7 +40,8 @@ class NotifySubscriptionsBuilder { $0[$1.id] = ScopeValue( id: $1.id, name: $1.name, - description: $1.description, + description: $1.description, + imageUrls: $1.imageUrls, enabled: selectedScope.contains($1.id) ) } diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index 9a5ac3609..0cc7f313e 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -1,7 +1,7 @@ import Foundation import Database -public struct NotifySubscription: DatabaseObject, SqliteRow { +public struct NotifySubscription: Codable, Equatable, SqliteRow { public let topic: String public let account: Account public let relay: RelayProtocolOptions @@ -11,8 +11,8 @@ public struct NotifySubscription: DatabaseObject, SqliteRow { public let symKey: String public let appAuthenticationKey: String - public var databaseId: String { - return topic + private var id: String { + return "\(account.absoluteString)-\(metadata.url)" } public init(decoder: SqliteRowDecoder) throws { @@ -36,6 +36,7 @@ public struct NotifySubscription: DatabaseObject, SqliteRow { encoder.encodeDate(expiry, for: "expiry") encoder.encodeString(symKey, for: "symKey") encoder.encodeString(appAuthenticationKey, for: "appAuthenticationKey") + encoder.encodeString(id, for: "id") return encoder } @@ -66,12 +67,14 @@ public struct ScopeValue: Codable, Equatable { public let id: String public let name: String public let description: String + public let imageUrls: NotifyImageUrls? public let enabled: Bool - public init(id: String, name: String, description: String, enabled: Bool) { + public init(id: String, name: String, description: String, imageUrls: NotifyImageUrls?, enabled: Bool) { self.id = id self.name = name self.description = description + self.imageUrls = imageUrls self.enabled = enabled } } diff --git a/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift b/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift index 87a7daada..150ed00d4 100644 --- a/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift +++ b/Sources/WalletConnectRelay/ClientAuth/ClientIdAuthenticator.swift @@ -1,23 +1,19 @@ import Foundation public protocol ClientIdAuthenticating { - func createAuthToken(url: String?) throws -> String + func createAuthToken(url: String) throws -> String } public final class ClientIdAuthenticator: ClientIdAuthenticating { private let clientIdStorage: ClientIdStoring - private var url: String - public init(clientIdStorage: ClientIdStoring, url: String) { + public init(clientIdStorage: ClientIdStoring) { self.clientIdStorage = clientIdStorage - self.url = url } - public func createAuthToken(url: String? = nil) throws -> String { - url.flatMap { self.url = $0 } - + public func createAuthToken(url: String) throws -> String { let keyPair = try clientIdStorage.getOrCreateKeyPair() - let payload = RelayAuthPayload(subject: getSubject(), audience: self.url) + let payload = RelayAuthPayload(subject: getSubject(), audience: url) return try payload.signAndCreateWrapper(keyPair: keyPair).jwtString } diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 4d232ac36..c16d55635 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.10.0"} +{"version": "1.11.0"} diff --git a/Sources/WalletConnectRelay/RelayClientFactory.swift b/Sources/WalletConnectRelay/RelayClientFactory.swift index 26788c48f..98066e6c8 100644 --- a/Sources/WalletConnectRelay/RelayClientFactory.swift +++ b/Sources/WalletConnectRelay/RelayClientFactory.swift @@ -45,8 +45,7 @@ public struct RelayClientFactory { let clientIdStorage = ClientIdStorage(defaults: keyValueStorage, keychain: keychainStorage, logger: logger) let socketAuthenticator = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://\(relayHost)" + clientIdStorage: clientIdStorage ) let relayUrlFactory = RelayUrlFactory( relayHost: relayHost, diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index 855901655..8187ae137 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -14,15 +14,18 @@ public actor VerifyClient: VerifyClientProtocol { let originVerifier: OriginVerifier let assertionRegistrer: AssertionRegistrer let appAttestationRegistrer: AppAttestationRegistrer + let verifyContextFactory: VerifyContextFactory init( originVerifier: OriginVerifier, assertionRegistrer: AssertionRegistrer, - appAttestationRegistrer: AppAttestationRegistrer + appAttestationRegistrer: AppAttestationRegistrer, + verifyContextFactory: VerifyContextFactory = VerifyContextFactory() ) { self.originVerifier = originVerifier self.assertionRegistrer = assertionRegistrer self.appAttestationRegistrer = appAttestationRegistrer + self.verifyContextFactory = verifyContextFactory } public func registerAttestationIfNeeded() async throws { @@ -34,23 +37,7 @@ public actor VerifyClient: VerifyClientProtocol { } nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { - guard isScam != true else { - return VerifyContext( - origin: origin, - validation: .scam - ) - } - if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { - return VerifyContext( - origin: origin, - validation: (originUrl.host == domainUrl.host) ? .valid : .invalid - ) - } else { - return VerifyContext( - origin: origin, - validation: .unknown - ) - } + verifyContextFactory.createVerifyContext(origin: origin, domain: domain, isScam: isScam) } public func registerAssertion() async throws { diff --git a/Sources/WalletConnectVerify/VerifyContextFactory.swift b/Sources/WalletConnectVerify/VerifyContextFactory.swift new file mode 100644 index 000000000..b687932e4 --- /dev/null +++ b/Sources/WalletConnectVerify/VerifyContextFactory.swift @@ -0,0 +1,24 @@ + +import Foundation + +class VerifyContextFactory { + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { + guard isScam != true else { + return VerifyContext( + origin: origin, + validation: .scam + ) + } + if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { + return VerifyContext( + origin: origin, + validation: (originUrl.host == domainUrl.host) ? .valid : .invalid + ) + } else { + return VerifyContext( + origin: origin, + validation: .unknown + ) + } + } +} diff --git a/Sources/Web3Wallet/Web3WalletDecryptionService.swift b/Sources/Web3Wallet/Web3WalletDecryptionService.swift index e4786129f..88b355b3b 100644 --- a/Sources/Web3Wallet/Web3WalletDecryptionService.swift +++ b/Sources/Web3Wallet/Web3WalletDecryptionService.swift @@ -1,5 +1,4 @@ import Foundation -import WalletConnectSign public final class Web3WalletDecryptionService { enum Errors: Error { diff --git a/Tests/NotifyTests/Stubs/NotifySubscription.swift b/Tests/NotifyTests/Stubs/NotifySubscription.swift index 7fd10597e..c5b47cf02 100644 --- a/Tests/NotifyTests/Stubs/NotifySubscription.swift +++ b/Tests/NotifyTests/Stubs/NotifySubscription.swift @@ -13,7 +13,7 @@ extension NotifySubscription { account: account, relay: relay, metadata: metadata, - scope: ["test": ScopeValue(id: "id", name: "name", description: "desc", enabled: true)], + scope: ["test": ScopeValue(id: "id", name: "name", description: "desc", imageUrls: nil, enabled: true)], expiry: expiry, symKey: symKey, appAuthenticationKey: "did:key:z6MkpTEGT75mnz8TiguXYYVnS1GbsNCdLo72R7kUCLShTuFV" diff --git a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift index 72dd37bc5..49df85f62 100644 --- a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift +++ b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift @@ -10,17 +10,14 @@ final class SocketAuthenticatorTests: XCTestCase { override func setUp() { clientIdStorage = ClientIdStorageMock() - sut = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://relay.walletconnect.com" - ) + sut = ClientIdAuthenticator(clientIdStorage: clientIdStorage) } func test() async throws { let keyRaw = Data(hex: "58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295") let signingKey = try SigningPrivateKey(rawRepresentation: keyRaw) clientIdStorage.keyPair = signingKey - let token = try sut.createAuthToken() + let token = try sut.createAuthToken(url: "wss://relay.walletconnect.com") XCTAssertNotNil(token) } } diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift index 4ab1c0a48..8d86455df 100644 --- a/Tests/RelayerTests/DispatcherTests.swift +++ b/Tests/RelayerTests/DispatcherTests.swift @@ -64,10 +64,7 @@ final class DispatcherTests: XCTestCase { let logger = ConsoleLoggerMock() let keychainStorageMock = DispatcherKeychainStorageMock() let clientIdStorage = ClientIdStorage(defaults: defaults, keychain: keychainStorageMock, logger: logger) - let socketAuthenticator = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://relay.walletconnect.com" - ) + let socketAuthenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage) let relayUrlFactory = RelayUrlFactory( relayHost: "relay.walletconnect.com", projectId: "1012db890cf3cfb0c1cdc929add657ba", diff --git a/Tests/VerifyTests/VerifyContextFactoryTests.swift b/Tests/VerifyTests/VerifyContextFactoryTests.swift new file mode 100644 index 000000000..ac540cc0e --- /dev/null +++ b/Tests/VerifyTests/VerifyContextFactoryTests.swift @@ -0,0 +1,38 @@ +import Foundation +import XCTest +@testable import WalletConnectVerify + + +class VerifyContextFactoryTests: XCTestCase { + var factory: VerifyContextFactory! + + override func setUp() { + super.setUp() + factory = VerifyContextFactory() + } + + override func tearDown() { + factory = nil + super.tearDown() + } + + func testScamValidation() { + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: true) + XCTAssertEqual(context.validation, .scam) + } + + func testValidOriginAndDomain() { + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: false) + XCTAssertEqual(context.validation, .valid) + } + + func testInvalidOriginAndDomain() { + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://different.com", isScam: false) + XCTAssertEqual(context.validation, .invalid) + } + + func testUnknownValidation() { + let context = factory.createVerifyContext(origin: nil, domain: "http://example.com", isScam: false) + XCTAssertEqual(context.validation, .unknown) + } + }