/// <summary>
        /// Builds a transaction to transfer funds on a smart contract network.
        /// </summary>
        /// <param name="request">An object containing the necessary parameters to build the transaction.</param>
        /// <returns></returns>
        public void BuildTransaction(BuildContractTransactionRequest request)
        {
            var path = "/api/SmartContracts/build-transaction";

            path = path.Replace("{format}", "json");

            var    queryParams  = new Dictionary <String, String>();
            var    headerParams = new Dictionary <String, String>();
            var    formParams   = new Dictionary <String, String>();
            var    fileParams   = new Dictionary <String, FileParameter>();
            String postBody     = null;

            postBody = ApiClient.Serialize(request);                                     // http body (model) parameter

            // authentication setting, if any
            String[] authSettings = new String[] {  };

            // make the HTTP request
            IRestResponse response = (IRestResponse)ApiClient.CallApi(path, Method.POST, queryParams, postBody, headerParams, formParams, fileParams, authSettings);

            if (((int)response.StatusCode) >= 400)
            {
                throw new ApiException((int)response.StatusCode, "Error calling BuildTransaction: " + response.Content, response.Content);
            }
            else if (((int)response.StatusCode) == 0)
            {
                throw new ApiException((int)response.StatusCode, "Error calling BuildTransaction: " + response.ErrorMessage, response.ErrorMessage);
            }

            return;
        }
        public void BuildTransferContext_SenderHasNoBalance_Fails()
        {
            string senderAddress = uint160.Zero.ToBase58Address(this.network);

            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 request = new BuildContractTransactionRequest
            {
                AccountName = "account 0",
                WalletName  = "wallet",
                Password    = "******",
                Sender      = 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>
                                {
                                    new HdAddress
                                    {
                                        Address = senderAddress
                                    }
                                },
                                Name = request.AccountName,
                            }
                        }
                    }
                }
            });

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

            BuildContractTransactionResult result = service.BuildTx(request);

            Assert.Equal(SmartContractTransactionService.InsufficientBalanceError, result.Error);
            Assert.NotNull(result.Message);
            Assert.Null(result.Response);
        }
Exemplo n.º 3
0
        public void BuildTransferContext_SenderHasNoBalance_Fails()
        {
            string senderAddress = uint160.Zero.ToBase58Address(this.network);

            var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object);

            var service = new SmartContractTransactionService(
                this.network,
                this.walletManager.Object,
                this.walletTransactionHandler.Object,
                this.stringSerializer.Object,
                this.callDataSerializer.Object,
                this.addressGenerator.Object,
                this.stateRepository.Object,
                reserveUtxoService,
                this.blockStore.Object,
                this.chainIndexer,
                this.primitiveSerializer.Object,
                this.contractAssemblyCache.Object,
                this.receiptRepository.Object);

            var request = new BuildContractTransactionRequest
            {
                AccountName = "account 0",
                WalletName  = "wallet",
                Password    = "******",
                Sender      = senderAddress,
            };

            var wallet = new Features.Wallet.Wallet();

            wallet.AccountsRoot.Add(new AccountRoot(wallet));
            var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts)
            {
                Name = request.AccountName
            };

            account0.ExternalAddresses.Add(new HdAddress()
            {
                Address = senderAddress
            });

            this.walletManager.Setup(x => x.GetWallet(request.WalletName))
            .Returns(wallet);

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

            BuildContractTransactionResult result = service.BuildTx(request);

            Assert.Equal(SmartContractTransactionService.InsufficientBalanceError, result.Error);
            Assert.NotNull(result.Message);
            Assert.Null(result.Response);
        }
        public void BuildTransferContext_AccountNotInWallet_Fails()
        {
            string senderAddress = uint160.Zero.ToBase58Address(this.network);

            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 request = new BuildContractTransactionRequest
            {
                AccountName = "account 0",
                WalletName  = "wallet",
                Password    = "******",
                Sender      = senderAddress,
            };

            // Create a wallet but without the correct account name
            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>(),
                                Name = "account 1"
                            }
                        }
                    }
                }
            });

            BuildContractTransactionResult result = service.BuildTx(request);

            Assert.Equal(SmartContractTransactionService.AccountNotInWalletError, result.Error);
            Assert.NotNull(result.Message);
            Assert.Null(result.Response);
        }
Exemplo n.º 5
0
        public void BuildTransferContext_AccountNotInWallet_Fails()
        {
            string senderAddress = uint160.Zero.ToBase58Address(this.network);

            var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object);

            var service = new SmartContractTransactionService(
                this.network,
                this.walletManager.Object,
                this.walletTransactionHandler.Object,
                this.stringSerializer.Object,
                this.callDataSerializer.Object,
                this.addressGenerator.Object,
                this.stateRepository.Object,
                reserveUtxoService,
                this.blockStore.Object,
                this.chainIndexer,
                this.primitiveSerializer.Object,
                this.contractAssemblyCache.Object,
                this.receiptRepository.Object);

            var request = new BuildContractTransactionRequest
            {
                AccountName = "account 0",
                WalletName  = "wallet",
                Password    = "******",
                Sender      = senderAddress,
            };

            var wallet = new Features.Wallet.Wallet();

            wallet.AccountsRoot.Add(new AccountRoot(wallet));
            var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts)
            {
                Name = "account 1"
            };

            // Create a wallet but without the correct account name
            this.walletManager.Setup(x => x.GetWallet(request.WalletName))
            .Returns(wallet);

            BuildContractTransactionResult result = service.BuildTx(request);

            Assert.Equal(SmartContractTransactionService.AccountNotInWalletError, result.Error);
            Assert.NotNull(result.Message);
            Assert.Null(result.Response);
        }
        public IActionResult BuildTransaction([FromBody] BuildContractTransactionRequest request)
        {
            if (!this.ModelState.IsValid)
            {
                return(ModelStateErrors.BuildErrorResponse(this.ModelState));
            }

            try
            {
                BuildContractTransactionResult result = this.smartContractTransactionService.BuildTx(request);

                return(this.Json(result.Response));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public void BuildTransferContextCorrectly()
        {
            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 BuildContractTransactionRequest
            {
                AccountName      = "account 0",
                FeeAmount        = "0.01",
                WalletName       = "wallet",
                Password         = "******",
                Sender           = senderAddress,
                ShuffleOutputs   = true,
                AllowUnconfirmed = true,
                ChangeAddress    = changeAddress,
                Recipients       = new List <RecipientModel>
                {
                    // In locales that use a , for the decimal point this would fail to be parsed unless we use the invariant culture
                    new RecipientModel {
                        Amount = amount.ToString(CultureInfo.InvariantCulture), DestinationAddress = recipientAddress
                    }
                }
            };

            var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object);

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

            var senderHdAddress = new HdAddress {
                Address = senderAddress
            };

            var wallet = new Features.Wallet.Wallet();

            wallet.AccountsRoot.Add(new AccountRoot(wallet));
            var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts)
            {
                Name = request.AccountName
            };

            account0.ExternalAddresses.Add(new HdAddress()
            {
                Address = senderAddress
            });

            this.walletManager.Setup(x => x.GetWallet(request.WalletName))
            .Returns(wallet);

            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());

            BuildContractTransactionResult result = service.BuildTx(request);

            // Check that the transaction builder is invoked, and that we:
            // - Ignore shuffleOutputs,
            // - Set inputs from sender
            // - Set recipients,
            // - Set change to sender
            this.walletTransactionHandler.Verify(w => w.BuildTransaction(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.Address == senderHdAddress.Address
                                                                                                         )));
        }
        public void BuildTransferContext_RecipientIsKnownContract_Fails()
        {
            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);

            var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object);

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

            var request = new BuildContractTransactionRequest
            {
                AccountName = "account 0",
                WalletName  = "wallet",
                Password    = "******",
                Sender      = senderAddress,
                Recipients  = new List <RecipientModel>
                {
                    new RecipientModel {
                        Amount = "1", DestinationAddress = recipientAddress
                    }
                }
            };

            var wallet = new Features.Wallet.Wallet();

            wallet.AccountsRoot.Add(new AccountRoot(wallet));
            var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts)
            {
                Name = request.AccountName
            };

            account0.ExternalAddresses.Add(new HdAddress()
            {
                Address = senderAddress
            });

            this.walletManager.Setup(x => x.GetWallet(request.WalletName))
            .Returns(wallet);

            this.walletManager.Setup(x => x.GetAddressBalance(request.Sender))
            .Returns(new AddressBalance {
                Address = request.Sender, AmountConfirmed = 10, AmountUnconfirmed = 0
            });

            this.walletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <string>(), 0))
            .Returns(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.stateRepository.Setup(s => s.IsExist(It.IsAny <uint160>())).Returns(true);

            BuildContractTransactionResult result = service.BuildTx(request);

            Assert.Equal(SmartContractTransactionService.TransferFundsToContractError, result.Error);
            Assert.NotNull(result.Message);
            Assert.Null(result.Response);
        }
Exemplo n.º 9
0
        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 = this.SelectInputs(request.WalletName, request.Sender, request.Outpoints);

            if (!selectedInputs.Any())
            {
                return(BuildContractTransactionResult.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(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));
        }
        public void BuildTransferContext_Recipient_Is_Not_P2PKH()
        {
            const int utxoIndex     = 0;
            uint256   utxoId        = uint256.Zero;
            uint256   utxoIdUnused  = uint256.One;
            string    senderAddress = uint160.Zero.ToBase58Address(this.network);
            string    changeAddress = new uint160(2).ToBase58Address(this.network);

            // This is a valid non-P2PKH address on CirrusTest.
            string recipientAddress = "xH1GHWVNKwdebkgiFPtQtM4qb3vrvNX2Rg";
            var    cirrusNetwork    = CirrusNetwork.NetworksSelector.Testnet();

            var amount = 1234.567M;

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

            SmartContractTransactionService service = new SmartContractTransactionService(
                cirrusNetwork,
                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());

            BuildContractTransactionResult result = service.BuildTx(request);

            // Check that the transaction builder is invoked, and that we:
            // - Ignore shuffleOutputs,
            // - Set inputs from sender
            // - Set recipients,
            // - Set change to sender
            this.walletTransactionHandler.Verify(w => w.BuildTransaction(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
                                                                                                         )));
        }