예제 #1
0
        public static HotBitcoinAddress CreateUnique(Organization organization, BitcoinChain chain, params int[] derivationPath)
        {
            ExtPubKey extPubKey = BitcoinUtility.BitcoinHotPublicRoot;

            extPubKey = extPubKey.Derive((uint)organization.Identity);
            string derivationPathString = string.Empty;

            foreach (int derivation in derivationPath) // requires that order is consistent from 0 to n-1
            {
                extPubKey             = extPubKey.Derive((uint)derivation);
                derivationPathString += " " + derivation.ToString(CultureInfo.InvariantCulture);
            }

            derivationPathString = derivationPathString.TrimStart();

            int hotBitcoinAddressId =
                SwarmDb.GetDatabaseForWriting()
                .CreateHotBitcoinAddress(organization.Identity, chain, derivationPathString);

            HotBitcoinAddress addressTemp = FromIdentityAggressive(hotBitcoinAddressId);

            // Derive the last step with the now-assigned unique identifier, then set address, read again, and return

            extPubKey = extPubKey.Derive((uint)addressTemp.UniqueDerive);

            string bitcoinAddressString = extPubKey.PubKey.GetAddress(Network.Main).ToString();

            SwarmDb.GetDatabaseForWriting().SetHotBitcoinAddressAddress(hotBitcoinAddressId, bitcoinAddressString);

            return(FromIdentityAggressive(hotBitcoinAddressId));
        }
예제 #2
0
        public static HotBitcoinAddress GetAddressOrForkCore(BitcoinChain chain, string bitcoinAddress)
        {
            try
            {
                return(FromBasic(SwarmDb.GetDatabaseForReading().GetHotBitcoinAddress(chain, bitcoinAddress)));
            }
            catch (Exception)
            {
                // Forked address off of Core is not registered yet

                HotBitcoinAddress coreAddress =
                    FromBasic(SwarmDb.GetDatabaseForReading().GetHotBitcoinAddress(BitcoinChain.Core, bitcoinAddress));

                return(Create(coreAddress.Organization, chain, coreAddress.DerivationPathIntArray));
            }
        }
예제 #3
0
        public void DeleteAll()
        {
            Dictionary <int, bool> addressIdLookup = new Dictionary <int, bool>();

            this.ForEach(item => addressIdLookup[item.HotBitcoinAddressId] = true);
            SwarmDb db = SwarmDb.GetDatabaseForWriting();

            // Delete the unspents in this collection

            this.ForEach(item => db.DeleteHotBitcoinAddressUnspent(item.Identity));

            // Recalculate the amount remaining in the addresses

            foreach (int addressId in addressIdLookup.Keys)
            {
                db.SetHotBitcoinAddressBalance(addressId,
                                               HotBitcoinAddress.FromIdentity(addressId).Unspents.AmountSatoshisTotal);
            }
        }
예제 #4
0
        public static void PerformAutomated(BitcoinChain chain)
        {
            // Perform all waiting hot payouts for all orgs in the installation

            throw new NotImplementedException("Waiting for rewrite for Bitcoin Cash");

            // TODO

            DateTime utcNow = DateTime.UtcNow;

            foreach (Organization organization in Organizations.GetAll())
            {
                // If this org doesn't do hotwallet, continue
                if (organization.FinancialAccounts.AssetsBitcoinHot == null)
                {
                    continue;
                }

                Payouts orgPayouts     = Payouts.Construct(organization);
                Payouts bitcoinPayouts = new Payouts();
                Dictionary <string, Int64> satoshiPayoutLookup     = new Dictionary <string, long>();
                Dictionary <string, Int64> nativeCentsPayoutLookup = new Dictionary <string, long>();
                Dictionary <int, Int64>    satoshiPersonLookup     = new Dictionary <int, long>();
                Dictionary <int, Int64>    nativeCentsPersonLookup = new Dictionary <int, long>();
                Int64 satoshisTotal = 0;

                string currencyCode = organization.Currency.Code;

                // For each ready payout that can automate, add an output to a constructed transaction

                TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder();
                foreach (Payout payout in orgPayouts)
                {
                    if (payout.ExpectedTransactionDate > utcNow)
                    {
                        continue; // payout is not due yet
                    }

                    if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length > 2 &&
                        payout.Account.Length < 4)
                    {
                        // If the payout address is still in quarantine, don't pay out yet

                        string addressSetTime = payout.RecipientPerson.BitcoinPayoutAddressTimeSet;
                        if (addressSetTime.Length > 4 && DateTime.Parse(addressSetTime, CultureInfo.InvariantCulture).AddHours(48) > utcNow)
                        {
                            continue; // still in quarantine
                        }

                        // Test the payout address - is it valid and can we handle it?

                        if (!BitcoinUtility.IsValidBitcoinAddress(payout.RecipientPerson.BitcoinPayoutAddress))
                        {
                            // Notify person that address is invalid, then clear it

                            NotificationStrings       primaryStrings   = new NotificationStrings();
                            NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();
                            primaryStrings[NotificationString.OrganizationName] = organization.Name;
                            secondaryStrings["BitcoinAddress"] = payout.RecipientPerson.BitcoinPayoutAddress;

                            OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_Bad,
                                                            primaryStrings, secondaryStrings,
                                                            People.FromSingle(payout.RecipientPerson));

                            payout.RecipientPerson.BitcoinPayoutAddress = string.Empty;

                            continue; // do not add this payout
                        }

                        // Ok, so it seems we're making this payout at this time.

                        bitcoinPayouts.Add(payout);

                        int recipientPersonId = payout.RecipientPerson.Identity;

                        if (!satoshiPersonLookup.ContainsKey(recipientPersonId))
                        {
                            satoshiPersonLookup[recipientPersonId]     = 0;
                            nativeCentsPersonLookup[recipientPersonId] = 0;
                        }

                        nativeCentsPersonLookup[recipientPersonId] += payout.AmountCents;

                        // Find the amount of satoshis for this payout

                        if (organization.Currency.IsBitcoinCore)
                        {
                            satoshiPayoutLookup[payout.ProtoIdentity]     = payout.AmountCents;
                            nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents;
                            satoshisTotal += payout.AmountCents;
                            satoshiPersonLookup[recipientPersonId] += payout.AmountCents;
                        }
                        else
                        {
                            // Convert currency
                            Money payoutAmount = new Money(payout.AmountCents, organization.Currency);
                            Int64 satoshis     = payoutAmount.ToCurrency(Currency.BitcoinCore).Cents;
                            satoshiPayoutLookup[payout.ProtoIdentity]     = satoshis;
                            nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents;
                            satoshisTotal += satoshis;
                            satoshiPersonLookup[recipientPersonId] += satoshis;
                        }
                    }
                    else if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length < 3 && payout.Account.Length < 4)
                    {
                        // There is a payout for this person, but they don't have a bitcoin payout address set. Send notification to this effect once a day.

                        if (utcNow.Minute != 0)
                        {
                            continue;
                        }
                        if (utcNow.Hour != 12)
                        {
                            continue;
                        }

                        NotificationStrings primaryStrings = new NotificationStrings();
                        primaryStrings[NotificationString.OrganizationName] = organization.Name;
                        OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_PleaseSet, primaryStrings, People.FromSingle(payout.RecipientPerson));
                    }
                    else if (payout.Account.StartsWith("bitcoin:"))
                    {
                    }
                }

                if (bitcoinPayouts.Count == 0)
                {
                    // no automated payments pending for this organization, nothing more to do
                    continue;
                }

                // We now have our desired payments. The next step is to find enough inputs to reach the required amount (plus fees; we're working a little blind here still).

                BitcoinTransactionInputs inputs      = null;
                Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain) * 20; // assume max 20k transaction size

                try
                {
                    inputs = BitcoinUtility.GetInputsForAmount(organization, satoshisTotal + satoshisMaximumAnticipatedFees);
                }
                catch (NotEnoughFundsException)
                {
                    // If we're at the whole hour, send a notification to people responsible for refilling the hotwallet

                    if (utcNow.Minute != 0)
                    {
                        continue; // we're not at the whole hour, so continue with next org instead
                    }

                    // Send urgent notification to top up the damn wallet so we can make payments

                    NotificationStrings primaryStrings = new NotificationStrings();
                    primaryStrings[NotificationString.CurrencyCode]     = organization.Currency.Code;
                    primaryStrings[NotificationString.OrganizationName] = organization.Name;
                    NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();
                    Int64 satoshisAvailable = HotBitcoinAddresses.ForOrganization(organization).BalanceSatoshisTotal;

                    secondaryStrings["AmountMissingMicrocoinsFloat"] =
                        ((satoshisTotal - satoshisAvailable + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2");

                    if (organization.Currency.IsBitcoinCore)
                    {
                        secondaryStrings["AmountNeededFloat"] = ((satoshisTotal + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2");
                        secondaryStrings["AmountWalletFloat"] = (satoshisAvailable / 100.0).ToString("N2");
                    }
                    else
                    {
                        // convert to org native currency

                        secondaryStrings["AmountNeededFloat"] =
                            (new Money(satoshisTotal, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2");
                        secondaryStrings["AmountWalletFloat"] =
                            (new Money(satoshisAvailable, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2");
                    }

                    OutboundComm.CreateNotification(organization,
                                                    NotificationResource.Bitcoin_Shortage_Critical, primaryStrings, secondaryStrings, People.FromSingle(Person.FromIdentity(1)));

                    continue; // with next organization
                }

                // If we arrive at this point, the previous function didn't throw, and we have enough money.
                // Ensure the existence of a cost account for bitcoin miner fees.

                organization.EnsureMinerFeeAccountExists();

                // Add the inputs to the transaction.

                txBuilder = txBuilder.AddCoins(inputs.Coins);
                txBuilder = txBuilder.AddKeys(inputs.PrivateKeys);
                Int64 satoshisInput = inputs.AmountSatoshisTotal;

                // Add outputs and prepare notifications

                Int64 satoshisUsed = 0;
                Dictionary <int, List <string> > notificationSpecLookup   = new Dictionary <int, List <string> >();
                Dictionary <int, List <Int64> >  notificationAmountLookup = new Dictionary <int, List <Int64> >();
                Payout            masterPayoutPrototype = Payout.Empty;
                HotBitcoinAddress changeAddress         = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // TODO: CHAIN!

                foreach (Payout payout in bitcoinPayouts)
                {
                    int recipientPersonId = payout.RecipientPerson.Identity;
                    if (!notificationSpecLookup.ContainsKey(recipientPersonId))
                    {
                        notificationSpecLookup[recipientPersonId]   = new List <string>();
                        notificationAmountLookup[recipientPersonId] = new List <Int64>();
                    }
                    notificationSpecLookup[recipientPersonId].Add(payout.Specification);
                    notificationAmountLookup[recipientPersonId].Add(payout.AmountCents);

                    if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("1"))  // regular address
                    {
                        txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(payout.RecipientPerson.BitcoinPayoutAddress),
                                                   new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity]));
                    }
                    else if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("3"))  // multisig
                    {
                        txBuilder = txBuilder.Send(new BitcoinScriptAddress(payout.RecipientPerson.BitcoinPayoutAddress, Network.Main),
                                                   new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity]));
                    }
                    else
                    {
                        throw new InvalidOperationException("Unhandled bitcoin address type in Payouts.PerformAutomated(): " + payout.RecipientPerson.BitcoinPayoutAddress);
                    }

                    satoshisUsed += satoshiPayoutLookup[payout.ProtoIdentity];

                    payout.MigrateDependenciesTo(masterPayoutPrototype);
                }

                // Set change address to wallet slush

                txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address));

                // Add fee

                int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count;
                // +inputs.Count for size variability

                Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain);

                txBuilder     = txBuilder.SendFees(new Satoshis(feeSatoshis));
                satoshisUsed += feeSatoshis;

                // Sign transaction - ready to execute

                Transaction txReady = txBuilder.BuildTransaction(true);

                // Verify that transaction is ready

                if (!txBuilder.Verify(txReady))
                {
                    // Transaction was not signed with the correct keys. This is a serious condition.

                    NotificationStrings primaryStrings = new NotificationStrings();
                    primaryStrings[NotificationString.OrganizationName] = organization.Name;

                    OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError,
                                                    primaryStrings);

                    throw new InvalidOperationException("Transaction is not signed enough");
                }

                // Broadcast transaction

                BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash);

                // Note the transaction hash

                string transactionHash = txReady.GetHash().ToString();

                // Delete all old inputs, adjust balance for addresses (re-register unused inputs)

                inputs.AsUnspents.DeleteAll();

                // Log the new unspent created by change (if there is any)

                if (satoshisInput - satoshisUsed > 0)
                {
                    SwarmDb.GetDatabaseForWriting()
                    .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash,
                                                               +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0);
                }

                // Register new balance of change address, should have increased by (satoshisInput-satoshisUsed)

                // TODO

                // Send notifications

                foreach (int personId in notificationSpecLookup.Keys)
                {
                    Person person = Person.FromIdentity(personId);

                    string spec = string.Empty;
                    for (int index = 0; index < notificationSpecLookup[personId].Count; index++)
                    {
                        spec += String.Format(" * {0,-40} {1,14:N2} {2,-4}\r\n", notificationSpecLookup[personId][index], notificationAmountLookup[personId][index] / 100.0, currencyCode);
                    }

                    spec = spec.TrimEnd();

                    NotificationStrings       primaryStrings   = new NotificationStrings();
                    NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();

                    primaryStrings[NotificationString.OrganizationName]         = organization.Name;
                    primaryStrings[NotificationString.CurrencyCode]             = organization.Currency.DisplayCode;
                    primaryStrings[NotificationString.EmbeddedPreformattedText] = spec;
                    secondaryStrings["AmountFloat"]        = (nativeCentsPersonLookup[personId] / 100.0).ToString("N2");
                    secondaryStrings["BitcoinAmountFloat"] = (satoshiPersonLookup[personId] / 100.0).ToString("N2");
                    secondaryStrings["BitcoinAddress"]     = person.BitcoinPayoutAddress; // warn: potential rare race condition here

                    OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PaidOut, primaryStrings,
                                                    secondaryStrings, People.FromSingle(person));
                }

                // Create the master payout from its prototype

                Payout masterPayout = Payout.CreateBitcoinPayoutFromPrototype(organization, masterPayoutPrototype, txReady.GetHash().ToString());

                // Finally, create ledger entries and notify

                NotificationStrings       masterPrimaryStrings   = new NotificationStrings();
                NotificationCustomStrings masterSecondaryStrings = new NotificationCustomStrings();

                masterPrimaryStrings[NotificationString.OrganizationName] = organization.Name;
                masterPrimaryStrings[NotificationString.CurrencyCode]     = organization.Currency.DisplayCode;
                masterSecondaryStrings["AmountFloat"] =
                    (new Swarmops.Logic.Financial.Money(satoshisUsed, Currency.BitcoinCore).ToCurrency(
                         organization.Currency).Cents / 100.0).ToString("N2", CultureInfo.InvariantCulture);
                masterSecondaryStrings["BitcoinAmountFloat"] = (satoshisUsed / 100.0).ToString("N2", CultureInfo.InvariantCulture);
                masterSecondaryStrings["PaymentCount"]       = bitcoinPayouts.Count.ToString("N0", CultureInfo.InvariantCulture);

                OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Hotwallet_Outflow,
                                                masterPrimaryStrings, masterSecondaryStrings);

                // TODO: special case for native-bitcoin organizations vs. fiat-currency organizations

                FinancialTransaction ledgerTransaction = FinancialTransaction.Create(organization, utcNow,
                                                                                     "Bitcoin automated payout");

                if (organization.Currency.IsBitcoinCore)
                {
                    ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeSatoshis), null);
                    ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeSatoshis, null);
                }
                else
                {
                    // If the ledger isn't using bitcoin natively, we need to translate the miner fee paid to ledger cents before entering it into ledger

                    Int64 feeCentsLedger = new Money(feeSatoshis, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents;
                    ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeCentsLedger), null).AmountForeignCents = new Money(satoshisUsed, Currency.BitcoinCore);
                    ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeCentsLedger, null);
                }

                ledgerTransaction.BlockchainHash = transactionHash;

                masterPayout.BindToTransactionAndClose(ledgerTransaction, null);
            }
        }
예제 #5
0
        public static void TestMultisigPayout()
        {
            throw new InvalidOperationException("This function is only for testing purposes. It pays real money. Don't use except for dev/test.");

            // disable "code unreachable" warning for this code
            // ReSharper disable once CSharpWarnings::CS0162
            #pragma warning disable 162,219
            Organization organization = Organization.Sandbox; // a few testing cents here in test environment

            string bitcoinTestAddress = "3KS6AuQbZARSvqnaHoHfL1Xhm3bTLFAzoK";

            // Make a small test payment to a multisig address

            TransactionBuilder txBuilder = null; // TODO TODO TODO TODO  new TransactionBuilder();
            Int64 satoshis = new Money(100, Currency.FromCode("SEK")).ToCurrency(Currency.BitcoinCore).Cents;
            BitcoinTransactionInputs inputs      = null;
            Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash) * 20; // assume max 20k transaction size

            try
            {
                inputs = BitcoinUtility.GetInputsForAmount(organization, satoshis + satoshisMaximumAnticipatedFees);
            }
            catch (NotEnoughFundsException)
            {
                Debugger.Break();
            }

            // If we arrive at this point, the previous function didn't throw, and we have enough money. Add the inputs to the transaction.

            txBuilder = txBuilder.AddCoins(inputs.Coins);
            txBuilder = txBuilder.AddKeys(inputs.PrivateKeys);
            Int64 satoshisInput = inputs.AmountSatoshisTotal;

            // Add outputs and prepare notifications

            Int64 satoshisUsed = 0;
            //Dictionary<int, List<string>> notificationSpecLookup = new Dictionary<int, List<string>>();
            //Dictionary<int, List<Int64>> notificationAmountLookup = new Dictionary<int, List<Int64>>();
            Payout            masterPayoutPrototype = Payout.Empty;
            HotBitcoinAddress changeAddress         = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core);

            // Add the test payment

            if (bitcoinTestAddress.StartsWith("1")) // regular address
            {
                txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(bitcoinTestAddress),
                                           new Satoshis(satoshis));
            }
            else if (bitcoinTestAddress.StartsWith("3")) // multisig
            {
                txBuilder = txBuilder.Send(new BitcoinScriptAddress(bitcoinTestAddress, Network.Main),
                                           new Satoshis(satoshis));
            }
            else
            {
                throw new InvalidOperationException("Unhandled address case");
            }
            satoshisUsed += satoshis;

            // Set change address to wallet slush

            txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address));

            // Add fee

            int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count;
            // +inputs.Count for size variability

            Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) *
                                BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash);

            txBuilder     = txBuilder.SendFees(new Satoshis(feeSatoshis));
            satoshisUsed += feeSatoshis;

            // Sign transaction - ready to execute

            Transaction txReady = txBuilder.BuildTransaction(true);

            // Verify that transaction is ready

            if (!txBuilder.Verify(txReady))
            {
                // Transaction was not signed with the correct keys. This is a serious condition.

                NotificationStrings primaryStrings = new NotificationStrings();
                primaryStrings[NotificationString.OrganizationName] = organization.Name;

                OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError,
                                                primaryStrings);

                throw new InvalidOperationException("Transaction is not signed enough");
            }

            // Broadcast transaction

            BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash);

            // Note the transaction hash

            string transactionHash = txReady.GetHash().ToString();

            // Delete all old inputs, adjust balance for addresses (re-register unused inputs)

            inputs.AsUnspents.DeleteAll();

            // Log the new unspent created by change (if there is any)

            if (satoshisInput - satoshisUsed > 0)
            {
                SwarmDb.GetDatabaseForWriting()
                .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash,
                                                           +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0);
            }

            // Restore "code unreachable", "unsued var" warnings
            #pragma warning restore 162,219

            // This puts the ledger out of sync, so only do this on Sandbox for various small-change (cents) testing
        }
예제 #6
0
        // TODO: Condense TestUnspents into ONE call for MULTIPLE addresses (separated by | for Unspents according to API docs)

        // TODO: Enable backend to call a running bitcoin node for all this instead of callign third party services



        public static bool TestUnspents(BitcoinChain chain, string address)
        {
            // This function queries the Blockchain API for the unspent coin.
            bool result = false;
            HotBitcoinAddress hotAddress = null;
            JObject           addressInfoResult;
            JObject           unspentJsonResult;

            switch (chain)
            {
            case BitcoinChain.Core:

                addressInfoResult =
                    JObject.Parse(
                        new WebClient().DownloadString(
                            "https://blockchain.info/address/" + address + "?format=json&api_key=" +
                            SystemSettings.BlockchainSwarmopsApiKey));

                if ((int)addressInfoResult["final_balance"] == 0)
                {
                    return(false);    // no funds on address at all at this time
                }

                try
                {
                    unspentJsonResult = JObject.Parse(
                        new WebClient().DownloadString("https://blockchain.info/unspent?active=" + address + "&api_key=" +
                                                       SystemSettings.BlockchainSwarmopsApiKey));
                }
                catch (WebException webException)
                {
                    // A 500 on the above _may_ mean that there's no unspent outpoints. It can also mean a data
                    // retrieval or network error, in which case the exception must absolutely not be interpreted
                    // as valid data of zero unspent outpoints.

                    try
                    {
                        if (webException.Response == null)
                        {
                            throw;     // if there's no response at all, we can't do shit
                        }

                        string errorResponseContent =
                            new StreamReader(webException.Response.GetResponseStream()).ReadToEnd();

                        if (errorResponseContent.Trim().StartsWith("No free outputs to spend"))
                        {
                            // all is okay network-wise, there just aren't any UTXOs so we're getting an error code for that

                            return(false);    // no further processing and there are no fresh transactions
                        }

                        throw;     // otherwise throw upward
                    }
                    catch (WebException)
                    {
                        // Ok, we tried, but there's apparently a network error so we need to abort this whole thing
                        throw;
                    }
                }

                foreach (var unspentJson in unspentJsonResult["unspent_outputs"])
                {
                    BitcoinUnspentTransactionOutput txUnspent = new BitcoinUnspentTransactionOutput()
                    {
                        BitcoinAddress         = address,
                        ConfirmationCount      = (UInt32)unspentJson["confirmations"],
                        Satoshis               = (UInt64)unspentJson["value"],
                        TransactionHash        = (string)unspentJson["tx_hash_big_endian"],
                        TransactionOutputIndex = (UInt32)unspentJson["tx_output_n"]
                    };

                    if (txUnspent.ConfirmationCount < 2)
                    {
                        // Fresh transactions, return true
                        result = true;
                    }

                    // Add unspent to database

                    if (hotAddress == null)
                    {
                        hotAddress = HotBitcoinAddress.FromAddress(chain, address);
                    }

                    SwarmDb.GetDatabaseForWriting()
                    .CreateHotBitcoinAddressUnspentConditional(hotAddress.Identity, txUnspent.TransactionHash,
                                                               (int)txUnspent.TransactionOutputIndex, (Int64)txUnspent.Satoshis,
                                                               (int)txUnspent.ConfirmationCount);
                }

                // Update hotaddress totals

                HotBitcoinAddresses.UpdateAllUnspentTotals();

                return(result);



            case BitcoinChain.Cash:

                // TODO: SELECTION OF BLOCK EXPLORER, ADDRESS STRING FORMAT TO GO WITH IT

                addressInfoResult =
                    JObject.Parse(
                        new WebClient().DownloadString(
                            "https://bitcoincash.blockexplorer.com/api/addr/" + address));

                JArray unspentArray;

                if ((int)addressInfoResult["balanceSat"] == 0 && (int)addressInfoResult["unconfirmedBalanceSat"] == 0)
                {
                    return(false);    // no funds on address at all at this time
                }

                try
                {
                    unspentArray = JArray.Parse(
                        new WebClient().DownloadString("https://bitcoincash.blockexplorer.com/api/addr/" + address + "/utxo"));
                }
                catch (WebException webException)
                {
                    // A 500 on the above _may_ mean that there's no unspent outpoints. It can also mean a data
                    // retrieval or network error, in which case the exception must absolutely not be interpreted
                    // as valid data of zero unspent outpoints.

                    try
                    {
                        if (webException.Response == null)
                        {
                            throw;     // if there's no response at all, we can't do shit
                        }

                        string errorResponseContent =
                            new StreamReader(webException.Response.GetResponseStream()).ReadToEnd();

                        if (errorResponseContent.Trim().StartsWith("No free outputs to spend"))
                        {
                            // all is okay network-wise, there just aren't any UTXOs so we're getting an error code for that

                            return(false);    // no further processing and there are no fresh transactions
                        }

                        throw;     // otherwise throw upward
                    }
                    catch (WebException)
                    {
                        // Ok, we tried, but there's apparently a network error so we need to abort this whole thing
                        throw;
                    }
                }

                foreach (JObject unspentJson in unspentArray.Children())
                {
                    BitcoinUnspentTransactionOutput txUnspent = new BitcoinUnspentTransactionOutput()
                    {
                        BitcoinAddress         = address,
                        ConfirmationCount      = (UInt32)unspentJson["confirmations"],
                        Satoshis               = (UInt64)unspentJson["satoshis"],
                        TransactionHash        = (string)unspentJson["txid"],
                        TransactionOutputIndex = (UInt32)unspentJson["vout"]
                    };

                    if (txUnspent.ConfirmationCount < 2)
                    {
                        // Fresh transactions, return true
                        result = true;
                    }

                    // Add unspent to database

                    if (hotAddress == null)
                    {
                        hotAddress = HotBitcoinAddress.GetAddressOrForkCore(chain, address);
                    }

                    SwarmDb.GetDatabaseForWriting()
                    .CreateHotBitcoinAddressUnspentConditional(hotAddress.Identity, txUnspent.TransactionHash,
                                                               (int)txUnspent.TransactionOutputIndex, (Int64)txUnspent.Satoshis,
                                                               (int)txUnspent.ConfirmationCount);
                }

                // Update hotaddress totals

                HotBitcoinAddresses.UpdateAllUnspentTotals();

                return(result);

            default:
                throw new NotImplementedException("Unimplemented bitcoin chain: " + chain);
            }
        }
예제 #7
0
 static public HotBitcoinAddressUnspents ForAddress(HotBitcoinAddress address)
 {
     return(FromArray(SwarmDb.GetDatabaseForWriting().GetHotBitcoinAddressUnspents(address)));  // "ForWriting" is intentional here
 }