private string FormatJson(HotBitcoinAddresses addresses) { StringBuilder result = new StringBuilder(16384); Dictionary <BitcoinChain, double> conversionRateLookup = new Dictionary <BitcoinChain, double>(); Dictionary <BitcoinChain, Int64> satoshisTotalLookup = new Dictionary <BitcoinChain, long>(); long fiatCentsPerCoreCoin = new Money(BitcoinUtility.SatoshisPerBitcoin, Currency.BitcoinCore).ToCurrency(_authenticationData.CurrentOrganization.Currency).Cents; long fiatCentsPerCashCoin = new Money(BitcoinUtility.SatoshisPerBitcoin, Currency.BitcoinCash).ToCurrency(_authenticationData.CurrentOrganization.Currency).Cents; conversionRateLookup[BitcoinChain.Cash] = fiatCentsPerCashCoin / 1.0 / BitcoinUtility.SatoshisPerBitcoin; // the "/1.0" converts to double implicitly conversionRateLookup[BitcoinChain.Core] = fiatCentsPerCoreCoin / 1.0 / BitcoinUtility.SatoshisPerBitcoin; result.Append("{\"rows\":["); int addressesWithFunds = 0; foreach (HotBitcoinAddress address in addresses) { HotBitcoinAddressUnspents unspents = HotBitcoinAddressUnspents.ForAddress(address); Int64 satoshisUnspentAddress = 0; StringBuilder childResult = new StringBuilder(16384); foreach (HotBitcoinAddressUnspent unspent in unspents) { childResult.Append("{"); childResult.AppendFormat( "\"id\":\"UTXO{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\"", unspent.Identity, Resources.Pages.Ledgers.BitcoinHotWallet_UnspentTransaction, unspent.TransactionHash, (unspent.AmountSatoshis / 100.0).ToString("N2"), (unspent.AmountSatoshis / 100.0 * conversionRateLookup[unspent.Address.Chain]).ToString("N2") ); satoshisUnspentAddress += unspent.AmountSatoshis; childResult.Append("},"); } if (unspents.Count > 0) { childResult.Remove(childResult.Length - 1, 1); // remove last comma } if (satoshisUnspentAddress > 0) { result.Append("{"); result.AppendFormat( "\"id\":\"{0}\"," + "\"derivePath\":\"{1}\"," + "\"address\":\"{2}\"," + "\"balanceMicrocoins\":\"{3}\"," + "\"balanceFiat\":\"{4}\",", address.Identity, address.Chain.ToString() + " " + address.DerivationPath, (address.Chain == BitcoinChain.Cash? address.HumanAddress.Substring("bitcoincash:".Length): address.ProtocolLevelAddress), JsonExpandingString(address.Identity, satoshisUnspentAddress), JsonExpandingString(address.Identity, (Int64)(satoshisUnspentAddress * conversionRateLookup[address.Chain])) ); result.Append("\"state\":\"closed\",\"children\":[" + childResult.ToString() + "]"); result.Append("},"); addressesWithFunds++; if (!satoshisTotalLookup.ContainsKey(address.Chain)) { satoshisTotalLookup[address.Chain] = 0; } satoshisTotalLookup[address.Chain] += satoshisUnspentAddress; } } if (addressesWithFunds > 0) { result.Remove(result.Length - 1, 1); // remove last comma } else // no funds at all in hotwallet { result.Append("{"); result.AppendFormat( "\"id\":\"0\"," + "\"derivePath\":\"0\"," + "\"address\":\"{0}\"," + "\"balanceMicrocoins\":\"{1}\"," + "\"balanceFiat\":\"{2}\"", Resources.Pages.Ledgers.BitcoinHotWallet_Empty, 0.0.ToString("N2"), 0.0.ToString("N2") ); result.Append("}"); } result.Append("]"); if (satoshisTotalLookup.Count > 0) { // We should also have a footer, because we need a total result.Append(",\"footer\":["); bool previousFooterRow = false; foreach (BitcoinChain chain in satoshisTotalLookup.Keys) { if (previousFooterRow) { result.Append(","); } result.Append("{"); result.AppendFormat( "\"derivePath\":\"" + Resources.Global.Global_Total.ToUpperInvariant() + " " + (string)chain.ToString().ToUpperInvariant() + "\",\"balanceMicrocoins\":\"{0}\",\"balanceFiat\":\"{1}\"", (satoshisTotalLookup[chain] / 100.0).ToString("N2"), (satoshisTotalLookup[chain] / 100.0 * conversionRateLookup[chain]).ToString("N2")); result.Append("}"); // on separate line to suppress warning*/ previousFooterRow = true; } result.Append("]"); } result.Append("}"); return(result.ToString()); }
static public AjaxCallResult ProcessTransactionReceived(string guid, string txHash) { AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly BitcoinChain chain = BitcoinChain.Cash; string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(chain, bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(chain, bitcoinAddress).Unspents; Int64 satoshisReceived = unspents.Last().AmountSatoshis; if (unspents.Last().TransactionHash != txHash && txHash.Length > 0) { // Race condition. Debugger.Break(); } HotBitcoinAddressUnspent utxoToReturn = unspents.Last(); Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); // Create success message and ledger transaction string successMessage = string.Empty; // TODO: Get the tx, get the input string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(chain, txHash) [0]; // assumes at least one input address // Return the money, too. Set fee for a 300-byte transaction. ReturnBitcoinEchoUtxoOrder backendOrder = new ReturnBitcoinEchoUtxoOrder(utxoToReturn); backendOrder.Create(authData.CurrentOrganization, authData.CurrentUser); string tx1Description = "Bitcoin technical echo test (will be repaid immediately)"; if (authData.CurrentOrganization.Currency.IsBitcoinCash) { // The ledger is native bitcoin, so cent units are satoshis FinancialTransaction ledgerTx1 = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, tx1Description); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -(satoshisReceived), authData.CurrentUser); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx1.BlockchainHash = txHash; // The return payment will be logged when made, so its hash can be recorded if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx1 = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, tx1Description); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -orgNativeCents, authData.CurrentUser); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); ledgerTx1.BlockchainHash = txHash; // The second transaction is logged when executed in the back-end order successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }
static public AjaxCallResult ProcessTransactionReceived(string guid, string txHash) { BitcoinChain chain = BitcoinChain.Cash; AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(chain, bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(chain, bitcoinAddress).Unspents; // TODO: Update the HotBitcoinAddress with the new amount? HotBitcoinAddressUnspent unspent = null; Int64 satoshisReceived = 0; foreach (HotBitcoinAddressUnspent potentialUnspent in unspents) { if (potentialUnspent.TransactionHash == txHash) { satoshisReceived = potentialUnspent.AmountSatoshis; unspent = potentialUnspent; } } if (unspent == null) // Supplied transaction hash was not found in collection { Debugger.Break(); // TODO: Something else than break the debugger } Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); // Make sure that the hotwallet native currency is bitcoin cash authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.ForeignCurrency = Currency.BitcoinCash; // Create success message and ledger transaction string successMessage = string.Empty; FinancialTransaction testTransaction = null; try { testTransaction = FinancialTransaction.FromBlockchainHash(authData.CurrentOrganization, txHash); // We've already seen this donation! Something is seriously bogus here Debugger.Break(); return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); } catch (ArgumentException) { // This exception is expected - the transaction should not yet exist } if (authData.CurrentOrganization.Currency.IsBitcoinCash) { // The ledger is native bitcoin cash, so units are Satoshis FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Donation (bitcoin to hotwallet)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.IncomeDonations, -satoshisReceived, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx.BlockchainHash = txHash; if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Donation (bitcoin to hotwallet)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.IncomeDonations, -orgNativeCents, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); ledgerTx.BlockchainHash = txHash; successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }
public override void Run() { HotBitcoinAddressUnspent utxoToReturn = HotBitcoinAddressUnspent.FromIdentity(UtxoIdentity); HotBitcoinAddress utxoAddress = utxoToReturn.Address; BitcoinSecret secretKey = utxoAddress.PrivateKey; // TODO: Verify that the utxoAddress is an EchoTest address, i.e. has second path component == BitcoinUtility.BitcoinEchoTestIndex string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(BitcoinChain.Cash, utxoToReturn.TransactionHash)[0]; // assumes at least one input address -- not coinbase // Return the money BitcoinTransactionInputs inputs = utxoToReturn.AsInputs; Int64 satoshisToReturn = utxoToReturn.AmountSatoshis; Coin[] coins = inputs.Coins; ICoin[] iCoins = coins; ISecret[] privateKeys = utxoToReturn.AsInputs.PrivateKeys; TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder = txBuilder.SendFees(new Satoshis(BitcoinUtility.EchoFeeSatoshis)); txBuilder = txBuilder.AddCoins(iCoins); txBuilder = txBuilder.AddKeys(privateKeys); if (returnAddress.StartsWith("1")) { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(returnAddress), new Satoshis(utxoToReturn.AmountSatoshis - BitcoinUtility.EchoFeeSatoshis)); } else if (returnAddress.StartsWith("3")) { txBuilder = txBuilder.Send(new BitcoinScriptAddress(returnAddress, Network.Main), new Satoshis(utxoToReturn.AmountSatoshis - BitcoinUtility.EchoFeeSatoshis)); } else { throw new ArgumentException("Unrecognized address format"); } Transaction tx = txBuilder.BuildTransaction(true, SigHash.ForkId | SigHash.All); BitcoinUtility.BroadcastTransaction(tx, BitcoinChain.Cash); utxoToReturn.Delete(); utxoAddress.UpdateTotal(); // Update the ledger string tx2Description = "Bitcoin echo test repayment"; FinancialTransaction ledgerTx2 = FinancialTransaction.Create(this.Organization, DateTime.UtcNow, tx2Description); if (this.Organization.Currency.IsBitcoinCash) { ledgerTx2.AddRow(this.Organization.FinancialAccounts.DebtsOther, satoshisToReturn, this.Person); ledgerTx2.AddRow(this.Organization.FinancialAccounts.AssetsBitcoinHot, -satoshisToReturn, this.Person); } else { Int64 centsPresentation = new Swarmops.Logic.Financial.Money(satoshisToReturn, Currency.BitcoinCash).ToCurrency(this.Organization.Currency).Cents; ledgerTx2.AddRow(this.Organization.FinancialAccounts.DebtsOther, centsPresentation, this.Person); ledgerTx2.AddRow(this.Organization.FinancialAccounts.AssetsBitcoinHot, -centsPresentation, this.Person); } ledgerTx2.BlockchainHash = tx.GetHash().ToString(); }
static public AjaxCallResult CheckTransactionReceived(string guid, string txHash) { AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(bitcoinAddress).Unspents; // TODO: Update the HotBitcoinAddress with the new amount? Int64 satoshisReceived = unspents.Last().AmountSatoshis; if (unspents.Last().TransactionHash != txHash && txHash.Length > 0) { // Race condition. Debugger.Break(); } Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.Bitcoin); // Make sure that the hotwallet native currency is bitcoin authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.ForeignCurrency = Currency.Bitcoin; // Create success message and ledger transaction string successMessage = string.Empty; // TODO: Get the tx, get the input string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(txHash) [0]; // assumes at least one input address if (authData.CurrentOrganization.Currency.IsBitcoin) { // The ledger is native bitcoin, so units are Satoshis FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Bitcoin echo test (will be repaid immediately)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -satoshisReceived, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx.BlockchainHash = txHash; if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } // TODO: Log the payout back, as an inbound invoice for immediate payout } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Bitcoin echo test (will be repaid immediately)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -orgNativeCents, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.Bitcoin); ledgerTx.BlockchainHash = txHash; successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); // TODO: Create a payout back for this amount -- needs to be specified in bitcoin -- as an inbound invoice } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }