From 49ccff18096f817b1a9151c64c1649dd66e9f4be Mon Sep 17 00:00:00 2001 From: cat Date: Thu, 14 Nov 2024 09:20:13 +0800 Subject: [PATCH 1/9] feat: add event track --- FRW/App/AppDelegate.swift | 76 +++++--- FRW/Modules/Buy/BuyProvderView.swift | 16 +- .../EVM/ViewModel/EVMEnableViewModel.swift | 9 +- .../Manager/MultiBackupManager.swift | 17 +- .../ViewModel/BackupMultiViewModel.swift | 15 +- .../ViewModel/BackupUploadViewModel.swift | 28 +++ .../RestoreMultiAccountViewModel.swift | 6 + .../StakeAmount/StakeAmountViewModel.swift | 102 ++++++++--- .../CreateProfileWaitingViewModel.swift | 73 +++++--- FRW/Modules/Wallet/WalletHomeView.swift | 2 + FRW/Services/FlowCoin/CadenceManager.swift | 21 ++- .../Manager/Config/RemoteConfigManager.swift | 3 + FRW/Services/Manager/EVMAccountManager.swift | 116 +++++++++---- FRW/Services/Manager/TransactionManager.swift | 163 +++++++++++++----- FRW/Services/Manager/UserManager.swift | 9 +- FRW/Services/Track/EventTrack+Account.swift | 4 +- FRW/Services/Track/EventTrack+Backup.swift | 2 +- FRW/Services/Track/EventTrack+General.swift | 29 +++- FRW/Services/Track/EventTrack.swift | 94 ++++++++-- FRW/Services/Track/EventTrackName.swift | 1 + 20 files changed, 594 insertions(+), 192 deletions(-) diff --git a/FRW/App/AppDelegate.swift b/FRW/App/AppDelegate.swift index 7f2877dd..13b95a07 100644 --- a/FRW/App/AppDelegate.swift +++ b/FRW/App/AppDelegate.swift @@ -20,25 +20,30 @@ import WalletCore import Web3Wallet #if DEBUG - import Atlantis +import Atlantis #endif let log = FlowLog.shared +// MARK: - AppDelegate + @main class AppDelegate: NSObject, UIApplicationDelegate { - var window: UIWindow? - lazy var coordinator = Coordinator(window: window!) - static var isUnitTest: Bool { #if DEBUG - return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil + return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil #else - return false + return false #endif } - func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + var window: UIWindow? + lazy var coordinator = Coordinator(window: window!) + + func application( + _: UIApplication, + didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { _ = LocalEnvManager.shared FirebaseApp.configure() @@ -61,11 +66,13 @@ class AppDelegate: NSObject, UIApplicationDelegate { let migration = Migration() migration.start() #if DEBUG - Atlantis.start() + Atlantis.start() #endif - let crowdinProviderConfig = CrowdinProviderConfig(hashString: "f4bff0f0e2ed98c2ba53a29qzvm", - sourceLanguage: "en") + let crowdinProviderConfig = CrowdinProviderConfig( + hashString: "f4bff0f0e2ed98c2ba53a29qzvm", + sourceLanguage: "en" + ) let crowdinSDKConfig = CrowdinSDKConfig .config() .with(crowdinProviderConfig: crowdinProviderConfig) @@ -83,7 +90,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { return true } - func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + func application( + _: UIApplication, + open url: URL, + options _: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { var parameters: [String: String] = [:] URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.forEach { parameters[$0.name] = $0.value @@ -91,8 +102,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { if let filtered = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems? .filter({ $0.name == "uri" && $0.value?.starts(with: "wc") ?? false }), - let item = filtered.first, let uri = item.value - { + let item = filtered.first, let uri = item.value { WalletConnectManager.shared.onClientConnected = { WalletConnectManager.shared.connect(link: uri) } @@ -101,7 +111,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { return GIDSignIn.sharedInstance.handle(url) } - func application(_: UIApplication, continue userActivity: NSUserActivity, restorationHandler _: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + func application( + _: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler _: @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { if let url = userActivity.webpageURL { let uri = AppExternalLinks.exactWCLink(link: url.absoluteString) WalletConnectManager.shared.onClientConnected = { @@ -121,12 +135,15 @@ extension AppDelegate { let largeFont = UIFont(name: "Inter", size: 24)?.bold let color = UIColor(named: "neutrals.text")! UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: color, .font: font!] - UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: color, .font: largeFont!] + UINavigationBar.appearance().largeTitleTextAttributes = [ + .foregroundColor: color, + .font: largeFont!, + ] } private func appConfig() { MultiAccountStorage.shared.upgradeFromOldVersionIfNeeded() - + _ = CadenceManager.shared _ = UserManager.shared _ = WalletManager.shared _ = BackupManager.shared @@ -141,7 +158,7 @@ extension AppDelegate { _ = ChildAccountManager.shared WalletManager.shared.bindChildAccountManager() NFTCatalogCache.cache.fetchIfNeed() - _ = CadenceManager.shared + if UserManager.shared.isLoggedIn { DeviceManager.shared.updateDevice() } @@ -169,7 +186,12 @@ extension AppDelegate { extension AppDelegate { private func setupUI() { - NotificationCenter.default.addObserver(self, selector: #selector(handleNetworkChange), name: .networkChange, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleNetworkChange), + name: .networkChange, + object: nil + ) window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = UIColor.LL.Neutrals.background @@ -187,20 +209,26 @@ extension AppDelegate { delay(.seconds(5)) { UIView.animate(withDuration: 0.2) { - self.window?.backgroundColor = currentNetwork.isMainnet ? UIColor.LL.Neutrals.background : UIColor(currentNetwork.color) + self.window?.backgroundColor = currentNetwork.isMainnet ? UIColor.LL.Neutrals + .background : UIColor(currentNetwork.color) } } } - @objc func handleNetworkChange() { - window?.backgroundColor = currentNetwork.isMainnet ? UIColor.LL.Neutrals.background : UIColor(currentNetwork.color) + @objc + func handleNetworkChange() { + window?.backgroundColor = currentNetwork.isMainnet ? UIColor.LL.Neutrals + .background : UIColor(currentNetwork.color) } } // MARK: Delegate extension AppDelegate { - func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + func application( + _: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { Task(priority: .high) { let deviceTokenString = deviceToken.map { data in String(format: "%02.2hhx", data) } UserDefaults.standard.set(deviceTokenString.joined(), forKey: "deviceToken") @@ -213,9 +241,9 @@ extension AppDelegate { // } } #if DEBUG - Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox) + Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox) #else - Messaging.messaging().setAPNSToken(deviceToken, type: .prod) + Messaging.messaging().setAPNSToken(deviceToken, type: .prod) #endif } } diff --git a/FRW/Modules/Buy/BuyProvderView.swift b/FRW/Modules/Buy/BuyProvderView.swift index 1e809520..254071a1 100644 --- a/FRW/Modules/Buy/BuyProvderView.swift +++ b/FRW/Modules/Buy/BuyProvderView.swift @@ -7,6 +7,8 @@ import SwiftUI +// MARK: - BuyProvderView + struct BuyProvderView: View { var body: some View { VStack(alignment: .center, spacing: 15) { @@ -16,6 +18,7 @@ struct BuyProvderView: View { Divider() Button { + EventTrack.General.rampClick(source: "moonpay") Task { await launchMoonPay() } @@ -35,8 +38,12 @@ struct BuyProvderView: View { if LocalUserDefaults.shared.flowNetwork == .mainnet { Button { + EventTrack.General.rampClick(source: "coinbase") guard let address = WalletManager.shared.getPrimaryWalletAddress(), - let url = URL(string: "https://pay.coinbase.com/buy/input?appId=d22a56bd-68b7-4321-9b25-aa357fc7f9ce&destinationWallets=%5B%7B%22address%22%3A%22\(address)%22%2C%22blockchains%22%3A%5B%22flow%22%5D%7D%5D") + let url = + URL( + string: "https://pay.coinbase.com/buy/input?appId=d22a56bd-68b7-4321-9b25-aa357fc7f9ce&destinationWallets=%5B%7B%22address%22%3A%22\(address)%22%2C%22blockchains%22%3A%5B%22flow%22%5D%7D%5D" + ) else { return } @@ -72,7 +79,10 @@ struct BuyProvderView: View { HUD.loading() - let request = MoonPayRequest(url: "https://buy.moonpay.com?apiKey=pk_live_6YNhgtZH8nyxkJiQRZsotO69G2loIyv0&defaultCurrencyCode=FLOW&colorCode=%23FC814A&walletAddress=\(address)") + let request = + MoonPayRequest( + url: "https://buy.moonpay.com?apiKey=pk_live_6YNhgtZH8nyxkJiQRZsotO69G2loIyv0&defaultCurrencyCode=FLOW&colorCode=%23FC814A&walletAddress=\(address)" + ) do { let response: MoonPayResponse = try await Network.request(FirebaseAPI.moonPay(request)) @@ -87,6 +97,8 @@ struct BuyProvderView: View { } } +// MARK: - BuyProvderView_Previews + struct BuyProvderView_Previews: PreviewProvider { static var previews: some View { BuyProvderView() diff --git a/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift b/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift index cdb2fa39..496c2ad1 100644 --- a/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift +++ b/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift @@ -8,7 +8,9 @@ import SwiftUI class EVMEnableViewModel: ObservableObject { - @Published var state: VPrimaryButtonState = .enabled + @Published + var state: VPrimaryButtonState = .enabled + func onSkip() { Router.pop() } @@ -17,7 +19,10 @@ class EVMEnableViewModel: ObservableObject { let minBalance = 0.000 let result = WalletManager.shared.activatedCoins.filter { tokenModel in if tokenModel.isFlowCoin, let symbol = tokenModel.symbol { - log.debug("[EVM] enable check balance: \(WalletManager.shared.getBalance(bySymbol: symbol))") + log + .debug( + "[EVM] enable check balance: \(WalletManager.shared.getBalance(bySymbol: symbol))" + ) return WalletManager.shared.getBalance(bySymbol: symbol) >= minBalance } return false diff --git a/FRW/Modules/MultiBackup/Manager/MultiBackupManager.swift b/FRW/Modules/MultiBackup/Manager/MultiBackupManager.swift index 983f1223..dca6300a 100644 --- a/FRW/Modules/MultiBackup/Manager/MultiBackupManager.swift +++ b/FRW/Modules/MultiBackup/Manager/MultiBackupManager.swift @@ -80,6 +80,7 @@ extension MultiBackupManager { var updatedTime: Double? = Date.now.timeIntervalSince1970 let deviceInfo: DeviceInfoRequest? var code: String? + var backupType: MultiBackupType? = .phrase func showDate() -> String { guard let updatedTime = updatedTime else { return "" } @@ -265,11 +266,23 @@ extension MultiBackupManager { switch type { case .google: try await login(from: type) - return try await gdTarget.getCurrentDriveItems() + var list = try await gdTarget.getCurrentDriveItems() + list = list.map { item in + var model = item + model.backupType = type + return model + } + return list case .passkey: return [] case .icloud: - return try await iCloudTarget.getCurrentDriveItems() + var list = try await iCloudTarget.getCurrentDriveItems() + list = list.map { item in + var model = item + model.backupType = type + return model + } + return list case .phrase: return [] } diff --git a/FRW/Modules/MultiBackup/ViewModel/BackupMultiViewModel.swift b/FRW/Modules/MultiBackup/ViewModel/BackupMultiViewModel.swift index 18070835..d5bdfb2d 100644 --- a/FRW/Modules/MultiBackup/ViewModel/BackupMultiViewModel.swift +++ b/FRW/Modules/MultiBackup/ViewModel/BackupMultiViewModel.swift @@ -80,7 +80,7 @@ class BackupMultiViewModel: ObservableObject { // MARK: - MultiBackupType -enum MultiBackupType: Int, CaseIterable { +enum MultiBackupType: Int, CaseIterable, Codable { case google = 0 case passkey = 1 case icloud = 2 @@ -152,6 +152,19 @@ enum MultiBackupType: Int, CaseIterable { return "icon.recovery" } } + + func methodName() -> String { + switch self { + case .google: + return "google_drive" + case .passkey: + return "passkey" + case .icloud: + return "icloud" + case .phrase: + return "seed_phrase" + } + } } // MARK: - BackupMultiViewModel.MultiItem diff --git a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift index e937752a..0f159b68 100644 --- a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift +++ b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift @@ -173,6 +173,7 @@ class BackupUploadViewModel: ObservableObject { } } catch { buttonState = .enabled + trackCreatFailed(message: error.localizedDescription) } } case .upload: @@ -213,6 +214,7 @@ class BackupUploadViewModel: ObservableObject { } } case .finish: + trackCreatSuccess() let nextIndex = currentIndex + 1 if items.count <= nextIndex { currentIndex = nextIndex @@ -233,3 +235,29 @@ class BackupUploadViewModel: ObservableObject { } } } + +extension BackupUploadViewModel { + private func trackSource() -> String { + var provider = "google_drive" + switch currentType { + case .google: + break + case .passkey: + provider = "" + case .icloud: + provider = "icloud" + case .phrase: + provider = "seed_phrase" + } + return provider + } + + func trackCreatSuccess() { + EventTrack.Backup.multiCreated(source: trackSource()) + } + + func trackCreatFailed(message: String) { + EventTrack.Backup + .multiCreatedFailed(source: trackSource(), reason: message) + } +} diff --git a/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift b/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift index 007339a3..640a4473 100644 --- a/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift +++ b/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift @@ -74,6 +74,12 @@ class RestoreMultiAccountViewModel: ObservableObject { guard selectedUser.count > 1 else { return } + if let item = selectedUser.first { + let methods = selectedUser.map { $0.backupType?.methodName() ?? "" } + EventTrack.Account + .recovered(address: item.address, mechanism: "multi-backup", methods: methods) + } + Task { do { HUD.loading() diff --git a/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift b/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift index 5d44396c..17dde42e 100644 --- a/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift +++ b/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift @@ -8,6 +8,8 @@ import Flow import SwiftUI +// MARK: - StakeAmountViewModel.ErrorType + extension StakeAmountViewModel { enum ErrorType { case none @@ -15,6 +17,8 @@ extension StakeAmountViewModel { case belowMinimumBalance case belowMinimumAmount + // MARK: Internal + var desc: String { switch self { case .none: @@ -31,17 +35,38 @@ extension StakeAmountViewModel { } } +// MARK: - StakeAmountViewModel + class StakeAmountViewModel: ObservableObject { - @Published var provider: StakingProvider - @Published var isUnstake: Bool + // MARK: Lifecycle + + init(provider: StakingProvider, isUnstake: Bool) { + self.provider = provider + self.isUnstake = isUnstake + self.balance = isUnstake ? (provider.currentNode?.stakingCount ?? 0) : WalletManager.shared + .getBalance(bySymbol: "flow") + } + + // MARK: Internal - @Published var inputText: String = "" - @Published var inputTextNum: Double = 0 - @Published var balance: Double = 0 - @Published var showConfirmView: Bool = false - @Published var errorType: StakeAmountViewModel.ErrorType = .none + @Published + var provider: StakingProvider + @Published + var isUnstake: Bool - @Published var isRequesting: Bool = false + @Published + var inputText: String = "" + @Published + var inputTextNum: Double = 0 + @Published + var balance: Double = 0 + @Published + var showConfirmView: Bool = false + @Published + var errorType: StakeAmountViewModel.ErrorType = .none + + @Published + var isRequesting: Bool = false var buttonState: VPrimaryButtonState { if isRequesting { @@ -56,11 +81,11 @@ class StakeAmountViewModel: ObservableObject { } var inputNumAsCurrencyString: String { - return "\(CurrencyCache.cache.currencySymbol)\(inputNumAsUSD.formatCurrencyString(considerCustomCurrency: true)) \(CurrencyCache.cache.currentCurrency.rawValue)" + "\(CurrencyCache.cache.currencySymbol)\(inputNumAsUSD.formatCurrencyString(considerCustomCurrency: true)) \(CurrencyCache.cache.currentCurrency.rawValue)" } var inputNumAsCurrencyStringInConfirmSheet: String { - return "\(CurrencyCache.cache.currencySymbol)\(inputNumAsUSD.formatCurrencyString(considerCustomCurrency: true))" + "\(CurrencyCache.cache.currencySymbol)\(inputNumAsUSD.formatCurrencyString(considerCustomCurrency: true))" } var yearReward: Double { @@ -72,19 +97,16 @@ class StakeAmountViewModel: ObservableObject { } var yearRewardWithCurrencyString: String { - let numString = (inputNumAsUSD * provider.rate).formatCurrencyString(considerCustomCurrency: true) + let numString = (inputNumAsUSD * provider.rate) + .formatCurrencyString(considerCustomCurrency: true) return "\(CurrencyCache.cache.currencySymbol)\(numString) \(CurrencyCache.cache.currentCurrency.rawValue)" } var isReadyForStake: Bool { - return errorType == .none && inputTextNum > 0 + errorType == .none && inputTextNum > 0 } - init(provider: StakingProvider, isUnstake: Bool) { - self.provider = provider - self.isUnstake = isUnstake - balance = isUnstake ? (provider.currentNode?.stakingCount ?? 0) : WalletManager.shared.getBalance(bySymbol: "flow") - } + // MARK: Private private func refreshState() { if inputTextNum > balance { @@ -161,35 +183,61 @@ extension StakeAmountViewModel { } if provider.delegatorId == nil { - debugPrint("StakeAmountViewModel: provider.delegatorId is nil, will create delegator id") + debugPrint( + "StakeAmountViewModel: provider.delegatorId is nil, will create delegator id" + ) // create delegator id to stake (only first time) - return try await FlowNetwork.createDelegatorId(providerId: provider.id, amount: inputTextNum) + let address = WalletManager.shared.getPrimaryWalletAddress() ?? "" + EventTrack.General + .delegationCreated( + address: address, + nodeId: provider.id, + amount: inputTextNum + ) + return try await FlowNetwork.createDelegatorId( + providerId: provider.id, + amount: inputTextNum + ) } guard let delegatorId = provider.delegatorId else { // can not be nil, something went wrong. - debugPrint("StakeAmountViewModel: delegatorId is still nil after fetch delegatorIds, something went wrong") + debugPrint( + "StakeAmountViewModel: delegatorId is still nil after fetch delegatorIds, something went wrong" + ) throw StakingError.unknown } debugPrint("StakeAmountViewModel: provider.delegatorId now get, will stake flow") - return try await FlowNetwork.stakeFlow(providerId: provider.id, delegatorId: delegatorId, amount: inputTextNum) + return try await FlowNetwork.stakeFlow( + providerId: provider.id, + delegatorId: delegatorId, + amount: inputTextNum + ) } private func unstake() async throws -> Flow.ID { if provider.delegatorId == nil { - debugPrint("StakeAmountViewModel: provider.delegatorId is nil, will create delegator id") + debugPrint( + "StakeAmountViewModel: provider.delegatorId is nil, will create delegator id" + ) } guard let delegatorId = provider.delegatorId else { // can not be nil, something went wrong. - debugPrint("StakeAmountViewModel: delegatorId is still nil after fetch delegatorIds, something went wrong") + debugPrint( + "StakeAmountViewModel: delegatorId is still nil after fetch delegatorIds, something went wrong" + ) throw StakingError.unknown } debugPrint("StakeAmountViewModel: provider.delegatorId now get, will unstake flow") - let txId = try await FlowNetwork.unstakeFlow(providerId: provider.id, delegatorId: delegatorId, amount: inputTextNum) + let txId = try await FlowNetwork.unstakeFlow( + providerId: provider.id, + delegatorId: delegatorId, + amount: inputTextNum + ) return txId } @@ -211,7 +259,11 @@ extension StakeAmountViewModel { DispatchQueue.main.async { self.isRequesting = false self.showConfirmView = false - let holder = TransactionManager.TransactionHolder(id: txId, type: .stakeFlow, data: Data()) + let holder = TransactionManager.TransactionHolder( + id: txId, + type: .stakeFlow, + data: Data() + ) TransactionManager.shared.newTransaction(holder: holder) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { Router.route(to: RouteMap.Wallet.backToTokenDetail) diff --git a/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift index f9244de4..992ecf08 100644 --- a/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift +++ b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift @@ -12,17 +12,11 @@ import SwiftUI import SwiftUIPager class CreateProfileWaitingViewModel: ObservableObject { - @Published var animationPhase: AnimationPhase = .initial - @Published var page: Page = .first() - @Published var createFinished = false - @Published var currentPage: Int = 0 + // MARK: Lifecycle - private var timer: Timer? - - private var cancellableSet = Set() - - var txId = Flow.ID(hex: "") - var callback: (Bool) -> Void + deinit { + EventTrack.Account.createdTimeStart() + } init(txId: String, callback: @escaping (Bool) -> Void) { self.txId = Flow.ID(hex: txId) @@ -33,29 +27,33 @@ class CreateProfileWaitingViewModel: ObservableObject { .receive(on: DispatchQueue.main) .map { $0 } .sink { walletInfo in - let isEmptyBlockChain = walletInfo?.currentNetworkWalletModel?.isEmptyBlockChain ?? true + let isEmptyBlockChain = walletInfo?.currentNetworkWalletModel? + .isEmptyBlockChain ?? true if !isEmptyBlockChain { self.createFinished = true + EventTrack.Account.createdTimeEnd() } }.store(in: &cancellableSet) DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: DispatchWorkItem(block: { self.startTimer() })) + EventTrack.Account.createdTimeStart() } - @objc private func onHolderStatusChanged(noti: Notification) { - guard let holder = noti.object as? TransactionManager.TransactionHolder, - holder.transactionId.hex == txId.hex - else { - return - } - guard let current = AnimationPhase(rawValue: holder.flowStatus.rawValue) else { - return - } + // MARK: Internal - animationPhase = current - } + @Published + var animationPhase: AnimationPhase = .initial + @Published + var page: Page = .first() + @Published + var createFinished = false + @Published + var currentPage: Int = 0 + + var txId = Flow.ID(hex: "") + var callback: (Bool) -> Void func onPageIndexChangeAction(_ index: Int) { withAnimation(.default) { @@ -78,9 +76,35 @@ class CreateProfileWaitingViewModel: ObservableObject { ConfettiManager.show() } + // MARK: Private + + private var timer: Timer? + + private var cancellableSet = Set() + + @objc + private func onHolderStatusChanged(noti: Notification) { + guard let holder = noti.object as? TransactionManager.TransactionHolder, + holder.transactionId.hex == txId.hex + else { + return + } + guard let current = AnimationPhase(rawValue: holder.flowStatus.rawValue) else { + return + } + + animationPhase = current + } + private func startTimer() { stopTimer() - let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(onTimer), userInfo: nil, repeats: true) + let timer = Timer.scheduledTimer( + timeInterval: 10, + target: self, + selector: #selector(onTimer), + userInfo: nil, + repeats: true + ) self.timer = timer RunLoop.main.add(timer, forMode: .common) } @@ -92,7 +116,8 @@ class CreateProfileWaitingViewModel: ObservableObject { } } - @objc private func onTimer() { + @objc + private func onTimer() { if page.index == 2 { page.update(.moveToFirst) } else { diff --git a/FRW/Modules/Wallet/WalletHomeView.swift b/FRW/Modules/Wallet/WalletHomeView.swift index 0dabcbf6..9049025b 100644 --- a/FRW/Modules/Wallet/WalletHomeView.swift +++ b/FRW/Modules/Wallet/WalletHomeView.swift @@ -477,6 +477,8 @@ struct WalletHomeView: View { Spacer() Button { + EventTrack.General + .security(type: SecurityManager.shared.securityType) UIImpactFeedbackGenerator(style: .soft).impactOccurred() Router.route(to: RouteMap.Wallet.buyCrypto) } label: { diff --git a/FRW/Services/FlowCoin/CadenceManager.swift b/FRW/Services/FlowCoin/CadenceManager.swift index 16b19453..1b11ed6f 100644 --- a/FRW/Services/FlowCoin/CadenceManager.swift +++ b/FRW/Services/FlowCoin/CadenceManager.swift @@ -45,7 +45,12 @@ class CadenceManager { if let response = loadCache() { scripts = response.scripts version = response.version ?? localVersion - log.info("[Cadence] local cache version is \(String(describing: response.version))") + log.info("[Cadence] cache version is \(String(describing: response.version))") + EventTrack.shared + .registerCadence( + scriptVersion: version, + cadenceVersion: current.version ?? "" + ) } else { do { guard let filePath = Bundle.main @@ -58,7 +63,12 @@ class CadenceManager { let providers = try JSONDecoder().decode(CadenceResponse.self, from: data) scripts = providers.scripts version = providers.version ?? localVersion - log.info("[Cadence] romote version is \(String(describing: providers.version))") + EventTrack.shared + .registerCadence( + scriptVersion: version, + cadenceVersion: current.version ?? "" + ) + log.info("[Cadence] local version is \(String(describing: providers.version))") } catch { log.error("CadenceManager -> decode failed", context: error) } @@ -77,6 +87,11 @@ class CadenceManager { if let version = response.data.version { self.version = version } + EventTrack.shared + .registerCadence( + scriptVersion: self.version, + cadenceVersion: self.current.version ?? "" + ) } } catch { log.error("CadenceManager -> fetch failed", context: error) @@ -98,8 +113,6 @@ class CadenceManager { } private func loadCache() -> CadenceResponse? { - return nil - guard let file = filePath() else { return nil } diff --git a/FRW/Services/Manager/Config/RemoteConfigManager.swift b/FRW/Services/Manager/Config/RemoteConfigManager.swift index 39707df9..14b368a6 100644 --- a/FRW/Services/Manager/Config/RemoteConfigManager.swift +++ b/FRW/Services/Manager/Config/RemoteConfigManager.swift @@ -33,6 +33,7 @@ class RemoteConfigManager { var contractAddress: ContractAddress? var isFailed: Bool = false + var isStaging: Bool = false var emptyAddress: String { switch LocalUserDefaults.shared.flowNetwork.toFlowType() { @@ -125,6 +126,7 @@ class RemoteConfigManager { let config = try decoder.decode(ENVConfig.self, from: decodeData) envConfig = config self.config = nil + isStaging = false if let currentVersion = Bundle.main .infoDictionary?["CFBundleShortVersionString"] as? String, let version = envConfig?.version { @@ -135,6 +137,7 @@ class RemoteConfigManager { if self.config == nil { self.config = envConfig?.staging + isStaging = true } } contractAddress = try FirebaseConfig.contractAddress.fetch(decoder: JSONDecoder()) diff --git a/FRW/Services/Manager/EVMAccountManager.swift b/FRW/Services/Manager/EVMAccountManager.swift index 7a24ba04..41d52307 100644 --- a/FRW/Services/Manager/EVMAccountManager.swift +++ b/FRW/Services/Manager/EVMAccountManager.swift @@ -9,33 +9,10 @@ import Combine import Foundation import SwiftUI -class EVMAccountManager: ObservableObject { - static let shared = EVMAccountManager() - - @Published var hasAccount: Bool = false - @Published var showEVM: Bool = false - @Published var accounts: [EVMAccountManager.Account] = [] { - didSet { - checkValid() - } - } - - private var cacheAccounts: [String: [String]] = LocalUserDefaults.shared.EVMAddress - - var openEVM: Bool { - return (CadenceManager.shared.current.evm?.createCoaEmpty) != nil - } - - var balance: Decimal = 0 - - private var cancelSets = Set() +// MARK: - EVMAccountManager - @Published var selectedAccount: EVMAccountManager.Account? = LocalUserDefaults.shared.selectedEVMAccount { - didSet { - LocalUserDefaults.shared.selectedEVMAccount = selectedAccount - NotificationCenter.default.post(name: .watchAddressDidChanged, object: nil) - } - } +class EVMAccountManager: ObservableObject { + // MARK: Lifecycle init() { UserManager.shared.$activatedUID @@ -68,7 +45,12 @@ class EVMAccountManager: ObservableObject { // self.clean() // }.store(in: &cancelSets) - NotificationCenter.default.addObserver(self, selector: #selector(willReset), name: .willResetWallet, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(willReset), + name: .willResetWallet, + object: nil + ) NotificationCenter.default.publisher(for: .transactionStatusDidChanged) .receive(on: DispatchQueue.main) @@ -78,6 +60,42 @@ class EVMAccountManager: ObservableObject { }.store(in: &cancelSets) } + // MARK: Internal + + static let shared = EVMAccountManager() + + @Published + var hasAccount: Bool = false + @Published + var showEVM: Bool = false + var balance: Decimal = 0 + + @Published + var accounts: [EVMAccountManager.Account] = [] { + didSet { + checkValid() + } + } + + var openEVM: Bool { + (CadenceManager.shared.current.evm?.createCoaEmpty) != nil + } + + @Published + var selectedAccount: EVMAccountManager.Account? = LocalUserDefaults.shared + .selectedEVMAccount { + didSet { + LocalUserDefaults.shared.selectedEVMAccount = selectedAccount + NotificationCenter.default.post(name: .watchAddressDidChanged, object: nil) + } + } + + // MARK: Private + + private var cacheAccounts: [String: [String]] = LocalUserDefaults.shared.EVMAddress + + private var cancelSets = Set() + private func clean() { log.debug("cleaned") DispatchQueue.main.async { @@ -108,12 +126,15 @@ class EVMAccountManager: ObservableObject { } } - @objc private func willReset() { + @objc + private func willReset() { clean() } - @objc private func onTransactionStatusChanged(_ noti: Notification) { - guard let obj = noti.object as? TransactionManager.TransactionHolder, obj.type == .editChildAccount else { + @objc + private func onTransactionStatusChanged(_ noti: Notification) { + guard let obj = noti.object as? TransactionManager.TransactionHolder, + obj.type == .editChildAccount else { return } @@ -195,11 +216,29 @@ extension EVMAccountManager { extension EVMAccountManager { func enableEVM() async throws { - let tid = try await FlowNetwork.createEVM() - let result = try await tid.onceSealed() - if result.isFailed { - log.error("[EVM] create EVM result: Failed") - throw EVMError.createAccount + let address = WalletManager.shared.getPrimaryWalletAddress() ?? "" + + do { + let tid = try await FlowNetwork.createEVM() + let result = try await tid.onceSealed() + if result.isFailed { + log.error("[EVM] create EVM result: Failed") + EventTrack.General + .coaCreation( + txId: tid.description, + flowAddress: address, + message: result.errorMessage + ) + throw EVMError.createAccount + } + } catch { + EventTrack.General + .coaCreation( + txId: "", + flowAddress: address, + message: error.localizedDescription + ) + throw error } } @@ -209,7 +248,7 @@ extension EVMAccountManager { } func fetchBalance(_ address: String) async throws -> Decimal { - return try await FlowNetwork.fetchEVMBalance(address: address) + try await FlowNetwork.fetchEVMBalance(address: address) } func fetchTokens() async throws -> [EVMTokenResponse] { @@ -221,6 +260,8 @@ extension EVMAccountManager { } } +// MARK: EVMAccountManager.Account + extension EVMAccountManager { struct Account: ChildAccountSideCellItem, Codable { var address: String @@ -246,8 +287,7 @@ extension EVMAccountManager { var isSelected: Bool { if let selectedAccount = EVMAccountManager.shared.selectedAccount, - selectedAccount.address.lowercased() == address.lowercased(), !address.isEmpty - { + selectedAccount.address.lowercased() == address.lowercased(), !address.isEmpty { return true } return false diff --git a/FRW/Services/Manager/TransactionManager.swift b/FRW/Services/Manager/TransactionManager.swift index dee8c8ef..f7fed61d 100644 --- a/FRW/Services/Manager/TransactionManager.swift +++ b/FRW/Services/Manager/TransactionManager.swift @@ -77,7 +77,18 @@ extension TransactionManager.TransactionHolder { var toFlowScanTransaction: FlowScanTransaction { let time = ISO8601Formatter.string(from: Date(timeIntervalSince1970: createTime)) - let model = FlowScanTransaction(authorizers: nil, contractInteractions: nil, error: errorMsg, eventCount: nil, hash: transactionId.hex, index: nil, payer: nil, proposer: nil, status: statusString, time: time) + let model = FlowScanTransaction( + authorizers: nil, + contractInteractions: nil, + error: errorMsg, + eventCount: nil, + hash: transactionId.hex, + index: nil, + payer: nil, + proposer: nil, + status: statusString, + time: time + ) return model } } @@ -102,6 +113,8 @@ extension TransactionManager { case success case failed + // MARK: Internal + var statusColor: UIColor { switch self { case .pending: @@ -115,21 +128,22 @@ extension TransactionManager { } class TransactionHolder: Codable { - var transactionId: Flow.ID - var createTime: TimeInterval - var status: Int = Flow.Transaction.Status.pending.rawValue - var internalStatus: TransactionManager.InternalStatus = .pending - var type: TransactionManager.TransactionType - var data: Data = .init() - var errorMsg: String? - - private var timer: Timer? - private var retryTimes: Int = 0 - - var flowStatus: Flow.Transaction.Status { - return Flow.Transaction.Status(status) + // MARK: Lifecycle + + init( + id: Flow.ID, + createTime: TimeInterval = Date().timeIntervalSince1970, + type: TransactionManager.TransactionType, + data: Data = Data() + ) { + self.transactionId = id + self.createTime = createTime + self.type = type + self.data = data } + // MARK: Internal + enum CodingKeys: String, CodingKey { case transactionId case createTime @@ -139,21 +153,27 @@ extension TransactionManager { case internalStatus } - init(id: Flow.ID, createTime: TimeInterval = Date().timeIntervalSince1970, type: TransactionManager.TransactionType, data: Data = Data()) { - transactionId = id - self.createTime = createTime - self.type = type - self.data = data + var transactionId: Flow.ID + var createTime: TimeInterval + var status: Int = Flow.Transaction.Status.pending.rawValue + var internalStatus: TransactionManager.InternalStatus = .pending + var type: TransactionManager.TransactionType + var data: Data = .init() + var errorMsg: String? + + var flowStatus: Flow.Transaction.Status { + Flow.Transaction.Status(status) } func decodedObject(_ type: T.Type) -> T? { - return try? JSONDecoder().decode(type, from: data) + try? JSONDecoder().decode(type, from: data) } func icon() -> URL? { switch type { case .transferCoin: - guard let model = decodedObject(CoinTransferModel.self), let token = WalletManager.shared.getToken(bySymbol: model.symbol) else { + guard let model = decodedObject(CoinTransferModel.self), + let token = WalletManager.shared.getToken(bySymbol: model.symbol) else { return nil } @@ -165,13 +185,15 @@ extension TransactionManager { case .transferNFT: return decodedObject(NFTTransferModel.self)?.nft.logoUrl case .fclTransaction: - guard let model = decodedObject(AuthzTransaction.self), let urlString = model.url else { + guard let model = decodedObject(AuthzTransaction.self), + let urlString = model.url else { return nil } return urlString.toFavIcon() case .unlinkAccount: - guard let iconString = decodedObject(ChildAccount.self)?.icon, let url = URL(string: iconString) else { + guard let iconString = decodedObject(ChildAccount.self)?.icon, + let url = URL(string: iconString) else { return nil } @@ -190,7 +212,13 @@ extension TransactionManager { return } - let timer = Timer(timeInterval: 2, target: self, selector: #selector(onCheck), userInfo: nil, repeats: false) + let timer = Timer( + timeInterval: 2, + target: self, + selector: #selector(onCheck), + userInfo: nil, + repeats: false + ) RunLoop.main.add(timer, forMode: .common) self.timer = timer @@ -205,7 +233,13 @@ extension TransactionManager { } } - @objc private func onCheck() { + // MARK: Private + + private var timer: Timer? + private var retryTimes: Int = 0 + + @objc + private func onCheck() { debugPrint("TransactionHolder -> onCheck") Task { @@ -213,7 +247,7 @@ extension TransactionManager { let result = try await FlowNetwork.getTransactionResult(by: transactionId.hex) debugPrint("TransactionHolder -> onCheck status: \(result.status)") - DispatchQueue.main.async { + DispatchQueue.main.async { [self] in if result.status == self.flowStatus, result.status < .sealed { self.startTimer() return @@ -223,14 +257,19 @@ extension TransactionManager { if result.isFailed { self.errorMsg = result.errorMessage self.internalStatus = .failed - debugPrint("TransactionHolder -> onCheck result failed: \(result.errorMessage)") + debugPrint( + "TransactionHolder -> onCheck result failed: \(result.errorMessage)" + ) } else if result.isComplete { self.internalStatus = .success } else { self.internalStatus = .pending self.startTimer() } - + self.trackResult( + result: result, + fromId: self.transactionId.hex + ) self.postNotification() } } catch { @@ -243,6 +282,8 @@ extension TransactionManager { } } + private func trackResult(result: Flow.TransactionResult, fromId: String) {} + private func postNotification() { debugPrint("TransactionHolder -> postNotification status: \(status)") NotificationCenter.default.post(name: .transactionStatusDidChanged, object: self) @@ -250,14 +291,10 @@ extension TransactionManager { } } -class TransactionManager: ObservableObject { - static let shared = TransactionManager() +// MARK: - TransactionManager - private lazy var rootFolder = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("transaction_cache") - private lazy var transactionCacheFile = rootFolder.appendingPathComponent("transaction_cache_file") - - @Published - private(set) var holders: [TransactionHolder] = [] +class TransactionManager: ObservableObject { + // MARK: Lifecycle init() { checkFolder() @@ -266,18 +303,46 @@ class TransactionManager: ObservableObject { startCheckIfNeeded() } + // MARK: Internal + + static let shared = TransactionManager() + + @Published + private(set) var holders: [TransactionHolder] = [] + + // MARK: Private + + private lazy var rootFolder = FileManager.default.urls( + for: .cachesDirectory, + in: .userDomainMask + ).first!.appendingPathComponent("transaction_cache") + private lazy var transactionCacheFile = rootFolder + .appendingPathComponent("transaction_cache_file") + private func addNotification() { - NotificationCenter.default.addObserver(self, selector: #selector(onHolderChanged(noti:)), name: .transactionStatusDidChanged, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(willReset), name: .willResetWallet, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(onHolderChanged(noti:)), + name: .transactionStatusDidChanged, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(willReset), + name: .willResetWallet, + object: nil + ) } - @objc private func willReset() { + @objc + private func willReset() { holders = [] saveHoldersToCache() postDidChangedNotification() } - @objc private func onHolderChanged(noti: Notification) { + @objc + private func onHolderChanged(noti: Notification) { guard let holder = noti.object as? TransactionHolder else { return } @@ -384,7 +449,8 @@ extension TransactionManager { func isTokenEnabling(symbol: String) -> Bool { for holder in holders { - if holder.type == .addToken, let token = holder.decodedObject(TokenModel.self), token.symbol == symbol { + if holder.type == .addToken, let token = holder.decodedObject(TokenModel.self), + token.symbol == symbol { return true } } @@ -394,7 +460,9 @@ extension TransactionManager { func isCollectionEnabling(contractName: String) -> Bool { for holder in holders { - if holder.type == .addCollection, let collection = holder.decodedObject(NFTCollectionInfo.self), collection.contractName == contractName { + if holder.type == .addCollection, + let collection = holder.decodedObject(NFTCollectionInfo.self), + collection.contractName == contractName { return true } } @@ -404,7 +472,8 @@ extension TransactionManager { func isNFTTransfering(id: String) -> Bool { for holder in holders { - if holder.type == .transferNFT, let model = holder.decodedObject(NFTTransferModel.self), model.nft.id == id { + if holder.type == .transferNFT, let model = holder.decodedObject(NFTTransferModel.self), + model.nft.id == id { return true } } @@ -419,7 +488,10 @@ extension TransactionManager { private func checkFolder() { do { if !FileManager.default.fileExists(atPath: rootFolder.relativePath) { - try FileManager.default.createDirectory(at: rootFolder, withIntermediateDirectories: true) + try FileManager.default.createDirectory( + at: rootFolder, + withIntermediateDirectories: true + ) } } catch { @@ -434,7 +506,10 @@ extension TransactionManager { do { let data = try Data(contentsOf: transactionCacheFile) - let list = try JSONDecoder().decode([TransactionManager.TransactionHolder].self, from: data) + let list = try JSONDecoder().decode( + [TransactionManager.TransactionHolder].self, + from: data + ) let filterdList = list.filter { $0.internalStatus == .pending } if !filterdList.isEmpty { diff --git a/FRW/Services/Manager/UserManager.swift b/FRW/Services/Manager/UserManager.swift index 33810711..9b60c19f 100644 --- a/FRW/Services/Manager/UserManager.swift +++ b/FRW/Services/Manager/UserManager.swift @@ -203,6 +203,7 @@ extension UserManager { accountKey: key.toCodableModel(), deviceInfo: IPManager.shared.toParams() ) + let model: RegisterResponse = try await Network.request(FRWAPI.User.register(request)) try await finishLogin(mnemonic: "", customToken: model.customToken, isRegiter: true) @@ -213,10 +214,16 @@ extension UserManager { key: model.id, value: privateKey.dataRepresentation ) + } else { log.error("store public key on iPhone failed") } - + EventTrack.Account + .create( + key: sec.key.publickeyValue ?? "", + signAlgo: key.signAlgo.id, + hashAlgo: key.hashAlgo.id + ) return model.txId } } diff --git a/FRW/Services/Track/EventTrack+Account.swift b/FRW/Services/Track/EventTrack+Account.swift index f1862ae1..de79e159 100644 --- a/FRW/Services/Track/EventTrack+Account.swift +++ b/FRW/Services/Track/EventTrack+Account.swift @@ -34,11 +34,11 @@ extension EventTrack.Account { EventTrack.timeEnd(event: EventTrack.Account.createdTime) } - static func recovered(address: String, machanism: String, methods: [String]) { + static func recovered(address: String, mechanism: String, methods: [String]) { EventTrack .send(event: EventTrack.Account.recovered, properties: [ "address": address, - "mechanism": machanism, + "mechanism": mechanism, "methods": methods, ]) } diff --git a/FRW/Services/Track/EventTrack+Backup.swift b/FRW/Services/Track/EventTrack+Backup.swift index 7eda39cd..10564c4a 100644 --- a/FRW/Services/Track/EventTrack+Backup.swift +++ b/FRW/Services/Track/EventTrack+Backup.swift @@ -19,7 +19,7 @@ extension EventTrack.Backup { ]) } - static func multiCreatedFailed(source: String) { + static func multiCreatedFailed(source: String, reason: String) { guard let address = WalletManager.shared.getPrimaryWalletAddress() else { return } diff --git a/FRW/Services/Track/EventTrack+General.swift b/FRW/Services/Track/EventTrack+General.swift index 717741a3..83438b96 100644 --- a/FRW/Services/Track/EventTrack+General.swift +++ b/FRW/Services/Track/EventTrack+General.swift @@ -15,7 +15,6 @@ extension EventTrack.General { ]) } - /// StakeAmountViewModel stake static func delegationCreated( address: String, nodeId: String, @@ -37,11 +36,35 @@ extension EventTrack.General { ]) } + static func coaCreation(txId: String, flowAddress: String, message: String) { + EventTrack + .send(event: EventTrack.General.coaCreation, properties: [ + "tx_id": txId, + "flow_address": flowAddress, + "error_message": message, + ]) + } + /// home page buy button clicked - static func security(type: String) { + static func security(type: SecurityManager.SecurityType) { EventTrack .send(event: EventTrack.General.securityTool, properties: [ - "type": type, + "type": type.trackLabel(), ]) } } + +extension SecurityManager.SecurityType { + func trackLabel() -> String { + switch self { + case .none: + "none" + case .pin: + "pin" + case .bionic: + "biometric" + case .both: + "both" + } + } +} diff --git a/FRW/Services/Track/EventTrack.swift b/FRW/Services/Track/EventTrack.swift index af084890..5f7e6e77 100644 --- a/FRW/Services/Track/EventTrack.swift +++ b/FRW/Services/Track/EventTrack.swift @@ -5,34 +5,48 @@ // Created by cat on 10/22/24. // +import Combine import Foundation import Mixpanel +// MARK: - EventTrack + class EventTrack { // MARK: Internal + static let shared = EventTrack() + static func start(token: String) { Mixpanel.initialize(token: token) - Mixpanel.mainInstance().registerSuperProperties(common()) + EventTrack.shared.registerAllSuper() + EventTrack.shared.monitor() #if DEBUG Mixpanel.mainInstance().loggingEnabled = true #endif } - // MARK: - Action + // MARK: Private - /// call when switch user - static func switchUser() { - guard let uid = UserManager.shared.activatedUID else { - // reset ? - return - } - Mixpanel.mainInstance().identify(distinctId: uid) - } + private var cancellableSet = Set() + + private func monitor() { + UserManager.shared.$activatedUID + .receive(on: DispatchQueue.main) + .map { $0 } + .sink { [weak self] userId in + self?.switchUser() + }.store(in: &cancellableSet) - static func updateNetwork() { - // flow_network + NotificationCenter.default.publisher(for: .networkChange) + .receive(on: DispatchQueue.main) + .sink { _ in + self.registerNetwork() + }.store(in: &cancellableSet) } +} + +extension EventTrack { + // MARK: - Action static func send(event: EventTrackNameProtocol, properties: [String: MixpanelType]? = nil) { Mixpanel @@ -47,16 +61,58 @@ class EventTrack { static func timeEnd(event: EventTrackNameProtocol, properties: [String: MixpanelType]? = nil) { Mixpanel.mainInstance().track(event: event.name, properties: properties) } +} - // MARK: Private +// MARK: - update Super - /// super properties - private static func common() -> [String: String] { - var param: [String: String] = [:] +extension EventTrack { + private func registerAllSuper() { + Mixpanel + .mainInstance() + .registerSuperProperties([Superkey.deviceId: UUIDManager.appUUID()]) + var env = "production" + if RemoteConfigManager.shared.isStaging { + env = "staging" + } else if isDevModel { + env = "development" + } + + Mixpanel.mainInstance().registerSuperProperties([Superkey.env: env]) + registerNetwork() + registerCadence(scriptVersion: "", cadenceVersion: "") + switchUser() + } + + /// call when switch user + private func switchUser() { + guard let uid = UserManager.shared.activatedUID else { + // reset ? + return + } + Mixpanel.mainInstance().identify(distinctId: uid) + } + + private func registerNetwork() { + let network = LocalUserDefaults.shared.flowNetwork.rawValue + Mixpanel.mainInstance().registerSuperProperties([Superkey.network: network]) + } + + func registerCadence(scriptVersion: String, cadenceVersion: String) { + Mixpanel.mainInstance().registerSuperProperties([Superkey.scriptVersion: scriptVersion]) + Mixpanel + .mainInstance() + .registerSuperProperties([Superkey.cadenceVersion: cadenceVersion]) + } +} - let scriptVersion = CadenceManager.shared.version - param["cadence_script_version"] = scriptVersion +// MARK: EventTrack.Superkey - return param +extension EventTrack { + enum Superkey { + static let network = "flow_network" + static let scriptVersion = "cadence_script_version" + static let cadenceVersion = "cadence_script_version" + static let deviceId = "fw_device_id" + static let env = "app_env" } } diff --git a/FRW/Services/Track/EventTrackName.swift b/FRW/Services/Track/EventTrackName.swift index a78ee423..3791ce5e 100644 --- a/FRW/Services/Track/EventTrackName.swift +++ b/FRW/Services/Track/EventTrackName.swift @@ -20,6 +20,7 @@ extension EventTrack { case rpcError = "script_error" case delegationCreated = "delegation_created" case rampClicked = "on_ramp_clicked" + case coaCreation = "coa_creation" case securityTool = "security_tool" // MARK: Internal From f9a2f83f2d73bf3caccb74a4a2de771e1ab33950 Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 18 Nov 2024 08:53:24 +0800 Subject: [PATCH 2/9] feat: fix the code by swiftformat automatic modification --- .../Profile/AccountSetting/ChildAccountDetailView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FRW/Modules/Profile/AccountSetting/ChildAccountDetailView.swift b/FRW/Modules/Profile/AccountSetting/ChildAccountDetailView.swift index 0f13bc2c..4ec1263d 100644 --- a/FRW/Modules/Profile/AccountSetting/ChildAccountDetailView.swift +++ b/FRW/Modules/Profile/AccountSetting/ChildAccountDetailView.swift @@ -71,7 +71,7 @@ class ChildAccountDetailViewModel: ObservableObject { if index == 0 { if var list = collections { if !showEmptyCollection { - list = list.filter { !$0.isEmpty } + list = list.filter { $0.count > 0 } } accessibleItems = list } else { @@ -413,14 +413,14 @@ struct ChildAccountDetailView: RouteableView { LLSegmenControl(titles: ["collections".localized, "coins_cap".localized]) { idx in vm.switchTab(index: idx) } - if vm.accessibleItems.isEmpty, !vm.isLoading { + if vm.accessibleItems.count == 0, !vm.isLoading { emptyAccessibleView } ForEach(vm.accessibleItems.indices, id: \.self) { idx in AccessibleItemView(item: vm.accessibleItems[idx]) { item in if let collectionInfo = item as? NFTCollection, let addr = vm.childAccount.addr, let pathId = collectionInfo.collection.path?.storagePathId(), - !collectionInfo.isEmpty { + collectionInfo.count > 0 { Router.route(to: RouteMap.NFT.collectionDetail( addr, pathId, From 8cd4bfe78b4254e6029dd8be024ad95a5e5ce180 Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 18 Nov 2024 08:54:09 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat=EF=BC=9A=20add=20track=20on=20flownetw?= =?UTF-8?q?ork=20adn=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FRW.xcodeproj/project.pbxproj | 4 +- FRW/Foundation/Model/Error.swift | 33 + .../ViewModel/BackupUploadViewModel.swift | 4 +- FRW/Modules/NFT/NFTTransferView.swift | 24 +- .../Send/WalletSendAmountViewModel.swift | 9 + FRW/Services/FlowCoin/CadenceManager.swift | 4 +- FRW/Services/Manager/TransactionManager.swift | 9 +- FRW/Services/Network/FlowNetwork.swift | 1224 +++++------------ FRW/Services/Track/EventTrack+Backup.swift | 1 + .../Track/EventTrack+Transaction.swift | 30 +- FRW/Services/Track/EventTrack.swift | 2 +- FRW/Services/Track/EventTrackName.swift | 1 + 12 files changed, 485 insertions(+), 860 deletions(-) diff --git a/FRW.xcodeproj/project.pbxproj b/FRW.xcodeproj/project.pbxproj index d9817179..b3626f49 100644 --- a/FRW.xcodeproj/project.pbxproj +++ b/FRW.xcodeproj/project.pbxproj @@ -8471,7 +8471,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.100; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -8514,7 +8514,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.100; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/FRW/Foundation/Model/Error.swift b/FRW/Foundation/Model/Error.swift index 169f9174..a0fd192c 100644 --- a/FRW/Foundation/Model/Error.swift +++ b/FRW/Foundation/Model/Error.swift @@ -7,6 +7,8 @@ import Foundation +// MARK: - LLError + enum LLError: Error { case aesKeyEncryptionFailed case aesEncryptionFailed @@ -26,6 +28,8 @@ enum LLError: Error { case unknown } +// MARK: - WalletError + enum WalletError: Error { case fetchFailed case fetchBalanceFailed @@ -37,6 +41,8 @@ enum WalletError: Error { case securityVerifyFailed } +// MARK: - BackupError + enum BackupError: Error { case missingUserName case missingMnemonic @@ -48,12 +54,16 @@ enum BackupError: Error { case CloudFileData } +// MARK: - GoogleBackupError + enum GoogleBackupError: Error { case missingLoginUser case noDriveScope case createFileError } +// MARK: - iCloudBackupError + enum iCloudBackupError: Error { case initError case invalidLoadData @@ -65,12 +75,16 @@ enum iCloudBackupError: Error { case fileIsNotExist } +// MARK: - NFTError + enum NFTError: Error { case noCollectionInfo case invalidTokenId case sendInvalidAddress } +// MARK: - StakingError + enum StakingError: Error { case stakingDisabled case stakingNeedSetup @@ -78,6 +92,8 @@ enum StakingError: Error { case stakingCreateDelegatorIdFailed case unknown + // MARK: Internal + var desc: String { switch self { case .stakingDisabled: @@ -90,8 +106,25 @@ enum StakingError: Error { } } +// MARK: - EVMError + enum EVMError: Error { case createAccount case findAddress case transactionResult } + +// MARK: - CadenceError + +enum CadenceError: Error { + case empty + + // MARK: Internal + + var message: String { + switch self { + case .empty: + "empty script" + } + } +} diff --git a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift index 0f159b68..abc6020b 100644 --- a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift +++ b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift @@ -173,7 +173,7 @@ class BackupUploadViewModel: ObservableObject { } } catch { buttonState = .enabled - trackCreatFailed(message: error.localizedDescription) + trackCreatFailed(message: "idle:" + error.localizedDescription) } } case .upload: @@ -190,6 +190,7 @@ class BackupUploadViewModel: ObservableObject { buttonState = .enabled hasError = true log.error(error) + trackCreatFailed(message: "upload:" + error.localizedDescription) } } case .regist: @@ -211,6 +212,7 @@ class BackupUploadViewModel: ObservableObject { buttonState = .enabled HUD.dismissLoading() log.error(error) + trackCreatFailed(message: "regist:" + error.localizedDescription) } } case .finish: diff --git a/FRW/Modules/NFT/NFTTransferView.swift b/FRW/Modules/NFT/NFTTransferView.swift index 120f6550..44a8cb6e 100644 --- a/FRW/Modules/NFT/NFTTransferView.swift +++ b/FRW/Modules/NFT/NFTTransferView.swift @@ -125,6 +125,19 @@ class NFTTransferViewModel: ObservableObject { case coa case eoa case linked + + var trackName: String { + switch self { + case .flow: + "flow" + case .coa: + "coa" + case .eoa: + "evm" + case .linked: + "child" + } + } } if isRequesting { @@ -333,7 +346,16 @@ class NFTTransferViewModel: ObservableObject { failedBlock() return } - + EventTrack.Transaction + .NFTTransfer( + from: currentAddress, + to: toAddress, + identifier: nft.response.flowIdentifier ?? "", + txId: tid?.hex ?? "", + fromType: fromAccountType.trackName, + toType: toAccountType.trackName, + isMove: false + ) let model = NFTTransferModel( nft: nft, target: self.targetContact, diff --git a/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift b/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift index f7f054b7..a891da9a 100644 --- a/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift +++ b/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift @@ -451,6 +451,15 @@ extension WalletSendAmountViewModel { return } + EventTrack.Transaction + .ftTransfer( + from: address, + to: targetAddress, + type: token.symbol ?? "", + amount: self.inputTokenNum, + identifier: token.contractId + ) + DispatchQueue.main.async { let obj = CoinTransferModel( amount: self.inputTokenNum, diff --git a/FRW/Services/FlowCoin/CadenceManager.swift b/FRW/Services/FlowCoin/CadenceManager.swift index 1b11ed6f..026b230d 100644 --- a/FRW/Services/FlowCoin/CadenceManager.swift +++ b/FRW/Services/FlowCoin/CadenceManager.swift @@ -413,10 +413,10 @@ extension String { return String(data: data, encoding: .utf8) } - public func toFunc() -> String { + public func toFunc() -> String? { guard let decodeStr = fromBase64() else { log.error("[Cadence] base decode failed") - return "" + return nil } let result = decodeStr.replacingOccurrences(of: "", with: platformInfo()) diff --git a/FRW/Services/Manager/TransactionManager.swift b/FRW/Services/Manager/TransactionManager.swift index f7fed61d..6f0b6336 100644 --- a/FRW/Services/Manager/TransactionManager.swift +++ b/FRW/Services/Manager/TransactionManager.swift @@ -282,7 +282,14 @@ extension TransactionManager { } } - private func trackResult(result: Flow.TransactionResult, fromId: String) {} + private func trackResult(result: Flow.TransactionResult, fromId: String) { + EventTrack.Transaction + .transactionResult( + txId: transactionId.hex, + successful: result.isComplete, + message: result.errorMessage + ) + } private func postNotification() { debugPrint("TransactionHolder -> postNotification status: \(status)") diff --git a/FRW/Services/Network/FlowNetwork.swift b/FRW/Services/Network/FlowNetwork.swift index ac819302..42c5127d 100644 --- a/FRW/Services/Network/FlowNetwork.swift +++ b/FRW/Services/Network/FlowNetwork.swift @@ -25,41 +25,25 @@ enum FlowNetwork { extension FlowNetwork { static func checkTokensEnable(address: Flow.Address) async throws -> [String: Bool] { - let cadence = CadenceManager.shared.current.ft?.isTokenListEnabled?.toFunc() ?? "" - return try await fetch(at: address, by: cadence) + try await fetch( + by: \.ft?.isTokenListEnabled, + arguments: [.address(address)] + ) } static func fetchBalance(at address: Flow.Address) async throws -> [String: Double] { - let cadence = CadenceManager.shared.current.ft?.getTokenListBalance?.toFunc() ?? "" - return try await fetch(at: address, by: cadence) + try await fetch( + by: \.ft?.getTokenListBalance, + arguments: [.address(address)] + ) } static func enableToken(at address: Flow.Address, token: TokenModel) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.ft?.addToken?.toFunc() ?? "" - let cadenceString = token.formatCadence(cadence: originCadence) - let fromKeyIndex = WalletManager.shared.keyIndex - - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - gasLimit { - 9999 - } - } + try await sendTransaction( + by: \.ft?.addToken, + with: token, + argumentList: [] + ) } static func transferToken( @@ -67,39 +51,10 @@ extension FlowNetwork { amount: Decimal, token: TokenModel ) async throws -> Flow.ID { - let cadenceString = TokenCadence.tokenTransfer(token: token, at: flow.chainID) - let currentAdd = WalletManager.shared.getPrimaryWalletAddress() ?? "" - let keyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: currentAdd), - keyIndex: keyIndex - ) - } - - authorizers { - Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - } - - arguments { - [.ufix64(amount), .address(address)] - } - - gasLimit { - 9999 - } - } + try await sendTransaction( + by: \.ft?.transferTokens, + with: token, + argumentList: [.ufix64(amount), .address(address)] ) } @@ -107,9 +62,8 @@ extension FlowNetwork { guard let fromAddress = WalletManager.shared.getPrimaryWalletAddress() else { throw LLError.invalidAddress } - let cadenceString = CadenceManager.shared.current.basic?.getAccountMinFlow?.toFunc() ?? "" let result: Decimal = try await fetch( - cadence: cadenceString, + by: \.basic?.getAccountMinFlow, arguments: [.address(Flow.Address(hex: fromAddress))] ) return result.doubleValue @@ -120,10 +74,10 @@ extension FlowNetwork { extension FlowNetwork { static func checkCollectionEnable(address: Flow.Address) async throws -> [String: Bool] { - let originCadence = CadenceManager.shared.current.nft?.checkNFTListEnabled?.toFunc() ?? "" - let cadence = originCadence.replace(by: ScriptAddress.addressMap()) -// let cadence = NFTCadence.collectionListCheckEnabled(with: list, on: flow.chainID) - let result: [String: Bool] = try await fetch(at: address, by: cadence) + let result: [String: Bool] = try await fetch( + by: \.nft?.checkNFTListEnabled, + arguments: [.address(address)] + ) return result } @@ -131,33 +85,10 @@ extension FlowNetwork { at address: Flow.Address, collection: NFTCollectionInfo ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.collection?.enableNFTStorage? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: originCadence) - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - gasLimit { - 9999 - } - } + try await sendTransaction( + by: \.collection?.enableNFTStorage, + with: collection, + argumentList: [] ) } @@ -180,46 +111,13 @@ extension FlowNetwork { throw NFTError.invalidTokenId } - var nftTransfer = CadenceManager.shared.current.collection?.sendNFT?.toFunc() ?? "" - let nbaNFTTransfer = CadenceManager.shared.current.collection?.sendNbaNFT?.toFunc() ?? "" - let result = CadenceManager.shared.current.version?.compareVersion(to: "1.0.0") - if result != .orderedAscending { - nftTransfer = CadenceManager.shared.current.collection?.sendNFT?.toFunc() ?? "" - } - - let cadenceString = collection - .formatCadence(script: nft.isNBA ? nbaNFTTransfer : nftTransfer) - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: fromAddress), - keyIndex: fromKeyIndex - ) - } - - authorizers { - Flow.Address(hex: fromAddress) - } + var nftTransfer: KeyPath = \.collection?.sendNFT + let nbaNFTTransfer: KeyPath = \.collection?.sendNbaNFT - arguments { - [.address(address), .uint64(tokenIdInt)] - } - - gasLimit { - 9999 - } - } + return try await sendTransaction( + by: nft.isNBA ? nbaNFTTransfer : nftTransfer, + with: collection, + argumentList: [.address(address), .uint64(tokenIdInt)] ) } } @@ -228,20 +126,20 @@ extension FlowNetwork { extension FlowNetwork { static func queryAddressByDomainFind(domain: String) async throws -> String { - let cadence = CadenceManager.shared.current.basic?.getFindAddress?.toFunc() ?? "" - return try await fetch(cadence: cadence, arguments: [.string(domain)]) + try await fetch(by: \.basic?.getFindAddress, arguments: [.string(domain)]) } static func queryAddressByDomainFlowns( domain: String, root: String = "fn" ) async throws -> String { - let cadence = CadenceManager.shared.current.basic?.getFlownsAddress?.toFunc() ?? "" - let realDomain = domain .replacingOccurrences(of: ".fn", with: "") .replacingOccurrences(of: ".meow", with: "") - return try await fetch(cadence: cadence, arguments: [.string(realDomain), .string(root)]) + return try await fetch( + by: \.basic?.getFlownsAddress, + arguments: [.string(realDomain), .string(root)] + ) } } @@ -255,45 +153,15 @@ extension FlowNetwork { amount: Decimal, root: String = Contact.DomainType.meow.domain ) async throws -> Flow.ID { - guard let address = WalletManager.shared.getPrimaryWalletAddress() else { - throw LLError.invalidAddress - } - let cadenceString = coin - .formatCadence( - cadence: CadenceManager.shared.current.domain?.claimFTFromInbox? - .toFunc() ?? "" - ) - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: address), - keyIndex: fromKeyIndex - ) - } - - authorizers { - Flow.Address(hex: address) - } - - arguments { - [.string(domain), .string(root), .string(key), .ufix64(amount)] - } - - gasLimit { - 9999 - } - } + try await sendTransaction( + by: \.domain?.claimFTFromInbox, + with: coin, + argumentList: [ + .string(domain), + .string(root), + .string(key), + .ufix64(amount), + ] ) } @@ -304,45 +172,10 @@ extension FlowNetwork { itemId: UInt64, root: String = Contact.DomainType.meow.domain ) async throws -> Flow.ID { - guard let address = WalletManager.shared.getPrimaryWalletAddress() else { - throw LLError.invalidAddress - } - let cadenceString = collection - .formatCadence( - script: CadenceManager.shared.current.domain?.claimNFTFromInbox? - .toFunc() ?? "" - ) - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: address), - keyIndex: fromKeyIndex - ) - } - - authorizers { - Flow.Address(hex: address) - } - - arguments { - [.string(domain), .string(root), .string(key), .uint64(itemId)] - } - - gasLimit { - 9999 - } - } + try await sendTransaction( + by: \.domain?.claimNFTFromInbox, + with: collection, + argumentList: [.string(domain), .string(root), .string(key), .uint64(itemId)] ) } } @@ -370,14 +203,10 @@ extension FlowNetwork { let tokenName = String(swapPaths.last?.split(separator: ".").last ?? "") let tokenAddress = String(swapPaths.last?.split(separator: ".")[1] ?? "").addHexPrefix() - let fromCadence = CadenceManager.shared.current.swap?.SwapExactTokensForTokens? - .toFunc() ?? "" - let toCadence = CadenceManager.shared.current.swap?.SwapTokensForExactTokens?.toFunc() ?? "" - var cadenceString = isFrom ? fromCadence : toCadence - cadenceString = cadenceString - .replace(by: ["Token1Name": tokenName, "Token1Addr": tokenAddress]) - .replace(by: ScriptAddress.addressMap()) - log.error("[Cadence] swap from:\n \(cadenceString)") + let fromCadence: KeyPath = \.swap?.SwapExactTokensForTokens + let toCadence: KeyPath = \.swap?.SwapTokensForExactTokens + var cadenceKeyPath = isFrom ? fromCadence : toCadence + var args = [Flow.Cadence.FValue]() args.append(.array(swapPaths.map { .string($0) })) @@ -395,36 +224,11 @@ extension FlowNetwork { args.append(.path(Flow.Argument.Path(domain: "public", identifier: tokenOutReceiverPath))) args.append(.path(Flow.Argument.Path(domain: "public", identifier: tokenOutBalancePath))) let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: address), - keyIndex: fromKeyIndex - ) - } - - authorizers { - Flow.Address(hex: address) - } - - arguments { - args - } - gasLimit { - 9999 - } - } + return try await sendTransaction( + by: cadenceKeyPath, + with: ["Token1Name": tokenName, "Token1Addr": tokenAddress], + argumentList: args ) } } @@ -437,47 +241,25 @@ enum LilicoError: Error { extension FlowNetwork { static func stakingIsEnabled() async throws -> Bool { - let cadence = CadenceManager.shared.current.staking?.checkStakingEnabled?.toFunc() ?? "" let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - return try await fetch(cadence: cadence, arguments: []) + return try await fetch(by: \.staking?.checkStakingEnabled, arguments: []) } static func accountStakingIsSetup() async throws -> Bool { - let cadence = CadenceManager.shared.current.staking?.checkSetup?.toFunc() ?? "" let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - return try await fetch(cadence: cadence, arguments: [.address(address)]) + return try await fetch(by: \.staking?.checkSetup, arguments: [.address(address)]) } static func claimUnstake(nodeID: String, delegatorId: Int, amount: Decimal) async throws -> Flow .ID { - guard let walletAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LilicoError.emptyWallet - } - - let address = Flow.Address(hex: walletAddress) - let cadenceOrigin = CadenceManager.shared.current.staking?.withdrawUnstaked?.toFunc() ?? "" - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceOrigin.replace(by: ScriptAddress.addressMap()) - } - - arguments { - [.string(nodeID), .uint32(UInt32(delegatorId)), .ufix64(amount)] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - } + try await sendTransaction( + by: \.staking?.withdrawUnstaked, + argumentList: [ + .string(nodeID), + .uint32(UInt32(delegatorId)), + .ufix64(amount), + ] + ) } static func reStakeUnstake( @@ -485,66 +267,23 @@ extension FlowNetwork { delegatorId: Int, amount: Decimal ) async throws -> Flow.ID { - guard let walletAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LilicoError.emptyWallet - } - - let address = Flow.Address(hex: walletAddress) - let cadenceOrigin = CadenceManager.shared.current.staking?.restakeUnstaked?.toFunc() ?? "" - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceOrigin.replace(by: ScriptAddress.addressMap()) - } - - arguments { - [.string(nodeID), .uint32(UInt32(delegatorId)), .ufix64(amount)] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - } + try await sendTransaction( + by: \.staking?.restakeUnstaked, + argumentList: [ + .string(nodeID), + .uint32(UInt32(delegatorId)), + .ufix64(amount), + ] + ) } + // FIXME: static func claimReward(nodeID: String, delegatorId: Int, amount: Decimal) async throws -> Flow .ID { - guard let walletAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LilicoError.emptyWallet - } - - let address = Flow.Address(hex: walletAddress) - let cadenceOrigin = CadenceManager.shared.current.staking?.withdrawReward?.toFunc() ?? "" - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceOrigin.replace(by: ScriptAddress.addressMap()) - } - - arguments { - [.string(nodeID), .uint32(UInt32(delegatorId)), .ufix64(amount)] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - } + try await sendTransaction( + by: \.staking?.withdrawReward, + argumentList: [.string(nodeID), .uint32(UInt32(delegatorId)), .ufix64(amount)] + ) } static func reStakeReward( @@ -552,68 +291,19 @@ extension FlowNetwork { delegatorId: Int, amount: Decimal ) async throws -> Flow.ID { - guard let walletAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LilicoError.emptyWallet - } - - let address = Flow.Address(hex: walletAddress) - let cadenceOrigin = CadenceManager.shared.current.staking?.restakeReward?.toFunc() ?? "" - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceOrigin.replace(by: ScriptAddress.addressMap()) - } - - arguments { - [.string(nodeID), .uint32(UInt32(delegatorId)), .ufix64(amount)] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - } + try await sendTransaction( + by: \.staking?.restakeReward, + argumentList: [ + .string(nodeID), + .uint32(UInt32(delegatorId)), + .ufix64(amount), + ] + ) } static func setupAccountStaking() async throws -> Bool { - let cadenceOrigin = CadenceManager.shared.current.staking?.setup?.toFunc() ?? "" - let cadenceString = cadenceOrigin.replace(by: ScriptAddress.addressMap()) - - guard let walletAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LilicoError.emptyWallet - } - let address = Flow.Address(hex: walletAddress) - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - } - ) - + let txId = try await sendTransaction(by: \.staking?.setup, argumentList: []) let result = try await txId.onceSealed() - if result.isFailed { debugPrint("FlowNetwork: setupAccountStaking failed msg: \(result.errorMessage)") return false @@ -623,74 +313,23 @@ extension FlowNetwork { } static func createDelegatorId(providerId: String, amount: Double = 0) async throws -> Flow.ID { - let cadenceOrigin = CadenceManager.shared.current.staking?.createDelegator?.toFunc() ?? "" - let cadenceString = cadenceOrigin.replace(by: ScriptAddress.addressMap()) - let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - arguments { - [.string(providerId), .ufix64(Decimal(amount))] - } - - gasLimit { - 9999 - } - } + let txId = try await sendTransaction( + by: \.staking?.createDelegator, + argumentList: [.string(providerId), .ufix64(Decimal(amount))] ) + return txId } static func stakeFlow(providerId: String, delegatorId: Int, amount: Double) async throws -> Flow .ID { - let cadenceOrigin = CadenceManager.shared.current.staking?.createStake?.toFunc() ?? "" - let cadenceString = cadenceOrigin.replace(by: ScriptAddress.addressMap()) - let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - arguments { - [.string(providerId), .uint32(UInt32(delegatorId)), .ufix64(Decimal(amount))] - } - - gasLimit { - 9999 - } - } + let txId = try await sendTransaction( + by: \.staking?.createStake, + argumentList: [ + .string(providerId), + .uint32(UInt32(delegatorId)), + .ufix64(Decimal(amount)), + ] ) return txId @@ -701,37 +340,13 @@ extension FlowNetwork { delegatorId: Int, amount: Double ) async throws -> Flow.ID { - let cadenceOrigin = CadenceManager.shared.current.staking?.unstake?.toFunc() ?? "" - let cadenceString = cadenceOrigin.replace(by: ScriptAddress.addressMap()) - let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - arguments { - [.string(providerId), .uint32(UInt32(delegatorId)), .ufix64(Decimal(amount))] - } - - gasLimit { - 9999 - } - } + let txId = try await sendTransaction( + by: \.staking?.unstake, + argumentList: [ + .string(providerId), + .uint32(UInt32(delegatorId)), + .ufix64(Decimal(amount)), + ] ) return txId @@ -739,23 +354,21 @@ extension FlowNetwork { static func queryStakeInfo() async throws -> [StakingNode]? { let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let cadence = CadenceManager.shared.current.staking?.getDelegatesInfoArray?.toFunc() ?? "" - let response: [StakingNode] = try await fetch(at: address, by: cadence) + let response: [StakingNode] = try await fetch( + by: \.staking?.getDelegatesInfoArray, + arguments: [.address(address)] + ) debugPrint("FlowNetwork -> queryStakeInfo, response = \(response)") return response } static func getStakingApyByWeek() async throws -> Double { - let candence = CadenceManager.shared.current.staking?.getApyWeekly?.toFunc() ?? "" - let result: Decimal = try await fetch(cadence: candence, arguments: []) - + let result: Decimal = try await fetch(by: \.staking?.getApyWeekly, arguments: []) return result.doubleValue } static func getStakingApyByYear() async throws -> Double { - let candence = CadenceManager.shared.current.staking?.getApr?.toFunc() ?? "" - let result: Decimal = try await fetch(cadence: candence, arguments: []) - + let result: Decimal = try await fetch(by: \.staking?.getApr, arguments: []) return result.doubleValue } @@ -794,44 +407,17 @@ extension FlowNetwork { extension FlowNetwork { static func queryChildAccountList() async throws -> [String] { let address = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let cadence = CadenceManager.shared.current.hybridCustody?.getChildAccount?.toFunc() ?? "" - let response: [String] = try await fetch(at: address, by: cadence) + let response: [String] = try await fetch( + by: \.hybridCustody?.getChildAccount, + arguments: [.address(address)] + ) return response } static func unlinkChildAccount(_ address: String) async throws -> Flow.ID { - let cadenceOrigin = CadenceManager.shared.current.hybridCustody?.unlinkChildAccount? - .toFunc() ?? "" - let cadenceString = cadenceOrigin.replace(by: ScriptAddress.addressMap()) - let walletAddress = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: walletAddress, keyIndex: fromKeyIndex) - } - - authorizers { - walletAddress - } - - arguments { - [.address(Flow.Address(hex: address))] - } - - gasLimit { - 9999 - } - } + let txId = try await sendTransaction( + by: \.hybridCustody?.unlinkChildAccount, + argumentList: [.address(Flow.Address(hex: address))] ) return txId @@ -873,44 +459,12 @@ extension FlowNetwork { desc: String, thumbnail: String ) async throws -> Flow.ID { - let editChildAccount = CadenceManager.shared.current.hybridCustody?.editChildAccount? - .toFunc() ?? "" - let cadenceString = editChildAccount.replace(by: ScriptAddress.addressMap()) - let walletAddress = Flow.Address(hex: WalletManager.shared.getPrimaryWalletAddress() ?? "") - let fromKeyIndex = WalletManager.shared.keyIndex - let txId = try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - cadenceString - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: walletAddress, keyIndex: fromKeyIndex) - } - - authorizers { - walletAddress - } - - arguments { - [ - .address(Flow.Address(hex: address)), - .string(name), - .string(desc), - .string(thumbnail), - ] - } - - gasLimit { - 9999 - } - } - ) + let txId = try await sendTransaction(by: \.hybridCustody?.editChildAccount, argumentList: [ + .address(Flow.Address(hex: address)), + .string(name), + .string(desc), + .string(thumbnail), + ]) return txId } @@ -955,12 +509,10 @@ extension FlowNetwork { identifier: String, collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.transferChildNFT? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) let childAddress = Flow.Address(hex: childAddress) return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.transferChildNFT, + with: collection, argumentList: [.address(childAddress), .string(identifier), .uint64(nftId)] ) } @@ -972,11 +524,9 @@ extension FlowNetwork { identifier: String, collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.transferNFTToChild? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) - return try await sendTransaction( - cadenceStr: cadenceString, + try await sendTransaction( + by: \.hybridCustody?.transferNFTToChild, + with: collection, argumentList: [ .address(Flow.Address(hex: childAddress)), .string(identifier), @@ -993,12 +543,11 @@ extension FlowNetwork { identifier: String, collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.sendChildNFT?.toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) let childAddr = Flow.Address(hex: childAddress) let toAddr = Flow.Address(hex: toAddress) return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.sendChildNFT, + with: collection, argumentList: [ .address(childAddr), .address(toAddr), @@ -1016,13 +565,11 @@ extension FlowNetwork { identifier: String, collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.sendChildNFTToChild? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) let childAddr = Flow.Address(hex: childAddress) let toAddr = Flow.Address(hex: toAddress) return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.sendChildNFTToChild, + with: collection, argumentList: [ .address(childAddr), .address(toAddr), @@ -1035,7 +582,10 @@ extension FlowNetwork { static func linkedAccountEnabledTokenList(address: String) async throws -> [String: Bool] { let cadence = CadenceManager.shared.current.ft?.isLinkedAccountTokenListEnabled? .toFunc() ?? "" - return try await fetch(at: Flow.Address(hex: address), by: cadence) + return try await fetch( + by: \.ft?.isLinkedAccountTokenListEnabled, + arguments: [.address(Flow.Address(hex: address))] + ) } static func checkChildLinkedCollections( @@ -1068,16 +618,14 @@ extension FlowNetwork { ids: [UInt64], collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.batchTransferChildNFT? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) let childAddress = Flow.Address(hex: address) let idMaped = ids.map { Flow.Cadence.FValue.uint64($0) } let ident = identifier.split(separator: "/").last.map { String($0) } ?? identifier return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.batchTransferChildNFT, + with: collection, argumentList: [.address(childAddress), .string(ident), .array(idMaped)] ) } @@ -1096,7 +644,8 @@ extension FlowNetwork { let ident = identifier.split(separator: "/").last.map { String($0) } ?? identifier return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.batchTransferNFTToChild, + with: collection, argumentList: [.address(childAddress), .string(ident), .array(idMaped)] ) } @@ -1108,16 +657,14 @@ extension FlowNetwork { ids: [UInt64], collection: NFTCollectionInfo ) async throws -> Flow.ID { - let accessible = CadenceManager.shared.current.hybridCustody?.batchSendChildNFTToChild? - .toFunc() ?? "" - let cadenceString = collection.formatCadence(script: accessible) let fromAddr = Flow.Address(hex: fromAddress) let toAddr = Flow.Address(hex: toAddress) let idMaped = ids.map { Flow.Cadence.FValue.uint64($0) } let ident = identifier.split(separator: "/").last.map { String($0) } ?? identifier return try await sendTransaction( - cadenceStr: cadenceString, + by: \.hybridCustody?.batchSendChildNFTToChild, + with: collection, argumentList: [.address(fromAddr), .address(toAddr), .string(ident), .array(idMaped)] ) } @@ -1166,38 +713,6 @@ extension FlowNetwork { } } -// MARK: - Base - -extension FlowNetwork { - private static func fetch( - at address: Flow.Address, - by cadence: String - ) async throws -> T { - let replacedCadence = cadence.replace(by: ScriptAddress.addressMap()) - - let response = try await flow.accessAPI.executeScriptAtLatestBlock( - script: Flow.Script(text: replacedCadence), - arguments: [.address(address)] - ) - let model: T = try response.decode() - return model - } - - private static func fetch( - cadence: String, - arguments: [Flow.Cadence.FValue] - ) async throws -> T { - let replacedCadence = cadence.replace(by: ScriptAddress.addressMap()) - - let response = try await flow.accessAPI.executeScriptAtLatestBlock( - script: Flow.Script(text: replacedCadence), - arguments: arguments - ) - let model: T = try response.decode() - return model - } -} - // MARK: - Extension extension Flow.TransactionResult { @@ -1233,31 +748,7 @@ extension Flow.TransactionResult { extension FlowNetwork { static func revokeAccountKey(by index: Int, at address: Flow.Address) async throws -> Flow.ID { - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction( - signers: WalletManager.shared.defaultSigners, - builder: { - cadence { - CadenceManager.shared.current.basic?.revokeKey?.toFunc() ?? "" - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - - authorizers { - address - } - - arguments { - [.int(index)] - } - } - ) + try await sendTransaction(by: \.basic?.revokeKey, argumentList: [.int(index)]) } static func addKeyToAccount( @@ -1265,32 +756,15 @@ extension FlowNetwork { accountKey: Flow.AccountKey, signers: [FlowSigner] ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.basic?.addKey?.toFunc() ?? "" - let fromKeyIndex = WalletManager.shared.keyIndex - return try await flow.sendTransaction(signers: signers) { - cadence { - originCadence - } - arguments { - [ - .string(accountKey.publicKey.hex), - .uint8(UInt8(accountKey.signAlgo.index)), - .uint8(UInt8(accountKey.hashAlgo.code)), - .ufix64(Decimal(accountKey.weight)), - ] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey(address: address, keyIndex: fromKeyIndex) - } - authorizers { - address - } - } + try await sendTransaction( + by: \.basic?.addKey, + argumentList: [ + .string(accountKey.publicKey.hex), + .uint8(UInt8(accountKey.signAlgo.index)), + .uint8(UInt8(accountKey.hashAlgo.code)), + .ufix64(Decimal(accountKey.weight)), + ] + ) } static func addKeyWithMulti( @@ -1300,35 +774,15 @@ extension FlowNetwork { accountKey: Flow.AccountKey, signers: [FlowSigner] ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.basic?.addKey?.toFunc() ?? "" - return try await flow.sendTransaction(signers: signers) { - cadence { - originCadence - } - arguments { - [ - .string(accountKey.publicKey.hex), - .uint8(UInt8(accountKey.signAlgo.index)), - .uint8(UInt8(accountKey.hashAlgo.code)), - .ufix64(Decimal(accountKey.weight)), - ] - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: address, - keyIndex: keyIndex, - sequenceNumber: sequenceNum - ) - } - authorizers { - address - } - } + try await sendTransaction( + by: \.basic?.addKey, + argumentList: [ + .string(accountKey.publicKey.hex), + .uint8(UInt8(accountKey.signAlgo.index)), + .uint8(UInt8(accountKey.hashAlgo.code)), + .ufix64(Decimal(accountKey.weight)), + ] + ) } } @@ -1339,34 +793,7 @@ extension FlowNetwork { guard let fromAddress = WalletManager.shared.getPrimaryWalletAddress() else { throw LLError.invalidAddress } - let originCadence = CadenceManager.shared.current.evm?.createCoaEmpty?.toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - let fromKeyIndex = WalletManager.shared.keyIndex - - return try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceStr - } - - payer { - RemoteConfigManager.shared.payer - } - - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: fromAddress), - keyIndex: fromKeyIndex - ) - } - - authorizers { - Flow.Address(hex: fromAddress) - } - - gasLimit { - 9999 - } - } + return try await sendTransaction(by: \.evm?.createCoaEmpty, argumentList: []) } static func findEVMAddress() async throws -> String { @@ -1402,10 +829,8 @@ extension FlowNetwork { guard let toAddress = WalletManager.shared.getPrimaryWalletAddress() else { throw LLError.invalidAddress } - let originCadence = CadenceManager.shared.current.evm?.withdrawCoa?.toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.evm?.withdrawCoa, argumentList: [ .ufix64(amount), .address(Flow.Address(hex: toAddress)), ]) @@ -1413,9 +838,7 @@ extension FlowNetwork { /// cadence to evm static func fundCoa(amount: Decimal) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.evm?.fundCoa?.toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + try await sendTransaction(by: \.evm?.fundCoa, argumentList: [ .ufix64(amount), ]) } @@ -1430,13 +853,12 @@ extension FlowNetwork { guard let amountParse = Decimal(string: amount) else { throw WalletError.insufficientBalance } - let originCadence = CadenceManager.shared.current.evm?.callContract?.toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) + var argData: Flow.Cadence.FValue = .array([]) if let toValue = data?.cadenceValue { argData = toValue } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.evm?.callContract, argumentList: [ .string(toAddress), .ufix64(amountParse), argData, @@ -1461,10 +883,7 @@ extension FlowNetwork { // transferFlowToEvmAddress static func sendFlowToEvm(evmAddress: String, amount: Decimal, gas: UInt64) async throws -> Flow .ID { - let originCadence = CadenceManager.shared.current.evm?.transferFlowToEvmAddress? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + try await sendTransaction(by: \.evm?.transferFlowToEvmAddress, argumentList: [ .string(evmAddress), .ufix64(amount), .uint64(gas), @@ -1474,10 +893,7 @@ extension FlowNetwork { /// transferFlowFromCoaToFlow static func sendFlowTokenFromCoaToFlow(amount: Decimal, address: String) async throws -> Flow .ID { - let originCadence = CadenceManager.shared.current.evm?.transferFlowFromCoaToFlow? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + try await sendTransaction(by: \.evm?.transferFlowFromCoaToFlow, argumentList: [ .ufix64(amount), .address(Flow.Address(hex: address)), ]) @@ -1488,12 +904,8 @@ extension FlowNetwork { amount: Decimal, recipient: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.bridge?.bridgeTokensToEvmAddressV2? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) let amountValue = Flow.Cadence.FValue.ufix64(amount) - - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.bridge?.bridgeTokensToEvmAddressV2, argumentList: [ .string(vaultIdentifier), amountValue, .string(recipient), @@ -1511,12 +923,14 @@ extension FlowNetwork { .toFunc() : CadenceManager.shared.current.bridge?.bridgeTokensToEvmV2?.toFunc() ) ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) + let keyPath: KeyPath = fromEvm ? \.bridge? + .bridgeTokensFromEvmV2 : \.bridge?.bridgeTokensToEvmV2 + var amountValue = Flow.Cadence.FValue.ufix64(amount) if let result = Utilities.parseToBigUInt(amount.description, decimals: decimals), fromEvm { amountValue = Flow.Cadence.FValue.uint256(result) } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: keyPath, argumentList: [ .string(vaultIdentifier), amountValue, ]) @@ -1527,11 +941,8 @@ extension FlowNetwork { amount: BigUInt, receiver: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.bridge?.bridgeTokensFromEvmToFlowV2? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) let amountValue = Flow.Cadence.FValue.uint256(amount) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.bridge?.bridgeTokensFromEvmToFlowV2, argumentList: [ .string(identifier), amountValue, .address(Flow.Address(hex: receiver)), @@ -1545,16 +956,13 @@ extension FlowNetwork { ids: [UInt64], fromEvm: Bool ) async throws -> Flow.ID { - let originCadence = ( - fromEvm ? CadenceManager.shared.current.bridge? - .batchBridgeNFTFromEvmV2?.toFunc() - : CadenceManager.shared.current.bridge?.batchBridgeNFTToEvmV2?.toFunc() - ) ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) + let keyPath: KeyPath = fromEvm ? \.bridge? + .batchBridgeNFTFromEvmV2 : \.bridge?.batchBridgeNFTToEvmV2 + let idMaped = fromEvm ? ids.map { Flow.Cadence.FValue.uint256(BigUInt($0)) } : ids .map { Flow.Cadence.FValue.uint64($0) } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: keyPath, argumentList: [ .string(identifier), .array(idMaped), ]) @@ -1565,14 +973,11 @@ extension FlowNetwork { id: String, toAddress: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.bridge?.bridgeNFTToEvmAddressV2? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) guard let nftId = UInt64(id) else { throw NFTError.invalidTokenId } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.bridge?.bridgeNFTToEvmAddressV2, argumentList: [ .string(identifier), .uint64(nftId), .string(toAddress), @@ -1584,15 +989,11 @@ extension FlowNetwork { id: String, receiver: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.bridge?.bridgeNFTFromEvmToFlowV2? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - guard let nftId = BigUInt(id) else { throw NFTError.invalidTokenId } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.bridge?.bridgeNFTFromEvmToFlowV2, argumentList: [ .string(identifier), .uint256(nftId), .address(Flow.Address(hex: receiver)), @@ -1613,9 +1014,7 @@ extension FlowNetwork { } static func coaLink() async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.evm?.coaLink?.toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: []) + try await sendTransaction(by: \.evm?.coaLink, argumentList: []) } /// evm contract address, eg. 0x7f27352D5F83Db87a5A3E00f4B07Cc2138D8ee52 @@ -1641,10 +1040,7 @@ extension FlowNetwork { id: UInt64, child: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.bridgeChildNFTToEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + try await sendTransaction(by: \.hybridCustody?.bridgeChildNFTToEvm, argumentList: [ .string(identifier), .uint64(id), .address(Flow.Address(hex: child)), @@ -1662,7 +1058,7 @@ extension FlowNetwork { let nftId = BigUInt(id) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.hybridCustody?.bridgeChildNFTFromEvm, argumentList: [ .string(identifier), .address(Flow.Address(hex: child)), .uint256(nftId), @@ -1674,17 +1070,15 @@ extension FlowNetwork { ids: [UInt64], child: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.batchBridgeChildNFTToEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - let idMaped = ids.map { Flow.Cadence.FValue.uint64($0) } - - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ - .string(identifier), - .address(Flow.Address(hex: child)), - .array(idMaped), - ]) + return try await sendTransaction( + by: \.hybridCustody?.batchBridgeChildNFTToEvm, + argumentList: [ + .string(identifier), + .address(Flow.Address(hex: child)), + .array(idMaped), + ] + ) } static func batchBridgeChildNFTFromCoa( @@ -1692,17 +1086,16 @@ extension FlowNetwork { ids: [UInt64], child: String ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.batchBridgeChildNFTFromEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) - let idMaped = ids.map { Flow.Cadence.FValue.uint64($0) } - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ - .string(identifier), - .address(Flow.Address(hex: child)), - .array(idMaped), - ]) + return try await sendTransaction( + by: \.hybridCustody?.batchBridgeChildNFTFromEvm, + argumentList: [ + .string(identifier), + .address(Flow.Address(hex: child)), + .array(idMaped), + ] + ) } static func bridgeChildTokenToCoa( @@ -1710,11 +1103,8 @@ extension FlowNetwork { child: String, amount: Decimal ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.bridgeChildFTToEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) let amountValue = Flow.Cadence.FValue.ufix64(amount) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.hybridCustody?.bridgeChildFTToEvm, argumentList: [ .string(vaultIdentifier), .address(Flow.Address(hex: child)), amountValue, @@ -1726,11 +1116,8 @@ extension FlowNetwork { child: String, amount: Decimal ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.bridgeChildFTFromEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) let amountValue = Flow.Cadence.FValue.ufix64(amount) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.hybridCustody?.bridgeChildFTFromEvm, argumentList: [ .string(vaultIdentifier), .address(Flow.Address(hex: child)), amountValue, @@ -1738,46 +1125,186 @@ extension FlowNetwork { } } +// MARK: - Base + extension FlowNetwork { + private static func fetch( + by keyPath: KeyPath, + arguments: [Flow.Cadence.FValue] + ) async throws -> T { + let funcName = keyPath.funcName() + guard let cadence = CadenceManager.shared.current[keyPath: keyPath]?.toFunc() else { + EventTrack.General + .rpcError( + error: CadenceError.empty.message, + scriptId: funcName + ) + log.error("[Cadence] empty script on \(funcName)") + throw CadenceError.empty + } + let replacedCadence = cadence.replace(by: ScriptAddress.addressMap()) + log.info("[Cadence] fetch on \(funcName)") + let response = try await flow.accessAPI.executeScriptAtLatestBlock( + script: Flow.Script(text: replacedCadence), + arguments: arguments + ) + let model: T = try response.decode() + return model + } + private static func sendTransaction( - cadenceStr: String, + by keyPath: KeyPath, argumentList: [Flow.Cadence.FValue] ) async throws -> Flow.ID { - let fromKeyIndex = WalletManager.shared.keyIndex - guard let fromAddress = WalletManager.shared.getPrimaryWalletAddress() else { - throw LLError.invalidAddress + let funcName = keyPath.funcName() + guard let cadence = CadenceManager.shared.current[keyPath: keyPath]?.toFunc() else { + EventTrack.General + .rpcError( + error: CadenceError.empty.message, + scriptId: funcName + ) + log.error("[Cadence] empty script on \(funcName)") + throw CadenceError.empty } - let tranId = try await flow.sendTransaction(signers: WalletManager.shared.defaultSigners) { - cadence { - cadenceStr - } + let replacedCadence = cadence.replace( + by: ScriptAddress.addressMap() + ) + log.info("[Cadence] transaction start on \(funcName)") + return try await sendTransaction( + funcName: funcName, + cadenceStr: replacedCadence, + argumentList: argumentList + ) + } - payer { - RemoteConfigManager.shared.payer - } - arguments { - argumentList - } - proposer { - Flow.TransactionProposalKey( - address: Flow.Address(hex: fromAddress), - keyIndex: fromKeyIndex + private static func sendTransaction( + by keyPath: KeyPath, + with content: [String: String], + argumentList: [Flow.Cadence.FValue] + ) async throws -> Flow.ID { + let funcName = keyPath.funcName() + guard let cadence = CadenceManager.shared.current[keyPath: keyPath]?.toFunc() else { + EventTrack.General + .rpcError( + error: CadenceError.empty.message, + scriptId: funcName ) - } + log.error("[Cadence] empty script on \(funcName)") + throw CadenceError.empty + } + let replacedCadence = cadence.replace(by: content).replace( + by: ScriptAddress.addressMap() + ) + log.info("[Cadence] transaction start on \(funcName)") + return try await sendTransaction( + funcName: funcName, + cadenceStr: replacedCadence, + argumentList: argumentList + ) + } - authorizers { - Flow.Address(hex: fromAddress) - } + private static func sendTransaction( + by keyPath: KeyPath, + with token: TokenModel, + argumentList: [Flow.Cadence.FValue] + ) async throws -> Flow.ID { + let funcName = keyPath.funcName() + guard let cadence = CadenceManager.shared.current[keyPath: keyPath]?.toFunc() else { + EventTrack.General + .rpcError( + error: CadenceError.empty.message, + scriptId: funcName + ) + log.error("[Cadence] empty script on \(funcName)") + throw CadenceError.empty + } + let replacedCadence = token.formatCadence(cadence: cadence) + log.info("[Cadence] transaction start on \(funcName)") + return try await sendTransaction( + funcName: funcName, + cadenceStr: replacedCadence, + argumentList: argumentList + ) + } + + private static func sendTransaction( + by keyPath: KeyPath, + with collection: NFTCollectionInfo, + argumentList: [Flow.Cadence.FValue] + ) async throws -> Flow.ID { + let funcName = keyPath.funcName() + guard let cadence = CadenceManager.shared.current[keyPath: keyPath]?.toFunc() else { + EventTrack.General + .rpcError( + error: CadenceError.empty.message, + scriptId: funcName + ) + log.error("[Cadence] empty script on \(funcName)") + throw CadenceError.empty + } + let replacedCadence = collection.formatCadence(script: cadence) + log.info("[Cadence] transaction start on \(funcName)") + return try await sendTransaction( + funcName: funcName, + cadenceStr: replacedCadence, + argumentList: argumentList + ) + } - gasLimit { - 9999 + private static func sendTransaction( + funcName: String, + cadenceStr: String, + argumentList: [Flow.Cadence.FValue] + ) async throws -> Flow.ID { + do { + let fromKeyIndex = WalletManager.shared.keyIndex + guard let fromAddress = WalletManager.shared.getPrimaryWalletAddress() else { + log.error("[Cadence] transaction invalid address on \(funcName)") + throw LLError.invalidAddress } + let tranId = try await flow + .sendTransaction(signers: WalletManager.shared.defaultSigners) { + cadence { + cadenceStr + } + + payer { + RemoteConfigManager.shared.payer + } + arguments { + argumentList + } + proposer { + Flow.TransactionProposalKey( + address: Flow.Address(hex: fromAddress), + keyIndex: fromKeyIndex + ) + } + + authorizers { + Flow.Address(hex: fromAddress) + } + + gasLimit { + 9999 + } + } + log.info("[Flow] transaction Id:\(tranId.description)") + return tranId + } catch { + EventTrack.General + .rpcError( + error: error.localizedDescription, + scriptId: funcName + ) + log.error("[Cadence] transaction error:\(error.localizedDescription)") + throw error } - log.info("[Flow] transaction Id:\(tranId.description)") - return tranId } } +// MARK: - Helper Category + extension Data { var cadenceValue: Flow.Cadence.FValue { .array(map { $0.cadenceValue }) @@ -1795,3 +1322,12 @@ extension String { compare(version, options: .numeric) } } + +extension KeyPath { + fileprivate func funcName() -> String { + "\(self)".split(separator: ".").last?.replacingOccurrences( + of: "?", + with: "" + ) ?? "" + } +} diff --git a/FRW/Services/Track/EventTrack+Backup.swift b/FRW/Services/Track/EventTrack+Backup.swift index 10564c4a..0fc83781 100644 --- a/FRW/Services/Track/EventTrack+Backup.swift +++ b/FRW/Services/Track/EventTrack+Backup.swift @@ -27,6 +27,7 @@ extension EventTrack.Backup { .send(event: EventTrack.Backup.multiCreationFailed, properties: [ "address": address, "providers": source, + "message": reason, ]) } } diff --git a/FRW/Services/Track/EventTrack+Transaction.swift b/FRW/Services/Track/EventTrack+Transaction.swift index 6a3c2fb6..8428cba0 100644 --- a/FRW/Services/Track/EventTrack+Transaction.swift +++ b/FRW/Services/Track/EventTrack+Transaction.swift @@ -20,7 +20,7 @@ extension EventTrack.Transaction { EventTrack .send(event: EventTrack.Transaction.flowSigned, properties: [ "cadence": cadence, - "id": txId, + "tx_id": txId, "authorizers": authorizers, "proposer": proposer, "payer": payer, @@ -33,7 +33,7 @@ extension EventTrack.Transaction { .send(event: EventTrack.Transaction.flowSigned, properties: [ "flow_address": flowAddress, "evm_address": evmAddress, - "id": txId, + "tx_id": txId, "success": success, ]) } @@ -58,16 +58,30 @@ extension EventTrack.Transaction { static func NFTTransfer( from: String, to: String, - type: String, - amount _: Double, - identifier: String + identifier: String, + txId: String, + fromType: String, + toType: String, + isMove: Bool ) { EventTrack - .send(event: EventTrack.Transaction.FTTransfer, properties: [ + .send(event: EventTrack.Transaction.NFTTransfer, properties: [ "from_address": from, "to_address": to, - "type": type, - "ft_identifier": identifier, + "nft_identifier": identifier, + "tx_id": txId, + "from_type": fromType, + "to_type": toType, + "isMove": isMove, + ]) + } + + static func transactionResult(txId: String, successful: Bool, message: String) { + EventTrack + .send(event: EventTrack.Transaction.result, properties: [ + "tx_id": txId, + "is_successful": successful, + "error_message": message, ]) } } diff --git a/FRW/Services/Track/EventTrack.swift b/FRW/Services/Track/EventTrack.swift index 5f7e6e77..a541cd19 100644 --- a/FRW/Services/Track/EventTrack.swift +++ b/FRW/Services/Track/EventTrack.swift @@ -111,7 +111,7 @@ extension EventTrack { enum Superkey { static let network = "flow_network" static let scriptVersion = "cadence_script_version" - static let cadenceVersion = "cadence_script_version" + static let cadenceVersion = "cadence_version" static let deviceId = "fw_device_id" static let env = "app_env" } diff --git a/FRW/Services/Track/EventTrackName.swift b/FRW/Services/Track/EventTrackName.swift index 3791ce5e..76aaf54c 100644 --- a/FRW/Services/Track/EventTrackName.swift +++ b/FRW/Services/Track/EventTrackName.swift @@ -54,6 +54,7 @@ extension EventTrack { case evmSigned = "evm_transaction_signed" case FTTransfer = "ft_transfer" case NFTTransfer = "nft_transfer" + case result = "transaction_result" // MARK: Internal From 9a7f6b7ab3dec89409b6f06adf4dc7cc2f736ab4 Mon Sep 17 00:00:00 2001 From: cat Date: Wed, 20 Nov 2024 13:20:00 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat=EF=BC=9AUpdated=20Buried=20Point=20Tim?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/RestoreMultiAccountViewModel.swift | 11 ++++++----- FRW/Services/Manager/TransactionManager.swift | 13 +++++++++---- FRW/Services/Track/EventTrack+Transaction.swift | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift b/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift index 640a4473..20c30755 100644 --- a/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift +++ b/FRW/Modules/MultiRestore/ViewModel/RestoreMultiAccountViewModel.swift @@ -30,6 +30,12 @@ class RestoreMultiAccountViewModel: ObservableObject { return } + if let item = selectedUser.first { + let methods = selectedUser.map { $0.backupType?.methodName() ?? "" } + EventTrack.Account + .recovered(address: item.address, mechanism: "multi-backup", methods: methods) + } + // If it is the current user, do nothing and return directly. if let userId = UserManager.shared.activatedUID, userId == selectedUserId { if (try? WallectSecureEnclave.Store.fetchModel(by: selectedUserId)) != nil { @@ -74,11 +80,6 @@ class RestoreMultiAccountViewModel: ObservableObject { guard selectedUser.count > 1 else { return } - if let item = selectedUser.first { - let methods = selectedUser.map { $0.backupType?.methodName() ?? "" } - EventTrack.Account - .recovered(address: item.address, mechanism: "multi-backup", methods: methods) - } Task { do { diff --git a/FRW/Services/Manager/TransactionManager.swift b/FRW/Services/Manager/TransactionManager.swift index 6f0b6336..69e4ee7e 100644 --- a/FRW/Services/Manager/TransactionManager.swift +++ b/FRW/Services/Manager/TransactionManager.swift @@ -260,16 +260,21 @@ extension TransactionManager { debugPrint( "TransactionHolder -> onCheck result failed: \(result.errorMessage)" ) + self.trackResult( + result: result, + fromId: self.transactionId.hex + ) } else if result.isComplete { self.internalStatus = .success + self.trackResult( + result: result, + fromId: self.transactionId.hex + ) } else { self.internalStatus = .pending self.startTimer() } - self.trackResult( - result: result, - fromId: self.transactionId.hex - ) + self.postNotification() } } catch { diff --git a/FRW/Services/Track/EventTrack+Transaction.swift b/FRW/Services/Track/EventTrack+Transaction.swift index 8428cba0..a9279fe5 100644 --- a/FRW/Services/Track/EventTrack+Transaction.swift +++ b/FRW/Services/Track/EventTrack+Transaction.swift @@ -76,12 +76,12 @@ extension EventTrack.Transaction { ]) } - static func transactionResult(txId: String, successful: Bool, message: String) { + static func transactionResult(txId: String, successful: Bool, message: String? = nil) { EventTrack .send(event: EventTrack.Transaction.result, properties: [ "tx_id": txId, "is_successful": successful, - "error_message": message, + "error_message": message ?? "", ]) } } From 6852f5b4fac9f4674fc420a309e8b9a710136270 Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 25 Nov 2024 17:55:32 +0800 Subject: [PATCH 5/9] feat: event track 2 --- .../Browser/Handler/JSMessageHandler.swift | 33 +++++++++++++++++-- .../ViewModel/BackupUploadViewModel.swift | 3 +- .../ProfileSecureViewViewModel.swift | 16 +++++---- .../TrustProvider/TrustJSMessageHandler.swift | 3 ++ FRW/Modules/Wallet/WalletHomeView.swift | 2 -- FRW/Services/Manager/EVMAccountManager.swift | 7 ++++ .../WalletConnectEVMHandler.swift | 10 ++++++ FRW/Services/Track/EventTrack+General.swift | 1 - .../Track/EventTrack+Transaction.swift | 8 +++-- 9 files changed, 68 insertions(+), 15 deletions(-) diff --git a/FRW/Modules/Browser/Handler/JSMessageHandler.swift b/FRW/Modules/Browser/Handler/JSMessageHandler.swift index 84fa8213..6624b034 100644 --- a/FRW/Modules/Browser/Handler/JSMessageHandler.swift +++ b/FRW/Modules/Browser/Handler/JSMessageHandler.swift @@ -5,6 +5,7 @@ // Created by Selina on 5/9/2022. // +import CryptoKit import Flow import TrustWeb3Provider import UIKit @@ -31,7 +32,7 @@ class JSMessageHandler: NSObject { private var processingServiceType: FCLServiceType? private var processingFCLResponse: FCLResponseProtocol? private var readyToSignEnvelope: Bool = false - + private var authzResponse: FCLAuthzResponse? private weak var processingLinkAccountViewModel: ChildAccountLinkViewModel? } @@ -102,6 +103,24 @@ extension JSMessageHandler { data: data ) TransactionManager.shared.newTransaction(holder: holder) + Task { + do { + let result = try await id.onceSealed() + let voucher = authzResponse?.body.voucher + EventTrack.Transaction + .flowSigned( + cadence: hashCadence( + cadence: voucher?.cadence? + .toHexEncodedString() ?? "" + ), + txId: tid, + authorizers: voucher?.authorizers ?? [], + proposer: voucher?.proposalKey.address ?? "", + payer: voucher?.payer ?? "", + success: !result.isFailed + ) + } catch {} + } if let linkAccountVM = processingLinkAccountViewModel { linkAccountVM.onTxID(id) @@ -110,6 +129,16 @@ extension JSMessageHandler { log.error("invalid message", context: error) } } + + private func hashCadence(cadence: String) -> String { + guard !cadence.isEmpty else { + return "" + } + let data = Data(cadence.utf8) + let hash = SHA256.hash(data: data) + let hashString = hash.compactMap { String(format: "%02x", $0) }.joined() + return hashString + } } extension JSMessageHandler { @@ -331,7 +360,7 @@ extension JSMessageHandler { log.debug("handle authz") processingFCLResponse = authzResponse - + self.authzResponse = authzResponse if readyToSignEnvelope, authzResponse.isSignEnvelope { log.debug("will sign envelope") signEnvelope(authzResponse, url: url) diff --git a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift index abc6020b..9628a059 100644 --- a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift +++ b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift @@ -206,7 +206,7 @@ class BackupUploadViewModel: ObservableObject { self.buttonState = .enabled } toggleProcess(process: .finish) -// onClickButton() + trackCreatSuccess() } catch { buttonState = .enabled @@ -216,7 +216,6 @@ class BackupUploadViewModel: ObservableObject { } } case .finish: - trackCreatSuccess() let nextIndex = currentIndex + 1 if items.count <= nextIndex { currentIndex = nextIndex diff --git a/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift b/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift index 611a6e6d..53c8f7f9 100644 --- a/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift +++ b/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift @@ -8,9 +8,12 @@ import SwiftUI class ProfileSecureViewModel: ObservableObject { - @Published var isBionicEnabled: Bool = SecurityManager.shared.isBionicEnabled - @Published var isPinCodeEnabled: Bool = SecurityManager.shared.isPinCodeEnabled - @Published var isLockOnExit: Bool = SecurityManager.shared.isLockOnExitEnabled + @Published + var isBionicEnabled: Bool = SecurityManager.shared.isBionicEnabled + @Published + var isPinCodeEnabled: Bool = SecurityManager.shared.isPinCodeEnabled + @Published + var isLockOnExit: Bool = SecurityManager.shared.isLockOnExitEnabled func changeBionicAction(_ isEnabled: Bool) { if SecurityManager.shared.isBionicEnabled == isEnabled { @@ -18,11 +21,12 @@ class ProfileSecureViewModel: ObservableObject { } if !isEnabled { + EventTrack.General.security(type: .none) SecurityManager.shared.disableBionic() isBionicEnabled = false return } - + EventTrack.General.security(type: .bionic) Task { let result = await SecurityManager.shared.enableBionic() if !result { @@ -44,12 +48,12 @@ class ProfileSecureViewModel: ObservableObject { HUD.error(title: "disable_pin_code_failed".localized) return } - + EventTrack.General.security(type: .none) HUD.success(title: "pin_code_disabled".localized) isPinCodeEnabled = false return } - + EventTrack.General.security(type: .pin) Router.route(to: RouteMap.PinCode.pinCode) } diff --git a/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift b/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift index be97882b..50348a48 100644 --- a/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift +++ b/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift @@ -383,8 +383,11 @@ extension TrustJSMessageHandler { if result.isFailed { HUD.error(title: "transaction failed") self.cancel(id: id) + EventTrack.Transaction + .evmSigned(txId: txid.hex, success: false) return } + EventTrack.Transaction.evmSigned(txId: txid.hex, success: true) let model = try await FlowNetwork.fetchEVMTransactionResult(txid: txid.hex) DispatchQueue.main.async { self.webVC?.webView.tw diff --git a/FRW/Modules/Wallet/WalletHomeView.swift b/FRW/Modules/Wallet/WalletHomeView.swift index 9049025b..0dabcbf6 100644 --- a/FRW/Modules/Wallet/WalletHomeView.swift +++ b/FRW/Modules/Wallet/WalletHomeView.swift @@ -477,8 +477,6 @@ struct WalletHomeView: View { Spacer() Button { - EventTrack.General - .security(type: SecurityManager.shared.securityType) UIImpactFeedbackGenerator(style: .soft).impactOccurred() Router.route(to: RouteMap.Wallet.buyCrypto) } label: { diff --git a/FRW/Services/Manager/EVMAccountManager.swift b/FRW/Services/Manager/EVMAccountManager.swift index 41d52307..de28f438 100644 --- a/FRW/Services/Manager/EVMAccountManager.swift +++ b/FRW/Services/Manager/EVMAccountManager.swift @@ -230,6 +230,13 @@ extension EVMAccountManager { message: result.errorMessage ) throw EVMError.createAccount + } else { + EventTrack.General + .coaCreation( + txId: tid.description, + flowAddress: address, + message: "" + ) } } catch { EventTrack.General diff --git a/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift b/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift index ef5021d6..e879d334 100644 --- a/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift +++ b/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift @@ -221,8 +221,18 @@ struct WalletConnectEVMHandler: WalletConnectChildHandlerProtocol { if tixResult.isFailed { HUD.error(title: "transaction failed") cancel() + EventTrack.Transaction + .evmSigned( + txId: txid.hex, + success: false + ) return } + EventTrack.Transaction + .evmSigned( + txId: txid.hex, + success: true + ) let model = try await FlowNetwork.fetchEVMTransactionResult(txid: txid.hex) DispatchQueue.main.async { confirm(model.hashString?.addHexPrefix() ?? "") diff --git a/FRW/Services/Track/EventTrack+General.swift b/FRW/Services/Track/EventTrack+General.swift index 83438b96..4bf89984 100644 --- a/FRW/Services/Track/EventTrack+General.swift +++ b/FRW/Services/Track/EventTrack+General.swift @@ -45,7 +45,6 @@ extension EventTrack.General { ]) } - /// home page buy button clicked static func security(type: SecurityManager.SecurityType) { EventTrack .send(event: EventTrack.General.securityTool, properties: [ diff --git a/FRW/Services/Track/EventTrack+Transaction.swift b/FRW/Services/Track/EventTrack+Transaction.swift index a9279fe5..53278ee7 100644 --- a/FRW/Services/Track/EventTrack+Transaction.swift +++ b/FRW/Services/Track/EventTrack+Transaction.swift @@ -28,10 +28,14 @@ extension EventTrack.Transaction { ]) } - static func evmSigned(flowAddress: String, evmAddress: String, txId: String, success: Bool) { + static func evmSigned(txId: String, success: Bool) { + guard let primaryAddress = WalletManager.shared.getPrimaryWalletAddress(), + let evmAddress = EVMAccountManager.shared.accounts.first?.showAddress else { + return + } EventTrack .send(event: EventTrack.Transaction.flowSigned, properties: [ - "flow_address": flowAddress, + "flow_address": primaryAddress, "evm_address": evmAddress, "tx_id": txId, "success": success, From 6c72a8e6ce5b20555682cce7cc103a4e6a9aaf71 Mon Sep 17 00:00:00 2001 From: cat Date: Tue, 26 Nov 2024 09:33:08 +0800 Subject: [PATCH 6/9] fix: event name error --- FRW.xcodeproj/project.pbxproj | 4 ++-- .../Wallet/CreateAccount/CreateProfileWaitingViewModel.swift | 2 +- FRW/Services/Track/EventTrack+Transaction.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FRW.xcodeproj/project.pbxproj b/FRW.xcodeproj/project.pbxproj index b3626f49..defa1464 100644 --- a/FRW.xcodeproj/project.pbxproj +++ b/FRW.xcodeproj/project.pbxproj @@ -8471,7 +8471,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.100; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -8514,7 +8514,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.100; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift index 992ecf08..18de80d3 100644 --- a/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift +++ b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift @@ -15,7 +15,7 @@ class CreateProfileWaitingViewModel: ObservableObject { // MARK: Lifecycle deinit { - EventTrack.Account.createdTimeStart() + EventTrack.Account.createdTimeEnd() } init(txId: String, callback: @escaping (Bool) -> Void) { diff --git a/FRW/Services/Track/EventTrack+Transaction.swift b/FRW/Services/Track/EventTrack+Transaction.swift index 53278ee7..8acca5e5 100644 --- a/FRW/Services/Track/EventTrack+Transaction.swift +++ b/FRW/Services/Track/EventTrack+Transaction.swift @@ -34,7 +34,7 @@ extension EventTrack.Transaction { return } EventTrack - .send(event: EventTrack.Transaction.flowSigned, properties: [ + .send(event: EventTrack.Transaction.evmSigned, properties: [ "flow_address": primaryAddress, "evm_address": evmAddress, "tx_id": txId, From cf284ac1c9c5db5d938c19e47af179e7b5d49bfb Mon Sep 17 00:00:00 2001 From: cat Date: Mon, 2 Dec 2024 17:14:52 +0800 Subject: [PATCH 7/9] feat: update event message --- .../ViewModel/BackupUploadViewModel.swift | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift index 9628a059..84ca54f5 100644 --- a/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift +++ b/FRW/Modules/MultiBackup/ViewModel/BackupUploadViewModel.swift @@ -212,7 +212,7 @@ class BackupUploadViewModel: ObservableObject { buttonState = .enabled HUD.dismissLoading() log.error(error) - trackCreatFailed(message: "regist:" + error.localizedDescription) + trackCreatFailed(message: "register:" + error.localizedDescription) } } case .finish: @@ -239,18 +239,7 @@ class BackupUploadViewModel: ObservableObject { extension BackupUploadViewModel { private func trackSource() -> String { - var provider = "google_drive" - switch currentType { - case .google: - break - case .passkey: - provider = "" - case .icloud: - provider = "icloud" - case .phrase: - provider = "seed_phrase" - } - return provider + return currentType.methodName() } func trackCreatSuccess() { From 4fee1fadef0794b67ddae8f8ecc0b9ba4c20c7b8 Mon Sep 17 00:00:00 2001 From: cat Date: Tue, 3 Dec 2024 09:18:58 +0800 Subject: [PATCH 8/9] feat: code optimize --- FRW/Modules/Buy/BuyProvderView.swift | 4 ++-- FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift | 5 +++-- FRW/Services/Track/EventTrack+General.swift | 8 ++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/FRW/Modules/Buy/BuyProvderView.swift b/FRW/Modules/Buy/BuyProvderView.swift index 254071a1..3137edbf 100644 --- a/FRW/Modules/Buy/BuyProvderView.swift +++ b/FRW/Modules/Buy/BuyProvderView.swift @@ -18,7 +18,7 @@ struct BuyProvderView: View { Divider() Button { - EventTrack.General.rampClick(source: "moonpay") + EventTrack.General.rampClick(source: .moonpay) Task { await launchMoonPay() } @@ -38,7 +38,7 @@ struct BuyProvderView: View { if LocalUserDefaults.shared.flowNetwork == .mainnet { Button { - EventTrack.General.rampClick(source: "coinbase") + EventTrack.General.rampClick(source: .coinbase) guard let address = WalletManager.shared.getPrimaryWalletAddress(), let url = URL( diff --git a/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift b/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift index 4fa9a296..ea080e84 100644 --- a/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift +++ b/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift @@ -20,8 +20,9 @@ class EVMEnableViewModel: ObservableObject { let result = WalletManager.shared.activatedCoins.filter { tokenModel in if tokenModel.isFlowCoin { - log.debug("[EVM] enable check balance: \(WalletManager.shared.getBalance(byId: tokenModel.contractId))") - return WalletManager.shared.getBalance(byId: tokenModel.contractId).doubleValue >= minBalance + let balance = WalletManager.shared.getBalance(byId: tokenModel.contractId) + log.debug("[EVM] enable check balance: \(balance)") + return balance.doubleValue >= minBalance } return false } diff --git a/FRW/Services/Track/EventTrack+General.swift b/FRW/Services/Track/EventTrack+General.swift index 4bf89984..c5287aee 100644 --- a/FRW/Services/Track/EventTrack+General.swift +++ b/FRW/Services/Track/EventTrack+General.swift @@ -8,6 +8,10 @@ import Foundation extension EventTrack.General { + enum RampSource: String { + case coinbase + case moonpay + } static func rpcError(error: String, scriptId: String) { EventTrack.send(event: EventTrack.General.rpcError, properties: [ "error": error, @@ -29,10 +33,10 @@ extension EventTrack.General { } /// BuyProvderView button action - static func rampClick(source: String) { + static func rampClick(source: RampSource) { EventTrack .send(event: EventTrack.General.rampClicked, properties: [ - "source": source, + "source": source.rawValue, ]) } From 098311cddfe0001a0a19baf3236c548b5e59f9e9 Mon Sep 17 00:00:00 2001 From: cat Date: Tue, 3 Dec 2024 09:36:27 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat=EF=BC=9A=20It=20might=20be=20useful=20?= =?UTF-8?q?if=20variable=20should=20be=20immutable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FRW/Services/Track/EventTrack.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FRW/Services/Track/EventTrack.swift b/FRW/Services/Track/EventTrack.swift index a541cd19..683cc2b5 100644 --- a/FRW/Services/Track/EventTrack.swift +++ b/FRW/Services/Track/EventTrack.swift @@ -70,11 +70,13 @@ extension EventTrack { Mixpanel .mainInstance() .registerSuperProperties([Superkey.deviceId: UUIDManager.appUUID()]) - var env = "production" + let env: String if RemoteConfigManager.shared.isStaging { env = "staging" } else if isDevModel { env = "development" + } else { + env = "production" } Mixpanel.mainInstance().registerSuperProperties([Superkey.env: env])