Ejemplo n.º 1
0
        /// <summary>
        /// Updates UI settings around the SIFT purchasing based on the currently selected account.
        /// </summary>
        private void UpdateSiftPurchaseSettings()
        {
            // Determine if ICO is open
            bool isIcoOpen = DateTime.UtcNow >= _ethereumManager.IcoStartDate && DateTime.UtcNow <= _ethereumManager.IcoEndDate;

            _wasIcoOpen = isIcoOpen;

            // If we haven't got an account or no balance, hide the buy section
            if (SelectedAccount == null || SelectedAccount.BalanceWei < EthereumManager.WeiPerSift)
            {
                SiftMaximumPurchase  = 0;
                SiftAmountToPurchase = 0;
                SiftInvestIsEnabled  = false;
                return;
            }

            // Determine maximum purchase volume - do the raw calculation then factor in gas to see if we need to take away one sift
            uint               maximumPurchaseVolume = (uint)(SelectedAccount.BalanceWei / EthereumManager.WeiPerSift);
            decimal            spendAmount           = maximumPurchaseVolume * EthereumManager.WeiPerSift;
            TransactionGasInfo gasInfo    = _ethereumManager.DefaultGasInfo;
            decimal            totalSpend = spendAmount + gasInfo.GasCost;

            if (totalSpend > SelectedAccount.BalanceWei)
            {
                if (maximumPurchaseVolume == 1)
                {
                    SiftMaximumPurchase  = 0;
                    SiftAmountToPurchase = 0;
                    SiftInvestIsEnabled  = false;
                    return;
                }
                maximumPurchaseVolume--;
            }

            // Setup the various settings for this account
            SiftMaximumPurchase  = maximumPurchaseVolume;
            SiftAmountToPurchase = SiftMaximumPurchase;
            SiftInvestIsEnabled  = SiftMaximumPurchase > 0 && isIcoOpen;
        }
        /// <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 provides the main entry point for the background thread that keeps ethereum wallet information synchronised.
        /// </summary>
        private void NetworkCheckThreadEntry()
        {
            Logger.ApplicationInstance.Info("Network status thread started");
            bool     connectedYet          = false;
            DateTime nextContractCheckTime = DateTime.UtcNow;

            while (_isAlive)
            {
                try
                {
                    // Get a list of all accounts currently know about by our wallet
                    string[] addresses = _web3.Eth.Accounts.SendRequestAsync().Result;

                    // First remove any existing accounts that do not exist in our new list
                    if (addresses != null)
                    {
                        EthereumAccount[] removedAccounts = Accounts.Where(ac => !addresses.Contains(ac.Address)).ToArray();
                        foreach (EthereumAccount removedAccount in removedAccounts)
                        {
                            Accounts.Remove(removedAccount);
                        }

                        // Process each of these accounts
                        foreach (string address in addresses)
                        {
                            // Get the current balance for this account
                            decimal balance = decimal.Parse(_web3.Eth.GetBalance.SendRequestAsync(address).Result.Value.ToString());

                            // And check the SIFT balance
                            ulong siftBalance = _tokenService.GetBalanceOfAsync <ulong>(address).Result;

                            // See if we have an existing account - if not we'll need to create a new one
                            EthereumAccount existingAccount = Accounts.FirstOrDefault(ac => ac.Address == address);
                            if (existingAccount == null)
                            {
                                Accounts.Add(new EthereumAccount(address, balance, siftBalance));
                                Logger.ApplicationInstance.Info("Found new account " + address + " with " + balance + " ETH / " + siftBalance + " SIFT");
                            }
                            else
                            {
                                if (existingAccount.BalanceWei != balance)
                                {
                                    existingAccount.BalanceWei = balance;
                                    Logger.ApplicationInstance.Info("Account " + address + " now has " + balance + " ETH");
                                }
                                if (existingAccount.SiftBalance != siftBalance)
                                {
                                    existingAccount.SiftBalance = siftBalance;
                                    Logger.ApplicationInstance.Info("Account " + address + " now has " + siftBalance + " SIFT");
                                }
                            }
                        }
                    }

                    // Let's get the block number and check if we're syncing to just set some basics bits and bobs up
                    BlockNumber = ulong.Parse(_web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result.Value.ToString());
                    IsSyncing   = _web3.Eth.Syncing.SendRequestAsync().Result.IsSyncing;

                    // Ask the contract our status but first check we've got the right contract
                    if (DateTime.UtcNow >= nextContractCheckTime)
                    {
                        // First check the ICO contract
                        Logger.ApplicationInstance.Debug("Checking ICO contract version");
                        decimal icoContractVersion = decimal.Parse(_icoContract.GetFunction("contractVersion").CallAsync <ulong>().Result.ToString());
                        if (icoContractVersion == IcoContractVersion)
                        {
                            // Get the phase of the ICO
                            bool isIcoPhase = _icoContract.GetFunction("icoPhase").CallAsync <bool>().Result;
                            ContractPhase = isIcoPhase ? ContractPhase.Ico : ContractPhase.Trading;

                            // Check if the ICO is abandoned
                            IsIcoAbandoned = _icoContract.GetFunction("icoAbandoned").CallAsync <bool>().Result;

                            // Check the ICO dates
                            IcoStartDate = DateFromTimestamp(_icoContract.GetFunction("icoStartTime").CallAsync <ulong>().Result);
                            IcoEndDate   = DateFromTimestamp(_icoContract.GetFunction("icoEndTime").CallAsync <ulong>().Result);

                            // Whilst we're here we also want to check the gas cost to send
                            DefaultGasInfo = CalculateGasCostForEtherSend("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", IcoContractAddress, 1000000000000000000m).Result;
                            Logger.ApplicationInstance.Error("Default gas calculating for sending as " + DefaultGasInfo.Gas + " at " + DefaultGasInfo.GasPrice + " = " + DefaultGasInfo.GasCost);
                        }
                        else
                        {
                            ContractPhase = ContractPhase.Unknown;
                            Logger.ApplicationInstance.Error("ICO contract mismatch - expected " + IcoContractVersion + " but got " + icoContractVersion + " at " + IcoContractAddress);
                        }

                        // Now check SIFT contract
                        Logger.ApplicationInstance.Debug("Checking SIFT contract version");
                        decimal siftContractVersion = decimal.Parse(_siftContract.GetFunction("contractVersion").CallAsync <ulong>().Result.ToString());
                        if (siftContractVersion != SiftContractVersion)
                        {
                            ContractPhase = ContractPhase.Unknown;
                            Logger.ApplicationInstance.Error("SIFT contract mismatch - expected " + SiftContractVersion + " but got " + siftContractVersion + " at " + SiftContractAddress);
                        }

                        // Now check the SIFT contract
                        nextContractCheckTime = DateTime.UtcNow.AddSeconds(10);
                    }

                    // If valid contract check total supply
                    if (ContractPhase != ContractPhase.Unknown)
                    {
                        Logger.ApplicationInstance.Debug("Checking total supply and shareholding");
                        TotalSupply = _tokenService.GetTotalSupplyAsync <ulong>().Result;
                        foreach (EthereumAccount account in Accounts)
                        {
                            account.ShareholdingPercentage = TotalSupply == 0 ? 0 : (decimal)account.SiftBalance / TotalSupply * 100;
                        }
                    }

                    // Signify a successful loop
                    LastChecksSuccessful = true;
                    if (!connectedYet)
                    {
                        connectedYet = true;
                        Logger.ApplicationInstance.Info("Completed first successful network check, we should be good to go");
                    }
                }
                catch (Exception ex)
                {
                    LastChecksSuccessful = false;
                    Logger.ApplicationInstance.Error("There was an unexpected error updating Ethereum network information", ex);
                }

                // Wait to try again
                DateTime nextTime = DateTime.UtcNow.AddSeconds(3);
                while (DateTime.UtcNow < nextTime && _isAlive)
                {
                    Thread.Sleep(100);
                }
            }
        }