/// <summary> /// Build a transacting using the coins client. /// This will communicate with the client over https. /// </summary> public override Task Build(IBitcoinClient client, TransactionContext transactionContext) { // this code should not be use in production as clients are not ssl enabled (no need for that) // to use in test scenarios delete this exception throw new NotImplementedException(); ////// create the transaction hex ////transactionContext.UnsignedRawTransaction = client.CreateRawTransactionAsync(transactionContext.CreateRawTransaction).Result; ////transactionContext.UnsignedTransaction = client.DecodeRawTransactionAsync(transactionContext.UnsignedRawTransaction).Result; ////// create transaction signature. ////transactionContext.SignRawTransaction = new SignRawTransaction(transactionContext.UnsignedRawTransaction); ////transactionContext.SendItems.Where(m => !m.Failed).ForEach(a => a.SpendFromAddresses.ForEach(m => transactionContext.SignRawTransaction.AddKey(m.PrivateKey))); ////// sign the transaction. ////transactionContext.SignedRawTransaction = client.SignRawTransactionAsync(transactionContext.SignRawTransaction).Result; ////transactionContext.DecodedRawTransaction = client.DecodeRawTransactionAsync(transactionContext.SignedRawTransaction.Hex).Result; ////return Task.FromResult(0); }
/// <summary> /// Build a transaction. /// </summary> public virtual Task Build(IBitcoinClient client, TransactionContext transactionContext) { var transaction = this.CreatePubKeyHashTransaction(this.CoinParameters, transactionContext.CreateRawTransaction); // create the transaction hex transactionContext.UnsignedRawTransaction = this.Serializer.ToHex(transaction); ////var createdHex = await client.CreateRawTransactionAsync(transactionContext.CreateRawTransaction); ////var created = this.Serializer.FromHex(createdHex); // validate ////transactionContext.UnsignedTransaction = await client.DecodeRawTransactionAsync(transactionContext.UnsignedRawTransaction); // create transaction signature and add the relevant private keys and previous outputs. transactionContext.SignRawTransaction = new SignRawTransaction(transactionContext.UnsignedRawTransaction); //transactionContext.SendItems.Where(m => !m.Failed).ForEach(m => transactionContext.SignRawTransaction.AddKey(m.SpendFromAddress.PrivateKey)); //transactionContext.PreviousRawTransactions.ForEach(p => p.VOut.ForEach(output => transactionContext.SignRawTransaction.AddInput(p.TxId, output.N, output.ScriptPubKey.Hex))); transactionContext.SendItems .Where(m => !m.Failed) .ForEach(m => m.SpendFromAddresses.ForEach(b => { transactionContext.SignRawTransaction.AddKey(b.PrivateKey); b.Transactions.ForEach(a => transactionContext.SignRawTransaction.AddInput(a.Hash, (int)a.Index, a.ScriptPubKeyHex)); })); // ready to sign transaction = this.Sign(this.CoinParameters, transactionContext.SignRawTransaction); transactionContext.SignedRawTransaction = new SignedRawTransaction { Hex = this.Serializer.ToHex(transaction), Complete = true }; ////var signed = await client.SignRawTransactionAsync(transactionContext.SignRawTransaction); // validate the transaction //transactionContext.DecodedRawTransaction = await client.DecodeRawTransactionAsync(transactionContext.SignedRawTransaction.Hex); return(Task.FromResult(true)); }
/// <summary> /// Create a transaction. /// This allows to create a transaction using the private keys, /// This means we don't need to open the wallet to send the transaction and all the handling of open wallet synchronization is not required. /// </summary> public static async Task <TransactionContext> CreateAsync(CoinParameters param, IBitcoinClient client, IEnumerable <TransactionInfo> items) { if (string.Equals(param.CoinTag, "DST")) { return(await CreateDeStreamAsync(param, client, items)); } var context = new TransactionContext { CoinParameters = param, PreviousRawTransactions = new List <DecodedRawTransaction>(), CreateRawTransaction = new CreateRawTransaction(), SendItems = new List <TransactionInfo>() }; foreach (var item in items) { context.SendItems.Add(item); // check if receiving address is already in the transaction, this is allowed but not supported by the client. if (item.SpendToAddresses.Select(s => s.PublicKey) .Intersect(item.SpendFromAddresses.Select(s => s.PublicKey)).Any()) { item.Fail(false, FailedReason.CannotSendToSelfUnlessChange); continue; } if (item.SpendFromAddresses.SelectMany(s => s.Transactions).None()) { item.Fail(false, FailedReason.NoTransactionsFound); continue; } // check if a transaction is already in use if (context.CreateRawTransaction.Inputs. Any(source => item.SpendFromAddresses.SelectMany(s => s.Transactions) .Any(target => source.TransactionId == target.Hash && source.Output == target.Index))) { item.Fail(true, FailedReason.TransactionInputAlreadyInUse); continue; } // check if receiving address is already in the transaction, this is allowed but not supported by the client. if (context.CreateRawTransaction.Outputs .Any(source => item.SpendToAddresses.Any(target => source.Key == target.PublicKey))) { item.Fail(true, FailedReason.ReceiveAddressAlreadyInOutput); continue; } if (item.RequestedFee <= 0) { item.Fail(false, FailedReason.FeeCannotBeZero); continue; } ////try ////{ //// var rawTrxListAsync = item.SpendFromAddresses //// .SelectMany(s => s.Transactions) //// .DistinctBy(t => t.Hash) //// .Where(a => context.PreviousRawTransactions.None(b => b.TxId == a.Hash)) //// .Select(async trx => await client.GetRawTransactionAsync(trx.Hash, 1)); //// var previousRawTransactions = (await Task.WhenAll(rawTrxListAsync)).ToList(); //// context.PreviousRawTransactions.AddRange(previousRawTransactions); //// // the Gridcoin hack //// GridcoinHack(context.CoinParameters, previousRawTransactions); ////} ////catch (Exception) ////{ //// item.Fail(false, FailedReason.FailedReadingTransactionsFromClient); //// continue; ////} // validate all the transaction outpoints have the ScriptPubKey Hex ////item.SpendFromAddresses.ForEach(spend => //// spend.Transactions.ForEach(strx => //// { //// var output = context.PreviousRawTransactions //// .Where(t => t.TxId == strx.Hash) //// .SelectMany(s => s.VOut) //// .SingleOrDefault(v => v.N == strx.Index); //// if (output.IsNotNull() && output.Value == strx.Value) //// { //// if (output.ScriptPubKey.IsNotNull()) //// { //// if (output.ScriptPubKey.Addresses.Any(addr => addr == spend.PublicKey)) //// { //// strx.ScriptPubKeyHex = output.ScriptPubKey.Hex; //// } //// } //// } //// })); if (item.SpendFromAddresses.SelectMany(s => s.Transactions).Any(t => t.ScriptPubKeyHex.IsNullOrEmpty())) { item.Fail(false, FailedReason.ScriptPubKeyHexNotFound); continue; } //// get the outputs associated with the sender address var spendToEnumerated = item.SpendToAddresses.ToList(); var spendFromEnumerated = item.SpendFromAddresses.SelectMany(s => s.Transactions).ToList(); ////var outputs = previousRawTransactions //// .ToDictionary(rawtrx => rawtrx.TxId, rawtrx => rawtrx.VOut //// .Where(vout => vout.ScriptPubKey.Addresses //// .Any(add => item.SpendFromAddresses.Any(a => a.PublicKey == add)))); //// calculate the sum of inputs and outputs. var inputSum = spendFromEnumerated.Select(t => t.Value).Sum(); var outputSum = spendToEnumerated.Select(spt => spt.Amount).Sum(); var change = inputSum - outputSum; if (change < 0) { item.Fail(false, FailedReason.InvalidSum); continue; } if (item.SpendToAddresses.Any(s => s.TakeFee)) { //// take the fee from a receiver address. var receiver = item.SpendToAddresses.First(s => s.TakeFee); if (receiver.Amount < item.RequestedFee) { item.Fail(false, FailedReason.InsufficientFeeOnReceiver); continue; } receiver.Amount -= item.RequestedFee; } else { //// take the fee from the sender if (change < item.RequestedFee) { item.Fail(false, FailedReason.InsufficientFeeOnSender); continue; } change -= item.RequestedFee; } if (change > 0) { if (item.ChangeAddress.IsNullOrEmpty()) { item.Fail(false, FailedReason.NoChangeAddressFound); continue; } } // try to calculate the fee if trx is too big the default fee may not be enough // http://bitcoin.stackexchange.com/questions/7537/calculator-for-estimated-tx-fees // https://en.bitcoin.it/wiki/Transaction_fees // http://bitcoin.stackexchange.com/questions/3400/what-is-the-exact-formula-for-calculating-transaction-fees // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending // formula fee = [Normal fee * (500 KB) / (500KB - Block size)] - [Normal fee / (1 - Block size / 500KB)] // trx size = [in*148 + out*34 + 10 plus or minus 'in'] //// TODO: move this code to a facotry to allow calculation of fee per coin //// coins have different block sizes sand this needs to be acocunted for as well var ins = spendFromEnumerated.Count(); var outs = spendToEnumerated.Count(); var sizeByte = Convert.ToDecimal((ins * 148) + (outs * 34) + 10 + (ins / 2)); // we'll calculate roughly the coin fee times size of transaction const decimal AvgSizeBytes = 1000; if (sizeByte >= AvgSizeBytes) { var feeRatio = sizeByte / AvgSizeBytes; var expectedFee = feeRatio * item.CoinFee; if (expectedFee > item.RequestedFee) { item.Fail(false, FailedReason.InsufficientFeeForTransactionSize); continue; } } spendFromEnumerated.ForEach(vout => context.CreateRawTransaction.AddInput(vout.Hash, (int)vout.Index)); spendToEnumerated.ForEach(spt => context.CreateRawTransaction.AddOutput(spt.PublicKey, spt.Amount)); if (change > 0) { context.CreateRawTransaction.AddOutput(item.ChangeAddress, change); } } if (context.SendItems.All(w => w.Failed)) { return(context); } //// create the builder and build the transaction (either a client using the coins client or locally if supported). await TransactionBuilder.Create(param).Build(client, context); // seems we are done here return(context); }
private static async Task <TransactionContext> CreateDeStreamAsync(CoinParameters param, IBitcoinClient client, IEnumerable <TransactionInfo> items) { const decimal feeRate = (decimal)0.0077; var context = new TransactionContext { CoinParameters = param, PreviousRawTransactions = new List <DecodedRawTransaction>(), CreateRawTransaction = new CreateRawTransaction(), SendItems = new List <TransactionInfo>() }; foreach (var item in items) { context.SendItems.Add(item); // check if receiving address is already in the transaction, this is allowed but not supported by the client. if (item.SpendToAddresses.Select(s => s.PublicKey) .Intersect(item.SpendFromAddresses.Select(s => s.PublicKey)).Any()) { item.Fail(false, FailedReason.CannotSendToSelfUnlessChange); continue; } if (item.SpendFromAddresses.SelectMany(s => s.Transactions).None()) { item.Fail(false, FailedReason.NoTransactionsFound); continue; } // check if a transaction is already in use if (context.CreateRawTransaction.Inputs.Any(source => item.SpendFromAddresses .SelectMany(s => s.Transactions) .Any(target => source.TransactionId == target.Hash && source.Output == target.Index))) { item.Fail(true, FailedReason.TransactionInputAlreadyInUse); continue; } // check if receiving address is already in the transaction, this is allowed but not supported by the client. if (context.CreateRawTransaction.Outputs .Any(source => item.SpendToAddresses.Any(target => source.Key == target.PublicKey))) { item.Fail(true, FailedReason.ReceiveAddressAlreadyInOutput); continue; } if (item.SpendFromAddresses.SelectMany(s => s.Transactions).Any(t => t.ScriptPubKeyHex.IsNullOrEmpty())) { item.Fail(false, FailedReason.ScriptPubKeyHexNotFound); continue; } //// get the outputs associated with the sender address var spendToEnumerated = item.SpendToAddresses.ToList(); var spendFromEnumerated = item.SpendFromAddresses.SelectMany(s => s.Transactions).ToList(); //// calculate the sum of inputs and outputs. var inputSum = spendFromEnumerated.Select(t => t.Value).Sum(); var outputSum = spendToEnumerated.Select(spt => spt.Amount).Sum(); var change = inputSum - outputSum; if (change < 0) { item.Fail(false, FailedReason.InvalidSum); continue; } if (item.SpendToAddresses.Any(s => s.TakeFee)) { item.SpendToAddresses.First(s => s.TakeFee).Amount /= decimal.One + feeRate; } else { change -= outputSum * feeRate; } if (change > 0) { if (item.ChangeAddress.IsNullOrEmpty()) { item.Fail(false, FailedReason.NoChangeAddressFound); continue; } } spendFromEnumerated.ForEach(vout => context.CreateRawTransaction.AddInput(vout.Hash, (int)vout.Index)); spendToEnumerated.ForEach(spt => context.CreateRawTransaction.AddOutput(spt.PublicKey, spt.Amount)); if (change > 0) { context.CreateRawTransaction.AddOutput(item.ChangeAddress, change); context.CreateRawTransaction.AddInput(item.ChangeAddress, -1); } } if (context.SendItems.All(w => w.Failed)) { return(context); } //// create the builder and build the transaction (either a client using the coins client or locally if supported). await TransactionBuilder.Create(param).Build(client, context); // seems we are done here return(context); }