diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json b/Example/DApp/Assets.xcassets/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.imageset/Contents.json similarity index 100% rename from Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/Contents.json rename to Example/DApp/Assets.xcassets/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.imageset/Contents.json diff --git a/Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/solana (1).png b/Example/DApp/Assets.xcassets/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.imageset/solana (1).png similarity index 100% rename from Example/DApp/Assets.xcassets/solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ.imageset/solana (1).png rename to Example/DApp/Assets.xcassets/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.imageset/solana (1).png diff --git a/Example/DApp/Modules/Sign/SignInteractor.swift b/Example/DApp/Modules/Sign/SignInteractor.swift index 8fa7d0e2b..8c35d79c1 100644 --- a/Example/DApp/Modules/Sign/SignInteractor.swift +++ b/Example/DApp/Modules/Sign/SignInteractor.swift @@ -6,8 +6,7 @@ enum Proposal { static let requiredNamespaces: [String: ProposalNamespace] = [ "eip155": ProposalNamespace( chains: [ - Blockchain("eip155:1")!, - Blockchain("eip155:137")! + Blockchain("eip155:1")! ], methods: [ "eth_sendTransaction", @@ -20,12 +19,22 @@ enum Proposal { static let optionalNamespaces: [String: ProposalNamespace] = [ "solana": ProposalNamespace( chains: [ - Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! + Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")! ], methods: [ "solana_signMessage", "solana_signTransaction" ], events: [] + ), + "eip155": ProposalNamespace( + chains: [ + Blockchain("eip155:137")! + ], + methods: [ + "eth_sendTransaction", + "personal_sign", + "eth_signTypedData" + ], events: [] ) ] } diff --git a/Example/DApp/Modules/Sign/SignPresenter.swift b/Example/DApp/Modules/Sign/SignPresenter.swift index 656a5be23..a4e58c95a 100644 --- a/Example/DApp/Modules/Sign/SignPresenter.swift +++ b/Example/DApp/Modules/Sign/SignPresenter.swift @@ -16,7 +16,7 @@ final class SignPresenter: ObservableObject { let chains = [ Chain(name: "Ethereum", id: "eip155:1"), Chain(name: "Polygon", id: "eip155:137"), - Chain(name: "Solana", id: "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ") + Chain(name: "Solana", id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp") ] private let interactor: SignInteractor @@ -98,7 +98,7 @@ final class SignPresenter: ObservableObject { Task { do { ActivityIndicatorManager.shared.start() - let uri = try await Sign.instance.authenticate(.stub(methods: nil)) + let uri = try await Sign.instance.authenticate(.stub(methods: ["personal_sign"])) walletConnectUri = uri ActivityIndicatorManager.shared.stop() router.presentNewPairing(walletConnectUri: walletConnectUri!) diff --git a/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan b/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan index 955607e33..40b79df1d 100644 --- a/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan +++ b/Example/ExampleApp.xcodeproj/IntegrationTests.xctestplan @@ -58,7 +58,6 @@ "ChatTests", "ENSResolverTests", "HistoryTests", - "SignClientTests\/testEIP1271SessionAuthenticated()", "SyncDerivationServiceTests", "SyncTests" ], diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index ee3931981..9f5e8dba5 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */; }; - 7694A5262874296A0001257E /* RegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7694A5252874296A0001257E /* RegistryTests.swift */; }; 842B1D132B988BC5007F1EF8 /* Web3Modal in Frameworks */ = {isa = PBXBuildFile; productRef = 842B1D122B988BC5007F1EF8 /* Web3Modal */; }; 842B1D152B988BC5007F1EF8 /* Web3ModalUI in Frameworks */ = {isa = PBXBuildFile; productRef = 842B1D142B988BC5007F1EF8 /* Web3ModalUI */; }; 842B1D172B988BF0007F1EF8 /* Web3ModalUI in Frameworks */ = {isa = PBXBuildFile; productRef = 842B1D162B988BF0007F1EF8 /* Web3ModalUI */; }; @@ -56,7 +55,6 @@ 84CE642B27981DF000142511 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642927981DF000142511 /* LaunchScreen.storyboard */; }; 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; }; 84D093EB2B4EA6CB005B1925 /* ActivityIndicatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */; }; - 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; }; 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */; }; 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, ); }; }; @@ -211,7 +209,6 @@ A5E03DFD286465D100888481 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03DFC286465D100888481 /* Stubs.swift */; }; A5E03DFF2864662500888481 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = A5E03DFE2864662500888481 /* WalletConnect */; }; A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */ = {isa = PBXBuildFile; productRef = A5E03E00286466EA00888481 /* WalletConnectChat */; }; - A5E03E03286466F400888481 /* ChatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E02286466F400888481 /* ChatTests.swift */; }; A5E03E1128646F8000888481 /* KeychainStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E1028646F8000888481 /* KeychainStorageMock.swift */; }; A5E22D1A2840C62A00E36487 /* Engine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D192840C62A00E36487 /* Engine.swift */; }; A5E22D1C2840C85D00E36487 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D1B2840C85D00E36487 /* App.swift */; }; @@ -397,7 +394,6 @@ 764E1D5526F8DADE00A1FB15 /* WalletConnectSwiftV2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WalletConnectSwiftV2; path = ..; sourceTree = ""; }; 764E1D5626F8DB6000A1FB15 /* WalletConnectSwiftV2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WalletConnectSwiftV2; path = ..; sourceTree = ""; }; 767DC83428997F8E00080FA9 /* EthSendTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthSendTransaction.swift; sourceTree = ""; }; - 7694A5252874296A0001257E /* RegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistryTests.swift; sourceTree = ""; }; 84310D04298BC980000C15B6 /* MainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainInteractor.swift; sourceTree = ""; }; 8439CB88293F658E00F2F2E2 /* PushMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessage.swift; sourceTree = ""; }; 844749F329B9E5B9005F520B /* RelayIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RelayIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -438,7 +434,6 @@ 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 = ""; }; 84D093EA2B4EA6CB005B1925 /* ActivityIndicatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorManager.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 = ""; }; @@ -569,7 +564,6 @@ A5E03DED286464DB00888481 /* IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A5E03DF9286465C700888481 /* SignClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignClientTests.swift; sourceTree = ""; }; A5E03DFC286465D100888481 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; - A5E03E02286466F400888481 /* ChatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTests.swift; sourceTree = ""; }; A5E03E1028646F8000888481 /* KeychainStorageMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainStorageMock.swift; sourceTree = ""; }; A5E22D192840C62A00E36487 /* Engine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Engine.swift; sourceTree = ""; }; A5E22D1B2840C85D00E36487 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; @@ -981,7 +975,6 @@ children = ( A58A1ECA29BF457800A82A20 /* ENS */, A54195992934BFDD0035AD19 /* Signer */, - 84D2A66528A4F51E0088AE09 /* AuthTests.swift */, ); path = Auth; sourceTree = ""; @@ -1462,21 +1455,11 @@ 84CEC64728D8A98900D081A8 /* Pairing */, A5E03E0A28646A8A00888481 /* Stubs */, A5E03E0928646A8100888481 /* Sign */, - A5E03E0828646A7B00888481 /* Chat */, 84D2A66728A4F5260088AE09 /* Auth */, ); path = IntegrationTests; sourceTree = ""; }; - A5E03E0828646A7B00888481 /* Chat */ = { - isa = PBXGroup; - children = ( - A5E03E02286466F400888481 /* ChatTests.swift */, - 7694A5252874296A0001257E /* RegistryTests.swift */, - ); - path = Chat; - sourceTree = ""; - }; A5E03E0928646A8100888481 /* Sign */ = { isa = PBXGroup; children = ( @@ -2414,12 +2397,9 @@ A541959E2934BFEF0035AD19 /* CacaoSignerTests.swift in Sources */, A59CF4F6292F83D50031A42F /* DefaultSignerFactory.swift in Sources */, 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */, - A5E03E03286466F400888481 /* ChatTests.swift in Sources */, 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */, 845B8D8C2934B36C0084A966 /* Account.swift in Sources */, - 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */, 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, - 7694A5262874296A0001257E /* RegistryTests.swift in Sources */, A541959F2934BFEF0035AD19 /* SignerTests.swift in Sources */, A58A1ECC29BF458600A82A20 /* ENSResolverTests.swift in Sources */, A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */, @@ -2897,11 +2877,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = PNDecryptionService/PNDecryptionServiceRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 7; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PNDecryptionService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = PNDecryptionService; @@ -2916,7 +2894,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp.PNDecryptionService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp.PNDecryptionService"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -3115,12 +3092,10 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = WalletApp/WalletAppRelease.entitlements; - CODE_SIGN_IDENTITY = "Apple Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 7; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; @@ -3139,7 +3114,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift deleted file mode 100644 index aed8cba64..000000000 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ /dev/null @@ -1,212 +0,0 @@ -// -//import Foundation -//import XCTest -//@testable import WalletConnectUtils -//@testable import WalletConnectKMS -//import WalletConnectRelay -//import Combine -//@testable import Auth -//import WalletConnectPairing -//import WalletConnectNetworking -//import WalletConnectVerify -// -//final class AuthTests: XCTestCase { -// var appPairingClient: PairingClient! -// var walletPairingClient: PairingClient! -// -// var appAuthClient: AuthClient! -// var walletAuthClient: AuthClient! -// -// let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")! -// let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f") -// let eip1271Signature = "0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c" -// private var publishers = [AnyCancellable]() -// -// override func setUp() { -// setupClients() -// } -// -// private func setupClients(iatProvider: IATProvider = DefaultIATProvider()) { -// (appPairingClient, appAuthClient) = makeClients(prefix: "🤖 App", iatProvider: iatProvider) -// (walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", iatProvider: iatProvider) -// } -// -// func makeClients(prefix: String, iatProvider: IATProvider) -> (PairingClient, AuthClient) { -// let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) -// let keyValueStorage = RuntimeKeyValueStorage() -// let keychain = KeychainStorageMock() -// let relayClient = RelayClientFactory.create( -// relayHost: InputConfig.relayHost, -// projectId: InputConfig.projectId, -// keyValueStorage: keyValueStorage, -// keychainStorage: keychain, -// socketFactory: DefaultSocketFactory(), -// networkMonitor: NetworkMonitor(), -// logger: logger) -// -// let networkingClient = NetworkingClientFactory.create( -// relayClient: relayClient, -// logger: logger, -// keychainStorage: keychain, -// keyValueStorage: keyValueStorage) -// -// let pairingClient = PairingClientFactory.create( -// logger: logger, -// keyValueStorage: keyValueStorage, -// keychainStorage: keychain, -// networkingClient: networkingClient) -// -// let authClient = AuthClientFactory.create( -// metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "", universal: nil)), -// projectId: InputConfig.projectId, -// crypto: DefaultCryptoProvider(), -// logger: logger, -// keyValueStorage: keyValueStorage, -// keychainStorage: keychain, -// networkingClient: networkingClient, -// pairingRegisterer: pairingClient, -// iatProvider: iatProvider) -// -// let clientId = try! networkingClient.getClientId() -// logger.debug("My client id is: \(clientId)") -// -// return (pairingClient, authClient) -// } -// -// func testRequest() async { -// let requestExpectation = expectation(description: "request delivered to wallet") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { _ in -// requestExpectation.fulfill() -// }.store(in: &publishers) -// wait(for: [requestExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testEIP191RespondSuccess() async { -// let responseExpectation = expectation(description: "successful response delivered") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { [unowned self] request in -// Task(priority: .high) { -// let signerFactory = DefaultSignerFactory() -// let signer = MessageSignerFactory(signerFactory: signerFactory).create(projectId: InputConfig.projectId) -// let payload = try! request.0.payload.cacaoPayload(address: walletAccount.address) -// let signature = try! signer.sign(payload: payload, privateKey: prvKey, type: .eip191) -// try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: walletAccount) -// } -// } -// .store(in: &publishers) -// appAuthClient.authResponsePublisher.sink { (_, result) in -// guard case .success = result else { XCTFail(); return } -// responseExpectation.fulfill() -// } -// .store(in: &publishers) -// wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testEIP1271RespondSuccess() async { -// setupClients(iatProvider: IATProviderMock()) -// -// let account = Account(chainIdentifier: "eip155:1", address: "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71")! -// -// let responseExpectation = expectation(description: "successful response delivered") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams( -// domain: "localhost", -// chainId: "eip155:1", -// nonce: "1665443015700", -// aud: "http://localhost:3000/", -// nbf: nil, -// exp: "2022-10-11T23:03:35.700Z", -// statement: nil, -// requestId: nil, -// resources: nil -// ), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { [unowned self] request in -// Task(priority: .high) { -// let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) -// try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: account) -// } -// } -// .store(in: &publishers) -// appAuthClient.authResponsePublisher.sink { (_, result) in -// guard case .success = result else { XCTFail(); return } -// responseExpectation.fulfill() -// } -// .store(in: &publishers) -// wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testEIP191RespondError() async { -// let responseExpectation = expectation(description: "error response delivered") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { [unowned self] request in -// Task(priority: .high) { -// let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) -// try! await walletAuthClient.respond(requestId: request.0.id, signature: signature, from: walletAccount) -// } -// } -// .store(in: &publishers) -// appAuthClient.authResponsePublisher.sink { (_, result) in -// guard case let .failure(error) = result, error == .signatureVerificationFailed else { XCTFail(); return } -// responseExpectation.fulfill() -// } -// .store(in: &publishers) -// wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testUserRespondError() async { -// let responseExpectation = expectation(description: "error response delivered") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { [unowned self] request in -// Task(priority: .high) { -// try! await walletAuthClient.reject(requestId: request.0.id) -// } -// } -// .store(in: &publishers) -// appAuthClient.authResponsePublisher.sink { (_, result) in -// guard case .failure(let error) = result else { XCTFail(); return } -// XCTAssertEqual(error, .userRejeted) -// responseExpectation.fulfill() -// } -// .store(in: &publishers) -// wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testRespondSignatureVerificationFailed() async { -// let responseExpectation = expectation(description: "invalid signature response delivered") -// let uri = try! await appPairingClient.create() -// try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) -// -// try? await walletPairingClient.pair(uri: uri) -// walletAuthClient.authRequestPublisher.sink { [unowned self] request in -// Task(priority: .high) { -// let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" -// let cacaoSignature = CacaoSignature(t: .eip191, s: invalidSignature) -// try! await walletAuthClient.respond(requestId: request.0.id, signature: cacaoSignature, from: walletAccount) -// } -// } -// .store(in: &publishers) -// appAuthClient.authResponsePublisher.sink { (_, result) in -// guard case .failure(let error) = result else { XCTFail(); return } -// XCTAssertEqual(error, .signatureVerificationFailed) -// responseExpectation.fulfill() -// } -// .store(in: &publishers) -// wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) -// } -//} -//>>>>>>> bd8835437254fb808be3ac89fdcff43fa13c3b87 diff --git a/Example/IntegrationTests/Auth/Signer/CacaoSignerTests.swift b/Example/IntegrationTests/Auth/Signer/CacaoSignerTests.swift index 90a17b00e..c35cd57d5 100644 --- a/Example/IntegrationTests/Auth/Signer/CacaoSignerTests.swift +++ b/Example/IntegrationTests/Auth/Signer/CacaoSignerTests.swift @@ -50,8 +50,8 @@ class CacaoSignerTest: XCTestCase { func testCacaoSign() throws { let account = Account("eip155:1:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")! - let cacaoPayload = try payload.cacaoPayload(account: account) - let formatted = try SIWECacaoFormatter().formatMessage(from: cacaoPayload) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload(authPayload: payload, account: account) + let formatted = try SIWEFromCacaoPayloadFormatter().formatMessage(from: cacaoPayload) XCTAssertEqual(formatted, message) XCTAssertEqual(try signer.sign(payload: cacaoPayload, privateKey: privateKey, type: .eip191), signature) } diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift deleted file mode 100644 index 09c96faf0..000000000 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -//import Foundation -//import XCTest -//@testable import WalletConnectChat -//import WalletConnectUtils -//@testable import WalletConnectKMS -//@testable import WalletConnectSync -//import WalletConnectRelay -//import Combine -//import Web3 -// -//final class ChatTests: XCTestCase { -// var invitee1: ChatClient! -// var inviter1: ChatClient! -// var invitee2: ChatClient! -// var inviter2: ChatClient! -// private var publishers = [AnyCancellable]() -// -// var inviteeAccount: Account { -// return Account("eip155:1:" + pk1.address.hex(eip55: true))! -// } -// -// var inviterAccount: Account { -// return Account("eip155:1:" + pk2.address.hex(eip55: true))! -// } -// -// let pk1 = try! EthereumPrivateKey() -// let pk2 = try! EthereumPrivateKey() -// -// var privateKey1: Data { -// return Data(pk1.rawPrivateKey) -// } -// var privateKey2: Data { -// return Data(pk2.rawPrivateKey) -// } -// -// override func setUp() async throws { -// invitee1 = makeClient(prefix: "🦖 Invitee", account: inviteeAccount) -// inviter1 = makeClient(prefix: "🍄 Inviter", account: inviterAccount) -// -// try await invitee1.register(account: inviteeAccount, domain: "") { message in -// return self.sign(message, privateKey: self.privateKey1) -// } -// try await inviter1.register(account: inviterAccount, domain: "") { message in -// return self.sign(message, privateKey: self.privateKey2) -// } -// } -// -// func makeClient(prefix: String, account: Account) -> ChatClient { -// let keyserverURL = URL(string: "https://keys.walletconnect.com")! -// let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) -// let keyValueStorage = RuntimeKeyValueStorage() -// let keychain = KeychainStorageMock() -// let relayClient = RelayClientFactory.create( -// relayHost: InputConfig.relayHost, -// projectId: InputConfig.projectId, -// keyValueStorage: keyValueStorage, -// keychainStorage: keychain, -// socketFactory: DefaultSocketFactory(), -// networkMonitor: NetworkMonitor(), -// logger: logger) -// -// let networkingInteractor = NetworkingClientFactory.create( -// relayClient: relayClient, -// logger: logger, -// keychainStorage: keychain, -// keyValueStorage: keyValueStorage) -// -// let syncClient = SyncClientFactory.create( -// networkInteractor: networkingInteractor, -// bip44: DefaultBIP44Provider(), -// keychain: keychain -// ) -// -// let clientId = try! networkingInteractor.getClientId() -// logger.debug("My client id is: \(clientId)") -// -// return ChatClientFactory.create(keyserverURL: keyserverURL, relayClient: relayClient, networkingInteractor: networkingInteractor, keychain: keychain, logger: logger, storage: keyValueStorage, syncClient: syncClient) -// } -// -// func testInvite() async throws { -// let inviteExpectation = expectation(description: "invitation expectation") -// inviteExpectation.expectedFulfillmentCount = 2 -// -// invitee1.newReceivedInvitePublisher.sink { _ in -// inviteExpectation.fulfill() -// }.store(in: &publishers) -// -// inviter1.newSentInvitePublisher.sink { _ in -// inviteExpectation.fulfill() -// }.store(in: &publishers) -// -// let inviteePublicKey = try await inviter1.resolve(account: inviteeAccount) -// let invite = Invite(message: "", inviterAccount: inviterAccount, inviteeAccount: inviteeAccount, inviteePublicKey: inviteePublicKey) -// _ = try await inviter1.invite(invite: invite) -// -// wait(for: [inviteExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testAcceptAndCreateNewThread() async throws { -// let newThreadInviterExpectation = expectation(description: "new thread on inviting client expectation") -// let newThreadinviteeExpectation = expectation(description: "new thread on invitee client expectation") -// -// invitee1.newReceivedInvitePublisher.sink { [unowned self] invite in -// Task { try! await invitee1.accept(inviteId: invite.id) } -// }.store(in: &publishers) -// -// invitee1.newThreadPublisher.sink { _ in -// newThreadinviteeExpectation.fulfill() -// }.store(in: &publishers) -// -// inviter1.newThreadPublisher.sink { _ in -// newThreadInviterExpectation.fulfill() -// }.store(in: &publishers) -// -// let inviteePublicKey = try await inviter1.resolve(account: inviteeAccount) -// let invite = Invite(message: "", inviterAccount: inviterAccount, inviteeAccount: inviteeAccount, inviteePublicKey: inviteePublicKey) -// try await inviter1.invite(invite: invite) -// -// wait(for: [newThreadinviteeExpectation, newThreadInviterExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// func testMessage() async throws { -// let messageExpectation = expectation(description: "message received") -// messageExpectation.expectedFulfillmentCount = 4 -// -// invitee1.newReceivedInvitePublisher.sink { [unowned self] invite in -// Task { try! await invitee1.accept(inviteId: invite.id) } -// }.store(in: &publishers) -// -// invitee1.newThreadPublisher.sink { [unowned self] thread in -// Task { try! await invitee1.message(topic: thread.topic, message: "message1") } -// }.store(in: &publishers) -// -// inviter1.newThreadPublisher.sink { [unowned self] thread in -// Task { try! await inviter1.message(topic: thread.topic, message: "message2") } -// }.store(in: &publishers) -// -// inviter1.newMessagePublisher.sink { message in -// if message.authorAccount == self.inviterAccount { -// XCTAssertEqual(message.message, "message2") -// } else { -// XCTAssertEqual(message.message, "message1") -// } -// messageExpectation.fulfill() -// }.store(in: &publishers) -// -// invitee1.newMessagePublisher.sink { message in -// if message.authorAccount == self.inviteeAccount { -// XCTAssertEqual(message.message, "message1") -// } else { -// XCTAssertEqual(message.message, "message2") -// } -// messageExpectation.fulfill() -// }.store(in: &publishers) -// -// let inviteePublicKey = try await inviter1.resolve(account: inviteeAccount) -// let invite = Invite(message: "", inviterAccount: inviterAccount, inviteeAccount: inviteeAccount, inviteePublicKey: inviteePublicKey) -// try await inviter1.invite(invite: invite) -// -// wait(for: [messageExpectation], timeout: InputConfig.defaultTimeout) -// } -// -// private func sign(_ message: String, privateKey: Data) -> SigningResult { -// let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) -// return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191)) -// } -//} -//>>>>>>> bd8835437254fb808be3ac89fdcff43fa13c3b87 diff --git a/Example/IntegrationTests/Chat/RegistryTests.swift b/Example/IntegrationTests/Chat/RegistryTests.swift deleted file mode 100644 index 0c659241f..000000000 --- a/Example/IntegrationTests/Chat/RegistryTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -import XCTest -import WalletConnectNetworking -import WalletConnectKMS -import WalletConnectUtils -@testable import WalletConnectChat -@testable import WalletConnectIdentity - -final class RegistryTests: XCTestCase { - - let account = Account("eip155:1:0x15bca56b6e2728aec2532df9d436bd1600e86688")! - let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a") - - var sut: IdentityService! - var storage: IdentityStorage! - var signer: MessageSigner! - - override func setUp() { - let keyserverURL = URL(string: "https://keys.walletconnect.com")! - let httpService = HTTPNetworkClient(host: keyserverURL.host!) - let identityNetworkService = IdentityNetworkService(httpService: httpService) - let keychain = KeychainStorageMock() - let ksm = KeyManagementService(keychain: keychain) - storage = IdentityStorage(keychain: keychain) - sut = IdentityService ( - keyserverURL: keyserverURL, - kms: ksm, - storage: storage, - networkService: identityNetworkService, - iatProvader: DefaultIATProvider(), - messageFormatter: SIWECacaoFormatter() - ) - signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() - } - -// func testRegisterIdentityAndInviteKey() async throws { -// let publicKey = try await sut.registerIdentity(account: account, onSign: onSign) -// -// let iss = DIDKey(rawData: Data(hex: publicKey)).did(variant: .ED25519) -// let resolvedAccount = try await sut.resolveIdentity(iss: iss) -// XCTAssertEqual(resolvedAccount, account) -// -// let recovered = try storage.getIdentityKey(for: account).publicKey.hexRepresentation -// XCTAssertEqual(publicKey, recovered) -// -// let inviteKey = try await sut.registerInvite(account: account) -// -// let recoveredKey = try storage.getInviteKey(for: account) -// XCTAssertEqual(inviteKey, recoveredKey) -// -// let resolvedKey = try await sut.resolveInvite(account: account) -// XCTAssertEqual(inviteKey.did, resolvedKey) -// -// _ = try await sut.goPrivate(account: account) -// try await sut.unregister(account: account, onSign: onSign) -// } -} - -private extension RegistryTests { - - func onSign(_ message: String) -> SigningResult { - let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) - return .signed(signature) - } -} diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 66718151c..2b08baca7 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -281,8 +281,8 @@ private extension NotifyTests { private extension NotifyClient { - func register(account: Account, domain: String, isLimited: Bool = false, onSign: @escaping (String) -> CacaoSignature) async throws { - let params = try await prepareRegistration(account: account, domain: domain) + func register(account: Account, domain: String, onSign: @escaping (String) -> CacaoSignature) async throws { + let params = try await prepareRegistration(account: account, domain: "https://\(domain)") let signature = onSign(params.message) try await register(params: params, signature: signature) } diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 59b5afa59..1d68f9305 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -196,7 +196,9 @@ final class SignClientTests: XCTestCase { let requestParams = [EthSendTransaction.stub()] let responseParams = "0xdeadbeef" let chain = Blockchain("eip155:1")! - + + // sleep is needed as emitRequestIfPending() will be called on client init and then on request itself, second request would be debouced + sleep(1) wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { do { @@ -247,6 +249,8 @@ final class SignClientTests: XCTestCase { let chain = Blockchain("eip155:1")! + // sleep is needed as emitRequestIfPending() will be called on client init and then on request itself, second request would be debouced + sleep(1) wallet.sessionProposalPublisher.sink { [unowned self] (proposal, _) in Task(priority: .high) { try await wallet.approve(proposalId: proposal.id, namespaces: sessionNamespaces) @@ -455,7 +459,7 @@ final class SignClientTests: XCTestCase { events: ["any"] ), "solana": ProposalNamespace( - chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + chains: [Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!], methods: ["solana_signMessage"], events: ["any"] ) @@ -477,12 +481,12 @@ final class SignClientTests: XCTestCase { Blockchain("eip155:137")!, Blockchain("eip155:1")!, Blockchain("eip155:5")!, - Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! + Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")! ], methods: ["personal_sign", "eth_sendTransaction", "solana_signMessage"], events: ["any"], accounts: [ - Account(blockchain: Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, address: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, + Account(blockchain: Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!, address: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!, Account(blockchain: Blockchain("eip155:1")!, address: "0x00")!, Account(blockchain: Blockchain("eip155:137")!, address: "0x00")!, Account(blockchain: Blockchain("eip155:5")!, address: "0x00")! @@ -973,7 +977,8 @@ final class SignClientTests: XCTestCase { let requestParams = [EthSendTransaction.stub()] let responseParams = "0xdeadbeef" let chain = Blockchain("eip155:1")! - + // sleep is needed as emitRequestIfPending() will be called on client init and then on request itself, second request would be debouced + sleep(1) wallet.authRequestPublisher.sink { [unowned self] (request, _) in Task(priority: .high) { let signerFactory = DefaultSignerFactory() diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 34cad9e0c..d1e34537d 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -103,7 +103,7 @@ public class AuthClient: AuthClientProtocol { @available(*, deprecated, message: "Use SignClient or Web3Wallet for message formatting.") public func formatMessage(payload: AuthPayload, address: String) throws -> String { - return try SIWECacaoFormatter().formatMessage(from: payload.cacaoPayload(address: address)) + return try SIWEFromCacaoPayloadFormatter().formatMessage(from: payload.cacaoPayload(address: address)) } private func setUpPublishers() { diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index c2748c5bd..ae73f8d7e 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -42,7 +42,7 @@ public struct AuthClientFactory { ) -> AuthClient { let kms = KeyManagementService(keychain: keychainStorage) let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) - let messageFormatter = SIWECacaoFormatter() + let messageFormatter = SIWEFromCacaoPayloadFormatter() let appRequestService = AppRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider) let verifyClient = VerifyClientFactory.create() let verifyContextStore = CodableStore(defaults: keyValueStorage, identifier: VerifyStorageIdentifiers.context.rawValue) diff --git a/Sources/Auth/Services/App/AppRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift index 0973e1cf1..25c7917e0 100644 --- a/Sources/Auth/Services/App/AppRespondSubscriber.swift +++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift @@ -6,7 +6,7 @@ class AppRespondSubscriber { private let logger: ConsoleLogging private let rpcHistory: RPCHistory private let signatureVerifier: MessageVerifier - private let messageFormatter: SIWECacaoFormatting + private let messageFormatter: SIWEFromCacaoPayloadFormatter private let pairingRegisterer: PairingRegisterer private var publishers = [AnyCancellable]() @@ -17,7 +17,7 @@ class AppRespondSubscriber { rpcHistory: RPCHistory, signatureVerifier: MessageVerifier, pairingRegisterer: PairingRegisterer, - messageFormatter: SIWECacaoFormatting) { + messageFormatter: SIWEFromCacaoPayloadFormatter) { self.networkingInteractor = networkingInteractor self.logger = logger self.rpcHistory = rpcHistory diff --git a/Sources/WalletConnectIdentity/IdentityClient.swift b/Sources/WalletConnectIdentity/IdentityClient.swift index 8958b5112..10b0dc922 100644 --- a/Sources/WalletConnectIdentity/IdentityClient.swift +++ b/Sources/WalletConnectIdentity/IdentityClient.swift @@ -24,7 +24,7 @@ public final class IdentityClient { public func prepareRegistration(account: Account, domain: String, - statement: String, + statement: String? = nil, resources: [String]) async throws -> IdentityRegistrationParams { let registration = try await identityService.prepareRegistration(account: account, domain: domain, statement: statement, resources: resources) diff --git a/Sources/WalletConnectIdentity/IdentityClientFactory.swift b/Sources/WalletConnectIdentity/IdentityClientFactory.swift index 236a939f7..57720a4f8 100644 --- a/Sources/WalletConnectIdentity/IdentityClientFactory.swift +++ b/Sources/WalletConnectIdentity/IdentityClientFactory.swift @@ -11,7 +11,7 @@ public final class IdentityClientFactory { let httpService = HTTPNetworkClient(host: keyserver.host!) let identityStorage = IdentityStorage(keychain: keychain) let identityNetworkService = IdentityNetworkService(httpService: httpService) - let identityService = IdentityService(keyserverURL: keyserver, kms: kms, storage: identityStorage, networkService: identityNetworkService, iatProvader: DefaultIATProvider(), messageFormatter: SIWECacaoFormatter()) + let identityService = IdentityService(keyserverURL: keyserver, kms: kms, storage: identityStorage, networkService: identityNetworkService, iatProvader: DefaultIATProvider(), messageFormatter: SIWEFromCacaoPayloadFormatter()) return IdentityClient( identityService: identityService, identityStorage: identityStorage, diff --git a/Sources/WalletConnectIdentity/IdentityService.swift b/Sources/WalletConnectIdentity/IdentityService.swift index 96cd34215..179ee478f 100644 --- a/Sources/WalletConnectIdentity/IdentityService.swift +++ b/Sources/WalletConnectIdentity/IdentityService.swift @@ -7,7 +7,7 @@ actor IdentityService { private let storage: IdentityStorage private let networkService: IdentityNetworking private let iatProvader: IATProvider - private let messageFormatter: SIWECacaoFormatting + private let messageFormatter: SIWEFromCacaoFormatting init( keyserverURL: URL, @@ -15,7 +15,7 @@ actor IdentityService { storage: IdentityStorage, networkService: IdentityNetworking, iatProvader: IATProvider, - messageFormatter: SIWECacaoFormatting + messageFormatter: SIWEFromCacaoFormatting ) { self.keyserverURL = keyserverURL self.kms = kms @@ -27,20 +27,34 @@ actor IdentityService { func prepareRegistration(account: Account, domain: String, - statement: String, + statement: String? = nil, resources: [String]) throws -> IdentityRegistrationParams { let identityKey = SigningPrivateKey() + let uri = buildUri(domain: domain, didKey: identityKey.publicKey.did) + + let recapUrns = resources.compactMap { try? RecapUrn(urn: $0)} + + let mergedRecap = try? RecapUrnMergingService.merge(recapUrns: recapUrns) + var payloadStatement: String? + if let mergedRecapUrn = mergedRecap { + // If there's a merged recap, generate its statement + payloadStatement = try SiweStatementBuilder.buildSiweStatement(statement: statement, mergedRecapUrn: mergedRecapUrn) + } else { + // If no merged recap, use the original statement + payloadStatement = statement + } + let payload = CacaoPayload( iss: account.did, domain: domain, - aud: identityKey.publicKey.did, + aud: uri, version: getVersion(), nonce: getNonce(), iat: iatProvader.iat, nbf: nil, exp: nil, - statement: statement, + statement: payloadStatement, requestId: nil, resources: resources ) @@ -50,6 +64,10 @@ actor IdentityService { return IdentityRegistrationParams(message: message, payload: payload, privateIdentityKey: identityKey) } + func buildUri(domain: String, didKey: String) -> String { + return "bundleid://\(domain)?walletconnect_identity_key=\(didKey)" + } + // TODO: Verifications func registerIdentity(params: IdentityRegistrationParams, signature: CacaoSignature) async throws -> String { let account = try params.account diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 6b5aa22fc..7664d62f4 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -1,6 +1,7 @@ import Foundation import Combine + public class NotifyClient { private var publishers = Set() @@ -87,12 +88,11 @@ public class NotifyClient { self.subscriptionWatcher = subscriptionWatcher } - public func prepareRegistration(account: Account, domain: String, allApps: Bool = true) async throws -> IdentityRegistrationParams { + public func prepareRegistration(account: Account, domain: String) async throws -> IdentityRegistrationParams { return try await identityClient.prepareRegistration( account: account, domain: domain, - statement: makeStatement(allApps: allApps), - resources: [keyserverURL.absoluteString] + resources: [keyserverURL.absoluteString, createAuthorizationRecap()] ) } @@ -172,6 +172,12 @@ public class NotifyClient { return messages.count == limit } + + /// returns notify recap for all apps + private func createAuthorizationRecap() -> String { + // {"att":{"https://notify.walletconnect.com":{"manage/all-apps-notifications":[{}]}}} + "urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9ub3RpZnkud2FsbGV0Y29ubmVjdC5jb20iOnsibWFuYWdlL2FsbC1hcHBzLW5vdGlmaWNhdGlvbnMiOlt7fV19fX0" + } } private extension NotifyClient { diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index eca36dd02..25d66792e 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.14.0"} +{"version": "1.15.0"} diff --git a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift index 8a533f9be..e223dd142 100644 --- a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift @@ -6,7 +6,7 @@ class AuthResponseSubscriber { private let logger: ConsoleLogging private let rpcHistory: RPCHistory private let signatureVerifier: MessageVerifier - private let messageFormatter: SIWECacaoFormatting + private let messageFormatter: SIWEFromCacaoFormatting private let pairingRegisterer: PairingRegisterer private var publishers = [AnyCancellable]() private let sessionStore: WCSessionStorage @@ -25,7 +25,7 @@ class AuthResponseSubscriber { pairingRegisterer: PairingRegisterer, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, - messageFormatter: SIWECacaoFormatting, + messageFormatter: SIWEFromCacaoFormatting, sessionNamespaceBuilder: SessionNamespaceBuilder, authResponseTopicRecordsStore: CodableStore) { self.networkingInteractor = networkingInteractor @@ -57,7 +57,6 @@ class AuthResponseSubscriber { let requestId = payload.id let cacaos = payload.response.cacaos - let authRequestPayload = payload.request.authPayload Task { do { @@ -66,7 +65,7 @@ class AuthResponseSubscriber { authResponsePublisherSubject.send((requestId, .failure(error as! AuthError))) return } - let session = try createSession(from: payload.response, selfParticipant: payload.request.requester, pairingTopic: pairingTopic, authRequestPayload: authRequestPayload) + let session = try createSession(from: payload.response, selfParticipant: payload.request.requester, pairingTopic: pairingTopic) authResponsePublisherSubject.send((requestId, .success((session, cacaos)))) } @@ -98,8 +97,7 @@ class AuthResponseSubscriber { private func createSession( from response: SessionAuthenticateResponseParams, selfParticipant: Participant, - pairingTopic: String, - authRequestPayload: AuthPayload + pairingTopic: String ) throws -> Session? { let selfPublicKey = try AgreementPublicKey(hex: selfParticipant.publicKey) diff --git a/Sources/WalletConnectSign/Auth/Services/AuthPayloadBuilder.swift b/Sources/WalletConnectSign/Auth/Services/AuthPayloadBuilder.swift index 1db2dc9b5..19685d83a 100644 --- a/Sources/WalletConnectSign/Auth/Services/AuthPayloadBuilder.swift +++ b/Sources/WalletConnectSign/Auth/Services/AuthPayloadBuilder.swift @@ -6,12 +6,13 @@ public struct AuthPayloadBuilder { public static func build(payload: AuthPayload, supportedEVMChains: [Blockchain], supportedMethods: [String]) throws -> AuthPayload { // Attempt to find a valid session recap URN from the resources - guard let existingSessionRecapUrn = payload.resources?.first(where: { (try? SignRecap(urn: $0)) != nil }) else { + guard let recap = payload.resources?.last, + let _ = try? SignRecap(urn: recap) else { return payload } // Use SessionRecapBuilder to create a new session recap based on the existing valid URN - let newSessionRecap = try SignRecapBuilder.build(requestedSessionRecap: existingSessionRecapUrn, requestedChains: payload.chains, supportedEVMChains: supportedEVMChains, supportedMethods: supportedMethods) + let newSessionRecap = try SignRecapBuilder.build(requestedSessionRecap: recap, requestedChains: payload.chains, supportedEVMChains: supportedEVMChains, supportedMethods: supportedMethods) // Encode the new session recap to its URN format let newSessionRecapUrn = newSessionRecap.urn diff --git a/Sources/WalletConnectSign/Auth/Services/CacaosBuilder.swift b/Sources/WalletConnectSign/Auth/Services/CacaosBuilder.swift new file mode 100644 index 000000000..572eba175 --- /dev/null +++ b/Sources/WalletConnectSign/Auth/Services/CacaosBuilder.swift @@ -0,0 +1,52 @@ + +import Foundation +import WalletConnectUtils + +struct CacaosBuilder { + public static func makeCacao(authPayload: AuthPayload, signature: WalletConnectUtils.CacaoSignature, account: WalletConnectUtils.Account) throws -> Cacao { + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload(authPayload: authPayload, account: account) + let header = CacaoHeader(t: "eip4361") + return Cacao(h: header, p: cacaoPayload, s: signature) + } + +} + +struct CacaoPayloadBuilder { + public static func makeCacaoPayload(authPayload: AuthPayload, account: WalletConnectUtils.Account) throws -> CacaoPayload { + var mergedRecap: RecapUrn? + var resources: [String]? = nil + + if let recapUrns = authPayload.resources?.compactMap({ try? RecapUrn(urn: $0) }), !recapUrns.isEmpty { + mergedRecap = try? RecapUrnMergingService.merge(recapUrns: recapUrns) + } + + var statement: String? = authPayload.statement + if let mergedRecapUrn = mergedRecap { + statement = try SiweStatementBuilder.buildSiweStatement(statement: authPayload.statement, mergedRecapUrn: mergedRecapUrn) + } + + // Initialize resources with the filtered list only if authPayload.resources was not nil + if authPayload.resources != nil { + resources = authPayload.resources?.filter { !$0.starts(with: "urn:recap:") } + if let mergedRecapUrnString = mergedRecap?.urn { + resources?.append(mergedRecapUrnString) + } + } + + return CacaoPayload( + iss: account.did, + domain: authPayload.domain, + aud: authPayload.aud, + version: authPayload.version, + nonce: authPayload.nonce, + iat: authPayload.iat, + nbf: authPayload.nbf, + exp: authPayload.exp, + statement: statement, + requestId: authPayload.requestId, + resources: resources + ) + } +} + + diff --git a/Sources/WalletConnectSign/Auth/Services/CacaosProvider.swift b/Sources/WalletConnectSign/Auth/Services/CacaosProvider.swift deleted file mode 100644 index 5ab42d879..000000000 --- a/Sources/WalletConnectSign/Auth/Services/CacaosProvider.swift +++ /dev/null @@ -1,10 +0,0 @@ - -import Foundation - -struct CacaosProvider { - public func makeCacao(authPayload: AuthPayload, signature: WalletConnectUtils.CacaoSignature, account: WalletConnectUtils.Account) throws -> Cacao { - let payload = try authPayload.cacaoPayload(account: account) - let header = CacaoHeader(t: "eip4361") - return Cacao(h: header, p: payload, s: signature) - } -} diff --git a/Sources/WalletConnectSign/Auth/Services/ProposalNamespaceBuilder.swift b/Sources/WalletConnectSign/Auth/Services/ProposalNamespaceBuilder.swift index eb11fc983..3c4ca0861 100644 --- a/Sources/WalletConnectSign/Auth/Services/ProposalNamespaceBuilder.swift +++ b/Sources/WalletConnectSign/Auth/Services/ProposalNamespaceBuilder.swift @@ -9,16 +9,19 @@ struct ProposalNamespaceBuilder { } static func buildNamespace(from params: AuthRequestParams) throws -> [String: ProposalNamespace] { + var methods = Set(params.methods ?? []) + if methods.isEmpty { + methods = ["personal_sign"] + } let chains: Set = Set(params.chains.compactMap { Blockchain($0) }) guard chains.allSatisfy({$0.namespace == "eip155"}) else { throw Errors.unsupportedChain } - let methods = Set(params.methods ?? []) return [ "eip155": ProposalNamespace( chains: chains, methods: methods, - events: [] + events: ["chainChanged", "accountsChanged"] )] } } diff --git a/Sources/WalletConnectSign/Auth/Services/SignRecap.swift b/Sources/WalletConnectSign/Auth/Services/SignRecap.swift index 973378553..bcda4fdba 100644 --- a/Sources/WalletConnectSign/Auth/Services/SignRecap.swift +++ b/Sources/WalletConnectSign/Auth/Services/SignRecap.swift @@ -20,7 +20,7 @@ struct SignRecap { } let base64Part = urn.dropFirst("urn:recap:".count) - guard let jsonData = Data(base64Encoded: String(base64Part)), + guard let jsonData = Data(base64urlEncoded: String(base64Part)), let decodedData = try? JSONDecoder().decode(RecapData.self, from: jsonData) else { throw Errors.invalidRecapStructure } diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthResponder.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthResponder.swift index bec7e6502..3d9ae4304 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthResponder.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthResponder.swift @@ -8,7 +8,7 @@ actor AuthResponder { } private let networkingInteractor: NetworkInteracting private let kms: KeyManagementService - private let messageFormatter: SIWECacaoFormatting + private let messageFormatter: SIWEFromCacaoFormatting private let signatureVerifier: MessageVerifier private let rpcHistory: RPCHistory private let verifyContextStore: CodableStore @@ -25,7 +25,7 @@ actor AuthResponder { kms: KeyManagementService, rpcHistory: RPCHistory, signatureVerifier: MessageVerifier, - messageFormatter: SIWECacaoFormatting, + messageFormatter: SIWEFromCacaoFormatting, verifyContextStore: CodableStore, walletErrorResponder: WalletErrorResponder, pairingRegisterer: PairingRegisterer, diff --git a/Sources/WalletConnectSign/Auth/Types/AuthPayload.swift b/Sources/WalletConnectSign/Auth/Types/AuthPayload.swift index 6c13e9002..8edab5317 100644 --- a/Sources/WalletConnectSign/Auth/Types/AuthPayload.swift +++ b/Sources/WalletConnectSign/Auth/Types/AuthPayload.swift @@ -57,20 +57,4 @@ public struct AuthPayload: Codable, Equatable { self.requestId = requestParams.requestId self.resources = requestParams.resources } - - func cacaoPayload(account: Account) throws -> CacaoPayload { - return CacaoPayload( - iss: account.did, - domain: domain, - aud: aud, - version: version, - nonce: nonce, - iat: iat, - nbf: nbf, - exp: exp, - statement: statement, - requestId: requestId, - resources: resources - ) - } } diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 82b1d0a53..435f34f98 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -52,7 +52,6 @@ final class SessionEngine { setupUpdateSubscriptions() setupExpirationSubscriptions() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in - guard let self = self else {return} sessionRequestsProvider.emitRequestIfPending() } } diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index e57b62324..b0fe8caf7 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -348,11 +348,12 @@ public final class SignClient: SignClientProtocol { } public func formatAuthMessage(payload: AuthPayload, account: Account) throws -> String { - return try SIWECacaoFormatter().formatMessage(from: payload.cacaoPayload(account: account), includeRecapInTheStatement: true) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload(authPayload: payload, account: account) + return try SIWEFromCacaoPayloadFormatter().formatMessage(from: cacaoPayload) } public func buildSignedAuthObject(authPayload: AuthPayload, signature: WalletConnectUtils.CacaoSignature, account: Account) throws -> AuthObject { - try CacaosProvider().makeCacao(authPayload: authPayload, signature: signature, account: account) + try CacaosBuilder.makeCacao(authPayload: authPayload, signature: signature, account: account) } public func buildAuthPayload(payload: AuthPayload, supportedEVMChains: [Blockchain], supportedMethods: [String]) throws -> AuthPayload { diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index c55c29467..e07c86816 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -95,7 +95,7 @@ public struct SignClientFactory { //Auth let authResponseTopicRecordsStore = CodableStore(defaults: keyValueStorage, identifier: SignStorageIdentifiers.authResponseTopicRecord.rawValue) - let messageFormatter = SIWECacaoFormatter() + let messageFormatter = SIWEFromCacaoPayloadFormatter() let appRequestService = SessionAuthRequestService(networkingInteractor: networkingClient, kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider, authResponseTopicRecordsStore: authResponseTopicRecordsStore) let messageVerifierFactory = MessageVerifierFactory(crypto: crypto) diff --git a/Sources/WalletConnectSigner/Signer/MessageSigner.swift b/Sources/WalletConnectSigner/Signer/MessageSigner.swift index b1c8efc4c..fed68c3e2 100644 --- a/Sources/WalletConnectSigner/Signer/MessageSigner.swift +++ b/Sources/WalletConnectSigner/Signer/MessageSigner.swift @@ -7,9 +7,9 @@ public struct MessageSigner { } private let signer: EthereumSigner - private let messageFormatter: SIWECacaoFormatting + private let messageFormatter: SIWEFromCacaoFormatting - init(signer: EthereumSigner, messageFormatter: SIWECacaoFormatting) { + init(signer: EthereumSigner, messageFormatter: SIWEFromCacaoFormatting) { self.signer = signer self.messageFormatter = messageFormatter } diff --git a/Sources/WalletConnectSigner/Signer/MessageSignerFactory.swift b/Sources/WalletConnectSigner/Signer/MessageSignerFactory.swift index 0506e6fa1..5fc0a5eb4 100644 --- a/Sources/WalletConnectSigner/Signer/MessageSignerFactory.swift +++ b/Sources/WalletConnectSigner/Signer/MessageSignerFactory.swift @@ -11,7 +11,7 @@ public struct MessageSignerFactory { public func create() -> MessageSigner { return MessageSigner( signer: signerFactory.createEthereumSigner(), - messageFormatter: SIWECacaoFormatter() + messageFormatter: SIWEFromCacaoPayloadFormatter() ) } } diff --git a/Sources/WalletConnectUtils/SIWE/RecapStatementBuilder.swift b/Sources/WalletConnectUtils/SIWE/RecapStatementBuilder.swift index e3561c644..859a01521 100644 --- a/Sources/WalletConnectUtils/SIWE/RecapStatementBuilder.swift +++ b/Sources/WalletConnectUtils/SIWE/RecapStatementBuilder.swift @@ -1,53 +1,18 @@ import Foundation -struct RecapUrn { - enum Errors: Error { - case invalidUrn - case invalidPayload - case invalidJsonStructure - } - - let urn: String - let recapData: RecapData - - init(urn: String) throws { - guard urn.hasPrefix("urn:recap") else { throw Errors.invalidUrn } - - let components = urn.components(separatedBy: ":") - guard components.count > 2, let jsonData = Data(base64Encoded: components.dropFirst(2).joined(separator: ":")) else { - throw Errors.invalidPayload - } - - do { - self.recapData = try JSONDecoder().decode(RecapData.self, from: jsonData) - } catch { - throw Errors.invalidJsonStructure - } - - self.urn = urn - } -} - -struct RecapData: Decodable { - var att: [String: [String: [AnyCodable]]]? - var prf: [String]? -} - struct RecapStatementBuilder { enum Errors: Error { case noActionsAuthorized - case emptyRecapsForbidden } - static func buildRecapStatement(recapUrns: [RecapUrn]) throws -> String { - guard recapUrns.count > 0 else { throw Errors.emptyRecapsForbidden } - var statementParts: [String] = [] - var actionCounter = 1 + static func buildRecapStatement(recapUrn: RecapUrn) throws -> String { + var statementParts: [String] = [] + var actionCounter = 1 - recapUrns.forEach { urn in - let decodedRecap = urn.recapData + // Processing only the last URN. + let decodedRecap = recapUrn.recapData - guard let attValue = decodedRecap.att else { return } + guard let attValue = decodedRecap.att else { throw Errors.noActionsAuthorized } let sortedResourceKeys = attValue.keys.sorted() @@ -67,13 +32,12 @@ struct RecapStatementBuilder { actionCounter += 1 } } - } - if statementParts.isEmpty { - throw Errors.noActionsAuthorized - } else { - let formattedStatement = statementParts.joined(separator: ". ") - return "I further authorize the stated URI to perform the following actions on my behalf: \(formattedStatement)." + if statementParts.isEmpty { + throw Errors.noActionsAuthorized + } else { + let formattedStatement = statementParts.joined(separator: ". ") + return "I further authorize the stated URI to perform the following actions on my behalf: \(formattedStatement)." + } } - } } diff --git a/Sources/WalletConnectUtils/SIWE/RecapUrn.swift b/Sources/WalletConnectUtils/SIWE/RecapUrn.swift new file mode 100644 index 000000000..161c80530 --- /dev/null +++ b/Sources/WalletConnectUtils/SIWE/RecapUrn.swift @@ -0,0 +1,63 @@ +import Foundation + +public struct RecapData: Codable { + var att: [String: [String: [AnyCodable]]]? + var prf: [String]? +} + +public struct RecapUrn { + enum Errors: Error { + case invalidUrn + case invalidPayload + case invalidJsonStructure + } + + public let urn: String + public let recapData: RecapData + + public init(urn: String) throws { + guard urn.hasPrefix("urn:recap") else { throw Errors.invalidUrn } + + let components = urn.components(separatedBy: ":") + guard components.count > 2 else { + throw Errors.invalidPayload + } + + let base64urlEncodedPayload = components.dropFirst(2).joined(separator: ":") + guard let jsonData = Data(base64urlEncoded: base64urlEncodedPayload) else { + throw Errors.invalidPayload + } + + do { + self.recapData = try JSONDecoder().decode(RecapData.self, from: jsonData) + } catch { + throw Errors.invalidJsonStructure + } + + self.urn = urn + } +} + +public extension Data { + /// Initializes a Data object with a base64url encoded String. + init?(base64urlEncoded: String) { + var base64 = base64urlEncoded + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + + // Add padding if necessary + while base64.count % 4 != 0 { + base64 += "=" + } + + self.init(base64Encoded: base64) + } + + /// Returns a base64url encoded String. + func base64urlEncodedString() -> String { + return self.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .trimmingCharacters(in: ["="]) // Remove any padding + } +} diff --git a/Sources/WalletConnectUtils/SIWE/RecapUrnMergingService.swift b/Sources/WalletConnectUtils/SIWE/RecapUrnMergingService.swift new file mode 100644 index 000000000..02c7452d8 --- /dev/null +++ b/Sources/WalletConnectUtils/SIWE/RecapUrnMergingService.swift @@ -0,0 +1,59 @@ +import Foundation + +public class RecapUrnMergingService { + public enum Errors: Error { + case emptyRecapUrns + case encodingFailed + } + + public static func merge(recapUrns: [RecapUrn]) throws -> RecapUrn { + guard !recapUrns.isEmpty else { + throw Errors.emptyRecapUrns + } + + // If there's only one URN, return it directly. + if recapUrns.count == 1 { + return recapUrns.first! + } + + var mergedAtt: [String: [String: [AnyCodable]]] = [:] + + // Aggregate all actions under their respective keys + for recapUrn in recapUrns { + guard let att = recapUrn.recapData.att else { continue } + for (key, value) in att { + if var existingValue = mergedAtt[key] { + for (actionKey, actionValue) in value { + existingValue[actionKey] = (existingValue[actionKey] ?? []) + actionValue + } + mergedAtt[key] = existingValue + } else { + mergedAtt[key] = value + } + } + } + + // Sort and then ensure actions are also sorted, if necessary. + let sortedMergedAtt = mergedAtt + .sorted { $0.key < $1.key } + .reduce(into: [String: [String: [AnyCodable]]]()) { (result, pair) in + let (resource, actions) = pair + let sortedActions = actions + .sorted { $0.key < $1.key } + .reduce(into: [String: [AnyCodable]]()) { (actionsResult, actionPair) in + actionsResult[actionPair.key] = actionPair.value + } + result[resource] = sortedActions + } + + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys] + guard let jsonData = try? encoder.encode(RecapData(att: sortedMergedAtt, prf: nil)), + let jsonBase64 = jsonData.base64EncodedString().addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { + throw Errors.encodingFailed + } + + let mergedUrnString = "urn:recap:\(jsonBase64)" + return try RecapUrn(urn: mergedUrnString) + } +} diff --git a/Sources/WalletConnectUtils/SIWE/SIWECacaoFormatter.swift b/Sources/WalletConnectUtils/SIWE/SIWECacaoFormatter.swift deleted file mode 100644 index 4721f69db..000000000 --- a/Sources/WalletConnectUtils/SIWE/SIWECacaoFormatter.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -public protocol SIWECacaoFormatting { - func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String -} -public extension SIWECacaoFormatting { - func formatMessage(from payload: CacaoPayload) throws -> String { - return try formatMessage(from: payload, includeRecapInTheStatement: false) - } -} -public struct SIWECacaoFormatter: SIWECacaoFormatting { - - public init() { } - - public func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String { - let iss = try DIDPKH(did: payload.iss) - let message = SIWEMessage( - domain: payload.domain, - uri: payload.aud, - address: iss.account.address, - version: payload.version, - nonce: payload.nonce, - chainId: iss.account.reference, - iat: payload.iat, - nbf: payload.nbf, - exp: payload.exp, - statement: payload.statement, - requestId: payload.requestId, - resources: payload.resources - ) - return try message.formatted() - } -} - diff --git a/Sources/WalletConnectUtils/SIWE/SIWEFromCacaoPayloadFormatter.swift b/Sources/WalletConnectUtils/SIWE/SIWEFromCacaoPayloadFormatter.swift new file mode 100644 index 000000000..70a7f2dec --- /dev/null +++ b/Sources/WalletConnectUtils/SIWE/SIWEFromCacaoPayloadFormatter.swift @@ -0,0 +1,61 @@ +import Foundation + +public protocol SIWEFromCacaoFormatting { + func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String +} +public extension SIWEFromCacaoFormatting { + func formatMessage(from payload: CacaoPayload) throws -> String { + return try formatMessage(from: payload, includeRecapInTheStatement: true) + } +} + + +public struct SIWEFromCacaoPayloadFormatter: SIWEFromCacaoFormatting { + + public init() {} + + public func formatMessage(from payload: CacaoPayload, includeRecapInTheStatement: Bool) throws -> String { + let iss = try DIDPKH(did: payload.iss) + let address = iss.account.address + let chainId = iss.account.reference + + // Directly use the statement from payload, add a newline if it exists + let statementLine = payload.statement.flatMap { "\n\($0)" } ?? "" + + // Format the message with all details + let formattedMessage = """ + \(payload.domain) wants you to sign in with your Ethereum account: + \(address) + \(statementLine) + + URI: \(payload.aud) + Version: \(payload.version) + Chain ID: \(chainId) + Nonce: \(payload.nonce) + Issued At: \(payload.iat)\(formatExpLine(exp: payload.exp))\(formatNbfLine(nbf: payload.nbf))\(formatRequestIdLine(requestId: payload.requestId))\(formatResourcesSection(resources: payload.resources)) + """ + return formattedMessage + } + + // Helper methods for formatting individual parts of the message + private func formatExpLine(exp: String?) -> String { + guard let exp = exp else { return "" } + return "\nExpiration Time: \(exp)" + } + + private func formatNbfLine(nbf: String?) -> String { + guard let nbf = nbf else { return "" } + return "\nNot Before: \(nbf)" + } + + private func formatRequestIdLine(requestId: String?) -> String { + guard let requestId = requestId else { return "" } + return "\nRequest ID: \(requestId)" + } + + private func formatResourcesSection(resources: [String]?) -> String { + guard let resources = resources else { return "" } + let resourcesList = resources.reduce("") { $0 + "\n- \($1)" } + return resources.isEmpty ? "\nResources:" : "\nResources:" + resourcesList + } +} diff --git a/Sources/WalletConnectUtils/SIWE/SIWEMessage.swift b/Sources/WalletConnectUtils/SIWE/SIWEMessage.swift deleted file mode 100644 index 6d1d2a936..000000000 --- a/Sources/WalletConnectUtils/SIWE/SIWEMessage.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation - -public struct SIWEMessage: Equatable { - public let domain: String - public let uri: String // aud - public let address: String - public let version: String - public let nonce: String - public let chainId: String - public let iat: String - public let nbf: String? - public let exp: String? - public let statement: String? - public let requestId: String? - public let resources: [String]? - - public init(domain: String, uri: String, address: String, version: String, nonce: String, chainId: String, iat: String, nbf: String?, exp: String?, statement: String?, requestId: String?, resources: [String]?) { - self.domain = domain - self.uri = uri - self.address = address - self.version = version - self.nonce = nonce - self.chainId = chainId - self.iat = iat - self.nbf = nbf - self.exp = exp - self.statement = statement - self.requestId = requestId - self.resources = resources - } - - public func formatted() throws -> String { - - let statementLine = try getStatementLine() - return """ - \(domain) wants you to sign in with your Ethereum account: - \(address) - \(statementLine) - - URI: \(uri) - Version: \(version) - Chain ID: \(chainId) - Nonce: \(nonce) - Issued At: \(iat)\(expLine)\(nbfLine)\(requestIdLine)\(resourcesSection) - """ - } - - private func getStatementLine() throws -> String { - if let recaps = resources?.compactMap({ try? RecapUrn(urn: $0) }), - !recaps.isEmpty { - do { - let recapStatement = try RecapStatementBuilder.buildRecapStatement(recapUrns: recaps) - if let statement = statement { - return "\n\(statement) \(recapStatement)" - } else { - return "\n\(recapStatement)" - } - } catch { - throw error - } - } else { - guard let statement = statement else { return "" } - return "\n\(statement)" - } - - } - - - - private func decodeUrnToJson(urn: String) -> [String: [String: [String: [String]]]]? { - // Check if the URN is in the correct format - guard urn.starts(with: "urn:recap:") else { return nil } - - // Extract the Base64 encoded JSON part from the URN - let base64EncodedJson = urn.replacingOccurrences(of: "urn:recap:", with: "") - - // Decode the Base64 encoded JSON - guard let jsonData = Data(base64Encoded: base64EncodedJson) else { return nil } - - // Deserialize the JSON data into the desired dictionary - do { - let decodedDictionary = try JSONDecoder().decode([String: [String: [String: [String]]]].self, from: jsonData) - return decodedDictionary - } catch { - return nil - } - } -} - -private extension SIWEMessage { - - var expLine: String { - guard let exp = exp else { return "" } - return "\nExpiration Time: \(exp)" - } - - var nbfLine: String { - guard let nbf = nbf else { return "" } - return "\nNot Before: \(nbf)" - } - - var requestIdLine: String { - guard let requestId = requestId else { return "" } - return "\nRequest ID: \(requestId)" - } - - var resourcesSection: String { - guard let resources = resources else { return "" } - return resources.reduce("\nResources:") { $0 + "\n- \($1)" } - } -} diff --git a/Sources/WalletConnectUtils/SIWE/SiweStatementBuilder.swift b/Sources/WalletConnectUtils/SIWE/SiweStatementBuilder.swift new file mode 100644 index 000000000..fceed8b05 --- /dev/null +++ b/Sources/WalletConnectUtils/SIWE/SiweStatementBuilder.swift @@ -0,0 +1,22 @@ + +import Foundation + +public class SiweStatementBuilder { + public static func buildSiweStatement(statement: String?, mergedRecapUrn: RecapUrn?) throws -> String { + var finalStatement = statement ?? "" + + if let mergedRecapUrn = mergedRecapUrn { + // Generate recap statement from the merged RecapUrn + let recapStatement = try RecapStatementBuilder.buildRecapStatement(recapUrn: mergedRecapUrn) + // Append recap statement to the original statement, if it exists + if !finalStatement.isEmpty { + finalStatement += " \(recapStatement)" + } else { + finalStatement = recapStatement + } + } + + return finalStatement.isEmpty ? "" : "\(finalStatement)" + } +} + diff --git a/Tests/ChatTests/RegistryServiceTests.swift b/Tests/ChatTests/RegistryServiceTests.swift deleted file mode 100644 index 8f6cb51b7..000000000 --- a/Tests/ChatTests/RegistryServiceTests.swift +++ /dev/null @@ -1,106 +0,0 @@ -import Foundation -import XCTest -@testable import WalletConnectChat -import WalletConnectUtils -import WalletConnectNetworking -import WalletConnectKMS -@testable import TestingUtils -@testable import WalletConnectIdentity - -final class RegistryServiceTests: XCTestCase { - var resubscriptionService: ResubscriptionService! - var identityClient: IdentityClient! - var identityStorage: IdentityStorage! - var networkService: IdentityNetwotkServiceMock! - var networkingInteractor: NetworkingInteractorMock! - var kms: KeyManagementServiceMock! - - let account = Account("eip155:1:0x1AAe9864337E821f2F86b5D27468C59AA333C877")! - let privateKey = "4dc0055d1831f7df8d855fc8cd9118f4a85ddc05395104c4cb0831a6752621a8" - - let cacaoStub: Cacao = { - return Cacao(h: .init(t: ""), p: .init(iss: "", domain: "", aud: "", version: "", nonce: "", iat: "", nbf: "", exp: nil, statement: nil, requestId: nil, resources: nil), s: .init(t: .eip191, s: "")) - }() - - let inviteKeyStub = "62720d14643acf0f7dd87513b079502f56be414a2f2ea4719342cf088c794173" - - override func setUp() { - networkingInteractor = NetworkingInteractorMock() - kms = KeyManagementServiceMock() - identityStorage = IdentityStorage(keychain: KeychainStorageMock()) - networkService = IdentityNetwotkServiceMock(cacao: cacaoStub, inviteKey: inviteKeyStub) - - let identitySevice = IdentityService( - keyserverURL: URL(string: "https://www.url.com")!, - kms: kms, - storage: identityStorage, - networkService: networkService, - iatProvader: DefaultIATProvider(), - messageFormatter: SIWECacaoFormatter() - ) - identityClient = IdentityClient(identityService: identitySevice, identityStorage: identityStorage, logger: ConsoleLoggerMock()) - - resubscriptionService = ResubscriptionService(networkingInteractor: networkingInteractor, kms: kms, logger: ConsoleLoggerMock()) - } - -// func testRegister() async throws { -// let pubKey = try await identityClient.register(account: account, onSign: onSign) -// -// XCTAssertTrue(networkService.callRegisterIdentity) -// -// let identityKey = try identityStorage.getIdentityKey(for: account) -// XCTAssertEqual(identityKey.publicKey.hexRepresentation, pubKey) -// } - -// func testGoPublic() async throws { -// XCTAssertTrue(networkingInteractor.subscriptions.isEmpty) -// -// _ = try await identityClient.register(account: account, onSign: onSign) -// let inviteKey = try await identityClient.goPublic(account: account) -// try await resubscriptionService.subscribeForInvites(inviteKey: inviteKey) -// -// XCTAssertNoThrow(try identityStorage.getInviteKey(for: account)) -// XCTAssertTrue(networkService.callRegisterInvite) -// -// XCTAssertEqual(networkingInteractor.subscriptions.count, 1) -// XCTAssertNotNil(kms.getPublicKey(for: networkingInteractor.subscriptions[0])) -// } - -// func testUnregister() async throws { -// XCTAssertThrowsError(try identityStorage.getIdentityKey(for: account)) -// -// _ = try await identityClient.register(account: account, onSign: onSign) -// XCTAssertNoThrow(try identityStorage.getIdentityKey(for: account)) -// -// try await identityClient.unregister(account: account, onSign: onSign) -// XCTAssertThrowsError(try identityStorage.getIdentityKey(for: account)) -// XCTAssertTrue(networkService.callRemoveIdentity) -// } - - func testGoPrivate() async throws { - let invitePubKey = try AgreementPublicKey(hex: inviteKeyStub) - try identityStorage.saveInviteKey(invitePubKey, for: account) - - let identityKey = SigningPrivateKey() - try identityStorage.saveIdentityKey(identityKey, for: account) - - let topic = invitePubKey.rawRepresentation.sha256().toHexString() - try await networkingInteractor.subscribe(topic: topic) - - let inviteKey = try await identityClient.goPrivate(account: account) - resubscriptionService.unsubscribeFromInvites(inviteKey: inviteKey) - - XCTAssertThrowsError(try identityStorage.getInviteKey(for: account)) - XCTAssertTrue(networkingInteractor.unsubscriptions.contains(topic)) - } - - func testResolve() async throws { - let inviteKey = try await identityClient.resolveInvite(account: account) - - XCTAssertEqual(inviteKey, inviteKeyStub) - } - - private func onSign(_ message: String) -> SigningResult { - return .signed(CacaoSignature(t: .eip191, s: "")) - } -} diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index f7c65f72d..f0c0a9fc3 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -803,7 +803,7 @@ final class AutoNamespacesValidationTests: XCTestCase { func testAutoNamespacesDifferentChainEmptyOptinalEvents() { let accounts = [ Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")!, - Account(blockchain: Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, address: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! + Account(blockchain: Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!, address: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")! ] let requiredNamespaces = [ "eip155": ProposalNamespace( @@ -818,7 +818,7 @@ final class AutoNamespacesValidationTests: XCTestCase { events: [] ), "solana": ProposalNamespace( - chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + chains: [Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!], methods: ["solana_signMessage"], events: [] ) @@ -827,7 +827,7 @@ final class AutoNamespacesValidationTests: XCTestCase { let sessionNamespaces = try! AutoNamespaces.build( sessionProposal: sessionProposal, - chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + chains: [Blockchain("eip155:1")!, Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!], methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], events: ["chainChanged", "accountsChanged"], accounts: accounts @@ -842,9 +842,9 @@ final class AutoNamespacesValidationTests: XCTestCase { events: ["chainChanged", "accountsChanged"] ), "solana": SessionNamespace( - chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + chains: [Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!], accounts: Set([ - Account(blockchain: Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, address: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, + Account(blockchain: Blockchain("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!, address: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")!, ]), methods: ["solana_signMessage"], events: [] diff --git a/Tests/WalletConnectSignTests/SIWEMessageFormatterTests.swift b/Tests/WalletConnectSignTests/SIWEFromCacaoPayloadFormatterTests.swift similarity index 65% rename from Tests/WalletConnectSignTests/SIWEMessageFormatterTests.swift rename to Tests/WalletConnectSignTests/SIWEFromCacaoPayloadFormatterTests.swift index 57c877fc9..53773cbb2 100644 --- a/Tests/WalletConnectSignTests/SIWEMessageFormatterTests.swift +++ b/Tests/WalletConnectSignTests/SIWEFromCacaoPayloadFormatterTests.swift @@ -3,12 +3,12 @@ import Foundation @testable import WalletConnectSign import XCTest -class SIWEMessageFormatterTests: XCTestCase { - var sut: SIWECacaoFormatter! +class SIWEFromCacaoPayloadFormatterTests: XCTestCase { + var sut: SIWEFromCacaoPayloadFormatter! let address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" override func setUp() { - sut = SIWECacaoFormatter() + sut = SIWEFromCacaoPayloadFormatter() } func testFormatMessage() throws { @@ -28,7 +28,8 @@ class SIWEMessageFormatterTests: XCTestCase { - ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ - https://example.com/my-web2-claim.json """ - let message = try sut.formatMessage(from: AuthPayload.stub().cacaoPayload(account: Account.stub())) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload(authPayload: AuthPayload.stub(), account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } @@ -48,11 +49,12 @@ class SIWEMessageFormatterTests: XCTestCase { - ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ - https://example.com/my-web2-claim.json """ - let message = try sut.formatMessage( - from: AuthPayload.stub( + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( requestParams: AuthRequestParams.stub(statement: nil) - ).cacaoPayload(account: Account.stub()) - ) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } @@ -70,10 +72,36 @@ class SIWEMessageFormatterTests: XCTestCase { Nonce: 32891756 Issued At: 2021-09-30T16:25:24Z """ - let message = try sut.formatMessage( - from: AuthPayload.stub( - requestParams: AuthRequestParams.stub(resources: nil)).cacaoPayload(account: Account.stub()) - ) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(resources: nil) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) + XCTAssertEqual(message, expectedMessage) + } + + func testResourcesEmptyArray() throws { + let expectedMessage = + """ + service.invalid wants you to sign in with your Ethereum account: + 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 + + I accept the ServiceOrg Terms of Service: https://service.invalid/tos + + URI: https://service.invalid/login + Version: 1 + Chain ID: 1 + Nonce: 32891756 + Issued At: 2021-09-30T16:25:24Z + Resources: + """ + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(resources: []) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } @@ -90,10 +118,12 @@ class SIWEMessageFormatterTests: XCTestCase { Nonce: 32891756 Issued At: 2021-09-30T16:25:24Z """ - let message = try sut.formatMessage( - from: AuthPayload.stub( - requestParams: AuthRequestParams.stub(statement: nil, resources: nil)).cacaoPayload(account: Account.stub()) - ) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(statement: nil, resources: nil) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } @@ -117,12 +147,13 @@ class SIWEMessageFormatterTests: XCTestCase { """ + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(resources: [validRecapUrn]) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) - let payload = try AuthPayload.stub( - requestParams: AuthRequestParams.stub(resources: [validRecapUrn]) - ).cacaoPayload(account: Account.stub()) - - let message = try sut.formatMessage(from: payload, includeRecapInTheStatement: true) XCTAssertEqual(message, expectedMessage) } @@ -145,19 +176,19 @@ class SIWEMessageFormatterTests: XCTestCase { - urn:recap:eyJhdHQiOiB7ImVpcDE1NSI6IHsicmVxdWVzdC9ldGhfc2VuZFRyYW5zYWN0aW9uIjogW10sICJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOiBbXX19fQ== """ - - let payload = try AuthPayload.stub( - requestParams: AuthRequestParams.stub(statement: nil,resources: [validRecapUrn]) - ).cacaoPayload(account: Account.stub()) - - let message = try sut.formatMessage(from: payload, includeRecapInTheStatement: true) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(statement: nil, resources: [validRecapUrn]) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } func testWithSignAndNotifyRecaps() throws { let recap1 = "urn:recap:ewogICAiYXR0Ijp7CiAgICAgICJlaXAxNTUiOnsKICAgICAgICAgInJlcXVlc3QvZXRoX3NlbmRUcmFuc2FjdGlvbiI6IFt7fV0sCiAgICAgICAgICJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOiBbe31dCiAgICAgIH0KICAgfQp9" - let recap2 = "urn:recap:ewogICAiYXR0Ijp7CiAgICAgICJodHRwczovL25vdGlmeS53YWxsZXRjb25uZWN0LmNvbS9hbGwtYXBwcyI6ewogICAgICAgICAiY3J1ZC9ub3RpZmljYXRpb25zIjogW3t9XSwKICAgICAgICAgImNydWQvc3Vic2NyaXB0aW9ucyI6IFt7fV0KICAgICAgfQogICB9Cn0=" + let recap2 = "urn:recap:ewogICAiYXR0Ijp7CiAgICAgICJodHRwczovL25vdGlmeS53YWxsZXRjb25uZWN0LmNvbS9hbGwtYXBwcyI6ewogICAgICAgICAiY3J1ZC9ub3RpZmljYXRpb25zIjogW3t9XSwKICAgICAgICAgImNydWQvc3Vic2NyaXB0aW9ucyI6IFt7fV0KICAgICAgfQogICB9Cn0" let expectedMessage = """ @@ -172,17 +203,17 @@ class SIWEMessageFormatterTests: XCTestCase { Nonce: 32891756 Issued At: 2021-09-30T16:25:24Z Resources: - - urn:recap:ewogICAiYXR0Ijp7CiAgICAgICJlaXAxNTUiOnsKICAgICAgICAgInJlcXVlc3QvZXRoX3NlbmRUcmFuc2FjdGlvbiI6IFt7fV0sCiAgICAgICAgICJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOiBbe31dCiAgICAgIH0KICAgfQp9 - - urn:recap:ewogICAiYXR0Ijp7CiAgICAgICJodHRwczovL25vdGlmeS53YWxsZXRjb25uZWN0LmNvbS9hbGwtYXBwcyI6ewogICAgICAgICAiY3J1ZC9ub3RpZmljYXRpb25zIjogW3t9XSwKICAgICAgICAgImNydWQvc3Vic2NyaXB0aW9ucyI6IFt7fV0KICAgICAgfQogICB9Cn0= + - urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3RcL2V0aF9zZW5kVHJhbnNhY3Rpb24iOlt7fV0sInJlcXVlc3RcL3BlcnNvbmFsX3NpZ24iOlt7fV19LCJodHRwczpcL1wvbm90aWZ5LndhbGxldGNvbm5lY3QuY29tXC9hbGwtYXBwcyI6eyJjcnVkXC9ub3RpZmljYXRpb25zIjpbe31dLCJjcnVkXC9zdWJzY3JpcHRpb25zIjpbe31dfX19 """ let uri = "https://service.invalid?walletconnect_notify_key=did:key:z6MktW4hKdsvcXgt9wXmYbSD5sH4NCk5GmNZnokP9yh2TeCf" - let payload = try AuthPayload.stub( - requestParams: AuthRequestParams.stub(uri: uri, statement: nil,resources: [recap1, recap2]) - ).cacaoPayload(account: Account.stub()) - - let message = try sut.formatMessage(from: payload, includeRecapInTheStatement: true) + let cacaoPayload = try CacaoPayloadBuilder.makeCacaoPayload( + authPayload: AuthPayload.stub( + requestParams: AuthRequestParams.stub(uri: uri, statement: nil, resources: [recap1, recap2]) + ), + account: Account.stub()) + let message = try sut.formatMessage(from: cacaoPayload) XCTAssertEqual(message, expectedMessage) } } diff --git a/Tests/WalletConnectSignTests/Stub/SIWEMessageFormatterMock.swift b/Tests/WalletConnectSignTests/Stub/SIWEMessageFormatterMock.swift deleted file mode 100644 index 3fd5658aa..000000000 --- a/Tests/WalletConnectSignTests/Stub/SIWEMessageFormatterMock.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -@testable import WalletConnectUtils - -class SIWEMessageFormatterMock: SIWECacaoFormatting { - func formatMessage(from payload: WalletConnectUtils.CacaoPayload, includeRecapInTheStatement: Bool) throws -> String { - fatalError() - } - - - var formattedMessage: String! - - func formatMessages(from payload: CacaoPayload) throws -> String { - return formattedMessage - } -} diff --git a/Tests/WalletConnectUtilsTests/RecapStatementBuilderTests.swift b/Tests/WalletConnectUtilsTests/RecapStatementBuilderTests.swift index b5cf40478..66fe838f1 100644 --- a/Tests/WalletConnectUtilsTests/RecapStatementBuilderTests.swift +++ b/Tests/WalletConnectUtilsTests/RecapStatementBuilderTests.swift @@ -17,7 +17,7 @@ class RecapStatementBuilderTests: XCTestCase { let expectedStatement = "I further authorize the stated URI to perform the following actions on my behalf: (1) 'request': 'eth_sendTransaction', 'personal_sign' for 'eip155'." - let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrns: [urn]) + let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrn: urn) XCTAssertEqual(recapStatement, expectedStatement) } @@ -36,98 +36,11 @@ class RecapStatementBuilderTests: XCTestCase { let expectedStatement = "I further authorize the stated URI to perform the following actions on my behalf: (1) 'request': 'eth_sendTransaction', 'personal_sign' for 'eip155'." - let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrns: [urn]) + let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrn: urn) XCTAssertEqual(recapStatement, expectedStatement) } - func testMultipleRecaps() { - // First recap structure - let decodedRecap1: [String: [String: [String: [String]]]] = [ - "att": [ - "eip155": [ - "request/eth_sendTransaction": [], - "request/personal_sign": [] - ] - ] - ] - - // Second recap structure, as provided - let decodedRecap2: [String: [String: [String: [String]]]] = [ - "att": [ - "https://example.com/pictures/": [ - "crud/delete": [], - "crud/update": [], - "other/action": [] - ] - ] - ] - - // Encoding both recaps - let encoded1 = try! JSONEncoder().encode(decodedRecap1).base64EncodedString() - let encoded2 = try! JSONEncoder().encode(decodedRecap2).base64EncodedString() - - // Creating URNs - let urn1 = try! RecapUrn(urn: "urn:recap:\(encoded1)") - let urn2 = try! RecapUrn(urn: "urn:recap:\(encoded2)") - - // Expected statement combining both recaps - let expectedStatement = "I further authorize the stated URI to perform the following actions on my behalf: (1) 'request': 'eth_sendTransaction', 'personal_sign' for 'eip155'. (2) 'crud': 'delete', 'update' for 'https://example.com/pictures/'. (3) 'other': 'action' for 'https://example.com/pictures/'." - - // Generating the recap statement from both URNs - let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrns: [urn1, urn2]) - - // Asserting the generated statement against the expected statement - XCTAssertEqual(recapStatement, expectedStatement) - } - - func testRecapNotifyAndSign() throws { - let notifyRecapJson = """ - { - "att":{ - "https://notify.walletconnect.com/all-apps":{ - "crud/notifications": [{}], - "crud/subscriptions": [{}] - } - } - } - """ - - let signRecapJson = """ - { - "att":{ - "eip155":{ - "request/eth_sendTransaction": [{}], - "request/personal_sign": [{}] - } - } - } - """ - - // Correctly constructing Data from JSON strings - guard let notifyRecapData = notifyRecapJson.data(using: .utf8), - let signRecapData = signRecapJson.data(using: .utf8) else { - XCTFail("Failed to create Data from JSON strings") - return - } - - let encodedNotify = notifyRecapData.base64EncodedString() - let encodedSign = signRecapData.base64EncodedString() - - let urn1 = try RecapUrn(urn: "urn:recap:\(encodedSign)") - let urn2 = try RecapUrn(urn: "urn:recap:\(encodedNotify)") - - let expectedStatement = """ - I further authorize the stated URI to perform the following actions on my behalf: (1) 'request': 'eth_sendTransaction', 'personal_sign' for 'eip155'. (2) 'crud': 'notifications', 'subscriptions' for 'https://notify.walletconnect.com/all-apps'. - """ - - // Generating the recap statement from both URNs, with 'sign' recap first - let recapStatement = try RecapStatementBuilder.buildRecapStatement(recapUrns: [urn1, urn2]) - - // Asserting the generated statement against the expected statement - XCTAssertEqual(recapStatement, expectedStatement) - - } func testComplexRecap() { @@ -166,7 +79,7 @@ class RecapStatementBuilderTests: XCTestCase { let expectedStatement = "I further authorize the stated URI to perform the following actions on my behalf: (1) 'crud': 'delete', 'update' for 'https://example.com/pictures/'. (2) 'other': 'action' for 'https://example.com/pictures/'. (3) 'msg': 'receive', 'send' for 'mailto:username@example.com'." - let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrns: [urn]) + let recapStatement = try! RecapStatementBuilder.buildRecapStatement(recapUrn: urn) // Asserting the generated statement against the expected statement XCTAssertEqual(recapStatement, expectedStatement) @@ -183,7 +96,7 @@ class RecapStatementBuilderTests: XCTestCase { let urn = try! RecapUrn(urn: "urn:recap:\(encoded)") // Assert that building a statement with no actions throws an error - XCTAssertThrowsError(try RecapStatementBuilder.buildRecapStatement(recapUrns: [urn])) { error in + XCTAssertThrowsError(try RecapStatementBuilder.buildRecapStatement(recapUrn: urn)) { error in XCTAssertEqual(error as? RecapStatementBuilder.Errors, RecapStatementBuilder.Errors.noActionsAuthorized) } } diff --git a/Tests/WalletConnectUtilsTests/RecapUrnMergingServiceTests.swift b/Tests/WalletConnectUtilsTests/RecapUrnMergingServiceTests.swift new file mode 100644 index 000000000..719cf7a45 --- /dev/null +++ b/Tests/WalletConnectUtilsTests/RecapUrnMergingServiceTests.swift @@ -0,0 +1,67 @@ +import XCTest +@testable import WalletConnectUtils + +class RecapUrnMergingTests: XCTestCase { + func testMergeRecapUrns() throws { + // Encode your test data to Base64 + let notifyRecapJson = """ + { + "att":{ + "https://notify.walletconnect.com/all-apps":{ + "crud/notifications": [{}], + "crud/subscriptions": [{}] + } + } + } + """ + let signRecapJson = """ + { + "att":{ + "eip155":{ + "request/eth_sendTransaction": [{}], + "request/personal_sign": [{}] + } + } + } + """ + + guard let notifyBase64 = notifyRecapJson.data(using: .utf8)?.base64EncodedString(), + let signBase64 = signRecapJson.data(using: .utf8)?.base64EncodedString() else { + XCTFail("Failed to encode JSON strings to Base64") + return + } + + // Create URNs from Base64 encoded strings + let urn1 = try RecapUrn(urn: "urn:recap:\(notifyBase64)") + let urn2 = try RecapUrn(urn: "urn:recap:\(signBase64)") + + // Merge the URNs using your merging logic + let mergedRecap = try RecapUrnMergingService.merge(recapUrns: [urn1, urn2]) + + // Define the expected merged structure + let expectedMergeJson = """ + { + "att":{ + "https://notify.walletconnect.com/all-apps":{ + "crud/notifications": [{}], + "crud/subscriptions": [{}] + }, + "eip155":{ + "request/eth_sendTransaction": [{}], + "request/personal_sign": [{}] + } + } + } + """ + + // Convert expected JSON to `RecapData` + let expectedMergeData = expectedMergeJson.data(using: .utf8)! + let expectedMergeRecap = try! JSONDecoder().decode(RecapData.self, from: expectedMergeData) + + // Perform your assertions + XCTAssertEqual(mergedRecap.recapData.att?.count, expectedMergeRecap.att?.count) + for (key, value) in mergedRecap.recapData.att ?? [:] { + XCTAssertEqual(value.keys.sorted(), expectedMergeRecap.att?[key]?.keys.sorted()) + } + } +} diff --git a/Tests/WalletConnectUtilsTests/RecapUrnTests.swift b/Tests/WalletConnectUtilsTests/RecapUrnTests.swift index 08e098a3b..9d7b87a49 100644 --- a/Tests/WalletConnectUtilsTests/RecapUrnTests.swift +++ b/Tests/WalletConnectUtilsTests/RecapUrnTests.swift @@ -14,7 +14,7 @@ class RecapUrnTests: XCTestCase { let invalidPayloadUrn = "urn:recap:invalidPayload" XCTAssertThrowsError(try RecapUrn(urn: invalidPayloadUrn)) { error in - XCTAssertEqual(error as? RecapUrn.Errors, RecapUrn.Errors.invalidPayload) + XCTAssertEqual(error as? RecapUrn.Errors, RecapUrn.Errors.invalidJsonStructure) } }