From 33f52b02d375033a4a420aafc49dfe7be6f202d1 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Tue, 23 Jul 2024 12:45:13 +0200 Subject: [PATCH] WIP: use archived account Ditch ImportedAccount type completely to avoid errors in legacy CI lanes. --- src/GWallet.Backend/Account.fs | 30 +++- src/GWallet.Backend/Config.fs | 4 +- .../GWallet.Backend-legacy.fsproj | 1 - src/GWallet.Backend/GWallet.Backend.fsproj | 1 - .../UtxoCoin/ImportedAccount.fs | 35 ---- .../UtxoCoin/UtxoCoinAccount.fs | 10 -- src/GWallet.Frontend.Console/Program.fs | 149 ++++++++++-------- 7 files changed, 110 insertions(+), 120 deletions(-) delete mode 100644 src/GWallet.Backend/UtxoCoin/ImportedAccount.fs diff --git a/src/GWallet.Backend/Account.fs b/src/GWallet.Backend/Account.fs index 1e1cfa9e6..8cd850802 100644 --- a/src/GWallet.Backend/Account.fs +++ b/src/GWallet.Backend/Account.fs @@ -7,6 +7,8 @@ open System.Threading.Tasks open GWallet.Backend.FSharpUtil.UwpHacks +open NBitcoin + // this exception, if it happens, it would cause a crash because we don't handle it yet type UnhandledCurrencyServerException(currency: Currency, innerException: Exception) = @@ -395,12 +397,30 @@ module Account = |> ignore Config.RemoveNormalAccount account - let ArchiveImportedAccount (importedAccount: UtxoCoin.ImportedAccount) = - let currency = (importedAccount :> IAccount).Currency + let CreateArchivedAccountFromSeedMenmonic (mnemonic: string) : UtxoCoin.ArchivedUtxoAccount = + let rootKey = Mnemonic(mnemonic).DeriveExtKey().Derive(KeyPath("m/84'/0'/0'")) + let firstReceivingAddressKey = rootKey.Derive(0u).Derive(0u) + + let currency = Currency.BTC + let network = UtxoCoin.Account.GetNetwork currency let privateKeyString = - importedAccount.PrivateKey.GetWif(UtxoCoin.Account.GetNetwork currency).ToWif() - CreateArchivedAccount currency privateKeyString - |> ignore + firstReceivingAddressKey.PrivateKey.GetWif(network).ToWif() + let archivedAccount = CreateArchivedAccount currency privateKeyString + + let fromAccountFileToPrivateKey (accountConfigFile: FileRepresentation) = + Key.Parse(accountConfigFile.Content(), network) + + let fromAccountFileToPublicAddress (accountConfigFile: FileRepresentation) = + fromAccountFileToPrivateKey(accountConfigFile).PubKey.GetAddress(ScriptPubKeyType.Segwit, network).ToString() + + let fromAccountFileToPublicKey (accountConfigFile: FileRepresentation) = + fromAccountFileToPrivateKey(accountConfigFile).PubKey + + UtxoCoin.ArchivedUtxoAccount( + currency, + archivedAccount.AccountFile, + fromAccountFileToPublicAddress, + fromAccountFileToPublicKey) let SweepArchivedFunds (account: ArchivedAccount) (balance: decimal) diff --git a/src/GWallet.Backend/Config.fs b/src/GWallet.Backend/Config.fs index a0acf9d70..94f00d9a2 100644 --- a/src/GWallet.Backend/Config.fs +++ b/src/GWallet.Backend/Config.fs @@ -159,7 +159,6 @@ module Config = let configDirForAccounts = GetConfigDirForAccounts() Directory.Delete(configDirForAccounts.FullName, true) - // we don't expose this as public because we don't want to allow removing archived accounts let private RemoveAccount (account: BaseAccount): unit = let configFile = GetFile (account:>IAccount).Currency account if not configFile.Exists then @@ -173,6 +172,9 @@ module Config = let RemoveReadOnlyAccount (account: ReadOnlyAccount): unit = RemoveAccount account + let RemoveArchivedAccount (account: ArchivedAccount): unit = + RemoveAccount account + /// NOTE: the real initialization happens in Account.fs , see isInitialized let internal Init() = // In case a new token was added it will not have a config for an existing user diff --git a/src/GWallet.Backend/GWallet.Backend-legacy.fsproj b/src/GWallet.Backend/GWallet.Backend-legacy.fsproj index 8654d05eb..7f6b17334 100644 --- a/src/GWallet.Backend/GWallet.Backend-legacy.fsproj +++ b/src/GWallet.Backend/GWallet.Backend-legacy.fsproj @@ -70,7 +70,6 @@ - diff --git a/src/GWallet.Backend/GWallet.Backend.fsproj b/src/GWallet.Backend/GWallet.Backend.fsproj index 20cc35a95..75e953bc2 100644 --- a/src/GWallet.Backend/GWallet.Backend.fsproj +++ b/src/GWallet.Backend/GWallet.Backend.fsproj @@ -39,7 +39,6 @@ - diff --git a/src/GWallet.Backend/UtxoCoin/ImportedAccount.fs b/src/GWallet.Backend/UtxoCoin/ImportedAccount.fs deleted file mode 100644 index 1e9bc6037..000000000 --- a/src/GWallet.Backend/UtxoCoin/ImportedAccount.fs +++ /dev/null @@ -1,35 +0,0 @@ -namespace GWallet.Backend.UtxoCoin - -open GWallet.Backend - -open NBitcoin - -type ImportedAccount(mnemonic: string) = - let rootKey = Mnemonic(mnemonic).DeriveExtKey().Derive(KeyPath("m/84'/0'/0'")) - let firstReceivingAddressKey = rootKey.Derive(0u).Derive(0u) - let firstReceivingAddressPubKey = firstReceivingAddressKey.GetPublicKey() - let publicAddress = firstReceivingAddressKey.GetPublicKey().GetAddress(ScriptPubKeyType.Segwit, Network.Main).ToString() - - interface IUtxoAccount with - member self.Currency = Currency.BTC - member self.PublicAddress = publicAddress - member self.PublicKey = firstReceivingAddressPubKey - - member internal self.PrivateKey = firstReceivingAddressKey.PrivateKey - - member self.GetTotalBalance() = - async { - let! maybeBalance = - Account.GetShowableBalanceAndImminentIncomingPayment - self - ServerSelectionMode.Fast - None - return maybeBalance |> Option.map (fun (balance, _) -> balance) - } - - member self.SendFunds (destinationAccount: IAccount) (amount: TransferAmount) = - Account.SendPaymentFromImportedAccount - self - destinationAccount.PublicAddress - amount - firstReceivingAddressKey.PrivateKey diff --git a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs index f3cb2c58e..ccaba5d12 100644 --- a/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs +++ b/src/GWallet.Backend/UtxoCoin/UtxoCoinAccount.fs @@ -535,16 +535,6 @@ module Account = let finalTransaction = SignTransaction account txMetadata destination amount password BroadcastRawTransaction baseAccount.Currency finalTransaction ignoreHigherMinerFeeThanAmount - let internal SendPaymentFromImportedAccount (account: IUtxoAccount) - (destination: string) - (amount: TransferAmount) - (privateKey: Key) = - async { - let! txMetadata = EstimateFee account amount destination - let signedTransaction = SignTransactionWithPrivateKey account txMetadata destination amount privateKey - return! BroadcastRawTransaction account.Currency (signedTransaction.ToHex()) false - } - // TODO: maybe move this func to Backend.Account module, or simply inline it (simple enough) let public ExportUnsignedTransactionToJson trans = Marshalling.Serialize trans diff --git a/src/GWallet.Frontend.Console/Program.fs b/src/GWallet.Frontend.Console/Program.fs index 46d6db9c1..143389d56 100644 --- a/src/GWallet.Frontend.Console/Program.fs +++ b/src/GWallet.Frontend.Console/Program.fs @@ -351,11 +351,11 @@ module Program = () let TransferFundsFromWalletUsingMenmonic() = - let rec askForMnemonic() : UtxoCoin.ImportedAccount = + let rec askForMnemonic() : UtxoCoin.ArchivedUtxoAccount = Console.WriteLine "Enter mnemonic seed phrase (12, 15, 18, 21 or 24 words):" let mnemonic = Console.ReadLine() try - UtxoCoin.ImportedAccount mnemonic + Account.CreateArchivedAccountFromSeedMenmonic mnemonic with | :? FormatException as exn -> printfn "Error reading mnemonic seed phrase: %s" exn.Message @@ -364,72 +364,87 @@ module Program = let importedAccount = askForMnemonic() let currency = BTC - let maybeTotalBalance = importedAccount.GetTotalBalance() |> Async.RunSynchronously - match maybeTotalBalance with - | None -> Console.WriteLine "Could not retrieve balance" - | Some 0.0m -> Console.WriteLine "Balance on imported account is zero. No funds to transfer." - | Some balance -> - let _, maybeUsdValue = UserInteraction.GetAccountBalance importedAccount |> Async.RunSynchronously - - printfn - "Balance on imported account: %s BTC (%s)" - (balance.ToString()) - (UserInteraction.BalanceInUsdString balance maybeUsdValue) - - let rec chooseAccount() = - Console.WriteLine "Choose account to send funds to:" - Console.WriteLine() - let allAccounts = Account.GetAllActiveAccounts() |> Seq.toList - let btcAccounts = allAccounts |> List.filter (fun acc -> acc.Currency = currency) - - match btcAccounts with - | [ singleAccount ] -> Some singleAccount - | [] -> failwith "No BTC accounts found" - | _ -> - allAccounts |> Seq.iteri (fun i account -> - if account.Currency = currency then - let balance, maybeUsdValue = - UserInteraction.GetAccountBalance account - |> Async.RunSynchronously - UserInteraction.DisplayAccountStatus (i + 1) account balance maybeUsdValue - |> Seq.iter Console.WriteLine - ) + let deleteImportedAccountFile() = + Config.RemoveArchivedAccount importedAccount - Console.Write("Write the account number (or 0 to cancel): ") - let accountNumber = Console.ReadLine() - match Int32.TryParse(accountNumber) with - | false, _ -> chooseAccount() - | true, 0 -> None - | true, accountParsed -> - let theAccountChosen = - try - let selectedAccount = allAccounts.[accountParsed - 1] - if selectedAccount.Currency = BTC then - Some selectedAccount - else - chooseAccount() - with - | _ -> chooseAccount() - theAccountChosen - - match chooseAccount() with - | Some targetAccount -> - let destination = targetAccount.PublicAddress - let transferAmount = TransferAmount(balance, balance, currency) // send all funds - let maybeFee = UserInteraction.AskFee importedAccount transferAmount destination - match maybeFee with - | None -> () - | Some(_fee) -> - let txId = importedAccount.SendFunds targetAccount transferAmount |> Async.RunSynchronously - let uri = BlockExplorer.GetTransaction currency txId - printfn "Transaction successful:\n%s" (uri.ToString()) - - Console.WriteLine "Archiving imported account..." - Account.ArchiveImportedAccount importedAccount - Console.WriteLine "Account archived." - - UserInteraction.PressAnyKeyToContinue() - | None -> () + try + let maybeTotalBalance, maybeUsdValue = UserInteraction.GetAccountBalance importedAccount |> Async.RunSynchronously + match maybeTotalBalance with + | NotFresh _ -> + Console.WriteLine "Could not retrieve balance" + deleteImportedAccountFile() + UserInteraction.PressAnyKeyToContinue() + | Fresh 0.0m -> + Console.WriteLine "Balance on imported account is zero. No funds to transfer." + deleteImportedAccountFile() + UserInteraction.PressAnyKeyToContinue() + | Fresh balance -> + printfn + "Balance on imported account: %s BTC (%s)" + (balance.ToString()) + (UserInteraction.BalanceInUsdString balance maybeUsdValue) + + let rec chooseAccount() = + Console.WriteLine "Choose account to send funds to:" + Console.WriteLine() + let allAccounts = Account.GetAllActiveAccounts() |> Seq.toList + let btcAccounts = allAccounts |> List.filter (fun acc -> acc.Currency = currency) + + match btcAccounts with + | [ singleAccount ] -> Some singleAccount + | [] -> failwith "No BTC accounts found" + | _ -> + allAccounts |> Seq.iteri (fun i account -> + if account.Currency = currency then + let balance, maybeUsdValue = + UserInteraction.GetAccountBalance account + |> Async.RunSynchronously + UserInteraction.DisplayAccountStatus (i + 1) account balance maybeUsdValue + |> Seq.iter Console.WriteLine + ) + + Console.Write("Write the account number (or 0 to cancel): ") + let accountNumber = Console.ReadLine() + match Int32.TryParse(accountNumber) with + | false, _ -> chooseAccount() + | true, 0 -> None + | true, accountParsed -> + let theAccountChosen = + try + let selectedAccount = allAccounts.[accountParsed - 1] + if selectedAccount.Currency = BTC then + Some selectedAccount + else + chooseAccount() + with + | _ -> chooseAccount() + theAccountChosen + + match chooseAccount() with + | Some targetAccount -> + let destination = targetAccount.PublicAddress + let transferAmount = TransferAmount(balance, balance, currency) // send all funds + let maybeFee = UserInteraction.AskFee importedAccount transferAmount destination + match maybeFee with + | None -> () + | Some(fee) -> + let txId = + Account.SweepArchivedFunds + importedAccount + balance + targetAccount + fee + false + |> Async.RunSynchronously + let uri = BlockExplorer.GetTransaction currency txId + printfn "Transaction successful:\n%s" (uri.ToString()) + UserInteraction.PressAnyKeyToContinue() + | None -> + deleteImportedAccountFile() + with + | _ -> + deleteImportedAccountFile() + reraise() let WalletOptions(): unit = let rec AskWalletOption(): GenericWalletOption =