diff --git a/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.pbxproj b/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.pbxproj index cc884da..919cd17 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.pbxproj +++ b/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.pbxproj @@ -624,7 +624,7 @@ repositoryURL = "https://github.com/web3auth/web3auth-swift-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e963060..519a51e 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-aggregate-verifier-example/ios-aggregate-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "originHash" : "428db49f2469ce532203871f53e396c14888e00beed7b452615bf9c4184f4fe7", "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift.git", + "state" : { + "revision" : "f604cef21bb7269f76136c68bd664c6c993f8e28", + "version" : "1.9.1" + } + }, { "identity" : "bigint", "kind" : "remoteSourceControl", @@ -19,6 +28,15 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, { "identity" : "generic-json-swift", "kind" : "remoteSourceControl", @@ -28,6 +46,24 @@ "version" : "2.0.2" } }, + { + "identity" : "jsonsafeencoding-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state" : { + "revision" : "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version" : "2.0.0" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -55,6 +91,15 @@ "version" : "6.1.0" } }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/sovran-swift.git", + "state" : { + "revision" : "24867f3e4ac62027db9827112135e6531b6f4051", + "version" : "1.1.2" + } + }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -136,6 +181,15 @@ "version" : "1.20.0" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "web3.swift", "kind" : "remoteSourceControl", @@ -150,8 +204,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/web3auth/web3auth-swift-sdk", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "58d147d2ccbc50add1ca7c55d5e3ff7f7dbf3e29", + "version" : "12.0.0" } }, { diff --git a/ios-aggregate-verifier-example/ios-aggregate-example/ContentView.swift b/ios-aggregate-verifier-example/ios-aggregate-example/ContentView.swift index 4e23288..ca75b83 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example/ContentView.swift +++ b/ios-aggregate-verifier-example/ios-aggregate-example/ContentView.swift @@ -16,15 +16,20 @@ struct ContentView: View { LoginView(vm: vm) } } + Spacer() } .navigationTitle(vm.navigationTitle) - Spacer() } .onAppear { + print("[ContentView] onAppear - loggedIn:", vm.loggedIn) Task { try await vm.setup() } } + .onChange(of: vm.loggedIn) { newValue in + print("[ContentView] vm.loggedIn changed:", newValue) + print("[ContentView] vm.user is nil:", vm.user == nil) + } } } diff --git a/ios-aggregate-verifier-example/ios-aggregate-example/LoginView.swift b/ios-aggregate-verifier-example/ios-aggregate-example/LoginView.swift index 25d4d1d..9bb9eca 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example/LoginView.swift +++ b/ios-aggregate-verifier-example/ios-aggregate-example/LoginView.swift @@ -29,3 +29,4 @@ struct LoginView_Previews: PreviewProvider { LoginView(vm: ViewModel()) } } + diff --git a/ios-aggregate-verifier-example/ios-aggregate-example/UserDetailView.swift b/ios-aggregate-verifier-example/ios-aggregate-example/UserDetailView.swift index ed43615..fe1f325 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example/UserDetailView.swift +++ b/ios-aggregate-verifier-example/ios-aggregate-example/UserDetailView.swift @@ -11,7 +11,7 @@ struct UserDetailView: View { if let user = viewModel.user { List { Section { - Text("\(user.privKey ?? "")") + Text("\(user.privateKey ?? "")") } header: { Text("Private key") } diff --git a/ios-aggregate-verifier-example/ios-aggregate-example/ViewModel.swift b/ios-aggregate-verifier-example/ios-aggregate-example/ViewModel.swift index fd0b071..a690606 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example/ViewModel.swift +++ b/ios-aggregate-verifier-example/ios-aggregate-example/ViewModel.swift @@ -1,14 +1,32 @@ import Foundation import Web3Auth +import web3 +import FetchNodeDetails class ViewModel: ObservableObject { - lazy var web3Auth: Web3Auth? = nil + var web3Auth: Web3Auth? @Published var loggedIn: Bool = false - @Published var user: Web3AuthState? + @Published var user: Web3AuthResponse? @Published var isLoading = false @Published var navigationTitle: String = "" - private var clientId = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" - private var network: Network = .sapphire_mainnet + @Published var privateKey: String = "" + @Published var ed25519PrivKey: String = "" + @Published var userInfo: Web3AuthUserInfo? + @Published var showError: Bool = false + var errorMessage: String = "" + + private var clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private var redirectUrl = "web3auth.ios-aggregate-example://auth" + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private var buildEnv: BuildEnv = .production + private var chainConfig: [Chains] = [ + Chains( + chainNamespace: .eip155, + chainId: "0x1", + rpcTarget: "https://mainnet.infura.io/v3/79921cf5a1f149f7af0a0fef80cf3363", + ticker: "ETH" + ) + ] func setup() async throws { guard web3Auth == nil else { return } @@ -16,34 +34,46 @@ class ViewModel: ObservableObject { isLoading = true navigationTitle = "Loading" }) - web3Auth = try await Web3Auth(W3AInitParams( - clientId: clientId, network: network, - redirectUrl: "web3auth.ios-aggregate-example://auth", - loginConfig: [ - TypeOfLogin.google.rawValue: - .init( - verifier: "aggregate-sapphire", - typeOfLogin: .google, - name: "Web3Auth-Aggregate-Verifier-Google-Example", - clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com", - verifierSubIdentifier: "w3a-google" - ), - TypeOfLogin.jwt.rawValue: - .init( - verifier: "aggregate-sapphire", - typeOfLogin: .jwt, - name: "Web3Auth-Aggregate-Verifier-GitHub-Example", - clientId: "hiLqaop0amgzCC0AXo4w0rrG9abuJTdu", - verifierSubIdentifier: "w3a-a0-github" - ) - ], - // 259200 allows user to stay authenticated for 3 days with Web3Auth. - // Default is 86400, which is 1 day. - sessionTime: 259200 + + var authConfig: [AuthConnectionConfig] = [] + + // Add Google configuration + authConfig.append( + AuthConnectionConfig( + authConnectionId: "w3a-google", + authConnection: .GOOGLE, + name: "Web3Auth-Aggregate-Verifier-Google-Example", + clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com", + groupedAuthConnectionId: "aggregate-sapphire" + ) + ) + + // Add GitHub configuration + authConfig.append( + AuthConnectionConfig( + authConnectionId: "w3a-a0-github", + authConnection: .CUSTOM, + name: "Web3Auth-Aggregate-Verifier-GitHub-Example", + clientId: "hiLqaop0amgzCC0AXo4w0rrG9abuJTdu", + groupedAuthConnectionId: "aggregate-sapphire" + ) + ) + + web3Auth = try await Web3Auth(options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + authConnectionConfig: authConfig, + sessionTime: 259200, // 3 days authentication period + web3AuthNetwork: web3AuthNetwork )) + await MainActor.run(body: { - if self.web3Auth?.state != nil { - user = web3Auth?.state + print("user: ", self.web3Auth?.web3AuthResponse) + print("conditional: ", self.web3Auth?.web3AuthResponse != nil) + if let existingUser = self.web3Auth?.web3AuthResponse { + user = existingUser + handleUserDetails() loggedIn = true } isLoading = false @@ -51,62 +81,160 @@ class ViewModel: ObservableObject { }) } + @MainActor func handleUserDetails() { + do { + loggedIn = true + privateKey = web3Auth?.getPrivateKey() ?? "" + ed25519PrivKey = try web3Auth?.getEd25519PrivateKey() ?? "" + userInfo = try web3Auth?.getUserInfo() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + func loginWithGoogle() { - Task{ + Task { do { - let result = try await web3Auth?.login( - W3ALoginParams( - loginProvider: .GOOGLE, - dappShare: nil, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: nil, isVerifierIdCaseSensitive: nil, additionalParams: nil) - )) - await MainActor.run(body: { - user = result - loggedIn = true - }) + let loginResult = try await web3Auth?.login( + loginParams: LoginParams( + authConnection: .GOOGLE, + authConnectionId: "w3a-google", + groupedAuthConnectionId: "aggregate-sapphire", + mfaLevel: .DEFAULT, + curve: .SECP256K1 + ) + ) + await MainActor.run { + user = loginResult + } + await handleUserDetails() } catch { - print("Error") + await MainActor.run { + errorMessage = error.localizedDescription + showError = true + } } } } func loginWithGitHub() { - Task{ + Task { do { - let result = try await web3Auth?.login( - W3ALoginParams( - loginProvider: .JWT, - dappShare: nil, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: "github", domain: "https://web3auth.au.auth0.com", client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: "email", isVerifierIdCaseSensitive: false, additionalParams: nil) - )) - await MainActor.run(body: { - user = result - loggedIn = true - }) + let loginResult = try await web3Auth?.login( + loginParams: LoginParams( + authConnection: .CUSTOM, + authConnectionId: "w3a-a0-github", + groupedAuthConnectionId: "aggregate-sapphire", + mfaLevel: .DEFAULT, + extraLoginOptions: ExtraLoginOptions( + connection: "github", + domain: "https://web3auth.au.auth0.com", + userIdField: "email", + isUserIdCaseSensitive: false + ), + curve: .SECP256K1 + ) + ) + await MainActor.run { + user = loginResult + } + await handleUserDetails() } catch { - print("Error") + await MainActor.run { + errorMessage = error.localizedDescription + showError = true + } } } } - func logout() async throws { - try await web3Auth?.logout() - await MainActor.run(body: { - loggedIn = false - }) + @MainActor func logout() { + Task { + do { + try await web3Auth?.logout() + loggedIn = false + user = nil + privateKey = "" + ed25519PrivKey = "" + userInfo = nil + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + } + + @MainActor func showWalletUI() { + Task { + do { + try await web3Auth?.showWalletUI() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + } + + @MainActor func enableMFA() { + Task { + do { + _ = try await web3Auth?.enableMFA() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + } + + @MainActor func manageMFA() { + Task { + do { + _ = try await web3Auth?.manageMFA() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + } + + @MainActor func request() { + Task { + do { + let key = self.web3Auth!.getPrivateKey() + let pk = try KeyUtil.generatePublicKey(from: Data(hexString: key) ?? Data()) + let pkAddress = KeyUtil.generateAddress(from: pk).asString() + let checksumAddress = EthereumAddress(pkAddress).toChecksumAddress() + + var params = [Any]() + params.append("Hello from Web3Auth!") + params.append(checksumAddress) + params.append("Web3Auth Aggregate Example") + + let signResponse = try await self.web3Auth?.request(method: "personal_sign", requestParams: params) + if let response = signResponse { + print("Sign response received: \(response)") + } else { + print("No sign response received.") + } + } catch { + errorMessage = error.localizedDescription + showError = true + } + } } } extension ViewModel { - func showResult(result: Web3AuthState) { + func showResult(result: Web3AuthResponse) { print(""" Signed in successfully! - Private key: \(result.privKey ?? "") - Ed25519 Private key: \(result.ed25519PrivKey ?? "") + Private key: \(result.privateKey ?? "") + Ed25519 Private key: \(result.ed25519PrivateKey ?? "") User info: Name: \(result.userInfo?.name ?? "") Profile image: \(result.userInfo?.profileImage ?? "N/A") - Type of login: \(result.userInfo?.typeOfLogin ?? "") + AuthConnection: \(result.userInfo?.authConnection ?? "") """) } } diff --git a/ios-aggregate-verifier-example/ios-aggregate-example/web3RPC.swift b/ios-aggregate-verifier-example/ios-aggregate-example/web3RPC.swift index a3ec066..c3ae69c 100644 --- a/ios-aggregate-verifier-example/ios-aggregate-example/web3RPC.swift +++ b/ios-aggregate-verifier-example/ios-aggregate-example/web3RPC.swift @@ -7,7 +7,7 @@ import Web3Auth import SwiftUI class Web3RPC : ObservableObject { - var user: Web3AuthState + var user: Web3AuthResponse private var client: EthereumClientProtocol public var address: EthereumAddress private var account: EthereumAccount @@ -20,7 +20,7 @@ class Web3RPC : ObservableObject { @Published var sentTransactionID:String = "" @Published var publicAddress: String = "" - init?(user: Web3AuthState){ + init?(user: Web3AuthResponse){ self.user = user do{ client = EthereumHttpClient(url: URL(string: RPC_URL)!, network: .sepolia) @@ -117,13 +117,13 @@ class Web3RPC : ObservableObject { } -extension Web3AuthState: EthereumSingleKeyStorageProtocol { +extension Web3AuthResponse: EthereumSingleKeyStorageProtocol { public func storePrivateKey(key: Data) throws { } public func loadPrivateKey() throws -> Data { - guard let privKeyData = self.privKey?.web3.hexData else { + guard let privKeyData = self.privateKey?.web3.hexData else { throw SampleAppError.somethingWentWrong } return privKeyData diff --git a/ios-aptos-example/ios-aptos-example.xcodeproj/project.pbxproj b/ios-aptos-example/ios-aptos-example.xcodeproj/project.pbxproj index f416ed7..2426f2f 100644 --- a/ios-aptos-example/ios-aptos-example.xcodeproj/project.pbxproj +++ b/ios-aptos-example/ios-aptos-example.xcodeproj/project.pbxproj @@ -666,7 +666,7 @@ repositoryURL = "https://github.com/Web3Auth/web3auth-swift-sdk/"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; A4DCEE6F2C8B36670048A663 /* XCRemoteSwiftPackageReference "aptos-swift-sdk" */ = { diff --git a/ios-aptos-example/ios-aptos-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-aptos-example/ios-aptos-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4f35286..ad0225c 100644 --- a/ios-aptos-example/ios-aptos-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-aptos-example/ios-aptos-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "originHash" : "6b85e04c538f006ce2b9ae9841cd84700094308248d004c8af2dfa2cd222c76f", "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift.git", + "state" : { + "revision" : "f604cef21bb7269f76136c68bd664c6c993f8e28", + "version" : "1.9.1" + } + }, { "identity" : "aptos-swift-sdk", "kind" : "remoteSourceControl", @@ -37,6 +46,33 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, + { + "identity" : "jsonsafeencoding-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state" : { + "revision" : "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version" : "2.0.0" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -73,6 +109,15 @@ "version" : "6.1.0" } }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/sovran-swift.git", + "state" : { + "revision" : "24867f3e4ac62027db9827112135e6531b6f4051", + "version" : "1.1.2" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -118,13 +163,22 @@ "version" : "1.0.2" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "web3auth-swift-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/Web3Auth/web3auth-swift-sdk/", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "58d147d2ccbc50add1ca7c55d5e3ff7f7dbf3e29", + "version" : "12.0.0" } } ], diff --git a/ios-aptos-example/ios-aptos-example/Helper/AptosHelper.swift b/ios-aptos-example/ios-aptos-example/Helper/AptosHelper.swift index 77de716..cd53bce 100644 --- a/ios-aptos-example/ios-aptos-example/Helper/AptosHelper.swift +++ b/ios-aptos-example/ios-aptos-example/Helper/AptosHelper.swift @@ -21,12 +21,12 @@ class AptosHelper { /// Initializes the Aptos client and account using the provided Web3Auth private key. /// - Parameter privateKey: The private key received from Web3Auth. func initialize(privateKey: String) async throws { - aptosClient = Aptos(aptosConfig: .testnet) + aptosClient = Aptos(aptosConfig: .devnet) if !privateKey.isEmpty { account = try generateAptosAccount(privateKey: privateKey) } - print("Aptos client and account initialized successfully.") + print("Aptos client and account initialized successfully on devnet.") } /// Generates an Aptos account using a Web3Auth private key. @@ -61,51 +61,55 @@ class AptosHelper { /// Fetches the balance of AptosCoin for the current account. /// - Returns: The account balance as a string. func getBalance() async throws -> String { - let payload = InputViewFunctionData( - function: "0x1::account::exists_at", - functionArguments: [ - account.accountAddress - ] - ) - - let exists: Bool = try await aptosClient.general.view(payload: payload)[0] - - // Return 0, if account doesn't exist - if !exists { - return "0" - } - - - let aptosCoinResource = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" - + // Use view function to get balance (most reliable method) do { - let resource: Coin = try await aptosClient.account.getAccountResource( - address: account.accountAddress, - resourceType: aptosCoinResource + let payload = InputViewFunctionData( + function: "0x1::coin::balance", + typeArguments: ["0x1::aptos_coin::AptosCoin"], + functionArguments: [account.accountAddress] ) - let atomicBalance = resource.coin.value - let balanceValue = formatBalanceToString(atomicBalanceString: atomicBalance) - print("Balance for account \(account.accountAddress.toString()): \(balanceValue)") - return balanceValue - } catch let error { - throw NSError(domain: "AptosHelper", code: 4, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch balance: \(error.localizedDescription)"]) + let result = try await aptosClient.general.view(payload: payload) + + if let balanceArray = result as? [Any], + let stringValue = balanceArray.first as? String, + let atomicBalance = UInt64(stringValue) { + return formatBalanceToString(atomicBalance: atomicBalance) + } + + return "0" + } catch { + print("Failed to fetch balance: \(error.localizedDescription)") + return "0" } } - /// Requests an airdrop of Aptos tokens from the testnet faucet and returns the transaction hash. + /// Requests an airdrop of Aptos tokens from the devnet faucet and returns the transaction hash. /// - Returns: A string representing the transaction hash of the airdrop request. func airdropFaucet() async throws -> String { - let userTransaction = try await aptosClient.faucet.fundAccount(accountAddress: account.accountAddress, amount: 100_000_000) - - if userTransaction.success { - print("Airdrop successful! Transaction hash: \(userTransaction.hash)") - } else { - throw NSError(domain: "AptosHelper", code: 5, userInfo: [NSLocalizedDescriptionKey: "Airdrop failed with status: \(userTransaction.vmStatus)"]) + let faucetAmount = 100_000_000 // 1 APT + print("Attempting to fund account: \(account.accountAddress.toString()) with amount: \(faucetAmount) (1 APT) on devnet") + + // SDK method + do { + let userTransaction = try await aptosClient.faucet.fundAccount(accountAddress: account.accountAddress, amount: faucetAmount) + + if userTransaction.success { + // Wait for the transaction to be confirmed on-chain + let confirmedTxn = try await aptosClient.transaction.waitForTransaction(transactionHash: userTransaction.hash) + + // Add delay to ensure balance is updated + try await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds + + return confirmedTxn.hash + } else { + throw NSError(domain: "AptosHelper", code: 5, userInfo: [NSLocalizedDescriptionKey: "Airdrop failed with status: \(userTransaction.vmStatus)"]) + } + } catch { + throw NSError(domain: "AptosHelper", code: 6, userInfo: [NSLocalizedDescriptionKey: "Devnet faucet request failed: \(error.localizedDescription)"]) } - return userTransaction.hash } - + /// Executes a self-transfer of AptosCoin within the same account. /// - Returns: The transaction hash of the self-transfer. func selfTransfer() async throws -> String { @@ -132,25 +136,34 @@ class AptosHelper { ) let txn = try await aptosClient.transaction.waitForTransaction(transactionHash: response.hash) + + // Add a small delay to ensure balance is updated + try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second return txn.hash } - /// Converts atomic balance string units to human-readable APT balance string. - /// - Parameter atomicBalanceString: The balance in atomic units as a string. + /// Converts atomic balance to human-readable APT balance string. + /// - Parameter atomicBalance: The balance in atomic units (octas). /// - Returns: A formatted string representing the balance in APT units. - private func formatBalanceToString(atomicBalanceString: String) -> String { - guard let atomicBalance = Int(atomicBalanceString) else { - return "Invalid balance" - } - + private func formatBalanceToString(atomicBalance: UInt64) -> String { let aptBalance = Double(atomicBalance) / 100_000_000.0 let numberFormatter = NumberFormatter() numberFormatter.numberStyle = .decimal - numberFormatter.maximumFractionDigits = 5 + numberFormatter.maximumFractionDigits = 6 numberFormatter.minimumFractionDigits = 2 return numberFormatter.string(from: NSNumber(value: aptBalance)) ?? "\(aptBalance)" } + + /// Converts atomic balance string units to human-readable APT balance string. + /// - Parameter atomicBalanceString: The balance in atomic units as a string. + /// - Returns: A formatted string representing the balance in APT units. + private func formatBalanceToString(atomicBalanceString: String) -> String { + guard let atomicBalance = UInt64(atomicBalanceString) else { + return "Invalid balance" + } + return formatBalanceToString(atomicBalance: atomicBalance) + } } diff --git a/ios-aptos-example/ios-aptos-example/Helper/Web3AuthHelper.swift b/ios-aptos-example/ios-aptos-example/Helper/Web3AuthHelper.swift index f02508d..d34064a 100644 --- a/ios-aptos-example/ios-aptos-example/Helper/Web3AuthHelper.swift +++ b/ios-aptos-example/ios-aptos-example/Helper/Web3AuthHelper.swift @@ -7,19 +7,26 @@ import Foundation import Web3Auth +import FetchNodeDetails class Web3AuthHelper { var web3Auth: Web3Auth! + private let clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private let redirectUrl = "com.w3a.ios-aptos-example://auth" + private let web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private let buildEnv: BuildEnv = .production + /// Initializes the Web3Auth client with the required parameters for the specific network. /// - Throws: An error if initialization fails. func initialize() async throws { web3Auth = try await Web3Auth( - W3AInitParams( - clientId: "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ", // Replace with your actual client ID - network: Network.sapphire_mainnet, // Change network based on your requirements - redirectUrl: "com.w3a.ios-aptos-example://auth" // Update the redirect URL as per your app's configuration + options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + web3AuthNetwork: web3AuthNetwork ) ) print("Web3Auth initialized successfully.") @@ -28,7 +35,7 @@ class Web3AuthHelper { /// Checks if the user is currently authenticated with Web3Auth. /// - Returns: A boolean indicating whether the user is authenticated. func isUserAuthenticated() -> Bool { - return web3Auth.state != nil + return web3Auth.web3AuthResponse != nil } /// Logs out the currently authenticated user. @@ -51,7 +58,7 @@ class Web3AuthHelper { /// - Throws: An error if the private key retrieval fails. /// - Returns: The private key as a hex string. func getAptosPrivateKey() throws -> String { - let privateKey = web3Auth.getEd25519PrivKey() + let privateKey = try web3Auth.getEd25519PrivateKey() print("Private key retrieved: \(privateKey)") return privateKey } @@ -60,10 +67,12 @@ class Web3AuthHelper { /// - Parameter email: The user's email for login. /// - Throws: An error if the login process fails. func login(email: String) async throws { - let _ = try await web3Auth.login( - W3ALoginParams( - loginProvider: Web3AuthProvider.EMAIL_PASSWORDLESS, // Using passwordless email login - extraLoginOptions: ExtraLoginOptions(login_hint: email) // Providing the email as a login hint + _ = try await web3Auth.login( + loginParams: LoginParams( + authConnection: .EMAIL_PASSWORDLESS, + mfaLevel: .DEFAULT, + extraLoginOptions: ExtraLoginOptions(login_hint: email), + curve: .ED25519 ) ) print("User logged in successfully with email: \(email)") diff --git a/ios-aptos-example/ios-aptos-example/ViewModels/MainViewModel.swift b/ios-aptos-example/ios-aptos-example/ViewModels/MainViewModel.swift index 8143e1a..b18b473 100644 --- a/ios-aptos-example/ios-aptos-example/ViewModels/MainViewModel.swift +++ b/ios-aptos-example/ios-aptos-example/ViewModels/MainViewModel.swift @@ -122,13 +122,14 @@ class MainViewModel: ObservableObject { func selfTransfer() { Task { do { - // Step 1: Initiate self-transfer + // Step 1: Initiate self-transfer (includes waiting for confirmation) let hash = try await aptosHelper.selfTransfer() print("Self-transfer successful with hash: \(hash)") - showAlert(content: "Self-transfer successful with hash: \(hash). Wait for a few seconds to reflect!") // Step 2: Reload balance after the transfer try await loadBalance() + + showAlert(content: "Self-transfer successful!\n\nTransaction: \(hash)\n\nYour balance has been updated.") } catch let error { showAlert(content: error.localizedDescription) } @@ -143,18 +144,20 @@ class MainViewModel: ObservableObject { } } - /// Requests an airdrop from the Aptos testnet faucet and updates the balance. + /// Requests an airdrop from the Aptos devnet faucet and updates the balance. func airdropFaucet() { Task { do { - // Step 1: Request airdrop + // Step 1: Request airdrop from devnet (includes waiting for confirmation) let txnHash = try await aptosHelper.airdropFaucet() - showAlert(content: "Airdropped 1 Aptos. Transaction hash: \(txnHash). Wait for a few seconds to reflect!") // Step 2: Reload balance after airdrop try await loadBalance() + + showAlert(content: "Successfully airdropped 1 APT!\n\nTransaction: \(txnHash)\n\nYour balance has been updated.") } catch let error { - showAlert(content: error.localizedDescription) + print("Error in airdropFaucet: \(error.localizedDescription)") + showAlert(content: "Devnet faucet error: \(error.localizedDescription)") } } } diff --git a/ios-aptos-example/ios-aptos-example/Views/HomeView.swift b/ios-aptos-example/ios-aptos-example/Views/HomeView.swift index 1eff59f..bfe8d09 100644 --- a/ios-aptos-example/ios-aptos-example/Views/HomeView.swift +++ b/ios-aptos-example/ios-aptos-example/Views/HomeView.swift @@ -17,7 +17,7 @@ struct HomeView: View { header: Text("Aptos Balance"), content: { Text(viewModel.balance) - Text("The sample uses Aptos testnet, you can request faucet from aptosfaucet.com.").font(.caption) + Text("This sample uses Aptos devnet. Tap 'Request Devnet Faucet' to get 1 APT for testing.").font(.caption) } ) @@ -46,13 +46,13 @@ struct HomeView: View { Text("Self transfer 0.0001 Aptos") }) - Text("The sample uses Aptos testnet, you can choose any Aptos network. Self transfer 0.0001 Aptos will perform self transfer of Aptos. You'll need to have testnet faucet to perform transaction.").font(.caption) + Text("The sample uses Aptos devnet. Self transfer will send 0.0001 APT to yourself. You'll need devnet tokens to perform transactions.").font(.caption) Button(action: { viewModel.airdropFaucet() }, label: { - Text("Request Testnet Faucet") + Text("Request Devnet Faucet") }) } diff --git a/ios-auth0-example/ios-auth0-example.xcodeproj/project.pbxproj b/ios-auth0-example/ios-auth0-example.xcodeproj/project.pbxproj index 59a13a6..ed31bc9 100644 --- a/ios-auth0-example/ios-auth0-example.xcodeproj/project.pbxproj +++ b/ios-auth0-example/ios-auth0-example.xcodeproj/project.pbxproj @@ -616,7 +616,7 @@ repositoryURL = "https://github.com/Web3Auth/web3auth-swift-sdk/"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; 2833BEE02BD6246900243B09 /* XCRemoteSwiftPackageReference "web3" */ = { diff --git a/ios-auth0-example/ios-auth0-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-auth0-example/ios-auth0-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e0cc04b..901ce50 100644 --- a/ios-auth0-example/ios-auth0-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-auth0-example/ios-auth0-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "originHash" : "7cbb23c8504bf7142e6a373dc1e1a829954d2e18d5df31faae1d2304660f8792", "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift.git", + "state" : { + "revision" : "f604cef21bb7269f76136c68bd664c6c993f8e28", + "version" : "1.9.1" + } + }, { "identity" : "bigint", "kind" : "remoteSourceControl", @@ -19,6 +28,15 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, { "identity" : "generic-json-swift", "kind" : "remoteSourceControl", @@ -28,6 +46,24 @@ "version" : "2.0.2" } }, + { + "identity" : "jsonsafeencoding-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state" : { + "revision" : "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version" : "2.0.0" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -55,6 +91,15 @@ "version" : "6.1.0" } }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/sovran-swift.git", + "state" : { + "revision" : "24867f3e4ac62027db9827112135e6531b6f4051", + "version" : "1.1.2" + } + }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -136,6 +181,15 @@ "version" : "1.20.0" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "web3.swift", "kind" : "remoteSourceControl", @@ -150,8 +204,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Web3Auth/web3auth-swift-sdk/", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "58d147d2ccbc50add1ca7c55d5e3ff7f7dbf3e29", + "version" : "12.0.0" } }, { diff --git a/ios-auth0-example/ios-auth0-example/UserDetailView.swift b/ios-auth0-example/ios-auth0-example/UserDetailView.swift index ed43615..a1e27ba 100644 --- a/ios-auth0-example/ios-auth0-example/UserDetailView.swift +++ b/ios-auth0-example/ios-auth0-example/UserDetailView.swift @@ -11,7 +11,7 @@ struct UserDetailView: View { if let user = viewModel.user { List { Section { - Text("\(user.privKey ?? "")") + Text("\(viewModel.privateKey)") } header: { Text("Private key") } @@ -33,8 +33,8 @@ struct UserDetailView: View { Text("Public key") } Section { - Text("Name \(user.userInfo?.name ?? "")") - Text("Email \(user.userInfo?.email ?? "")") + Text("Name \(viewModel.userInfo?.name ?? "")") + Text("Email \(viewModel.userInfo?.email ?? "")") } header: { Text("User Info") diff --git a/ios-auth0-example/ios-auth0-example/ViewModel.swift b/ios-auth0-example/ios-auth0-example/ViewModel.swift index fd56afe..1934b7e 100644 --- a/ios-auth0-example/ios-auth0-example/ViewModel.swift +++ b/ios-auth0-example/ios-auth0-example/ViewModel.swift @@ -1,103 +1,119 @@ import Foundation +import web3 import Web3Auth +import FetchNodeDetails class ViewModel: ObservableObject { var web3Auth: Web3Auth? @Published var loggedIn: Bool = false - @Published var user: Web3AuthState? + @Published var user: Web3AuthResponse? @Published var isLoading = false @Published var navigationTitle: String = "" - private var clientId = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" - private var network: Network = .sapphire_mainnet + @Published var privateKey: String = "" + @Published var ed25519PrivKey: String = "" + @Published var userInfo: Web3AuthUserInfo? + @Published var showError: Bool = false + var errorMessage: String = "" + private var clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private var redirectUrl: String = "web3auth.ios-auth0-example://auth" + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private var buildEnv: BuildEnv = .production func setup() async throws { guard web3Auth == nil else { return } await MainActor.run(body: { isLoading = true navigationTitle = "Loading" }) - web3Auth = try await Web3Auth(W3AInitParams( - clientId: clientId, network: network, - redirectUrl: "web3auth.ios-auth0-example://auth", - loginConfig: [ - TypeOfLogin.jwt.rawValue: - .init( - verifier: "w3a-auth0-demo", - typeOfLogin: .jwt, - clientId: "hUVVf4SEsZT7syOiL0gLU9hFEtm2gQ6O" - ) - ], - whiteLabel: W3AWhiteLabelData( - appName: "Web3Auth Stub", - logoLight: "https://images.web3auth.io/web3auth-logo-w.svg", - logoDark: "https://images.web3auth.io/web3auth-logo-w.svg", - defaultLanguage: .en, // en, de, ja, ko, zh, es, fr, pt, nl - mode: .dark, - theme: ["primary": "#d53f8c"]), - mfaSettings: MfaSettings( - deviceShareFactor: MfaSetting(enable: true, priority: 1), - backUpShareFactor: MfaSetting(enable: true, priority: 2), - socialBackupFactor: MfaSetting(enable: true, priority: 3), - passwordFactor: MfaSetting(enable: true, priority: 4), - passkeysFactor: MfaSetting(enable: true, priority: 5), - authenticatorFactor: MfaSetting(enable: true, priority: 6) - ), - // 259200 allows user to stay authenticated for 3 days with Web3Auth. - // Default is 86400, which is 1 day. - sessionTime: 259200 + + // Configure Auth0 connection + let authConfig = [ + AuthConnectionConfig( + authConnectionId: "w3a-auth0-demo", + authConnection: .CUSTOM, + clientId: "hUVVf4SEsZT7syOiL0gLU9hFEtm2gQ6O" + ) + ] + + web3Auth = try await Web3Auth(options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + authConnectionConfig: authConfig, + sessionTime: 259200, + web3AuthNetwork: web3AuthNetwork )) + await MainActor.run(body: { - if self.web3Auth?.state != nil { - user = web3Auth?.state + if self.web3Auth?.web3AuthResponse != nil { + handleUserDetails() loggedIn = true } isLoading = false navigationTitle = loggedIn ? "UserInfo" : "SignIn" }) } + + @MainActor func handleUserDetails() { + do { + loggedIn = true + user = try web3Auth?.getWeb3AuthResponse() + privateKey = web3Auth?.getPrivateKey() ?? "" + ed25519PrivKey = try web3Auth?.getEd25519PrivateKey() ?? "" + userInfo = try web3Auth?.getUserInfo() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } func loginWithAuth0() { Task{ do { - let result = try await web3Auth?.login( - W3ALoginParams( - loginProvider: .JWT, - dappShare: nil, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: "https://web3auth.au.auth0.com", client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: "sub", isVerifierIdCaseSensitive: nil, additionalParams: nil), - mfaLevel: .NONE, - curve: .SECP256K1 - )) - await MainActor.run(body: { - user = result - loggedIn = true - }) - + _ = try await web3Auth?.login(loginParams: LoginParams( + authConnection: .CUSTOM, + authConnectionId: "w3a-auth0-demo", + mfaLevel: .DEFAULT, + extraLoginOptions: ExtraLoginOptions( + display: nil, prompt: nil, max_age: nil, ui_locales: nil, + id_token_hint: nil, id_token: nil, login_hint: nil, + acr_values: nil, scope: nil, audience: nil, connection: nil, + domain: "https://web3auth.au.auth0.com", client_id: nil, + redirect_uri: nil, leeway: nil, userIdField: "sub", + isUserIdCaseSensitive: nil, additionalParams: nil + ), + curve: .SECP256K1 + )) + await handleUserDetails() } catch { - print("Error") + print("Error: \(error)") } } } - func logout() async throws { - try await web3Auth?.logout() - - await MainActor.run(body: { - loggedIn = false - }) - + @MainActor func logout() { + Task { + do { + try await web3Auth?.logout() + loggedIn = false + } catch { + errorMessage = error.localizedDescription + showError = true + } + } } } extension ViewModel { - func showResult(result: Web3AuthState) { + func showResult(result: Web3AuthResponse) { print(""" Signed in successfully! - Private key: \(result.privKey ?? "") - Ed25519 Private key: \(result.ed25519PrivKey ?? "") + Private key: \(result.privateKey ?? "") + Ed25519 Private key: \(result.ed25519PrivateKey ?? "") User info: Name: \(result.userInfo?.name ?? "") Profile image: \(result.userInfo?.profileImage ?? "N/A") - Type of login: \(result.userInfo?.typeOfLogin ?? "") + AuthConnection: \(result.userInfo?.authConnection ?? "") """) } } diff --git a/ios-auth0-example/ios-auth0-example/web3RPC.swift b/ios-auth0-example/ios-auth0-example/web3RPC.swift index a3ec066..1d53b21 100644 --- a/ios-auth0-example/ios-auth0-example/web3RPC.swift +++ b/ios-auth0-example/ios-auth0-example/web3RPC.swift @@ -7,7 +7,7 @@ import Web3Auth import SwiftUI class Web3RPC : ObservableObject { - var user: Web3AuthState + var user: Web3AuthResponse private var client: EthereumClientProtocol public var address: EthereumAddress private var account: EthereumAccount @@ -20,11 +20,12 @@ class Web3RPC : ObservableObject { @Published var sentTransactionID:String = "" @Published var publicAddress: String = "" - init?(user: Web3AuthState){ + init?(user: Web3AuthResponse){ self.user = user do{ client = EthereumHttpClient(url: URL(string: RPC_URL)!, network: .sepolia) - account = try EthereumAccount(keyStorage: user as EthereumSingleKeyStorageProtocol ) + let keyStorage = Web3AuthKeyStorage(response: user) + account = try EthereumAccount(keyStorage: keyStorage) address = account.address } catch { return nil @@ -58,16 +59,17 @@ class Web3RPC : ObservableObject { func getBalance() { - Task { - let blockChanged = await checkLatestBlockChanged() + Task { [weak self] in + guard let self = self else { return } + let blockChanged = await self.checkLatestBlockChanged() guard blockChanged == true else { return } - let _ = client.eth_getBalance(address: self.address, block: .Latest) { [unowned self] result in + let _ = self.client.eth_getBalance(address: self.address, block: .Latest) { [weak self] result in switch result { case .success(let weiValue): let balance = TorusWeb3Utils.toEther(wei: weiValue) // Access the value directly - DispatchQueue.main.async { [weak self] in + DispatchQueue.main.async { self?.balance = balance } case .failure(let error): @@ -117,20 +119,24 @@ class Web3RPC : ObservableObject { } -extension Web3AuthState: EthereumSingleKeyStorageProtocol { +// Wrapper to avoid extending imported types with imported protocols +class Web3AuthKeyStorage: EthereumSingleKeyStorageProtocol { + private let response: Web3AuthResponse + + init(response: Web3AuthResponse) { + self.response = response + } + public func storePrivateKey(key: Data) throws { - + // No-op for read-only storage } public func loadPrivateKey() throws -> Data { - guard let privKeyData = self.privKey?.web3.hexData else { + guard let privKeyData = response.privateKey?.web3.hexData else { throw SampleAppError.somethingWentWrong } return privKeyData - } - - } public enum SampleAppError:Error{ diff --git a/ios-firebase-example/ios-firebase-example.xcodeproj/project.pbxproj b/ios-firebase-example/ios-firebase-example.xcodeproj/project.pbxproj index 3dd2f99..773a13b 100644 --- a/ios-firebase-example/ios-firebase-example.xcodeproj/project.pbxproj +++ b/ios-firebase-example/ios-firebase-example.xcodeproj/project.pbxproj @@ -642,7 +642,7 @@ repositoryURL = "https://github.com/Web3Auth/web3auth-swift-sdk/"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/ios-firebase-example/ios-firebase-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-firebase-example/ios-firebase-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e8301ca..1422f03 100644 --- a/ios-firebase-example/ios-firebase-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-firebase-example/ios-firebase-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -37,6 +37,15 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", @@ -100,6 +109,15 @@ "version" : "2.3.0" } }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -253,6 +271,15 @@ "version" : "1.2.1" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "web3.swift", "kind" : "remoteSourceControl", @@ -267,8 +294,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Web3Auth/web3auth-swift-sdk/", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "branch" : "feat/auth_service_v10_changes", + "revision" : "c43ab8ab6e9c4051de371db819f1cc18b3d91746" } }, { diff --git a/ios-firebase-example/ios-firebase-example/LoginView.swift b/ios-firebase-example/ios-firebase-example/LoginView.swift index 85f0c7b..e556464 100644 --- a/ios-firebase-example/ios-firebase-example/LoginView.swift +++ b/ios-firebase-example/ios-firebase-example/LoginView.swift @@ -6,12 +6,13 @@ struct LoginView: View { List { Button( action: { - vm.loginViaFirebaseEP() + vm.loginWithGoogle() }, label: { - Text("Sign via Firebase") + Text("Continue with Google") } ) + .disabled(vm.isAuthenticating) } } diff --git a/ios-firebase-example/ios-firebase-example/UserDetailView.swift b/ios-firebase-example/ios-firebase-example/UserDetailView.swift index 497f4a0..16d6d2d 100644 --- a/ios-firebase-example/ios-firebase-example/UserDetailView.swift +++ b/ios-firebase-example/ios-firebase-example/UserDetailView.swift @@ -10,10 +10,10 @@ struct UserDetailView: View { @State private var signature = "" var body: some View { - if let user = viewModel.user { + if viewModel.loggedIn { List { Section { - Text("\(user.privKey ?? "")") + Text("\(viewModel.privateKey)") } header: { Text("Private key") } @@ -35,8 +35,8 @@ struct UserDetailView: View { Text("Public key") } Section { - Text("Name \(user.userInfo?.name ?? "")") - Text("Email \(user.userInfo?.email ?? "")") + Text("Name \(viewModel.userInfo?.name ?? "")") + Text("Email \(viewModel.userInfo?.email ?? "")") } header: { Text("User Info") @@ -89,7 +89,7 @@ struct UserDetailView: View { Button { viewModel.launchWalletServices() } label: { - Text("Launch Wallet Services") + Text("Show Wallet UI") } Button { @@ -99,16 +99,15 @@ struct UserDetailView: View { } Button { - viewModel.request{ - result in - self.signature = result - } + viewModel.manageMFA() } label: { - Text("Request Signature") + Text("Manage MFA") } - if(!signature.isEmpty) { - Text(signature) + Button { + viewModel.request() + } label: { + Text("Request Signature") } } @@ -116,15 +115,7 @@ struct UserDetailView: View { Section { Button { - Task.detached { - do { - try await viewModel.logout() - } catch { - DispatchQueue.main.async { - showingAlert = true - } - } - } + viewModel.logout() } label: { Text("Logout") .foregroundColor(.red) diff --git a/ios-firebase-example/ios-firebase-example/ViewModel.swift b/ios-firebase-example/ios-firebase-example/ViewModel.swift index 0c453c1..14904fd 100644 --- a/ios-firebase-example/ios-firebase-example/ViewModel.swift +++ b/ios-firebase-example/ios-firebase-example/ViewModel.swift @@ -1,17 +1,27 @@ import Foundation +import web3 import Web3Auth +import FetchNodeDetails import FirebaseCore import FirebaseAuth class ViewModel: ObservableObject { var web3Auth: Web3Auth? @Published var loggedIn: Bool = false - @Published var user: Web3AuthState? + @Published var user: Web3AuthResponse? @Published var isLoading = false + @Published var isAuthenticating = false @Published var navigationTitle: String = "" - private var clientId = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" - private var network: Network = .sapphire_mainnet - private var loginParams: W3ALoginParams! + @Published var privateKey: String = "" + @Published var ed25519PrivKey: String = "" + @Published var userInfo: Web3AuthUserInfo? + @Published var showError: Bool = false + var errorMessage: String = "" + private var clientID: String = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private var redirectUrl: String = "web3auth.ios-firebase-example://auth" + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private var buildEnv: BuildEnv = .production + private var loginParams: LoginParams? func setup() async throws { guard web3Auth == nil else { return } @@ -20,153 +30,209 @@ class ViewModel: ObservableObject { navigationTitle = "Loading" }) - web3Auth = try await Web3Auth(W3AInitParams( - clientId: clientId, - network: network, - redirectUrl: "web3auth.ios-firebase-example://auth", - loginConfig: [ - TypeOfLogin.jwt.rawValue: - .init( - verifier: "w3a-firebase-demo", - typeOfLogin: .jwt, - clientId: self.clientId - ) - ], - mfaSettings: MfaSettings( - deviceShareFactor: MfaSetting(enable: true, priority: 1), - backUpShareFactor: MfaSetting(enable: true, priority: 2), - socialBackupFactor: MfaSetting(enable: true, priority: 3), - passwordFactor: MfaSetting(enable: true, priority: 4) - ), - // 259200 allows user to stay authenticated for 3 days with Web3Auth. - // Default is 86400, which is 1 day. - sessionTime: 259200 - - + // Configure Firebase connection + let authConfig = [ + AuthConnectionConfig( + authConnectionId: "w3a-firebase-demo", + authConnection: .CUSTOM, + clientId: clientID + ) + ] + + web3Auth = try await Web3Auth(options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + authConnectionConfig: authConfig, + sessionTime: 259200, + web3AuthNetwork: web3AuthNetwork )) - loginParams = try await prepareLoginParams() await MainActor.run(body: { - if self.web3Auth?.state != nil { - user = web3Auth?.state + if self.web3Auth?.web3AuthResponse != nil { + handleUserDetails() loggedIn = true } isLoading = false navigationTitle = loggedIn ? "UserInfo" : "SignIn" }) } + + @MainActor func handleUserDetails() { + do { + loggedIn = true + user = try web3Auth?.getWeb3AuthResponse() + privateKey = ((web3Auth?.getPrivateKey() != "") ? web3Auth?.getPrivateKey() : try web3Auth?.getWeb3AuthResponse().factorKey) ?? "" + ed25519PrivKey = web3Auth?.getEd25519PrivateKey() ?? "" + userInfo = try web3Auth?.getUserInfo() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } - func launchWalletServices() { + @MainActor func launchWalletServices() { Task { do { - try await web3Auth!.launchWalletServices( - chainConfig: ChainConfig( - chainId: "0xaa36a7", - rpcTarget: "https://eth-sepolia.public.blastapi.io" - ) - ) + try await web3Auth?.showWalletUI() } catch { - print(error.localizedDescription) + errorMessage = error.localizedDescription + showError = true } } } - func enableMFA() { + @MainActor func enableMFA() { Task { do { - loginParams = try await prepareLoginParams() - _ = try await self.web3Auth?.enableMFA(prepareLoginParams()) - + _ = try await self.web3Auth?.enableMFA() } catch { - print(error.localizedDescription) + errorMessage = error.localizedDescription + showError = true } } } - func request(signature: @escaping(String) -> ()) { + @MainActor func manageMFA() { Task { do { + _ = try await self.web3Auth?.manageMFA() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } + } + + @MainActor func request() { + Task { + do { + let key = self.web3Auth!.getPrivateKey() + let pk = try KeyUtil.generatePublicKey(from: Data(hexString: key) ?? Data()) + let pkAddress = KeyUtil.generateAddress(from: pk).asString() + let checksumAddress = EthereumAddress(pkAddress).toChecksumAddress() var params = [Any]() - let address: String? = Web3RPC( - user: web3Auth!.state! - )?.address.toChecksumAddress() params.append("Hello, Web3Auth from iOS!") - params.append( - address! - ) - + params.append(checksumAddress) params.append("Web3Auth") - - let result = try await self.web3Auth?.request( - chainConfig: ChainConfig( - chainId: "0x89", - rpcTarget: "https://polygon.llamarpc.com" - ), - method: "personal_sign", - requestParams: params - ) - - if result!.success { - signature(result!.result!) + let signResponse = try await self.web3Auth?.request(method: "personal_sign", requestParams: params) + if let response = signResponse { + print("Sign response received: \(response)") } else { - signature(result!.error!) + print("No sign response received.") } } catch { - print(error.localizedDescription) + errorMessage = error.localizedDescription + showError = true } } } func loginViaFirebaseEP() { + // Prevent concurrent logins which can cause continuation misuse + guard !isAuthenticating else { return } + isAuthenticating = true Task{ do { - let res = try await Auth.auth().signIn(withEmail: "custom+id_token@firebase.login", password: "Welcome@W3A") - self.loginParams = try await prepareLoginParams() - let result = try await web3Auth?.login(loginParams) - await MainActor.run(body: { - user = result - loggedIn = true - }) + // Firebase sign-in to obtain fresh ID token + _ = try await Auth.auth().signIn(withEmail: "custom+id_token@firebase.login", password: "Welcome@W3A") + + // Build fresh login params per invocation + let params = try await prepareLoginParams() + + // Ensure Web3Auth is initialized + guard let web3Auth = web3Auth else { + throw NSError(domain: "Web3Auth", code: -1, userInfo: [NSLocalizedDescriptionKey: "Web3Auth not initialized"]) + } + + _ = try await web3Auth.login(loginParams: params) + await handleUserDetails() } catch let error { print("Error: ", error) + await MainActor.run { + self.errorMessage = error.localizedDescription + self.showError = true + } + } + await MainActor.run { + self.isAuthenticating = false + } + } + } + + func loginWithGoogle() { + // Prevent concurrent logins + guard !isAuthenticating else { return } + isAuthenticating = true + Task { + do { + // Ensure Web3Auth is initialized + guard let web3Auth = web3Auth else { + throw NSError(domain: "Web3Auth", code: -1, userInfo: [NSLocalizedDescriptionKey: "Web3Auth not initialized"]) + } + // Direct Google login via Web3Auth + _ = try await web3Auth.login(loginParams: LoginParams( + authConnection: .GOOGLE, + mfaLevel: .DEFAULT, + curve: .SECP256K1 + )) + await handleUserDetails() + } catch { + await MainActor.run { + self.errorMessage = error.localizedDescription + self.showError = true + } + } + await MainActor.run { + self.isAuthenticating = false } } } - private func prepareLoginParams() async throws -> W3ALoginParams { + private func prepareLoginParams() async throws -> LoginParams { let idToken = try await Auth.auth().currentUser?.getIDTokenResult(forcingRefresh: true) - return W3ALoginParams( - loginProvider: .JWT, - dappShare: nil, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: idToken?.token, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: "sub", isVerifierIdCaseSensitive: nil, additionalParams: nil), - mfaLevel: .NONE, + return LoginParams( + authConnection: .CUSTOM, + authConnectionId: "w3a-firebase-demo", + mfaLevel: .DEFAULT, + extraLoginOptions: ExtraLoginOptions( + display: nil, prompt: nil, max_age: nil, ui_locales: nil, + id_token_hint: nil, id_token: idToken?.token, login_hint: nil, + acr_values: nil, scope: nil, audience: nil, connection: nil, + domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, + userIdField: "sub", isUserIdCaseSensitive: nil, additionalParams: nil + ), curve: .SECP256K1 ) } - func logout() async throws { - try await web3Auth?.logout() - - await MainActor.run(body: { - loggedIn.toggle() - }) + @MainActor func logout() { + Task { + do { + try await web3Auth?.logout() + loggedIn = false + } catch { + errorMessage = error.localizedDescription + showError = true + } + } } } extension ViewModel { - func showResult(result: Web3AuthState) { + func showResult(result: Web3AuthResponse) { print(""" Signed in successfully! - Private key: \(result.privKey ?? "") - Ed25519 Private key: \(result.ed25519PrivKey ?? "") + Private key: \(result.privateKey ?? "") + Ed25519 Private key: \(result.ed25519PrivateKey ?? "") User info: Name: \(result.userInfo?.name ?? "") Profile image: \(result.userInfo?.profileImage ?? "N/A") - Type of login: \(result.userInfo?.typeOfLogin ?? "") + AuthConnection: \(result.userInfo?.authConnection ?? "") """) } } diff --git a/ios-firebase-example/ios-firebase-example/web3RPC.swift b/ios-firebase-example/ios-firebase-example/web3RPC.swift index a3ec066..4533761 100644 --- a/ios-firebase-example/ios-firebase-example/web3RPC.swift +++ b/ios-firebase-example/ios-firebase-example/web3RPC.swift @@ -7,7 +7,7 @@ import Web3Auth import SwiftUI class Web3RPC : ObservableObject { - var user: Web3AuthState + var user: Web3AuthResponse private var client: EthereumClientProtocol public var address: EthereumAddress private var account: EthereumAccount @@ -20,11 +20,12 @@ class Web3RPC : ObservableObject { @Published var sentTransactionID:String = "" @Published var publicAddress: String = "" - init?(user: Web3AuthState){ + init?(user: Web3AuthResponse){ self.user = user do{ client = EthereumHttpClient(url: URL(string: RPC_URL)!, network: .sepolia) - account = try EthereumAccount(keyStorage: user as EthereumSingleKeyStorageProtocol ) + let keyStorage = Web3AuthKeyStorage(response: user) + account = try EthereumAccount(keyStorage: keyStorage) address = account.address } catch { return nil @@ -117,20 +118,24 @@ class Web3RPC : ObservableObject { } -extension Web3AuthState: EthereumSingleKeyStorageProtocol { +// Wrapper to avoid extending imported types with imported protocols +class Web3AuthKeyStorage: EthereumSingleKeyStorageProtocol { + private let response: Web3AuthResponse + + init(response: Web3AuthResponse) { + self.response = response + } + public func storePrivateKey(key: Data) throws { - + // No-op for read-only storage } public func loadPrivateKey() throws -> Data { - guard let privKeyData = self.privKey?.web3.hexData else { + guard let privKeyData = response.privateKey?.web3.hexData else { throw SampleAppError.somethingWentWrong } return privKeyData - } - - } public enum SampleAppError:Error{ diff --git a/ios-playground/ios-playground.xcodeproj/project.pbxproj b/ios-playground/ios-playground.xcodeproj/project.pbxproj index 5cb4ef0..a2e25c6 100644 --- a/ios-playground/ios-playground.xcodeproj/project.pbxproj +++ b/ios-playground/ios-playground.xcodeproj/project.pbxproj @@ -687,7 +687,7 @@ repositoryURL = "https://github.com/Web3Auth/web3auth-swift-sdk/"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; 2888565B2BDA5EDC0087B92C /* XCRemoteSwiftPackageReference "web3" */ = { diff --git a/ios-playground/ios-playground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-playground/ios-playground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2acf78f..63525c8 100644 --- a/ios-playground/ios-playground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-playground/ios-playground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "originHash" : "7cbb23c8504bf7142e6a373dc1e1a829954d2e18d5df31faae1d2304660f8792", "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift.git", + "state" : { + "revision" : "f604cef21bb7269f76136c68bd664c6c993f8e28", + "version" : "1.9.1" + } + }, { "identity" : "bigint", "kind" : "remoteSourceControl", @@ -19,6 +28,15 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, { "identity" : "generic-json-swift", "kind" : "remoteSourceControl", @@ -28,6 +46,24 @@ "version" : "2.0.2" } }, + { + "identity" : "jsonsafeencoding-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state" : { + "revision" : "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version" : "2.0.0" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -55,6 +91,15 @@ "version" : "6.1.0" } }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/sovran-swift.git", + "state" : { + "revision" : "24867f3e4ac62027db9827112135e6531b6f4051", + "version" : "1.1.2" + } + }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -145,6 +190,15 @@ "version" : "1.2.1" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "web3.swift", "kind" : "remoteSourceControl", @@ -159,8 +213,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Web3Auth/web3auth-swift-sdk/", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "58d147d2ccbc50add1ca7c55d5e3ff7f7dbf3e29", + "version" : "12.0.0" } }, { diff --git a/ios-playground/ios-playground/Helpers/EthereumHelper.swift b/ios-playground/ios-playground/Helpers/EthereumHelper.swift index 8884a40..01a2deb 100644 --- a/ios-playground/ios-playground/Helpers/EthereumHelper.swift +++ b/ios-playground/ios-playground/Helpers/EthereumHelper.swift @@ -15,8 +15,9 @@ class EthereumHelper { private var client: EthereumHttpClient! private var chainId: Int! - func setUp(web3AuthState: Web3AuthState, rpcUrl: String, chainId: Int) throws { - self.ethereumAccount = try EthereumAccount.init(keyStorage: web3AuthState as EthereumSingleKeyStorageProtocol) + func setUp(web3AuthResponse: Web3AuthResponse, rpcUrl: String, chainId: Int) throws { + let keyStorage = Web3AuthKeyStorage(response: web3AuthResponse) + self.ethereumAccount = try EthereumAccount.init(keyStorage: keyStorage) self.client = EthereumHttpClient(url: URL.init(string: rpcUrl)!, network: .custom(chainId.description)) self.chainId = chainId } @@ -134,16 +135,22 @@ class EthereumHelper { } -extension Web3AuthState: EthereumSingleKeyStorageProtocol { +// Wrapper class to conform Web3AuthResponse to EthereumSingleKeyStorageProtocol +class Web3AuthKeyStorage: EthereumSingleKeyStorageProtocol { + private let response: Web3AuthResponse + + init(response: Web3AuthResponse) { + self.response = response + } + public func storePrivateKey(key: Data) throws { - + // Not implemented - Web3Auth manages keys } public func loadPrivateKey() throws -> Data { - guard let data = self.privKey?.web3.hexData else { + guard let privKeyData = response.privateKey?.web3.hexData else { throw PlaygroundError.decodingError } - - return data + return privKeyData } } diff --git a/ios-playground/ios-playground/Helpers/Web3AuthHelper.swift b/ios-playground/ios-playground/Helpers/Web3AuthHelper.swift index 2e91141..f41f37d 100644 --- a/ios-playground/ios-playground/Helpers/Web3AuthHelper.swift +++ b/ios-playground/ios-playground/Helpers/Web3AuthHelper.swift @@ -7,23 +7,30 @@ import Foundation import Web3Auth +import FetchNodeDetails class Web3AuthHelper { var web3Auth: Web3Auth! + private let clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private let redirectUrl = "com.w3a.ios-playground://auth" + private let web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private let buildEnv: BuildEnv = .production + func initialize() async throws { web3Auth = try await Web3Auth( - W3AInitParams( - clientId: "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ", - network: Network.sapphire_mainnet, - redirectUrl: "com.w3a.ios-playground://auth" + options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + web3AuthNetwork: web3AuthNetwork ) ) } func isUserAuthenticated() -> Bool { - return web3Auth.state != nil + return web3Auth.web3AuthResponse != nil } func logOut() async throws { @@ -34,18 +41,24 @@ class Web3AuthHelper { return try web3Auth.getUserInfo() } + func getPrivateKey() -> String { + return web3Auth.getPrivateKey() + } + func getSolanaPrivateKey() throws -> String { - return web3Auth.getEd25519PrivKey() + return try web3Auth.getEd25519PrivateKey() + } + + func getWeb3AuthResponse() -> Web3AuthResponse? { + return web3Auth.web3AuthResponse } func login(email: String) async throws { - let _ = try await web3Auth.login( - W3ALoginParams( - loginProvider: Web3AuthProvider.EMAIL_PASSWORDLESS, + _ = try await web3Auth.login( + loginParams: LoginParams( + authConnection: .EMAIL_PASSWORDLESS, extraLoginOptions: ExtraLoginOptions(login_hint: email) ) ) - - return } } diff --git a/ios-playground/ios-playground/Models/MainViewModel.swift b/ios-playground/ios-playground/Models/MainViewModel.swift index 057f636..faa76c1 100644 --- a/ios-playground/ios-playground/Models/MainViewModel.swift +++ b/ios-playground/ios-playground/Models/MainViewModel.swift @@ -106,8 +106,11 @@ class MainViewModel: ObservableObject { private func prepareEthereumHelper() throws { self.ethereumHelper = EthereumHelper() + guard let web3AuthResponse = web3AuthHelper.getWeb3AuthResponse() else { + throw PlaygroundError.customErr("Web3Auth response not available") + } try self.ethereumHelper.setUp( - web3AuthState: web3AuthHelper.web3Auth!.state!, + web3AuthResponse: web3AuthResponse, rpcUrl: selectedChainConfig.rpcTarget, chainId: Int(selectedChainConfig.chainId)! ) diff --git a/ios-quick-start/ios-example.xcodeproj/project.pbxproj b/ios-quick-start/ios-example.xcodeproj/project.pbxproj index b9b38ec..530e643 100644 --- a/ios-quick-start/ios-example.xcodeproj/project.pbxproj +++ b/ios-quick-start/ios-example.xcodeproj/project.pbxproj @@ -616,7 +616,7 @@ repositoryURL = "https://github.com/web3auth/web3auth-swift-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/ios-quick-start/ios-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-quick-start/ios-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index aa7dd4a..eb1842b 100644 --- a/ios-quick-start/ios-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-quick-start/ios-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,15 +10,6 @@ "version" : "5.3.0" } }, - { - "identity" : "curvelib.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/tkey/curvelib.swift", - "state" : { - "revision" : "432bf1abe7ff505fc2ac9fcf697341ff5b2dc6d0", - "version" : "2.0.0" - } - }, { "identity" : "generic-json-swift", "kind" : "remoteSourceControl", @@ -28,15 +19,6 @@ "version" : "2.0.2" } }, - { - "identity" : "keychain-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/evgenyneu/keychain-swift.git", - "state" : { - "revision" : "d108a1fa6189e661f91560548ef48651ed8d93b9", - "version" : "20.0.0" - } - }, { "identity" : "secp256k1.swift", "kind" : "remoteSourceControl", @@ -46,15 +28,6 @@ "version" : "0.16.0" } }, - { - "identity" : "session-manager-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Web3Auth/session-manager-swift.git", - "state" : { - "revision" : "de18a6b3d98f67b3c77f84ebab73f7a51807a503", - "version" : "6.1.0" - } - }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -159,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/web3auth/web3auth-swift-sdk", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "4658a598c342f9113ba92dbd9140c62c5c2e32ce", + "version" : "1.0.0" } }, { diff --git a/ios-quick-start/ios-example/ContentView.swift b/ios-quick-start/ios-example/ContentView.swift index 253d4a3..99fc98a 100644 --- a/ios-quick-start/ios-example/ContentView.swift +++ b/ios-quick-start/ios-example/ContentView.swift @@ -21,7 +21,7 @@ struct ContentView: View { if vm.isLoading { ProgressView() } else { - if vm.loggedIn,let user = vm.user, let web3rpc = Web3RPC(user: user) { + if vm.loggedIn, let user = vm.user, let web3rpc = Web3RPC(user: user) { UserDetailView( web3RPC: web3rpc, viewModel: vm @@ -36,7 +36,11 @@ struct ContentView: View { } .onAppear { Task { - await vm.setup() + do { + try await vm.setup() + } catch { + print("Setup error: \(error)") + } } } } diff --git a/ios-quick-start/ios-example/LoginView.swift b/ios-quick-start/ios-example/LoginView.swift index bba2c56..b157649 100644 --- a/ios-quick-start/ios-example/LoginView.swift +++ b/ios-quick-start/ios-example/LoginView.swift @@ -23,7 +23,7 @@ struct LoginView: View { Button( action: { - vm.loginEmailPasswordless(provider: .EMAIL_PASSWORDLESS, email: emailInput) + vm.loginEmailPasswordless(email: emailInput) }, label: { Text("Sign In with Email Passwordless") @@ -54,7 +54,7 @@ struct LoginView: View { Button( action: { - vm.login(provider: .GOOGLE) + vm.login(authConnection: .GOOGLE) }, label: { Text("Sign In with Google") @@ -70,7 +70,7 @@ struct LoginView: View { Button( action: { - vm.login(provider: .APPLE) + vm.login(authConnection: .APPLE) }, label: { Text("Sign In with Apple") diff --git a/ios-quick-start/ios-example/UserDetailView.swift b/ios-quick-start/ios-example/UserDetailView.swift index c03dd96..de6cd93 100644 --- a/ios-quick-start/ios-example/UserDetailView.swift +++ b/ios-quick-start/ios-example/UserDetailView.swift @@ -8,12 +8,12 @@ struct UserDetailView: View { @StateObject var viewModel: ViewModel var body: some View { - if let user = viewModel.user { + if viewModel.loggedIn { List { // IMP START - Get User Info Section(header: Text("User Information")) { - Text("Name: \(user.userInfo?.name ?? "")") - Text("Email: \(user.userInfo?.email ?? "")") + Text("Name: \(viewModel.userInfo?.name ?? "")") + Text("Email: \(viewModel.userInfo?.email ?? "")") } // IMP END - Get User Info @@ -64,23 +64,13 @@ struct UserDetailView: View { } if isPrivateKeySectionVisible { Section(header: Text("Private Key")) { - Text("\(user.privKey ?? "")") + Text("\(viewModel.privateKey)") } } Section { Button { - Task.detached { - do { - - try await viewModel.logout() - - } catch { - DispatchQueue.main.async { - showingAlert = true - } - } - } + viewModel.logout() } label: { Label("Logout", systemImage: "arrow.left.square.fill") .foregroundColor(.red) diff --git a/ios-quick-start/ios-example/ViewModel.swift b/ios-quick-start/ios-example/ViewModel.swift index 215fd7a..2a27bde 100644 --- a/ios-quick-start/ios-example/ViewModel.swift +++ b/ios-quick-start/ios-example/ViewModel.swift @@ -1,21 +1,30 @@ import Foundation +import web3 // IMP START - Quick Start import Web3Auth +import FetchNodeDetails // IMP END - Quick Start class ViewModel: ObservableObject { var web3Auth: Web3Auth? @Published var loggedIn: Bool = false - @Published var user: Web3AuthState? + @Published var user: Web3AuthResponse? @Published var isLoading = false @Published var navigationTitle: String = "" + @Published var privateKey: String = "" + @Published var ed25519PrivKey: String = "" + @Published var userInfo: Web3AuthUserInfo? + @Published var showError: Bool = false + var errorMessage: String = "" // IMP START - Get your Web3Auth Client ID from Dashboard - private var clientId = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private var clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" // IMP END - Get your Web3Auth Client ID from Dashboard // IMP START - Whitelist bundle ID - private var network: Network = .sapphire_mainnet + private var redirectUrl: String = "web3auth.ios-example://auth" + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private var buildEnv: BuildEnv = .production // IMP END - Whitelist bundle ID - func setup() async { + func setup() async throws { guard web3Auth == nil else { return } await MainActor.run(body: { isLoading = true @@ -23,68 +32,85 @@ class ViewModel: ObservableObject { }) // IMP START - Initialize Web3Auth - do { - web3Auth = try await Web3Auth(W3AInitParams( - clientId: clientId, - network: network, - redirectUrl: "web3auth.ios-example://auth" - )) - } catch { - print("Something went wrong") - } + web3Auth = try await Web3Auth(options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + web3AuthNetwork: web3AuthNetwork + )) // IMP END - Initialize Web3Auth await MainActor.run(body: { - if self.web3Auth?.state != nil { - user = web3Auth?.state + if self.web3Auth?.web3AuthResponse != nil { + handleUserDetails() loggedIn = true } isLoading = false navigationTitle = loggedIn ? "UserInfo" : "SignIn" }) } + + @MainActor func handleUserDetails() { + do { + loggedIn = true + user = try web3Auth?.getWeb3AuthResponse() + privateKey = web3Auth?.getPrivateKey() ?? "" + ed25519PrivKey = try web3Auth?.getEd25519PrivateKey() ?? "" + userInfo = try web3Auth?.getUserInfo() + } catch { + errorMessage = error.localizedDescription + showError = true + } + } - func login(provider: Web3AuthProvider) { + func login(authConnection: AuthConnection) { Task { do { // IMP START - Login - let result = try await web3Auth?.login( - W3ALoginParams(loginProvider: provider) - ) + _ = try await web3Auth?.login(loginParams: LoginParams( + authConnection: authConnection, + mfaLevel: .DEFAULT, + curve: .SECP256K1 + )) // IMP END - Login - await MainActor.run(body: { - user = result - loggedIn = true - }) - + await handleUserDetails() } catch { print("Error") } } } - func logout() throws { + @MainActor func logout() { Task { - // IMP START - Logout - try await web3Auth?.logout() - // IMP END - Logout - await MainActor.run(body: { + do { + // IMP START - Logout + try await web3Auth?.logout() + // IMP END - Logout loggedIn = false - }) + } catch { + errorMessage = error.localizedDescription + showError = true + } } } - func loginEmailPasswordless(provider: Web3AuthProvider, email: String) { + func loginEmailPasswordless(email: String) { Task { do { // IMP START - Login - let result = try await web3Auth?.login(W3ALoginParams(loginProvider: provider, extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: email, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: nil, isVerifierIdCaseSensitive: nil, additionalParams: nil))) + _ = try await web3Auth?.login(loginParams: LoginParams( + authConnection: .EMAIL_PASSWORDLESS, + mfaLevel: .DEFAULT, + extraLoginOptions: ExtraLoginOptions( + display: nil, prompt: nil, max_age: nil, ui_locales: nil, + id_token_hint: nil, id_token: nil, login_hint: email, + acr_values: nil, scope: nil, audience: nil, connection: nil, + domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, + userIdField: nil, isUserIdCaseSensitive: nil, additionalParams: nil + ), + curve: .SECP256K1 + )) // IMP END - Login - await MainActor.run(body: { - user = result - loggedIn = true - navigationTitle = "UserInfo" - }) - + await handleUserDetails() } catch { print("Error") } diff --git a/ios-quick-start/ios-example/web3RPC.swift b/ios-quick-start/ios-example/web3RPC.swift index 6d4c303..2863109 100644 --- a/ios-quick-start/ios-example/web3RPC.swift +++ b/ios-quick-start/ios-example/web3RPC.swift @@ -7,7 +7,7 @@ import Web3Auth import SwiftUI class Web3RPC : ObservableObject { - var user: Web3AuthState + var user: Web3AuthResponse private var client: EthereumClientProtocol public var address: EthereumAddress private var account: EthereumAccount @@ -20,7 +20,7 @@ class Web3RPC : ObservableObject { @Published var sentTransactionID:String = "" @Published var publicAddress: String = "" - init?(user: Web3AuthState){ + init?(user: Web3AuthResponse){ self.user = user do{ client = EthereumHttpClient(url: URL(string: RPC_URL)!, network: .fromString("11155111")) @@ -117,13 +117,13 @@ class Web3RPC : ObservableObject { } -extension Web3AuthState: EthereumSingleKeyStorageProtocol { +extension Web3AuthResponse: EthereumSingleKeyStorageProtocol { public func storePrivateKey(key: Data) throws { } public func loadPrivateKey() throws -> Data { - guard let privKeyData = self.privKey?.web3.hexData else { + guard let privKeyData = self.privateKey?.web3.hexData else { throw SampleAppError.somethingWentWrong } return privKeyData diff --git a/ios-solana-example/ios-solana-example.xcodeproj/project.pbxproj b/ios-solana-example/ios-solana-example.xcodeproj/project.pbxproj index 16d2165..66f8cf3 100644 --- a/ios-solana-example/ios-solana-example.xcodeproj/project.pbxproj +++ b/ios-solana-example/ios-solana-example.xcodeproj/project.pbxproj @@ -651,7 +651,7 @@ repositoryURL = "https://github.com/Web3Auth/web3auth-swift-sdk/"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.4; + minimumVersion = 12.0.0; }; }; 28C341872CB4017F00360382 /* XCRemoteSwiftPackageReference "solana-swift" */ = { diff --git a/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a4029df..29e0712 100644 --- a/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios-solana-example/ios-solana-example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,22 @@ { "originHash" : "edf43263484c458b2f2acf14cf0abd81b2aafca008f8a166f2055d3b10bbb47a", "pins" : [ + { + "identity" : "analytics-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/analytics-swift.git", + "state" : { + "revision" : "f604cef21bb7269f76136c68bd664c6c993f8e28", + "version" : "1.9.1" + } + }, { "identity" : "bigint", "kind" : "remoteSourceControl", "location" : "https://github.com/attaswift/BigInt.git", "state" : { - "revision" : "114343a705df4725dfe7ab8a2a326b8883cfd79c", - "version" : "5.5.1" + "revision" : "e07e00fa1fd435143a2dcf8b7eec9a7710b2fdfe", + "version" : "5.7.0" } }, { @@ -19,6 +28,33 @@ "version" : "2.0.0" } }, + { + "identity" : "fetch-node-details-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/fetch-node-details-swift", + "state" : { + "revision" : "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version" : "8.0.1" + } + }, + { + "identity" : "jsonsafeencoding-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state" : { + "revision" : "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version" : "2.0.0" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift.git", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, { "identity" : "keychain-swift", "kind" : "remoteSourceControl", @@ -55,6 +91,15 @@ "version" : "5.0.0" } }, + { + "identity" : "sovran-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/segmentio/sovran-swift.git", + "state" : { + "revision" : "24867f3e4ac62027db9827112135e6531b6f4051", + "version" : "1.1.2" + } + }, { "identity" : "task-retrying-swift", "kind" : "remoteSourceControl", @@ -64,6 +109,15 @@ "version" : "2.0.0" } }, + { + "identity" : "torus-utils-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/torusresearch/torus-utils-swift.git", + "state" : { + "revision" : "235a70839d3ff5723402df95d665b15b8c551ad8", + "version" : "10.0.1" + } + }, { "identity" : "tweetnacl-swiftwrap", "kind" : "remoteSourceControl", @@ -78,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Web3Auth/web3auth-swift-sdk/", "state" : { - "revision" : "35d05c0af33687dac1a35968f498658c8c1893c7", - "version" : "11.0.4" + "revision" : "58d147d2ccbc50add1ca7c55d5e3ff7f7dbf3e29", + "version" : "12.0.0" } } ], diff --git a/ios-solana-example/ios-solana-example/Helpers/Web3AuthHelper.swift b/ios-solana-example/ios-solana-example/Helpers/Web3AuthHelper.swift index 8faa762..f9aa39a 100644 --- a/ios-solana-example/ios-solana-example/Helpers/Web3AuthHelper.swift +++ b/ios-solana-example/ios-solana-example/Helpers/Web3AuthHelper.swift @@ -7,28 +7,28 @@ import Foundation import Web3Auth +import FetchNodeDetails class Web3AuthHelper { var web3Auth: Web3Auth! + private var clientID = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" + private var redirectUrl = "com.w3a.ios-solana-example://auth" + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET + private var buildEnv: BuildEnv = .production func initialize() async throws { - do { - web3Auth = try await Web3Auth( - W3AInitParams( - clientId: "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ", - network: Network.sapphire_mainnet, - redirectUrl: "com.w3a.ios-solana-example://auth" - ) - ) - } catch let error { - print(error.localizedDescription) - } - + web3Auth = try await Web3Auth(options: .init( + clientId: clientID, + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + web3AuthNetwork: web3AuthNetwork, + useSFAKey: false + )) } func isUserAuthenticated() -> Bool { - return web3Auth.state != nil + return web3Auth.web3AuthResponse != nil } func logOut() async throws { @@ -40,14 +40,14 @@ class Web3AuthHelper { } func getSolanaPrivateKey() throws -> String { - return web3Auth.getEd25519PrivKey() + return try web3Auth.getEd25519PrivateKey() } func login() async throws { - let _ = try await web3Auth.login(W3ALoginParams( - loginProvider: Web3AuthProvider.GOOGLE) - ) - - return + _ = try await web3Auth.login(loginParams: LoginParams( + authConnection: .GOOGLE, + mfaLevel: .DEFAULT, + curve: .ED25519 + )) } } diff --git a/ios-solana-example/ios-solana-example/ViewModels/SolanaViewModel.swift b/ios-solana-example/ios-solana-example/ViewModels/SolanaViewModel.swift index 1bd6ee8..97c4aa1 100644 --- a/ios-solana-example/ios-solana-example/ViewModels/SolanaViewModel.swift +++ b/ios-solana-example/ios-solana-example/ViewModels/SolanaViewModel.swift @@ -7,6 +7,7 @@ import Foundation import SolanaSwift +import CryptoKit class SolanaViewModel: ObservableObject { private var solanaJSONRPCClient: JSONRPCAPIClient! @@ -36,10 +37,9 @@ class SolanaViewModel: ObservableObject { } } catch let error { - print(error) + print("❌ [SolanaViewModel] Error during initialization: \(error.localizedDescription)") } } - } private func reloadBalance() { diff --git a/ios-solana-example/ios-solana-example/ViewModels/ViewModel.swift b/ios-solana-example/ios-solana-example/ViewModels/ViewModel.swift index e9c164b..cdf16bf 100644 --- a/ios-solana-example/ios-solana-example/ViewModels/ViewModel.swift +++ b/ios-solana-example/ios-solana-example/ViewModels/ViewModel.swift @@ -18,10 +18,18 @@ class ViewModel: ObservableObject { func initilize() { Task { - web3AuthHelper = Web3AuthHelper() - try await web3AuthHelper.initialize() - DispatchQueue.main.async { - self.isUserAuthenticated = self.web3AuthHelper.isUserAuthenticated() + do { + web3AuthHelper = Web3AuthHelper() + try await web3AuthHelper.initialize() + DispatchQueue.main.async { + self.isUserAuthenticated = self.web3AuthHelper.isUserAuthenticated() + } + } catch { + DispatchQueue.main.async { + self.isErrorAvailable = true + self.error = error.localizedDescription + } + print("Initialization error: \(error)") } } } diff --git a/ios-solana-example/ios-solana-example/Views/HomeView.swift b/ios-solana-example/ios-solana-example/Views/HomeView.swift index 8753cf4..1f19199 100644 --- a/ios-solana-example/ios-solana-example/Views/HomeView.swift +++ b/ios-solana-example/ios-solana-example/Views/HomeView.swift @@ -111,9 +111,13 @@ struct HomeView: View { }) } }.onAppear(perform: { - solanaViewModel.initialize( - privateKey: try! viewModel.getSolanaPrivateKey() + do { + solanaViewModel.initialize( + privateKey: try viewModel.getSolanaPrivateKey() ) + } catch { + print("❌ [HomeView] Failed to initialize Solana: \(error.localizedDescription)") + } }) } }