/// <summary> /// Handle a fatal exception by calling our logger and then informing the user if possible. /// </summary> /// <param name="ex"> /// The exception that triggered this. /// </param> private static void HandleFatalException(Exception ex) { try { Logger.ApplicationInstance.Error("Fatal error within Sift.", ex); } catch { // Intentionally swallowed } string msg = "There was a fatal error within SIFT, please contact support with this message." + Environment.NewLine + ex; try { SiftDialog.ShowDialog("Fatal Error", msg); } catch { try { MessageBox.Show(msg); } catch { // Intentionally swallowed } } }
/// <summary> /// Shows a SIFT dialog without any buttons in non-modal mode. /// </summary> /// <param name="title"> /// The title of the message. /// </param> /// <param name="message"> /// The message to show. /// </param> /// <param name="isEthereum"> /// If this dialog is related to the ethereum network (true) and as such the ethereum logo should be displayed, otherwise false. /// </param> /// <returns> /// The dialog that was shown. /// </returns> public static SiftDialog ShowButtonless(string title, string message, bool isEthereum = false) { Func <SiftDialog> uiCallback = () => { SiftDialogViewModel viewModel = new SiftDialogViewModel { Title = title, Message = message, IsEthereumAnimatedLogoVisible = isEthereum, IsSiftLogoVisible = !isEthereum, IsLogButtonVisible = true, IsNoButtonVisible = false, IsReturnButtonVisible = false, IsYesButtonVisible = false }; SiftDialog dialog = new SiftDialog { DataContext = viewModel, Owner = Application.Current.MainWindow }; viewModel.CloseRequested += (s, e) => dialog.Close(); dialog.Show(); return(dialog); }; if (Application.Current.Dispatcher.CheckAccess()) { return(uiCallback()); } else { return(Application.Current.Dispatcher.Invoke(uiCallback)); } }
/// <summary> /// Perform an investment in SIFT using the current UI settings. /// </summary> private async Task Invest() { // Disable UI SiftInvestIsEnabled = false; // Check times if (DateTime.UtcNow < _ethereumManager.IcoStartDate) { SiftDialog.ShowDialog("ICO Not Open", "The ICO for SIFT does not open until " + _ethereumManager.IcoStartDate.ToShortDateString() + " (00:00 GMT). Please wait until this time to invest in SIFT."); return; } else if (DateTime.UtcNow > _ethereumManager.IcoEndDate) { SiftDialog.ShowDialog("ICO Not Open", "The ICO for SIFT finished at " + _ethereumManager.IcoStartDate.ToShortDateString() + " (00:00 GMT). As soon as the ICO has been closed you will be able to purchase SIFT via an open marketplace."); return; } // Perform the purchase SiftPurchaseResponse response = await _ethereumManager.PurchaseSift(SelectedAccount.Address, SiftAmountToPurchase); if (!response.WasSuccessful) { SiftDialog.ShowDialog("SIFT Investment Problem", "Sorry, there was a problem processing your transaction. Your SIFT could not be purchased at this time." + Environment.NewLine + Environment.NewLine + response.FailureReason); } else { TransactionToMine = _ethereumManager.EnqueueTransactionPendingReceipt(response.TransactionHash); if (TransactionToMine == null) { SiftDialog.ShowDialog("SIFT Delayed Investment", "Your transaction to buy SIFT was successfully sent with hash " + response.TransactionHash + ", but we could not validate the transaction. Your balance should update shortly, but if not please retry the transaction after checking your Ethereum wallet."); } else { // Hookup to wait to hear the status, or process it immediately if we have it Action act = () => { TransactionToMine.PropertyChanged += OnTransactionToMinePropertyChanged; _miningTransactionDialogViewModel = new SiftDialogViewModel { IsEthereumAnimatedLogoVisible = true, IsLogButtonVisible = true, Title = "Confirming SIFT Investment", Message = "Your transaction to invest in SIFT has been sent to the Ethereum network. Depending on the current network congestion it may take anywhere between a few seconds and minutes for the transaction to confirm. If the transaction does not confirm within a few minutes you can check your Ethereum wallet for more information." + Environment.NewLine + Environment.NewLine + "Please wait..." }; _miningTransactionDialog = new SiftDialog { DataContext = _miningTransactionDialogViewModel, Owner = Application.Current.MainWindow }; _miningTransactionDialogViewModel.CloseRequested += (s, e) => { _miningTransactionDialog.Close(); _miningTransactionDialog = null; _miningTransactionDialogViewModel = null; }; _miningTransactionDialog.ShowDialog(); }; if (Application.Current.Dispatcher.CheckAccess()) { act(); } else { Application.Current.Dispatcher.Invoke(act); } } } // Update the UI UpdateSiftPurchaseSettings(); }
/// <summary> /// Purchase SIFT from one of our accounts. /// </summary> /// <param name="address"> /// The account to use for purchase. /// </param> /// <param name="quantity"> /// The quantity to purchase. /// </param> /// <returns> /// Details of the purchase result indicating whether a success or failure happened. /// </returns> public async Task <SiftPurchaseResponse> PurchaseSift(string address, uint quantity) { try { // Ensure we have the balance decimal purchaseCostWei = WeiPerSift * quantity; EthereumAccount account = Accounts.FirstOrDefault(acc => acc.Address == address); if (account == null) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.UnknownAccount, "The specified account address is not known.")); } if (account.BalanceWei < purchaseCostWei) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.InsufficientFunds, "The specified account does not have sufficient funds.")); } // Determine the gas cost for this TransactionGasInfo gasInfo = await CalculateGasCostForEtherSend(address, IcoContractAddress, purchaseCostWei); decimal gasPrice = gasInfo.GasPrice; decimal gasCost = gasInfo.GasCost; decimal gas = gasInfo.Gas; decimal remainingGasMoney = account.BalanceWei - purchaseCostWei; if (remainingGasMoney < gasCost) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.InsufficientGas, "You do not have enough gas for this transaction.")); } byte gasMultiple = 25; decimal maximumGasCost; while ((maximumGasCost = gasCost * gasMultiple) * gasPrice > remainingGasMoney) { gasMultiple--; } // Prompt to unlock account Logger.ApplicationInstance.Debug("Asking user confirmation for sending " + purchaseCostWei + " from " + address + " to " + IcoContractAddress); Func <TransactionUnlockViewModel> fnc = new Func <TransactionUnlockViewModel>(() => { TransactionUnlockViewModel vm = new TransactionUnlockViewModel(gasCost, gasMultiple, gas, address, IcoContractAddress, purchaseCostWei); TransactionUnlockWindow window = new TransactionUnlockWindow { DataContext = vm }; window.ShowDialog(); return(vm); }); TransactionUnlockViewModel viewModel = Application.Current.Dispatcher.Invoke(fnc); if (viewModel.WasCancelled) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.UserCancelled, "User cancelled transaction on confirmation screen.")); } // Unlock the account SiftDialog dialog = SiftDialog.ShowButtonless("Sending Transaction", "Please wait whilst the transaction to buy SIFT is sent to the Ethereum network, this should only take a few seconds...", true); Logger.ApplicationInstance.Debug("Attempting to unlock " + address); if (!await _web3.Personal.UnlockAccount.SendRequestAsync(address, viewModel.Password == null ? string.Empty : viewModel.Password.ToString(), 120)) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.UnlockError, "Unable to unlock account with the supplied password.")); } // Send and mine the transaction Logger.ApplicationInstance.Debug("Account unlocked, sending " + purchaseCostWei + " from " + address + " to " + IcoContractAddress); TransactionInput transactionInput = new TransactionInput { From = address, To = IcoContractAddress, Value = new HexBigInteger(new BigInteger(purchaseCostWei)), GasPrice = new HexBigInteger(new BigInteger(viewModel.SelectedGasMultiplier * gasCost)), Gas = new HexBigInteger(new BigInteger(viewModel.Gas)) }; Logger.ApplicationInstance.Info("Sending transaction for " + purchaseCostWei + " to " + IcoContractAddress + " from " + address); string transactionHash = await _web3.Eth.Transactions.SendTransaction.SendRequestAsync(transactionInput); Action act = dialog.Close; if (dialog.Dispatcher.CheckAccess()) { act(); } else { dialog.Dispatcher.Invoke(act); } // Return the response of the transaction hash return(new SiftPurchaseResponse(transactionHash)); } catch (Exception ex) { Logger.ApplicationInstance.Error("Error purchasing SIFT", ex); if (ex.Message.Contains("personal_unlockAccount method not implemented")) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.MissingRpcPersonal, "Your geth installation doesn't have the personal RPC enabled, please enable it to continue.")); } else if (ex.Message.Contains("could not decrypt key with given passphrase")) { return(new SiftPurchaseResponse(SiftPurchaseFailureType.PasswordInvalid, "The password you supplied was incorrect")); } return(new SiftPurchaseResponse(SiftPurchaseFailureType.Unknown, ex.ToString())); } }
/// <summary> /// This method is the main entry point for the ethereum checks and it is responsible for changing message content. /// </summary> private void ThreadEntry() { // Always check for updates first StatusText = "Checking for updates to sift-win..."; StatusHeader = "Please Wait..."; try { // Login to update API Logger.ApplicationInstance.Debug("Checking for updated version of SIFT"); AuthenticationClient authClient = new AuthenticationClient(); AuthenticateUserResponse authResponse = authClient.AuthenticateJwtAsync(new AuthenticateUserRequest { Username = "******", Password = "******" }).Result; if (authResponse == null) { throw new Exception("Null response when checking authentication details"); } // Call to get latest summary information ProductClient client = new ProductClient(); ProductSummaryResponse response = client.ProductSummaryGetAsync("D7682386-897C-4798-84B3-911EDEB8BD44", "BEARER " + authResponse?.JsonWebToken).Result; if (response == null) { throw new Exception("Null response when checking product details"); } // Determine any changes to state an update UI accordingly Logger.ApplicationInstance.Debug("Latest version is " + response.LatestVersion + " from " + response.LatestDownloadUrl); if (!string.IsNullOrEmpty(response.LatestVersion)) { Logger.ApplicationInstance.Info("A new version of SIFT - version " + response.LatestVersion + " is available from " + response.LatestDownloadUrl); Version version = Version.Parse(response.LatestVersion); if (!System.Diagnostics.Debugger.IsAttached) { if (Assembly.GetEntryAssembly().GetName().Version < version && SiftDialog.ShowDialog("SIFT Upgrade Available", "A new version of SIFT is available (" + version + "). Would you like to download it now?", true).Value) { System.Diagnostics.Process.Start(string.IsNullOrWhiteSpace(response.LatestDownloadUrl) ? "http://smartift.com/sift-win/latest" : response.LatestDownloadUrl); } } } } catch (Exception ex) { Logger.ApplicationInstance.Error("Failed to check for latest version", ex); } finally { } DateTime started = DateTime.UtcNow; StatusText = MessageConnecting; while (_isAlive) { // Break out of loop if we know what we're doing if (_ethereumManager.ContractPhase != ContractPhase.Unknown && !_ethereumManager.IsSyncing && _ethereumManager.LastChecksSuccessful && _ethereumManager.BlockNumber > 0 && _ethereumManager.Accounts.Count > 0) { break; } // If we've just gone over 5 seconds display a "this may be broken" message and enable exit button if (DateTime.UtcNow > started.AddSeconds(5) || _ethereumManager.LastChecksSuccessful) { StatusHeader = "Error!"; if (!_ethereumManager.LastChecksSuccessful || _ethereumManager.BlockNumber < 1) { StatusText = MessageDelayedStart; } else if (_ethereumManager.Accounts.Count < 1) { StatusText = MessageNoAddresses; } else if (_ethereumManager.IsSyncing) { StatusText = MessageSyncing; } else { StatusText = MessageUnknownContractState; } IsErrorState = true; } // Wait to retry Thread.Sleep(100); } // If we're still alive show appropriate window if (_isAlive) { // Show correct window and close the splash screen Action act = () => { Window mainWindow = Application.Current.MainWindow; Window newWindow; if (_ethereumManager.ContractPhase == ContractPhase.Ico) { newWindow = new IcoWindow { DataContext = new IcoViewModel(_ethereumManager) } } ; else { newWindow = new PostIcoWindow { DataContext = new PostIcoViewModel(_ethereumManager) } }; newWindow.Show(); Application.Current.MainWindow = newWindow; mainWindow.Close(); }; Application.Current.Dispatcher.BeginInvoke(act); } }