public IActionResult GetAllAddresses([FromQuery] GetAllAddressesModel request)
        {
            Guard.NotNull(request, nameof(request));

            // Checks the request is valid.
            if (!this.ModelState.IsValid)
            {
                return(BuildErrorResponse(this.ModelState));
            }

            try
            {
                Wallet    wallet  = this.walletManager.GetWallet(request.WalletName);
                HdAccount account = wallet.GetAccountByCoinType(request.AccountName, this.coinType);

                AddressesModel model = new AddressesModel
                {
                    Addresses = account.GetCombinedAddresses().Select(address => new AddressModel
                    {
                        Address  = address.Address,
                        IsUsed   = address.Transactions.Any(),
                        IsChange = address.IsChangeAddress()
                    })
                };

                return(this.Json(model));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public BuildCallContractTransactionResponse BuildCallTx(BuildCallContractTransactionRequest request)
        {
            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0 && addressBalance.AmountUnconfirmed == 0)
            {
                return(BuildCallContractTransactionResponse.Failed(SenderNoBalanceError));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableInputsForAddress(request.WalletName, request.Sender);

            uint160 addressNumeric = request.ContractAddress.ToUint160(this.network);

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    object[] methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, addressNumeric, request.MethodName, methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCallContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, addressNumeric, request.MethodName);
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                if (account == null)
                {
                    return(BuildCallContractTransactionResponse.Failed($"No account with the name '{request.AccountName}' could be found."));
                }

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

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   context  = new TransactionBuildContext(this.network)
            {
                AccountReference = new WalletAccountReference(request.WalletName, request.AccountName),
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { new Recipient {
                                               Amount = request.Amount, ScriptPubKey = new Script(this.callDataSerializer.Serialize(txData))
                                           } }.ToList()
            };

            try
            {
                Transaction transaction = this.walletTransactionHandler.BuildTransaction(context);
                return(BuildCallContractTransactionResponse.Succeeded(request.MethodName, transaction, context.TransactionFee));
            }
            catch (Exception exception)
            {
                return(BuildCallContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#3
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 BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0 && addressBalance.AmountUnconfirmed == 0)
            {
                return(BuildCreateContractTransactionResponse.Failed(SenderNoBalanceError));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableInputsForAddress(request.WalletName, request.Sender);

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    object[] methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCreateContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                if (account == null)
                {
                    return(BuildCreateContractTransactionResponse.Failed($"No account with the name '{request.AccountName}' could be found."));
                }

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

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);

            byte[] serializedTxData = this.callDataSerializer.Serialize(txData);

            Result <ContractTxData> deserialized = this.callDataSerializer.Deserialize(serializedTxData);

            // We also want to ensure we're sending valid data: AKA it can be deserialized.
            if (deserialized.IsFailure)
            {
                return(BuildCreateContractTransactionResponse.Failed("Invalid data. If network requires code signing, check the code contains a signature."));
            }

            // HACK
            // If requiring a signature, also check the signature.
            if (this.network is ISignedCodePubKeyHolder holder)
            {
                var  signedTxData = (SignedCodeContractTxData)deserialized.Value;
                bool validSig     = new ContractSigner().Verify(holder.SigningContractPubKey, signedTxData.ContractExecutionCode, signedTxData.CodeSignature);

                if (!validSig)
                {
                    return(BuildCreateContractTransactionResponse.Failed("Signature in code does not come from required signing key."));
                }
            }

            var recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(serializedTxData)
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#5
0
        private BuildCallContractTransactionResponse BuildCallTx(BuildCallContractTransactionRequest request)
        {
            this.logger.LogTrace(request.ToString());

            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0)
            {
                return(BuildCallContractTransactionResponse.Failed($"The 'Sender' address you're trying to spend from doesn't have a confirmed balance. Current unconfirmed balance: {addressBalance.AmountUnconfirmed}. Please check the 'Sender' address."));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableTransactionsInWallet(request.WalletName, MinConfirmationsAllChecks).Where(x => x.Address.Address == request.Sender).Select(x => x.ToOutPoint()).ToList();

            ulong   gasPrice       = ulong.Parse(request.GasPrice);
            ulong   gasLimit       = ulong.Parse(request.GasLimit);
            uint160 addressNumeric = new Address(request.ContractAddress).ToUint160(this.network);

            SmartContractCarrier carrier;

            if (request.Parameters != null && request.Parameters.Any())
            {
                carrier = SmartContractCarrier.CallContract(ReflectionVirtualMachine.VmVersion, addressNumeric, request.MethodName, gasPrice, new Gas(gasLimit), request.Parameters);
            }
            else
            {
                carrier = SmartContractCarrier.CallContract(ReflectionVirtualMachine.VmVersion, addressNumeric, request.MethodName, gasPrice, new Gas(gasLimit));
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (gasPrice * gasLimit) + Money.Parse(request.FeeAmount);
            var   context  = new TransactionBuildContext(this.network)
            {
                AccountReference = new WalletAccountReference(request.WalletName, request.AccountName),
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { new Recipient {
                                               Amount = request.Amount, ScriptPubKey = new Script(carrier.Serialize())
                                           } }.ToList()
            };

            try
            {
                Transaction transaction = this.walletTransactionHandler.BuildTransaction(context);
                return(BuildCallContractTransactionResponse.Succeeded(request.MethodName, transaction, context.TransactionFee));
            }
            catch (Exception exception)
            {
                return(BuildCallContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#6
0
        public BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            if (!this.CheckBalance(request.Sender))
            {
                return(BuildCreateContractTransactionResponse.Failed(SenderNoBalanceError));
            }

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

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

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    object[] methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasPrice, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCreateContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasPrice, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccount(request.AccountName);
                if (account == null)
                {
                    return(BuildCreateContractTransactionResponse.Failed($"No account with the name '{request.AccountName}' could be found."));
                }

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

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);

            byte[] serializedTxData = this.callDataSerializer.Serialize(txData);

            Result <ContractTxData> deserialized = this.callDataSerializer.Deserialize(serializedTxData);

            // We also want to ensure we're sending valid data: AKA it can be deserialized.
            if (deserialized.IsFailure)
            {
                return(BuildCreateContractTransactionResponse.Failed("Invalid data. If network requires code signing, check the code contains a signature."));
            }

            var recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(serializedTxData)
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
        public GetTransactionModel GetTransaction(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Types.Wallet wallet  = this.walletManager.GetWalletByName(accountReference.WalletName);
            HdAccount    account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName);

            // Get the transaction from the wallet by looking into received and send transactions.
            List <HdAddress>             addresses            = account.GetCombinedAddresses().ToList();
            List <TransactionOutputData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress()).SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.Id == trxid)).ToList();
            List <TransactionOutputData> sendTransactions     = addresses.SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList();

            if (!receivedTransactions.Any() && !sendTransactions.Any())
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id.");
            }

            // Get the block hash from the transaction in the wallet.
            TransactionOutputData transactionFromWallet = null;
            uint256 blockHash = null;
            int?    blockHeight, blockIndex;

            if (receivedTransactions.Any())
            {
                blockHeight           = receivedTransactions.First().BlockHeight;
                blockIndex            = receivedTransactions.First().BlockIndex;
                blockHash             = receivedTransactions.First().BlockHash;
                transactionFromWallet = receivedTransactions.First();
            }
            else
            {
                blockHeight = sendTransactions.First().SpendingDetails.BlockHeight;
                blockIndex  = sendTransactions.First().SpendingDetails.BlockIndex;
                blockHash   = blockHeight != null?this.ChainIndexer.GetHeader(blockHeight.Value).HashBlock : null;
            }

            // Get the block containing the transaction (if it has  been confirmed).
            ChainedHeaderBlock chainedHeaderBlock = null;

            if (blockHash != null)
            {
                this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> {
                    blockHash
                }, b => { chainedHeaderBlock = b; });
            }

            Block       block = null;
            Transaction transactionFromStore      = null;

            if (chainedHeaderBlock != null)
            {
                block = chainedHeaderBlock.Block;
                transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid);
            }

            DateTimeOffset transactionTime;
            bool           isGenerated;
            string         hex;

            if (transactionFromStore != null)
            {
                // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates
                if (transactionFromStore is IPosTransactionWithTime posTrx)
                {
                    transactionTime = Utils.UnixTimeToDateTime(posTrx.Time);
                }
                else
                {
                    transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time);
                }

                isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake;
                hex         = transactionFromStore.ToHex();
            }
            else if (transactionFromWallet != null)
            {
                transactionTime = transactionFromWallet.CreationTime;
                isGenerated     = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true;
                hex             = transactionFromWallet.Hex;
            }
            else
            {
                transactionTime = sendTransactions.First().SpendingDetails.CreationTime;
                isGenerated     = false;
                hex             = null; // TODO get from mempool
            }

            var model = new GetTransactionModel
            {
                Confirmations   = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0,
                Isgenerated     = isGenerated ? true : (bool?)null,
                BlockHash       = blockHash,
                BlockIndex      = blockIndex ?? block?.Transactions.FindIndex(t => t.GetHash() == trxid),
                BlockTime       = block?.Header.BlockTime.ToUnixTimeSeconds(),
                TransactionId   = uint256.Parse(txid),
                TransactionTime = transactionTime.ToUnixTimeSeconds(),
                TimeReceived    = transactionTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = hex
            };

            Money feeSent = Money.Zero;

            if (sendTransactions.Any())
            {
                feeSent = wallet.GetSentTransactionFee(trxid);
            }

            // Send transactions details.
            foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments))
            {
                // Only a single item should appear per destination address.
                if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null)
                {
                    model.Details.Add(new GetTransactionDetailsModel
                    {
                        Address     = paymentDetail.DestinationAddress,
                        Category    = GetTransactionDetailsCategoryModel.Send,
                        Amount      = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC),
                        Fee         = -feeSent.ToDecimal(MoneyUnit.BTC),
                        OutputIndex = paymentDetail.OutputIndex
                    });
                }
            }

            // Get the ColdStaking script template if available.
            Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates();
            ScriptTemplate coldStakingTemplate            = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null;

            // Receive transactions details.
            foreach (TransactionOutputData trxInWallet in receivedTransactions)
            {
                // Skip the details if the script pub key is cold staking.
                // TODO: Verify if we actually need this any longer, after changing the internals to recognice account type!
                if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey))
                {
                    continue;
                }

                GetTransactionDetailsCategoryModel category;

                if (isGenerated)
                {
                    category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature;
                }
                else
                {
                    category = GetTransactionDetailsCategoryModel.Receive;
                }

                model.Details.Add(new GetTransactionDetailsModel
                {
                    Address     = trxInWallet.Address,
                    Category    = category,
                    Amount      = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC),
                    OutputIndex = trxInWallet.Index
                });
            }

            model.Amount = model.Details.Sum(d => d.Amount);
            model.Fee    = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee;

            return(model);
        }
        public BuildContractTransactionResult BuildTx(BuildContractTransactionRequest request)
        {
            Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);

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

            if (account == null)
            {
                return(BuildContractTransactionResult.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(BuildContractTransactionResult.Failure(SenderNotInWalletError, $"The given address {request.Sender} was not found in the wallet."));
            }

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

            (List <OutPoint> selectedInputs, string message) = SelectInputs(request.WalletName, request.Sender, request.Outpoints);
            if (!string.IsNullOrEmpty(message))
            {
                return(BuildContractTransactionResult.Failure(InvalidOutpointsError, message));
            }

            var recipients = new List <Recipient>();

            foreach (RecipientModel recipientModel in request.Recipients)
            {
                var 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(BuildContractTransactionResult.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
                });
            }

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

            Transaction transaction = this.walletTransactionHandler.BuildTransaction(context);

            var model = new WalletBuildTransactionModel
            {
                Hex           = transaction.ToHex(),
                Fee           = context.TransactionFee,
                TransactionId = transaction.GetHash()
            };

            return(BuildContractTransactionResult.Success(model));
        }
        protected override IEnumerable <EventBase> GetMessages()
        {
            foreach (string walletName in this.walletManager.GetWalletsNames())
            {
                WalletGeneralInfoClientEvent clientEvent = null;
                try
                {
                    Wallet.Types.Wallet          wallet               = this.walletManager.GetWallet(walletName);
                    IEnumerable <AccountBalance> balances             = this.walletManager.GetBalances(walletName, calculatSpendable: true);
                    IList <AccountBalanceModel>  accountBalanceModels = new List <AccountBalanceModel>();
                    foreach (var balance in balances)
                    {
                        HdAccount account = balance.Account;

                        var accountBalanceModel = new AccountBalanceModel
                        {
                            CoinType          = wallet.Network.Consensus.CoinType,
                            Name              = account.Name,
                            HdPath            = account.HdPath,
                            AmountConfirmed   = balance.AmountConfirmed,
                            AmountUnconfirmed = balance.AmountUnconfirmed,
                            SpendableAmount   = balance.SpendableAmount,
                            Addresses         = account.GetCombinedAddresses().Select(address =>
                            {
                                (Money confirmedAmount, Money unConfirmedAmount, bool anytrx) = address.GetBalances(wallet.walletStore, account.IsNormalAccount());
                                return(new AddressModel
                                {
                                    Address = address.Address,
                                    IsUsed = anytrx,
                                    IsChange = address.IsChangeAddress(),
                                    AmountConfirmed = confirmedAmount,
                                    AmountUnconfirmed = unConfirmedAmount
                                });
                            })
                        };

                        accountBalanceModels.Add(accountBalanceModel);
                    }

                    clientEvent = new WalletGeneralInfoClientEvent
                    {
                        WalletName = walletName,
                        WalletInfo = new WalletGeneralInfoModel
                        {
                            Network               = wallet.Network,
                            CreationTime          = wallet.CreationTime,
                            LastBlockSyncedHeight = wallet.AccountsRoot.Single().LastBlockSyncedHeight,
                            ConnectedNodes        = this.connectionManager.ConnectedPeers.Count(),
                            ChainTip              = this.chainIndexer.Tip.Height,
                            IsChainSynced         = this.chainIndexer.IsDownloaded(),
                            IsDecrypted           = true,
                        },
                        AccountsBalances = accountBalanceModels
                    };

                    // Get the wallet's file path.
                    (string folder, IEnumerable <string> fileNameCollection) = this.walletManager.GetWalletsFiles();
                    string searchFile =
                        Path.ChangeExtension(walletName, this.walletManager.GetWalletFileExtension());
                    string fileName = fileNameCollection.FirstOrDefault(i => i.Equals(searchFile));
                    if (!string.IsNullOrEmpty(folder) && !string.IsNullOrEmpty(fileName))
                    {
                        clientEvent.WalletInfo.WalletFilePath = Path.Combine(folder, fileName);
                    }
                }
                catch (Exception e)
                {
                    this.log.LogError(e, "Exception occurred: {0}");
                }

                if (null != clientEvent)
                {
                    yield return(clientEvent);
                }
            }
        }
示例#10
0
        private BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            this.logger.LogTrace(request.ToString());

            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0)
            {
                return(BuildCreateContractTransactionResponse.Failed($"The 'Sender' address you're trying to spend from doesn't have a confirmed balance. Current unconfirmed balance: {addressBalance.AmountUnconfirmed}. Please check the 'Sender' address."));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableTransactionsInWallet(request.WalletName, MinConfirmationsAllChecks).Where(x => x.Address.Address == request.Sender).Select(x => x.ToOutPoint()).ToList();

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                var methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);
            var   recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(this.callDataSerializer.Serialize(txData))
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#11
0
        public override async Task <WalletBuildTransactionModel> OfflineSignRequest(OfflineSignRequest request, CancellationToken cancellationToken)
        {
            return(await Task.Run(() =>
            {
                Transaction unsignedTransaction = this.network.CreateTransaction(request.UnsignedTransaction);

                uint256 originalTxId = unsignedTransaction.GetHash();

                var builder = new TransactionBuilder(this.network);
                var coins = new List <Coin>();
                var signingKeys = new List <ISecret>();

                ExtKey seedExtKey = this.walletManager.GetExtKey(new WalletAccountReference()
                {
                    AccountName = request.WalletAccount, WalletName = request.WalletName
                }, request.WalletPassword);

                // Have to determine which private key to use for each UTXO being spent.
                bool coldStakingWithdrawal = false;
                foreach (UtxoDescriptor utxo in request.Utxos)
                {
                    Script scriptPubKey = Script.FromHex(utxo.ScriptPubKey);

                    coins.Add(new Coin(uint256.Parse(utxo.TransactionId), uint.Parse(utxo.Index), Money.Parse(utxo.Amount), scriptPubKey));

                    // Now try get the associated private key. We therefore need to determine the address that contains the UTXO.
                    string address;
                    if (scriptPubKey.IsScriptType(ScriptType.ColdStaking))
                    {
                        ColdStakingScriptTemplate.Instance.ExtractScriptPubKeyParameters(scriptPubKey, out KeyId hotPubKeyHash, out KeyId coldPubKeyHash);

                        address = coldPubKeyHash.GetAddress(this.network).ToString();
                        coldStakingWithdrawal = true;
                    }
                    else
                    {
                        // We assume that if it wasn't a cold staking scriptPubKey then it must have been P2PKH.
                        address = scriptPubKey.GetDestinationAddress(this.network)?.ToString();

                        if (address == null)
                        {
                            throw new FeatureException(HttpStatusCode.BadRequest, "Could not resolve address.",
                                                       $"Could not resolve address from UTXO's scriptPubKey '{scriptPubKey.ToHex()}'.");
                        }
                    }

                    IEnumerable <HdAccount> accounts = this.walletManager.GetAccounts(request.WalletName);
                    IEnumerable <HdAddress> addresses = accounts.SelectMany(hdAccount => hdAccount.GetCombinedAddresses());

                    HdAddress hdAddress = addresses.FirstOrDefault(a => a.Address == address || a.Bech32Address == address);

                    if (coldStakingWithdrawal && hdAddress == null)
                    {
                        var coldStakingManager = this.walletManager as ColdStakingManager;
                        Wallet.Wallet wallet = coldStakingManager.GetWallet(request.WalletName);
                        HdAccount coldAccount = coldStakingManager.GetColdStakingAccount(wallet, true);
                        IEnumerable <HdAddress> coldAccountAddresses = coldAccount.GetCombinedAddresses();
                        hdAddress = coldAccountAddresses.FirstOrDefault(a => a.Address == address || a.Bech32Address == address);
                    }

                    // It is possible that the address is outside the gap limit. So if it is not found we optimistically presume the address descriptors will fill in the missing information later.
                    if (hdAddress != null)
                    {
                        ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(hdAddress.HdPath));
                        BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(this.network);
                        signingKeys.Add(addressPrivateKey);
                    }
                }

                // Address descriptors are 'easier' to look the private key up against if provided, but may not always be available.
                foreach (AddressDescriptor address in request.Addresses)
                {
                    ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(address.KeyPath));
                    BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(this.network);
                    signingKeys.Add(addressPrivateKey);
                }

                // Offline cold staking transaction handling. We check both the offline setup and the offline withdrawal cases here.
                if (unsignedTransaction.Outputs.Any(o => o.ScriptPubKey.IsScriptType(ScriptType.ColdStaking)) || coldStakingWithdrawal)
                {
                    // This will always be added in 'cold' mode if we are processing an offline signing request.
                    builder.Extensions.Add(new ColdStakingBuilderExtension(false));
                }

                builder.AddCoins(coins);
                builder.AddKeys(signingKeys.ToArray());
                builder.SignTransactionInPlace(unsignedTransaction);

                if (!builder.Verify(unsignedTransaction, out TransactionPolicyError[] errors))
                {
                    throw new FeatureException(HttpStatusCode.BadRequest, "Failed to validate signed transaction.",
                                               $"Failed to validate signed transaction '{unsignedTransaction.GetHash()}' from offline request '{originalTxId}'.");
                }

                var builtTransactionModel = new WalletBuildTransactionModel()
                {
                    TransactionId = unsignedTransaction.GetHash(), Hex = unsignedTransaction.ToHex(), Fee = request.Fee
                };

                return builtTransactionModel;
            }, cancellationToken));