/// <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); }
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); }
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); }
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 ))); }