Example #1
0
        public async Task CompleteTxOutputs(Guid operationId, Transaction tx)
        {
            var inputs = tx.Inputs.Select(o => new Output(o.PrevOut)).ToList();

            await SetInternalOutputsTxHash(operationId, tx.GetHash().ToString());

            await _spentOutputRepository.InsertSpentOutputsAsync(operationId, inputs);

            await RemoveUsedInternalOutputs(inputs);
        }
 public async Task CompleteTxOutputs(Guid operationId, NeoModules.NEP6.Transactions.Transaction tx)
 {
     var inputs = tx.Inputs.Select(o => new Output(o)).ToList();
     await _spentOutputRepository.InsertSpentOutputsAsync(operationId, inputs);
 }
Example #3
0
        /// <summary>
        /// Builds a transaction that sends value from one address to another.
        /// Change is spent to the source address, if necessary.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="feeFactor"></param>
        /// <returns></returns>
        /// <exception cref="BusinessException"></exception>
        public async Task <BuildTransactionResponse> BuildSingleTransactionAsync(BuildSingleTransactionRequest request, decimal feeFactor)
        {
            if (string.IsNullOrWhiteSpace(request.FromAddress))
            {
                throw new BusinessException(ErrorReason.BadRequest, "FromAddress missing");
            }
            if (string.IsNullOrWhiteSpace(request.ToAddress))
            {
                throw new BusinessException(ErrorReason.BadRequest, "ToAddress missing");
            }
            if (!long.TryParse(request.Amount, out var amount) || amount <= 0)
            {
                throw new BusinessException(ErrorReason.BadRequest, $"Invalid amount {amount}");
            }

            var feePerKb = await _feeService.GetFeePerKb();

            if (TransactionRules.IsDustAmount(amount, Transaction.PayToPubKeyHashPkScriptSize, new Amount(feePerKb)))
            {
                throw new BusinessException(ErrorReason.AmountTooSmall, "Amount is dust");
            }

            const int outputVersion = 0;
            const int lockTime      = 0;
            const int expiry        = 0;

            // Number of outputs newly build tx will contain,
            // not including the change address
            const int numOutputs = 1;

            // Lykke api doesn't have option to specify a change address.
            var changeAddress = Address.Decode(request.FromAddress);
            var toAddress     = Address.Decode(request.ToAddress);
            var allInputs     = (await GetUtxosForAddress(request.FromAddress)).ToList();

            long estFee         = 0;
            long totalSpent     = 0;
            var  consumedInputs = new List <Transaction.Input>();

            bool HasEnoughInputs(out long fee)
            {
                var calculateWithChange = false;

                while (true)
                {
                    var changeCount = calculateWithChange ? 1 : 0;
                    fee = _feeService.CalculateFee(feePerKb, consumedInputs.Count, numOutputs + changeCount, feeFactor);
                    var estAmount = amount + (request.IncludeFee ? 0 : fee);

                    if (totalSpent < estAmount)
                    {
                        return(false);
                    }
                    if (totalSpent == estAmount)
                    {
                        return(true);
                    }
                    if (totalSpent > estAmount && calculateWithChange)
                    {
                        return(true);
                    }

                    // Loop one more time but make sure change is accounted for this time.
                    if (totalSpent > estAmount)
                    {
                        calculateWithChange = true;
                    }
                }
            }

            // Accumulate inputs until we have enough to cover the cost
            // of the amount + fee
            foreach (var input in allInputs)
            {
                consumedInputs.Add(input);
                totalSpent += input.InputAmount;

                if (HasEnoughInputs(out estFee))
                {
                    break;
                }
            }

            // If all inputs do not have enough value to fund the transaction.
            if (totalSpent < amount + (request.IncludeFee ? 0 : estFee))
            {
                throw new BusinessException(ErrorReason.NotEnoughBalance, "Address balance too low");
            }

            // The fee either comes from the change or the sent amount
            var send   = amount - (request.IncludeFee ? estFee : 0);
            var change = (totalSpent - amount) - (request.IncludeFee ? 0 : estFee);

            // If all inputs do not have enough value to fund the transaction, throw error.
            if (request.IncludeFee && estFee > amount)
            {
                throw new BusinessException(ErrorReason.AmountTooSmall, "Amount not enough to include fee");
            }

            // If all inputs do not have enough value to fund the transaction, throw error.
            if (totalSpent < amount + (request.IncludeFee ? 0 : estFee))
            {
                throw new BusinessException(ErrorReason.NotEnoughBalance, "Address balance too low");
            }

            // Build outputs to address + change address.
            // If any of the outputs is zero value, exclude it.  For example, if there is no change.
            var outputs = new[] {
                new Transaction.Output(send, outputVersion, toAddress.BuildScript().Script),
                new Transaction.Output(change, outputVersion, changeAddress.BuildScript().Script)
            }.Where(o => o.Amount != 0).ToArray();

            var newTx = new Transaction(
                Transaction.SupportedVersion,
                consumedInputs.ToArray(),
                outputs,
                lockTime,
                expiry
                );

            await _spentOutputRepository.InsertSpentOutputsAsync(request.OperationId,
                                                                 consumedInputs.Select(p => new Output(p.PreviousOutpoint.Hash.ToString(), p.PreviousOutpoint.Index)));

            return(new BuildTransactionResponse
            {
                TransactionContext = HexUtil.FromByteArray(newTx.Serialize())
            });
        }