public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); if (command.Length < 2) { inProgress = false; msg = "invalid parameter count"; return(false); } bool erc20 = command[0].Equals("collectCoins", StringComparison.OrdinalIgnoreCase); if (erc20 || command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // ethereum RegisterTransfer gain orderId // ethereum CollectCoins amount string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt"); inProgress = false; if (erc20 && command.Length != 2 || !erc20 && command.Length != 3) { msg = "invalid parameter count"; return(false); } string gainString = "0"; string orderId = null; string amountString = null; if (erc20) { amountString = command[1]; } else { gainString = command[1]; orderId = command[2]; } string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "ethereum.secret is not set"; return(false); } var ethereumPrivateKey = secret; #if DEBUG string confirmationsCount = cfg["confirmationsCount"]; if (int.TryParse(confirmationsCount, out int parsedCount)) { mConfirmationsExpected = parsedCount; } #endif string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } string ethSrcAddress = EthECKey.GetPublicAddress(ethereumPrivateKey); var web3 = new Nethereum.Web3.Web3(rpcUrl); string srcAddressId = null; string dstAddressId = null; if (!erc20) { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix)) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = dealOrder.SrcAddress; dstAddressId = dealOrder.DstAddress; } else { dstAddressId = dealOrder.SrcAddress; srcAddressId = dealOrder.DstAddress; } amountString = dealOrder.Amount; } else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix)) { var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = repaymentOrder.SrcAddress; dstAddressId = repaymentOrder.DstAddress; } else { dstAddressId = repaymentOrder.SrcAddress; srcAddressId = repaymentOrder.DstAddress; } amountString = repaymentOrder.Amount; } else { msg = "unexpected referred order"; return(false); } } string payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxId = File.ReadAllText(progress); } else { string ethDstAddress = null; if (!erc20) { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var srcAddress = Address.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } Address dstAddress = Address.Parser.ParseFrom(protobuf); if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name)) { msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}"; return(false); } ethDstAddress = dstAddress.Value; if (!ethSrcAddress.Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } } BigInteger transferAmount; if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } BigInteger gain; if (!BigInteger.TryParse(gainString, out gain)) { msg = "Invalid amount"; return(false); } transferAmount = transferAmount + gain; if (transferAmount < 0) { msg = "Invalid amount"; return(false); } var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(ethSrcAddress).Result; TransactionSigner signer = new TransactionSigner(); HexBigInteger gasPrice; string gasPriceInGweiString = cfg["gasPriceInGwei"]; if (int.TryParse(gasPriceInGweiString, out int gasPriceOverride)) { gasPrice = new HexBigInteger(Nethereum.Util.UnitConversion.Convert.ToWei(gasPriceOverride, Nethereum.Util.UnitConversion.EthUnit.Gwei)); } else { gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result; } Console.WriteLine("gasPrice: " + gasPrice.Value.ToString()); string to; string data; BigInteger amount; HexBigInteger gasLimit; if (erc20) { string creditcoinContract = cfg["creditcoinContract"]; string creditcoinContractAbi = cfg["creditcoinContractAbi"]; to = creditcoinContract; var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract); var burn = contract.GetFunction("exchange"); var functionInput = new object[] { transferAmount, txBuilder.getSighash() }; data = burn.GetData(functionInput); gasLimit = burn.EstimateGasAsync(functionInput).Result; amount = 0; } else { gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(orderId, ethDstAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result; Console.WriteLine("gasLimit: " + gasLimit.Value.ToString()); to = ethDstAddress; data = orderId; amount = transferAmount; } string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data); payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result; Console.WriteLine("Ethereum Transaction ID: " + payTxId); File.WriteAllText(progress, $"{payTxId}"); } inProgress = true; while (true) { var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result; if (receipt.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; if (blockNumber.Value - receipt.BlockNumber.Value >= mConfirmationsExpected) { break; } } Thread.Sleep(1000); } File.Delete(progress); inProgress = false; if (erc20) { command = new string[] { command[0], ethSrcAddress, amountString, payTxId }; } else { command = new string[] { command[0], gainString, orderId, payTxId }; } var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { return(false); } Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid); return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 1); if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // bitcoin RegisterTransfer registeredSourceId amount sourceTxId string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt"); inProgress = false; Debug.Assert(command.Length == 4); string registeredSourceId = command[1]; string amountString = command[2]; string sourceTxIdString = command[3]; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "bitcoin.secret is not set"; return(false); } string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } //TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "bitcoin.confirmationsCount is not set"; return(false); } int confirmationsExpected = 1; if (!int.TryParse(confirmationsCount, out confirmationsExpected)) { msg = "bitcoin.confirmationsCount is not an int"; return(false); } string feeString = cfg["fee"]; if (string.IsNullOrWhiteSpace(feeString)) { msg = "bitcoin.fee is not set"; return(false); } if (!int.TryParse(feeString, out int fee)) { msg = "bitcoin.fee is not an int"; return(false); } var bitcoinPrivateKey = new BitcoinSecret(secret); var network = bitcoinPrivateKey.Network; var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); string payTxIdHash; uint256 payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxIdHash = File.ReadAllText(progress); if (!uint256.TryParse(payTxIdHash, out payTxId)) { msg = "corrupted progress file"; return(false); } } else { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); string destinationAddress; if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress)) { msg = "Escrow not found for " + name; return(false); } Money transferAmount; if (!Money.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } if (!address.Blockchain.Equals(name)) { msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {address.Blockchain}"; return(false); } var sourceAddress = bitcoinPrivateKey.GetAddress(); if (!sourceAddress.ToString().Equals(address.Address_, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } #if RELEASE if (network != NBitcoin.Network.Main) { msg = "bitcoin.secret is not defined for main network"; return(false); } #endif var sourceTxId = uint256.Parse(sourceTxIdString); var transactionResponse = rpcClient.GetRawTransaction(sourceTxId); //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount) var receivedCoins = transactionResponse.Outputs.AsCoins(); OutPoint outPointToSpend = null; TxOut outTxToSpend = null; Money amount = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; outTxToSpend = coin.TxOut; amount = (Money)coin.Amount; if (amount.CompareTo(transferAmount + fee * 2) < 0) { msg = $"Invalid transaction - needed: {transferAmount}, has: {amount.ToString()}"; return(false); } break; } } if (outPointToSpend == null) { msg = "Invalid transaction - no outputs that the client can spend"; return(false); } var addressFrom = BitcoinAddress.Create(address.Address_, network); var addressTo = BitcoinAddress.Create(destinationAddress, network); var message = address.Sighash; var bytes = Encoding.UTF8.GetBytes(message); var bitcoinTransactionBuilder = new TransactionBuilder(); var transaction = bitcoinTransactionBuilder .AddCoins(new Coin(outPointToSpend, outTxToSpend)) .AddKeys(bitcoinPrivateKey) .Send(addressTo.ScriptPubKey, transferAmount + fee) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero) .SendFees(new Money(fee, MoneyUnit.Satoshi)) .SetChange(addressFrom.ScriptPubKey) .BuildTransaction(true); if (!bitcoinTransactionBuilder.Verify(transaction)) { msg = "failed verify transaction"; return(false); } try { payTxId = rpcClient.SendRawTransaction(transaction); } catch (RPCException e) { msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}"; return(false); } if (payTxId == null) { msg = "failed to broadcast - unknown error"; return(false); } payTxIdHash = payTxId.ToString(); File.WriteAllText(progress, payTxIdHash); } inProgress = true; while (true) { var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId); if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= confirmationsExpected) { break; } Thread.Sleep(1000); } command = new string[] { command[0], registeredSourceId, amountString, feeString, payTxIdHash, ((network == Network.Main) ? "1" : "0") }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content); File.Delete(progress); inProgress = false; return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 1); if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // ethereum RegisterTransfer registeredSourceId amount string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt"); inProgress = false; Debug.Assert(command.Length == 3 || command.Length == 4); string registeredSourceId = command[1]; string amountString = command[2]; bool erc20 = command.Length == 4; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "ethereum.secret is not set"; return(false); } var ethereumPrivateKey = secret; // TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "ethereum.confirmationsCount is not set"; return(false); } if (!int.TryParse(confirmationsCount, out int confirmationsExpected)) { msg = "ethereum.confirmationsCount is not an int"; return(false); } string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } var web3 = new Nethereum.Web3.Web3(rpcUrl); string payTxId; BigInteger fee; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); var data = File.ReadAllText(progress).Split(':'); if (data.Length != 2) { msg = "Invalid progress data"; return(false); } payTxId = data[0]; if (!BigInteger.TryParse(data[1], out fee)) { msg = "Invalid progress data"; return(false); } } else { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); string destinationAddress; if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress)) { msg = "Escrow not found for " + name; return(false); } BigInteger transferAmount; if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } if (!address.Blockchain.Equals(name)) { msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {address.Blockchain}"; return(false); } string sourceAddress = EthECKey.GetPublicAddress(ethereumPrivateKey); if (!sourceAddress.Equals(address.Address_, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(sourceAddress).Result; TransactionSigner signer = new TransactionSigner(); // TODO maybe add a y/n choice for user to decline or configurable gas price override var gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result; Console.WriteLine("gasPrice: " + gasPrice.Value.ToString()); string to; string data; BigInteger amount; HexBigInteger gasLimit; if (erc20) { string creditcoinContract = cfg["creditcoinContract"]; string creditcoinContractAbi = cfg["creditcoinContractAbi"]; to = creditcoinContract; var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract); var burn = contract.GetFunction("exchange"); var functionInput = new object[] { transferAmount, address.Sighash }; data = burn.GetData(functionInput); gasLimit = burn.EstimateGasAsync(functionInput).Result; fee = gasLimit.Value * gasPrice.Value; amount = 0; } else { gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(registeredSourceId, destinationAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result; Console.WriteLine("gasLimit: " + gasLimit.Value.ToString()); fee = gasLimit.Value * gasPrice.Value; to = destinationAddress; data = address.Sighash; amount = transferAmount + fee; } string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data); payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result; Console.WriteLine("Ethereum Transaction ID: " + payTxId); File.WriteAllText(progress, $"{payTxId}:{fee.ToString()}"); } inProgress = true; while (true) { var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result; if (receipt.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; if (blockNumber.Value - receipt.BlockNumber.Value >= confirmationsExpected) { break; } } Thread.Sleep(1000); } File.Delete(progress); inProgress = false; command = new string[] { command[0], registeredSourceId, amountString, erc20 ? "0" : fee.ToString(), payTxId, erc20 ? "creditcoin" : "1" }; var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { return(false); } Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content); return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); if (command.Length < 2) { inProgress = false; msg = "invalid parameter count"; return(false); } if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // bitcoin RegisterTransfer srcAddressId dstAddressId orderId amount sourceTxId string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt"); inProgress = false; if (command.Length != 4) { msg = "invalid parameter count"; return(false); } string gainString = command[1]; string orderId = command[2]; string sourceTxIdString = command[3]; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "bitcoin.secret is not set"; return(false); } string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } #if DEBUG string confirmationsCount = cfg["confirmationsCount"]; if (int.TryParse(confirmationsCount, out int parsedCount)) { mConfirmationsExpected = parsedCount; } #endif string feeString = cfg["fee"]; if (string.IsNullOrWhiteSpace(feeString)) { msg = "bitcoin.fee is not set"; return(false); } if (!int.TryParse(feeString, out int fee)) { msg = "bitcoin.fee is not an int"; return(false); } var bitcoinPrivateKey = new BitcoinSecret(secret); var network = bitcoinPrivateKey.Network; var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); string srcAddressId; string dstAddressId; string amountString; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix)) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = dealOrder.SrcAddress; dstAddressId = dealOrder.DstAddress; } else { dstAddressId = dealOrder.SrcAddress; srcAddressId = dealOrder.DstAddress; } amountString = dealOrder.Amount; } else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix)) { var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = repaymentOrder.SrcAddress; dstAddressId = repaymentOrder.DstAddress; } else { dstAddressId = repaymentOrder.SrcAddress; srcAddressId = repaymentOrder.DstAddress; } amountString = repaymentOrder.Amount; } else { msg = "unexpected referred order"; return(false); } string payTxIdHash; uint256 payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxIdHash = File.ReadAllText(progress); if (!uint256.TryParse(payTxIdHash, out payTxId)) { msg = "corrupted progress file"; return(false); } } else { protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var srcAddress = Address.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var dstAddress = Address.Parser.ParseFrom(protobuf); long transferAmount; if (!long.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } long gain; if (!long.TryParse(gainString, out gain)) { msg = "Invalid amount"; return(false); } if (transferAmount + gain < transferAmount) { msg = "Overflow"; return(false); } transferAmount = transferAmount + gain; if (transferAmount < 0) { msg = "Invalid amount"; return(false); } if (transferAmount + fee < transferAmount) { msg = "Overflow"; return(false); } if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name)) { msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}"; return(false); } var sourceAddress = bitcoinPrivateKey.GetAddress(); if (!sourceAddress.ToString().Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } #if RELEASE if (network != NBitcoin.Network.Main) { msg = "bitcoin.secret is not defined for main network"; return(false); } #endif var sourceTxId = uint256.Parse(sourceTxIdString); var transactionResponse = rpcClient.GetRawTransaction(sourceTxId); //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount) var receivedCoins = transactionResponse.Outputs.AsCoins(); OutPoint outPointToSpend = null; TxOut outTxToSpend = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; outTxToSpend = coin.TxOut; long amount = coin.Amount.Satoshi; if (amount < transferAmount + fee) { msg = $"Invalid transaction - needed: {transferAmount} + {fee}, has: {amount.ToString()}"; return(false); } break; } } if (outPointToSpend == null) { msg = "Invalid transaction - no outputs that the client can spend"; return(false); } var addressFrom = BitcoinAddress.Create(srcAddress.Value, network); var addressTo = BitcoinAddress.Create(dstAddress.Value, network); var message = orderId; var bytes = Encoding.UTF8.GetBytes(message); var bitcoinTransactionBuilder = new TransactionBuilder(); var transaction = bitcoinTransactionBuilder .AddCoins(new Coin(outPointToSpend, outTxToSpend)) .AddKeys(bitcoinPrivateKey) .Send(addressTo.ScriptPubKey, transferAmount) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero) .SendFees(new Money(fee, MoneyUnit.Satoshi)) .SetChange(addressFrom.ScriptPubKey) .BuildTransaction(true); if (!bitcoinTransactionBuilder.Verify(transaction)) { msg = "failed verify transaction"; return(false); } try { payTxId = rpcClient.SendRawTransaction(transaction); } catch (RPCException e) { msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}"; return(false); } if (payTxId == null) { msg = "failed to broadcast - unknown error"; return(false); } payTxIdHash = payTxId.ToString(); File.WriteAllText(progress, payTxIdHash); } inProgress = true; while (true) { var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId); if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= mConfirmationsExpected) { break; } Thread.Sleep(1000); } command = new string[] { command[0], gainString, orderId, payTxIdHash }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid); File.Delete(progress); inProgress = false; return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }