public IActionResult EstimateFee([FromBody] ScTxFeeEstimateRequest request)
        {
            if (!this.ModelState.IsValid)
            {
                return(ModelStateErrors.BuildErrorResponse(this.ModelState));
            }

            try
            {
                EstimateFeeResult result = this.smartContractTransactionService.EstimateFee(request);

                return(this.Json(result.Fee));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
예제 #2
0
        public EstimateFeeResult EstimateFee(ScTxFeeEstimateRequest request)
        {
            Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);

            HdAccount account = wallet.GetAccount(request.AccountName);

            if (account == null)
            {
                return(EstimateFeeResult.Failure(AccountNotInWalletError, $"No account with the name '{request.AccountName}' could be found."));
            }

            HdAddress senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);

            if (senderAddress == null)
            {
                return(EstimateFeeResult.Failure(SenderNotInWalletError, $"The given address {request.Sender} was not found in the wallet."));
            }

            if (!this.CheckBalance(senderAddress.Address))
            {
                return(EstimateFeeResult.Failure(InsufficientBalanceError, SenderNoBalanceError));
            }

            List <OutPoint> selectedInputs = this.SelectInputs(request.WalletName, request.Sender, request.Outpoints);

            if (!selectedInputs.Any())
            {
                return(EstimateFeeResult.Failure(InvalidOutpointsError, "Invalid list of request outpoints have been passed to the method. Please ensure that the outpoints are spendable by the sender address."));
            }

            var recipients = new List <Recipient>();

            foreach (RecipientModel recipientModel in request.Recipients)
            {
                BitcoinAddress bitcoinAddress = BitcoinAddress.Create(recipientModel.DestinationAddress, this.network);

                // If it's a potential SC address, check if it's a contract.
                if (bitcoinAddress is BitcoinPubKeyAddress bitcoinPubKeyAddress)
                {
                    var address = new uint160(bitcoinPubKeyAddress.Hash.ToBytes());

                    if (this.stateRoot.IsExist(address))
                    {
                        return(EstimateFeeResult.Failure(TransferFundsToContractError,
                                                         $"The recipient address {recipientModel.DestinationAddress} is a contract. Transferring funds directly to a contract is not supported."));
                    }
                }

                recipients.Add(new Recipient
                {
                    ScriptPubKey = bitcoinAddress.ScriptPubKey,
                    Amount       = recipientModel.Amount
                });
            }

            // Build context
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = new WalletAccountReference(request.WalletName, request.AccountName),
                MinConfirmations = MinConfirmationsAllChecks,
                Shuffle          = false,
                OpReturnData     = request.OpReturnData,
                OpReturnAmount   = string.IsNullOrEmpty(request.OpReturnAmount) ? null : Money.Parse(request.OpReturnAmount),
                SelectedInputs   = selectedInputs,
                AllowOtherInputs = false,
                Recipients       = recipients,
                ChangeAddress    = senderAddress,

                // Unique for fee estimation
                TransactionFee = null,
                FeeType        = FeeParser.Parse(request.FeeType),
                Sign           = false,
            };

            Money fee = this.walletTransactionHandler.EstimateFee(context);

            return(EstimateFeeResult.Success(fee));
        }
        public void BuildFeeEstimationContextCorrectly()
        {
            const int utxoIndex        = 0;
            uint256   utxoId           = uint256.Zero;
            uint256   utxoIdUnused     = uint256.One;
            string    senderAddress    = uint160.Zero.ToBase58Address(this.network);
            string    recipientAddress = uint160.One.ToBase58Address(this.network);
            string    changeAddress    = new uint160(2).ToBase58Address(this.network);

            var amount = 1234.567M;

            var request = new ScTxFeeEstimateRequest
            {
                AccountName      = "account 0",
                WalletName       = "wallet",
                Sender           = senderAddress,
                ShuffleOutputs   = true,
                AllowUnconfirmed = true,
                ChangeAddress    = changeAddress,
                Recipients       = new List <RecipientModel>
                {
                    new RecipientModel {
                        Amount = amount.ToString(), DestinationAddress = recipientAddress
                    }
                },
                FeeType = "medium"
            };

            SmartContractTransactionService service = new SmartContractTransactionService(
                this.network,
                this.walletManager.Object,
                this.walletTransactionHandler.Object,
                this.stringSerializer.Object,
                this.callDataSerializer.Object,
                this.addressGenerator.Object,
                this.stateRepository.Object);

            var senderHdAddress = new HdAddress {
                Address = senderAddress
            };

            this.walletManager.Setup(x => x.GetWallet(request.WalletName))
            .Returns(new Features.Wallet.Wallet
            {
                AccountsRoot = new List <AccountRoot>
                {
                    new AccountRoot
                    {
                        Accounts = new List <HdAccount>
                        {
                            new HdAccount
                            {
                                ExternalAddresses = new List <HdAddress>
                                {
                                    senderHdAddress
                                },
                                Name = request.AccountName,
                            }
                        }
                    }
                }
            });

            this.walletManager.Setup(x => x.GetAddressBalance(request.Sender))
            .Returns(new AddressBalance {
                Address = request.Sender, AmountConfirmed = Money.FromUnit(amount, MoneyUnit.BTC), AmountUnconfirmed = 0
            });

            var outputs = new List <UnspentOutputReference>
            {
                new UnspentOutputReference
                {
                    Address = new HdAddress
                    {
                        Address = senderAddress
                    },
                    Transaction = new TransactionData
                    {
                        Id    = utxoId,
                        Index = utxoIndex,
                    }
                }, new UnspentOutputReference
                {
                    Address = new HdAddress
                    {
                        Address = senderAddress
                    },
                    Transaction = new TransactionData
                    {
                        Id    = utxoIdUnused,
                        Index = utxoIndex,
                    }
                }
            };

            this.walletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <string>(), 0)).Returns(outputs);

            this.walletTransactionHandler.Setup(x => x.BuildTransaction(It.IsAny <TransactionBuildContext>()))
            .Returns(new Transaction());

            EstimateFeeResult result = service.EstimateFee(request);

            // Check that the transaction builder is invoked, and that we:
            // - Ignore shuffleOutputs,
            // - Set inputs from sender
            // - Set recipients,
            // - Set change to sender
            // - Set the fee type correctly
            // - Set sign to false
            // - Set transaction fee to null
            this.walletTransactionHandler.Verify(w => w.EstimateFee(It.Is <TransactionBuildContext>(context =>
                                                                                                    context.AllowOtherInputs == false &&
                                                                                                    context.Shuffle == false &&
                                                                                                    context.SelectedInputs.All(i => outputs.Select(o => o.Transaction.Id).Contains(i.Hash)) &&
                                                                                                    context.Recipients.Single().Amount == Money.FromUnit(amount, MoneyUnit.BTC) &&
                                                                                                    context.ChangeAddress == senderHdAddress &&
                                                                                                    context.Sign == false &&
                                                                                                    context.TransactionFee == null &&
                                                                                                    context.FeeType == FeeType.Medium
                                                                                                    )));
        }