diff --git a/FRW.xcodeproj/project.pbxproj b/FRW.xcodeproj/project.pbxproj index 8976f854..7d081ba4 100644 --- a/FRW.xcodeproj/project.pbxproj +++ b/FRW.xcodeproj/project.pbxproj @@ -8531,7 +8531,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -8574,7 +8574,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.flowfoundation.wallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/FRW/App/AppDelegate.swift b/FRW/App/AppDelegate.swift index 85678f4e..ef7e9617 100644 --- a/FRW/App/AppDelegate.swift +++ b/FRW/App/AppDelegate.swift @@ -143,7 +143,7 @@ extension AppDelegate { private func appConfig() { MultiAccountStorage.shared.upgradeFromOldVersionIfNeeded() - + _ = CadenceManager.shared _ = UserManager.shared _ = WalletManager.shared _ = BackupManager.shared @@ -158,7 +158,7 @@ extension AppDelegate { _ = ChildAccountManager.shared WalletManager.shared.bindChildAccountManager() NFTCatalogCache.cache.fetchIfNeed() - _ = CadenceManager.shared + if UserManager.shared.isLoggedIn { DeviceManager.shared.updateDevice() } diff --git a/FRW/Foundation/Model/Error.swift b/FRW/Foundation/Model/Error.swift index 0fc47751..0e266616 100644 --- a/FRW/Foundation/Model/Error.swift +++ b/FRW/Foundation/Model/Error.swift @@ -114,3 +114,18 @@ enum EVMError: Error { 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/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/Buy/BuyProvderView.swift b/FRW/Modules/Buy/BuyProvderView.swift index cae1e58e..3137edbf 100644 --- a/FRW/Modules/Buy/BuyProvderView.swift +++ b/FRW/Modules/Buy/BuyProvderView.swift @@ -18,6 +18,7 @@ struct BuyProvderView: View { Divider() Button { + EventTrack.General.rampClick(source: .moonpay) Task { await launchMoonPay() } @@ -37,6 +38,7 @@ struct BuyProvderView: View { if LocalUserDefaults.shared.flowNetwork == .mainnet { Button { + 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 9760d838..ea080e84 100644 --- a/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift +++ b/FRW/Modules/EVM/ViewModel/EVMEnableViewModel.swift @@ -18,15 +18,11 @@ class EVMEnableViewModel: ObservableObject { func onClickEnable() { let minBalance = 0.000 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/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..84ca54f5 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: "idle:" + error.localizedDescription) } } case .upload: @@ -189,6 +190,7 @@ class BackupUploadViewModel: ObservableObject { buttonState = .enabled hasError = true log.error(error) + trackCreatFailed(message: "upload:" + error.localizedDescription) } } case .regist: @@ -204,12 +206,13 @@ class BackupUploadViewModel: ObservableObject { self.buttonState = .enabled } toggleProcess(process: .finish) -// onClickButton() + trackCreatSuccess() } catch { buttonState = .enabled HUD.dismissLoading() log.error(error) + trackCreatFailed(message: "register:" + error.localizedDescription) } } case .finish: @@ -233,3 +236,18 @@ class BackupUploadViewModel: ObservableObject { } } } + +extension BackupUploadViewModel { + private func trackSource() -> String { + return currentType.methodName() + } + + 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..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,6 +80,7 @@ class RestoreMultiAccountViewModel: ObservableObject { guard selectedUser.count > 1 else { return } + Task { do { HUD.loading() diff --git a/FRW/Modules/NFT/NFTTransferView.swift b/FRW/Modules/NFT/NFTTransferView.swift index dc7d6017..f9d3ea03 100644 --- a/FRW/Modules/NFT/NFTTransferView.swift +++ b/FRW/Modules/NFT/NFTTransferView.swift @@ -127,6 +127,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 { @@ -335,7 +348,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/Profile/ProfileSecure/ProfileSecureViewViewModel.swift b/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift index fd45e855..53c8f7f9 100644 --- a/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift +++ b/FRW/Modules/Profile/ProfileSecure/ProfileSecureViewViewModel.swift @@ -21,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 { @@ -47,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/Staking/StakeAmount/StakeAmountViewModel.swift b/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift index bc8f91ac..12035336 100644 --- a/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift +++ b/FRW/Modules/Staking/StakeAmount/StakeAmountViewModel.swift @@ -190,6 +190,13 @@ extension StakeAmountViewModel { "StakeAmountViewModel: provider.delegatorId is nil, will create delegator id" ) // create delegator id to stake (only first time) + 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 diff --git a/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift b/FRW/Modules/TrustProvider/TrustJSMessageHandler.swift index 50126fa4..5986f6ed 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/CreateAccount/CreateProfileWaitingViewModel.swift b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift index 177642c5..a9ef3dee 100644 --- a/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift +++ b/FRW/Modules/Wallet/CreateAccount/CreateProfileWaitingViewModel.swift @@ -14,6 +14,11 @@ import SwiftUIPager class CreateProfileWaitingViewModel: ObservableObject { // MARK: Lifecycle + deinit { + EventTrack.Account.createdTimeEnd() + } + + init(txId: String, callback: @escaping (Bool) -> Void) { self.txId = Flow.ID(hex: txId) self.callback = callback @@ -27,12 +32,14 @@ class CreateProfileWaitingViewModel: ObservableObject { .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() } // MARK: Internal diff --git a/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift b/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift index b2d4ad30..c05ee2bc 100644 --- a/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift +++ b/FRW/Modules/Wallet/Send/WalletSendAmountViewModel.swift @@ -474,6 +474,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 d48b174b..026b230d 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) @@ -398,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/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 671d7a4b..de28f438 100644 --- a/FRW/Services/Manager/EVMAccountManager.swift +++ b/FRW/Services/Manager/EVMAccountManager.swift @@ -216,11 +216,36 @@ 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 + } else { + EventTrack.General + .coaCreation( + txId: tid.description, + flowAddress: address, + message: "" + ) + } + } catch { + EventTrack.General + .coaCreation( + txId: "", + flowAddress: address, + message: error.localizedDescription + ) + throw error } } diff --git a/FRW/Services/Manager/TransactionManager.swift b/FRW/Services/Manager/TransactionManager.swift index 17302137..91a58b24 100644 --- a/FRW/Services/Manager/TransactionManager.swift +++ b/FRW/Services/Manager/TransactionManager.swift @@ -247,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 @@ -257,6 +257,11 @@ extension TransactionManager { if result.isFailed { self.errorMsg = result.errorMessage self.internalStatus = .failed + + self.trackResult( + result: result, + fromId: self.transactionId.hex + ) debugPrint("TransactionHolder -> onCheck result failed: \(result.errorMessage)") switch result.errorCode { @@ -267,6 +272,10 @@ extension TransactionManager { } } else if result.isComplete { self.internalStatus = .success + self.trackResult( + result: result, + fromId: self.transactionId.hex + ) } else { self.internalStatus = .pending self.startTimer() @@ -284,6 +293,15 @@ extension TransactionManager { } } + 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)") NotificationCenter.default.post(name: .transactionStatusDidChanged, object: self) 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/Manager/WalletConnect/WalletConnectEVMHandler.swift b/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift index 0d48505f..5abfbac2 100644 --- a/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift +++ b/FRW/Services/Manager/WalletConnect/WalletConnectEVMHandler.swift @@ -220,8 +220,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/Network/FlowNetwork.swift b/FRW/Services/Network/FlowNetwork.swift index 72146bf0..895213ea 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)] ) } @@ -1178,38 +725,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 { @@ -1245,31 +760,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( @@ -1277,32 +768,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( @@ -1312,35 +786,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)), + ] + ) } } @@ -1351,34 +805,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 { @@ -1414,10 +841,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)), ]) @@ -1425,9 +850,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), ]) } @@ -1442,13 +865,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, @@ -1473,10 +895,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), @@ -1486,10 +905,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)), ]) @@ -1500,12 +916,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), @@ -1523,13 +935,15 @@ 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 = amount.description.parseToBigUInt(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, ]) @@ -1540,11 +954,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)), @@ -1558,16 +969,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), ]) @@ -1578,14 +986,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), @@ -1597,15 +1002,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)), @@ -1626,9 +1027,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 @@ -1654,10 +1053,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)), @@ -1675,7 +1071,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), @@ -1687,17 +1083,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( @@ -1705,17 +1099,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( @@ -1723,11 +1116,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, @@ -1740,14 +1130,11 @@ extension FlowNetwork { amount: Decimal, decimals: Int ) async throws -> Flow.ID { - let originCadence = CadenceManager.shared.current.hybridCustody?.bridgeChildFTFromEvm? - .toFunc() ?? "" - let cadenceStr = originCadence.replace(by: ScriptAddress.addressMap()) guard let result = amount.description.parseToBigUInt(decimals: decimals) else { throw WalletError.insufficientBalance } let amountValue = Flow.Cadence.FValue.uint256(result) - return try await sendTransaction(cadenceStr: cadenceStr, argumentList: [ + return try await sendTransaction(by: \.hybridCustody?.bridgeChildFTFromEvm, argumentList: [ .string(vaultIdentifier), .address(Flow.Address(hex: child)), amountValue, @@ -1755,46 +1142,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 }) @@ -1835,3 +1362,12 @@ extension String { return mainPart } } + +extension KeyPath { + fileprivate func funcName() -> String { + "\(self)".split(separator: ".").last?.replacingOccurrences( + of: "?", + with: "" + ) ?? "" + } +} 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..0fc83781 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 } @@ -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+General.swift b/FRW/Services/Track/EventTrack+General.swift index 717741a3..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, @@ -15,7 +19,6 @@ extension EventTrack.General { ]) } - /// StakeAmountViewModel stake static func delegationCreated( address: String, nodeId: String, @@ -30,18 +33,41 @@ 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, + ]) + } + + 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+Transaction.swift b/FRW/Services/Track/EventTrack+Transaction.swift index 6a3c2fb6..8acca5e5 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, @@ -28,12 +28,16 @@ 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, + .send(event: EventTrack.Transaction.evmSigned, properties: [ + "flow_address": primaryAddress, "evm_address": evmAddress, - "id": txId, + "tx_id": txId, "success": success, ]) } @@ -58,16 +62,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? = nil) { + 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 af084890..683cc2b5 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,60 @@ 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()]) + let env: String + if RemoteConfigManager.shared.isStaging { + env = "staging" + } else if isDevModel { + env = "development" + } else { + env = "production" + } + + 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_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..76aaf54c 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 @@ -53,6 +54,7 @@ extension EventTrack { case evmSigned = "evm_transaction_signed" case FTTransfer = "ft_transfer" case NFTTransfer = "nft_transfer" + case result = "transaction_result" // MARK: Internal