Exemplo n.º 1
0
        public string[] Receive(string requestToken)
        {
            Safe safe = GetSafeFromCache(requestToken);

            var addresses = new List <string>();

            if (Config.ConnectionType == ConnectionType.Http)
            {
                Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerReceiveAddresses =
                    QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe, 7, HdPathType.Receive);

                foreach (var elem in operationsPerReceiveAddresses)
                {
                    if (elem.Value.Count == 0)
                    {
                        addresses.Add(elem.Key.ToString());
                    }
                }
            }
            else
            {
                throw new InvalidOperationException("Invalid connection type.");
            }

            return(addresses.ToArray());
        }
Exemplo n.º 2
0
        public BtcWalletHistory[] GetHistory(string requestToken)
        {
            var safe = GetSafeFromCache(requestToken);

            // 0. Query all operations, grouped our used safe addresses
            Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses =
                QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe);

            Dictionary <uint256, List <BalanceOperation> > operationsPerTransactions =
                QBitNinjaJutsus.GetOperationsPerTransactions(operationsPerAddresses);

            // 3. Create history records from the transactions
            // History records is arbitrary data we want to show to the user
            var txHistoryRecords = new List <Tuple <DateTimeOffset, Money, int, uint256> >();

            foreach (var elem in operationsPerTransactions)
            {
                var amount = Money.Zero;
                foreach (var op in elem.Value)
                {
                    amount += op.Amount;
                }
                var firstOp = elem.Value.First();

                txHistoryRecords
                .Add(new Tuple <DateTimeOffset, Money, int, uint256>(
                         firstOp.FirstSeen,
                         amount,
                         firstOp.Confirmations,
                         elem.Key));
            }

            // 4. Order the records by confirmations and time (Simply time does not work, because of a QBitNinja bug)
            var orderedTxHistoryRecords = txHistoryRecords
                                          .OrderByDescending(x => x.Item3) // Confirmations
                                          .ThenBy(x => x.Item1);           // FirstSeen
            var history = new List <BtcWalletHistory>();

            foreach (var record in orderedTxHistoryRecords)
            {
                history.Add(new BtcWalletHistory()
                {
                    DateTime      = record.Item1.DateTime,
                    AmountBtc     = record.Item2.ToDecimal(MoneyUnit.BTC),// Item2 is the ConfirmedBalance
                    Confirmed     = record.Item3 > 0,
                    TransactionId = record.Item4.ToBytes(lendian: true)
                });
            }
            return(history.ToArray());
        }
Exemplo n.º 3
0
        public SendResult Send(string requestToken, string address, string btcAmount)
        {
            Safe safe = GetSafeFromCache(requestToken);

            BitcoinAddress addressToSend = null;

            var Exit = new Func <string, SendResult>(
                (m) => new SendResult()
            {
                Success = false,
                Message = m.ToString()
            });

            var msg = new StringBuilder();

            try
            {
                addressToSend = BitcoinAddress.Create(address, Config.Network);
            }
            catch (Exception ex)
            {
                msg.AppendLine(ex.ToString());
            }

            if (addressToSend == null)
            {
                return(Exit(msg.ToString()));
            }


            if (Config.ConnectionType == ConnectionType.Http)
            {
                Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses =
                    QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe);

                // 1. Gather all the not empty private keys
                msg.AppendLine("Finding not empty private keys...");
                var operationsPerNotEmptyPrivateKeys = new Dictionary <BitcoinExtKey, List <BalanceOperation> >();
                foreach (var elem in operationsPerAddresses)
                {
                    var balance = Money.Zero;
                    foreach (var op in elem.Value)
                    {
                        balance += op.Amount;
                    }
                    if (balance > Money.Zero)
                    {
                        var secret = safe.FindPrivateKey(elem.Key);
                        operationsPerNotEmptyPrivateKeys.Add(secret, elem.Value);
                    }
                }

                // 2. Get the script pubkey of the change.
                msg.AppendLine("Select change address...");
                Script changeScriptPubKey = null;
                Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerChangeAddresses =
                    QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe, minUnusedKeys: 1, hdPathType: HdPathType.Change);
                foreach (var elem in operationsPerChangeAddresses)
                {
                    if (elem.Value.Count == 0)
                    {
                        changeScriptPubKey = safe.FindPrivateKey(elem.Key).ScriptPubKey;
                    }
                }
                if (changeScriptPubKey == null)
                {
                    throw new ArgumentNullException();
                }

                // 3. Gather coins can be spend
                msg.AppendLine("Gathering unspent coins...");
                Dictionary <Coin, bool> unspentCoins = QBitNinjaJutsus.GetUnspentCoins(operationsPerNotEmptyPrivateKeys.Keys);

                // 4. Get the fee
                msg.AppendLine("Calculating transaction fee...");
                Money fee;
                try
                {
                    var txSizeInBytes = 250;
                    using (var client = new HttpClient())
                    {
                        const string request = @"https://bitcoinfees.earn.com/api/v1/fees/recommended";
                        var          result  = client.GetAsync(request, HttpCompletionOption.ResponseContentRead).Result;
                        var          json    = JObject.Parse(result.Content.ReadAsStringAsync().Result);
                        var          fastestSatoshiPerByteFee = json.Value <decimal>("fastestFee");
                        fee = new Money(fastestSatoshiPerByteFee * txSizeInBytes, MoneyUnit.Satoshi);
                    }
                }
                catch
                {
                    return(Exit("Couldn't calculate transaction fee, try it again later."));
                }
                msg.AppendLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");

                // 5. How much money we can spend?
                Money availableAmount            = Money.Zero;
                Money unconfirmedAvailableAmount = Money.Zero;
                foreach (var elem in unspentCoins)
                {
                    // If can spend unconfirmed add all
                    if (Config.CanSpendUnconfirmed)
                    {
                        availableAmount += elem.Key.Amount;
                        if (!elem.Value)
                        {
                            unconfirmedAvailableAmount += elem.Key.Amount;
                        }
                    }
                    // else only add confirmed ones
                    else
                    {
                        if (elem.Value)
                        {
                            availableAmount += elem.Key.Amount;
                        }
                    }
                }

                // 6. How much to spend?
                Money amountToSend = null;
                if (string.Equals(btcAmount, "all", StringComparison.OrdinalIgnoreCase))
                {
                    amountToSend  = availableAmount;
                    amountToSend -= fee;
                }
                else
                {
                    amountToSend = ParseBtcString(btcAmount);
                }

                // 7. Do some checks
                if (amountToSend < Money.Zero || availableAmount < amountToSend + fee)
                {
                    return(Exit("Not enough coins."));
                }

                decimal feePc =
                    Math.Round((100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC));
                if (feePc > 1)
                {
                    msg.AppendLine();
                    msg.AppendLine($"The transaction fee is {feePc.ToString("0.#")}% of your transaction amount.");
                    msg.AppendLine(
                        $"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                    msg.AppendLine(
                        $"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                }

                var confirmedAvailableAmount = availableAmount - unconfirmedAvailableAmount;
                var totalOutAmount           = amountToSend + fee;
                if (confirmedAvailableAmount < totalOutAmount)
                {
                    var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount;
                    msg.AppendLine();
                    msg.AppendLine($"In order to complete this transaction " +
                                   $"you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")} unconfirmed btc.");
                }

                // 8. Select coins
                msg.AppendLine("Selecting coins...");
                var coinsToSpend            = new HashSet <Coin>();
                var unspentConfirmedCoins   = new List <Coin>();
                var unspentUnconfirmedCoins = new List <Coin>();
                foreach (var elem in unspentCoins)
                {
                    if (elem.Value)
                    {
                        unspentConfirmedCoins.Add(elem.Key);
                    }
                    else
                    {
                        unspentUnconfirmedCoins.Add(elem.Key);
                    }
                }

                bool haveEnough = QBitNinjaJutsus.SelectCoins(ref coinsToSpend, totalOutAmount, unspentConfirmedCoins);
                if (!haveEnough)
                {
                    haveEnough = QBitNinjaJutsus.SelectCoins(ref coinsToSpend, totalOutAmount, unspentUnconfirmedCoins);
                }
                if (!haveEnough)
                {
                    throw new Exception("Not enough funds.");
                }

                // 9. Get signing keys
                var signingKeys = new HashSet <ISecret>();
                foreach (var coin in coinsToSpend)
                {
                    foreach (var elem in operationsPerNotEmptyPrivateKeys)
                    {
                        if (elem.Key.ScriptPubKey == coin.ScriptPubKey)
                        {
                            signingKeys.Add(elem.Key);
                        }
                    }
                }

                // 10. Build the transaction
                msg.AppendLine("Signing transaction...");
                var builder = new TransactionBuilder();
                var tx      = builder
                              .AddCoins(coinsToSpend)
                              .AddKeys(signingKeys.ToArray())
                              .Send(addressToSend, amountToSend)
                              .SetChange(changeScriptPubKey)
                              .SendFees(fee)
                              .BuildTransaction(true);

                if (!builder.Verify(tx))
                {
                    return(Exit("Couldn't build the transaction."));
                }

                msg.AppendLine($"Transaction Id: {tx.GetHash()}");

                var qBitClient = new QBitNinjaClient(Config.Network);

                // QBit's success response is buggy so let's check manually, too
                BroadcastResponse broadcastResponse;
                var success = false;
                var tried   = 0;
                var maxTry  = 7;
                do
                {
                    tried++;
                    msg.AppendLine($"Try broadcasting transaction... ({tried})");
                    broadcastResponse = qBitClient.Broadcast(tx).Result;
                    var getTxResp = qBitClient.GetTransaction(tx.GetHash()).Result;
                    if (getTxResp == null)
                    {
                        Thread.Sleep(3000);
                    }
                    else
                    {
                        success = true;
                        break;
                    }
                } while (tried <= maxTry);
                if (!success)
                {
                    if (broadcastResponse.Error != null)
                    {
                        msg.AppendLine($"Error code: {broadcastResponse.Error.ErrorCode} Reason: {broadcastResponse.Error.Reason}");
                    }

                    return(Exit($"The transaction might not have been successfully broadcasted. Please check the Transaction ID in a block explorer."));
                }
                msg.AppendLine("Transaction is successfully propagated on the network.");
            }

            return(new SendResult()
            {
                Success = true,
                Message = msg.ToString()
            });
        }
Exemplo n.º 4
0
        public Model.BtcWallet GetBalances(string requestToken)
        {
            if (Config.ConnectionType == ConnectionType.Http)
            {
                Safe safe = GetSafeFromCache(requestToken);

                // 0. Query all operations, grouped by addresses
                Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses =
                    QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe);

                // 1. Get all address history records
                var addressHistoryRecords = (from elem in operationsPerAddresses
                                             from op in elem.Value
                                             select new AddressHistoryRecord(elem.Key, op)).ToList();

                // 2. Calculate wallet balances
                Money confirmedWalletBalance;
                Money unconfirmedWalletBalance;
                QBitNinjaJutsus.GetBalances(addressHistoryRecords, out confirmedWalletBalance, out unconfirmedWalletBalance);


                // 3. Group all address history records by addresses
                var addressHistoryRecordsPerAddresses = new Dictionary <BitcoinAddress, HashSet <AddressHistoryRecord> >();

                var result = new Dictionary <BitcoinAddress, BtcAddress>();

                foreach (var address in operationsPerAddresses.Keys)
                {
                    var recs = new HashSet <AddressHistoryRecord>();
                    foreach (var record in addressHistoryRecords)
                    {
                        if (record.Address == address)
                        {
                            recs.Add(record);
                        }
                    }
                    addressHistoryRecordsPerAddresses.Add(address, recs);

                    result.Add(address, new BtcAddress()
                    {
                        Address = address.ToString(),
                        History = addressHistoryRecordsPerAddresses[address]?
                                  .Select(h => new AddressHistory()
                        {
                            Address       = address.ToString(),
                            AmountBtc     = h.Amount.ToDecimal(MoneyUnit.BTC),
                            Confirmed     = h.Confirmed,
                            FirstSeen     = h.FirstSeen,
                            TransactionId = h.TransactionId.ToString()
                        }).ToList()
                    });
                }

                // 4. Calculate address balances

                foreach (var elem in addressHistoryRecordsPerAddresses)
                {
                    Money confirmedBalance;
                    Money unconfirmedBalance;
                    QBitNinjaJutsus.GetBalances(elem.Value, out confirmedBalance, out unconfirmedBalance);
                    if (confirmedBalance != Money.Zero || unconfirmedBalance != Money.Zero)
                    {
                        result[elem.Key].ConfirmedBalance   = confirmedBalance.ToDecimal(MoneyUnit.BTC);
                        result[elem.Key].UnconfirmedBalance = unconfirmedBalance.ToDecimal(MoneyUnit.BTC);
                    }
                }

                return(new Model.BtcWallet
                {
                    FileName = FileNameFromRequestTokens[requestToken],
                    Addresses = addressHistoryRecordsPerAddresses.Select(c => result[c.Key]).ToList(),
                    ConfirmedWalletBalance = confirmedWalletBalance.ToDecimal(MoneyUnit.BTC),
                    UnconfirmedWalletBalance = unconfirmedWalletBalance.ToDecimal(MoneyUnit.BTC)
                });
            }
            else
            {
                throw new NotImplementedException("Invalid connection type.");
            }
        }