public IActionResult BuildTransaction([FromBody] BuildTransactionRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors))); } var destination = BitcoinAddress.Create(request.DestinationAddress, this.network).ScriptPubKey; try { var transactionResult = this.walletManager.BuildTransaction(new WalletAccountReference(request.WalletName, request.AccountName), request.Password, destination, request.Amount, FeeParser.Parse(request.FeeType), request.AllowUnconfirmed ? 0 : 1); var model = new WalletBuildTransactionModel { Hex = transactionResult.hex, Fee = transactionResult.fee, TransactionId = transactionResult.transactionId }; return(this.Json(model)); } catch (Exception e) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public IActionResult BuildTransaction([FromBody] BuildTransactionRequest request) { // checks the request is valid if (!this.ModelState.IsValid) { var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors))); } try { var transactionResult = this.walletManager.BuildTransaction(request.WalletName, request.AccountName, request.Password, request.DestinationAddress, request.Amount, request.FeeType, request.AllowUnconfirmed ? 0 : 1); var model = new WalletBuildTransactionModel { Hex = transactionResult.hex, Fee = transactionResult.fee, TransactionId = transactionResult.transactionId }; return(this.Json(model)); } catch (Exception e) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public async Task GetRawTransactionWithTransactionInMempoolAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { //Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send a transaction. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = true, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList(), }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse response = await rpc.SendCommandAsync(RPCOperations.getrawtransaction, txId.ToString()); // Assert. response.ResultString.Should().Be(buildTransactionModel.Hex); } }
public IActionResult BuildTransaction([FromBody] BuildTransactionRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { Script destination = BitcoinAddress.Create(request.DestinationAddress, this.network).ScriptPubKey; var context = new TransactionBuildContext(this.network) { AccountReference = new WalletAccountReference(request.WalletName, request.AccountName), TransactionFee = string.IsNullOrEmpty(request.FeeAmount) ? null : Money.Parse(request.FeeAmount), MinConfirmations = request.AllowUnconfirmed ? 0 : 1, Shuffle = request.ShuffleOutputs ?? true, // We shuffle transaction outputs by default as it's better for anonymity. OpReturnData = request.OpReturnData, WalletPassword = request.Password, Recipients = new[] { new Recipient { Amount = request.Amount, ScriptPubKey = destination } }.ToList() }; context.SingleChangeAddress = request.SingleChangeAddress; if (!string.IsNullOrEmpty(request.FeeType)) { context.FeeType = FeeParser.Parse(request.FeeType); } Transaction transactionResult = this.walletTransactionHandler.BuildTransaction(context); var model = new WalletBuildTransactionModel { Hex = transactionResult.ToHex(), Fee = context.TransactionFee, TransactionId = transactionResult.GetHash() }; 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 IActionResult BuildWantedSystemMessage([FromBody] BuildWantedSystemMessageRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { // in wanted message transactions the payer address must be the input address (of the Tipster or the Reviewers multi-sig address) and the transaction fee must be the parameter. var payer = BitcoinAddress.Create(request.PayerAddress, this.network); var destination = BitcoinAddress.Create(request.DestinationAddress, this.network).ScriptPubKey; var context = new TransactionBuildContext(this.network) { AccountReference = new WalletAccountReference(request.WalletName, request.AccountName), Recipients = new[] { new Recipient { Amount = new Money(500, this.network.MoneyUnits.AtomicUnit), ScriptPubKey = destination } }.ToList(), WalletPassword = request.WalletPassword, FeeType = FeeType.Low, MinConfirmations = 0, Shuffle = false, PayerAddress = payer, Message = request.Message, MessageRecipient = request.DestinationAddress, EncryptMessage = request.EncryptMessage, Sign = false }; var transactionResult = this.walletTransactionHandler.BuildWantedSystemMessage(context); var model = new WalletBuildTransactionModel { TransactionId = transactionResult.GetHash(), Hex = transactionResult.ToHex(), Fee = context.TransactionFee }; 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 void DoTest() { var transactionRequest = new BuildTransactionRequest() { FeeAmount = "0.01", // Change this to the address that should receive the funds. OpReturnData = "PLv2NAsyn22cNbk5veopWCkypaN6DBR27L", AccountName = "account 0", AllowUnconfirmed = true, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = "2MyKFLbvhSouDYeAHhxsj9a5A4oV71j7SPR", Amount = "1.1" } }, Password = "******", WalletName = "test" }; WalletBuildTransactionModel model = Post <BuildTransactionRequest, WalletBuildTransactionModel>( "http://127.0.0.1:38221/api/wallet/build-transaction", transactionRequest); var transaction = new PosTransaction(model.Hex); var reader = new OpReturnDataReader(this.loggerFactory, Networks.Stratis.Testnet()); var extractor = new DepositExtractor(this.loggerFactory, this.federationGatewaySettings, reader, this.fullNode); IDeposit deposit = extractor.ExtractDepositFromTransaction(transaction, 2, 1); Assert.NotNull(deposit); Assert.Equal(transaction.GetHash(), deposit.Id); Assert.Equal(transactionRequest.OpReturnData, deposit.TargetAddress); Assert.Equal(Money.Parse(transactionRequest.Recipients[0].Amount), deposit.Amount); Assert.Equal((uint256)1, deposit.BlockHash); Assert.Equal(2, deposit.BlockNumber); // Post the transaction var sendRequest = new SendTransactionRequest() { Hex = model.Hex }; WalletSendTransactionModel model2 = Post <SendTransactionRequest, WalletSendTransactionModel>( "http://127.0.0.1:38221/api/wallet/send-transaction", sendRequest); }
public IActionResult BuildTransaction([FromBody] BuildTransactionRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid if (!this.ModelState.IsValid) { var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage)); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors))); } var destination = BitcoinAddress.Create(request.DestinationAddress, this.network).ScriptPubKey; try { var context = new TransactionBuildContext( new WalletAccountReference(request.WalletName, request.AccountName), new[] { new Recipient { Amount = request.Amount, ScriptPubKey = destination } }.ToList(), request.Password) { FeeType = FeeParser.Parse(request.FeeType), MinConfirmations = request.AllowUnconfirmed ? 0 : 1, Shuffle = true }; var transactionResult = this.walletTransactionHandler.BuildTransaction(context); var model = new WalletBuildTransactionModel { Hex = transactionResult.ToHex(), Fee = context.TransactionFee, TransactionId = transactionResult.GetHash() }; 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 IActionResult BuildTransaction([FromBody] BuildMultisigTransactionRequest request) { Guard.NotNull(request, nameof(request)); if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } try { var recipients = request .Recipients .Select(recipientModel => new Wallet.Recipient { ScriptPubKey = BitcoinAddress.Create(recipientModel.DestinationAddress, this.network).ScriptPubKey, Amount = recipientModel.Amount }) .ToList(); Key[] privateKeys = request .Secrets .Select(secret => new Mnemonic(secret.Mnemonic).DeriveExtKey(secret.Passphrase).PrivateKey) .ToArray(); Transaction transactionResult = this.fedMultiSigManualWithdrawalTransactionBuilder.BuildTransaction(recipients, privateKeys); var model = new WalletBuildTransactionModel { Hex = transactionResult.ToHex(), TransactionId = transactionResult.GetHash() }; return(this.Json(model)); } catch (Exception e) { LoggerExtensions.LogError(this.logger, "Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public async Task SignTransactionOffline() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode miningNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StraxRegTest150Miner).Start(); CoreNode onlineNode = builder.CreateStratisPosNode(this.network).Start(); CoreNode offlineNode = builder.CreateStratisPosNode(this.network).WithWallet().Start(); TestHelper.ConnectAndSync(miningNode, onlineNode); // Get the extpubkey from the offline node to restore on the online node. string extPubKey = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("wallet/extpubkey") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0" }) .GetJsonAsync <string>(); // Load the extpubkey onto the online node. await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/recover-via-extpubkey") .PostJsonAsync(new WalletExtPubRecoveryRequest { Name = "mywallet", AccountIndex = 0, ExtPubKey = extPubKey, CreationDate = DateTime.Today }) .ReceiveJson(); TestHelper.SendCoins(miningNode, onlineNode, Money.Coins(5.0m)); TestHelper.MineBlocks(miningNode, 1); // Build the offline signing template from the online node. No password is needed. BuildOfflineSignResponse offlineTemplate = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/build-offline-sign-request") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeAmount = "0.01", ShuffleOutputs = true, AllowUnconfirmed = true, Recipients = new List <RecipientModel>() { new RecipientModel { DestinationAddress = new Key().ScriptPubKey.GetDestinationAddress(this.network).ToString(), Amount = "1" } } }) .ReceiveJson <BuildOfflineSignResponse>(); // Now build the actual transaction on the offline node. It is not synced with the others and only has the information // in the signing request and its own wallet to construct the transaction with. WalletBuildTransactionModel builtTransactionModel = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("wallet/offline-sign-request") .PostJsonAsync(new OfflineSignRequest() { WalletName = offlineTemplate.WalletName, WalletAccount = offlineTemplate.WalletAccount, WalletPassword = "******", UnsignedTransaction = offlineTemplate.UnsignedTransaction, Fee = offlineTemplate.Fee, Utxos = offlineTemplate.Utxos, Addresses = offlineTemplate.Addresses }) .ReceiveJson <WalletBuildTransactionModel>(); // Send the signed transaction from the online node (doesn't really matter, could equally be from the mining node). await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = builtTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Check that the transaction is valid and therefore relayed, and able to be mined into a block. TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 1); TestHelper.MineBlocks(miningNode, 1); TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 0); } }
private async Task BuildAndSendDistributionTransactionsAsync() { foreach (SwapTransaction swapTransaction in this.swapTransactions) { if (this.distributedSwapTransactions.Any(d => d.TransactionHash == swapTransaction.TransactionHash)) { Console.WriteLine($"Swap already distributed: {swapTransaction.StraxAddress}:{Money.Satoshis(swapTransaction.SenderAmount).ToUnit(MoneyUnit.BTC)}"); continue; } try { var distributedSwapTransaction = new DistributedSwapTransaction(swapTransaction); var result = await $"http://localhost:{this.StraxNetwork.DefaultAPIPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = walletName, AccountName = "account 0", FeeType = "medium", Password = walletPassword, Recipients = this.GetRecipients(distributedSwapTransaction.StraxAddress, distributedSwapTransaction.SenderAmount) }) .ReceiveBytes(); WalletBuildTransactionModel buildTransactionModel = null; try { buildTransactionModel = JsonConvert.DeserializeObject <WalletBuildTransactionModel>(Encoding.ASCII.GetString(result)); } catch (Exception) { Console.WriteLine($"An error occurred processing swap {distributedSwapTransaction.TransactionHash}"); break; } distributedSwapTransaction.TransactionBuilt = true; WalletSendTransactionModel sendActionResult = await $"http://localhost:{this.StraxNetwork.DefaultAPIPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); distributedSwapTransaction.TransactionSent = true; distributedSwapTransaction.TransactionSentHash = sendActionResult.TransactionId.ToString(); Console.WriteLine($"Swap transaction built and sent to {distributedSwapTransaction.StraxAddress}:{Money.Satoshis(distributedSwapTransaction.SenderAmount).ToUnit(MoneyUnit.BTC)}"); // Append to the file. using (FileStream stream = File.Open(Path.Combine(this.swapFilePath, distributedSwapTransactionsFile), FileMode.Append)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.WriteRecord(distributedSwapTransaction); csv.NextRecord(); } this.distributedSwapTransactions.Add(distributedSwapTransaction); await Task.Delay(TimeSpan.FromSeconds(1)); } catch (Exception ex) { Console.WriteLine(ex.Message); break; } finally { } } }
public static BuildContractTransactionResult Success(WalletBuildTransactionModel model) { return(new BuildContractTransactionResult(model)); }
public async Task SignColdStakingSetupOffline() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode miningNode = builder.CreateStratisColdStakingNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StraxRegTest150Miner).Start(); CoreNode onlineNode = builder.CreateStratisColdStakingNode(this.network).Start(); CoreNode offlineNode = builder.CreateStratisColdStakingNode(this.network).WithWallet().Start(); // The offline node never gets connected to anything. TestHelper.ConnectAndSync(miningNode, onlineNode); // Get the extpubkey from the offline node to restore on the online node. string extPubKey = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("wallet/extpubkey") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0" }) .GetJsonAsync <string>(); // Load the extpubkey onto the online node. await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/recover-via-extpubkey") .PostJsonAsync(new WalletExtPubRecoveryRequest { Name = "coldwallet", AccountIndex = 0, ExtPubKey = extPubKey, CreationDate = DateTime.Today - TimeSpan.FromDays(1) }) .ReceiveJson(); // Get a mnemonic for the hot wallet. string hotWalletMnemonic = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/mnemonic") .GetJsonAsync <string>(); // Restore the hot wallet on the online node. // This is needed because the hot address needs to have a private key available to stake with. await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/recover") .PostJsonAsync(new WalletRecoveryRequest() { Name = "hotwallet", Mnemonic = hotWalletMnemonic, Password = "******", Passphrase = "", CreationDate = DateTime.Today - TimeSpan.FromDays(1) }) .ReceiveJson(); // Get the hot address from the online node. CreateColdStakingAccountResponse hotAccount = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/cold-staking-account") .PostJsonAsync(new CreateColdStakingAccountRequest() { WalletName = "hotwallet", WalletPassword = "******", IsColdWalletAccount = false }) .ReceiveJson <CreateColdStakingAccountResponse>(); string hotAddress = (await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/cold-staking-address") .SetQueryParams(new { walletName = "hotwallet", isColdWalletAddress = "false" }) .GetJsonAsync <GetColdStakingAddressResponse>()).Address; string coldWalletUnusedAddress = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/unusedaddress") .SetQueryParams(new { walletName = "coldwallet", accountName = "account 0" }) .GetJsonAsync <string>(); // Send some funds to the cold wallet's default (non-special) account to use for the staking setup. string fundTransaction = (await $"http://localhost:{miningNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", Password = "******", AccountName = "account 0", FeeType = "high", Recipients = new List <RecipientModel>() { new RecipientModel() { Amount = "5", DestinationAddress = coldWalletUnusedAddress } } }) .ReceiveJson <WalletBuildTransactionModel>()).Hex; await $"http://localhost:{miningNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest() { Hex = fundTransaction }) .ReceiveJson <WalletSendTransactionModel>(); TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length > 0); TestHelper.MineBlocks(miningNode, 1); // Set up cold staking account on offline node to get the needed cold address. CreateColdStakingAccountResponse coldAccount = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/cold-staking-account") .PostJsonAsync(new CreateColdStakingAccountRequest() { WalletName = "mywallet", WalletPassword = "******", IsColdWalletAccount = true }) .ReceiveJson <CreateColdStakingAccountResponse>(); string coldAddress = (await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/cold-staking-address") .SetQueryParams(new { walletName = "mywallet", isColdWalletAddress = "true" }) .GetJsonAsync <GetColdStakingAddressResponse>()).Address; // Build the offline cold staking template from the online node. No password is needed. BuildOfflineSignResponse offlineTemplate = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/setup-offline-cold-staking") .PostJsonAsync(new SetupOfflineColdStakingRequest() { ColdWalletAddress = coldAddress, HotWalletAddress = hotAddress, WalletName = "coldwallet", WalletAccount = "account 0", Amount = "5", // Check that we can send the entire available balance in the setup Fees = "0.01", SubtractFeeFromAmount = true, SegwitChangeAddress = false, SplitCount = 10 }) .ReceiveJson <BuildOfflineSignResponse>(); // Now build the actual transaction on the offline node. It is not synced with the others and only has the information // in the signing request and its own wallet to construct the transaction with. // Note that the wallet name and account name on the offline node may not actually match those from the online node. WalletBuildTransactionModel builtTransactionModel = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("wallet/offline-sign-request") .PostJsonAsync(new OfflineSignRequest() { WalletName = "mywallet", WalletAccount = offlineTemplate.WalletAccount, WalletPassword = "******", UnsignedTransaction = offlineTemplate.UnsignedTransaction, Fee = offlineTemplate.Fee, Utxos = offlineTemplate.Utxos, Addresses = offlineTemplate.Addresses }) .ReceiveJson <WalletBuildTransactionModel>(); Dictionary <string, int> txCountBefore = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/transactionCount") .SetQueryParams(new { walletName = "hotwallet", accountName = hotAccount.AccountName }) .GetJsonAsync <Dictionary <string, int> >(); Assert.True(txCountBefore.Values.First() == 0); // Send the signed transaction from the online node (doesn't really matter, could equally be from the mining node). await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = builtTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Check that the transaction is valid and therefore relayed, and able to be mined into a block. TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 1); TestHelper.MineBlocks(miningNode, 1); TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 0); Dictionary <string, int> txCountAfter = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/transactionCount") .SetQueryParams(new { walletName = "hotwallet", accountName = hotAccount.AccountName }) .GetJsonAsync <Dictionary <string, int> >(); Assert.True(txCountAfter.Values.First() > 0); string onlineNodeUnusedAddress = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/unusedaddress") .SetQueryParams(new { walletName = "hotwallet", accountName = "account 0" }) .GetJsonAsync <string>(); // Now attempt a withdrawal. First get the estimated fee. Money offlineWithdrawalFee = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/estimate-offline-cold-staking-withdrawal-tx-fee") .PostJsonAsync(new OfflineColdStakingWithdrawalFeeEstimationRequest() { WalletName = "hotwallet", AccountName = hotAccount.AccountName, ReceivingAddress = onlineNodeUnusedAddress, Amount = "4", // Withdraw part of the available balance in the cold account. SubtractFeeFromAmount = true }) .ReceiveJson <Money>(); // Now generate the actual unsigned template transaction. BuildOfflineSignResponse offlineWithdrawalTemplate = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("coldstaking/offline-cold-staking-withdrawal") .PostJsonAsync(new OfflineColdStakingWithdrawalRequest() { WalletName = "hotwallet", AccountName = hotAccount.AccountName, ReceivingAddress = onlineNodeUnusedAddress, Amount = "4", // Withdraw part of the available balance in the cold account. Fees = offlineWithdrawalFee.ToString(), SubtractFeeFromAmount = true }) .ReceiveJson <BuildOfflineSignResponse>(); WalletBuildTransactionModel builtWithdrawalTransactionModel = await $"http://localhost:{offlineNode.ApiPort}/api" .AppendPathSegment("wallet/offline-sign-request") .PostJsonAsync(new OfflineSignRequest() { WalletName = "mywallet", WalletAccount = "coldStakingColdAddresses", WalletPassword = "******", UnsignedTransaction = offlineWithdrawalTemplate.UnsignedTransaction, Fee = offlineWithdrawalTemplate.Fee, Utxos = offlineWithdrawalTemplate.Utxos, Addresses = offlineWithdrawalTemplate.Addresses }) .ReceiveJson <WalletBuildTransactionModel>(); Dictionary <string, int> txCountBefore2 = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/transactionCount") .SetQueryParams(new { walletName = "hotwallet", accountName = "account 0" }) .GetJsonAsync <Dictionary <string, int> >(); Assert.True(txCountBefore2.Values.First() == 0); // Send the signed transaction from the online node (doesn't really matter, could equally be from the mining node). await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = builtWithdrawalTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Check that the transaction is valid and therefore relayed, and able to be mined into a block. TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 1); TestHelper.MineBlocks(miningNode, 1); TestBase.WaitLoop(() => miningNode.CreateRPCClient().GetRawMempool().Length == 0); Dictionary <string, int> txCountAfter2 = await $"http://localhost:{onlineNode.ApiPort}/api" .AppendPathSegment("wallet/transactionCount") .SetQueryParams(new { walletName = "hotwallet", accountName = "account 0" }) .GetJsonAsync <Dictionary <string, int> >(); Assert.True(txCountAfter2.Values.First() > 0); } }
public async Task SendingFromManyAddressesToOneAddress() { int sendingAccountBalanceOnStart = 98000596; int receivingAccountBalanceOnStart = 0; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithWallet().Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithWallet().Start(); // Mine a few blocks to fund the sending node and connect the nodes. IEnumerable <string> addressesToFund = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 150 }) .GetJsonAsync <IEnumerable <string> >(); foreach (string address in addressesToFund) { TestHelper.MineBlocks(sendingNode, 1, syncNode: false, miningAddress: address); } TestHelper.ConnectAndSync(sendingNode, receivingNode); // Check balances. WalletBalanceModel sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(sendingAccountBalanceOnStart, MoneyUnit.BTC)); WalletBalanceModel receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed + receivingAccountBalance.AmountUnconfirmed).Should().Be(new Money(receivingAccountBalanceOnStart)); // Check max spendable amount. var maxBalanceResponse = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/maxbalance") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", feetype = "low", allowunconfirmed = true }) .GetJsonAsync <MaxSpendableAmountModel>(); Money totalToSpend = maxBalanceResponse.MaxSpendableAmount + maxBalanceResponse.Fee; // Act. // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with 50 recipients. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeAmount = maxBalanceResponse.Fee.ToString(), Password = "******", ShuffleOutputs = true, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = maxBalanceResponse.MaxSpendableAmount.ToString() }).ToList() }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Assert. // The sending node should have 50 (+ fee) fewer coins. sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(sendingAccountBalanceOnStart, MoneyUnit.BTC) - totalToSpend); // Mine and sync so that we make sure the receiving node is up to date. TestHelper.MineBlocks(sendingNode, 1); TestHelper.WaitForNodeToSync(sendingNode, receivingNode); // The receiving node should have 50 more coins. receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed + receivingAccountBalance.AmountUnconfirmed).Should().Be(new Money(receivingAccountBalanceOnStart) + maxBalanceResponse.MaxSpendableAmount); } }
public async Task SendingATransactionWithAnOpReturn() { int sendingAccountBalanceOnStart = 98000596; int receivingAccountBalanceOnStart = 0; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Check balances. WalletBalanceModel sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(sendingAccountBalanceOnStart, MoneyUnit.BTC)); WalletBalanceModel receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed + receivingAccountBalance.AmountUnconfirmed).Should().Be(new Money(receivingAccountBalanceOnStart)); // Act. // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = true, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList(), OpReturnData = "some data to send", OpReturnAmount = "1" }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Assert. // Mine and sync so that we make sure the receiving node is up to date. TestHelper.MineBlocks(sendingNode, 1); TestHelper.WaitForNodeToSync(sendingNode, receivingNode); // The receiving node should have coins. receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed).Should().Be(new Money(receivingAccountBalanceOnStart + 1, MoneyUnit.BTC)); // The sending node should have fewer coins. sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed).Should().Be(new Money(sendingAccountBalanceOnStart + 4 - 2, MoneyUnit.BTC)); // Check the transaction. string lastBlockHash = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("consensus/getbestblockhash") .GetJsonAsync <string>(); BlockTransactionDetailsModel block = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = lastBlockHash, showTransactionDetails = true, outputJson = true }) .GetJsonAsync <BlockTransactionDetailsModel>(); TransactionVerboseModel trx = block.Transactions.SingleOrDefault(t => t.TxId == buildTransactionModel.TransactionId.ToString()); trx.Should().NotBeNull(); Vout opReturnOutputFromBlock = trx.VOut.Single(t => t.ScriptPubKey.Type == "nulldata"); opReturnOutputFromBlock.Value.Should().Be(1); var script = opReturnOutputFromBlock.ScriptPubKey.Asm; string[] ops = script.Split(" "); ops[0].Should().Be("OP_RETURN"); Encoders.Hex.DecodeData(ops[1]).Should().BeEquivalentTo(System.Text.Encoding.UTF8.GetBytes("some data to send")); } }
public async Task SendingFromOneAddressToFiftyAddresses() { int sendingAccountBalanceOnStart = 98000596; int receivingAccountBalanceOnStart = 0; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Check balances. WalletBalanceModel sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(sendingAccountBalanceOnStart, MoneyUnit.BTC)); WalletBalanceModel receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); AccountBalanceModel receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed + receivingAccountBalance.AmountUnconfirmed).Should().Be(new Money(receivingAccountBalanceOnStart)); // Act. // Get 50 addresses to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 50 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with 50 recipients. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = true, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList() }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); // Assert. // The sending node should have 50 (+ fee) fewer coins. sendingNodeBalances = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single(); (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(sendingAccountBalanceOnStart - 50 - buildTransactionModel.Fee.ToDecimal(MoneyUnit.BTC), MoneyUnit.BTC)); // Mine and sync so that we make sure the receiving node is up to date. TestHelper.MineBlocks(sendingNode, 1); // The receiving node should have 50 more coins. receivingNodeBalances = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/balance") .SetQueryParams(new { walletName = "mywallet" }) .GetJsonAsync <WalletBalanceModel>(); receivingAccountBalance = receivingNodeBalances.AccountsBalances.Single(); (receivingAccountBalance.AmountConfirmed + receivingAccountBalance.AmountUnconfirmed).Should().Be(new Money(receivingAccountBalanceOnStart + 50, MoneyUnit.BTC)); } }
private BuildContractTransactionResult(WalletBuildTransactionModel model) { this.Response = model; }
public async Task GetTransactionOnUnconfirmedTransactionAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList(), }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; TestBase.WaitLoop(() => { WalletHistoryModel history = $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/history") .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" }) .GetAsync() .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult(); return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId)); }); TestBase.WaitLoop(() => { WalletHistoryModel history = $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/history") .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" }) .GetAsync() .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult(); return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId)); }); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcReceivingNode = receivingNode.CreateRPCClient(); RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 1.00000000); resultSendingWallet.Fee.Should().Be((decimal) - 0.0001); resultSendingWallet.Confirmations.Should().Be(0); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().BeNull(); resultSendingWallet.BlockIndex.Should().BeNull(); resultSendingWallet.BlockTime.Should().BeNull(); resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultSendingWallet.Details.Count.Should().Be(1); GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single(); detailsSendingWallet.Address.Should().Be(unusedaddresses.Single()); detailsSendingWallet.Amount.Should().Be((decimal) - 1.00000000); detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001); detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWallet.OutputIndex.Should().Be(1); // The output at index 0 is the change. GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>(); resultReceivingWallet.Amount.Should().Be((decimal)1.00000000); resultReceivingWallet.Fee.Should().BeNull(); resultReceivingWallet.Confirmations.Should().Be(0); resultReceivingWallet.TransactionId.Should().Be(txId); resultReceivingWallet.BlockHash.Should().BeNull(); resultReceivingWallet.BlockIndex.Should().BeNull(); resultReceivingWallet.BlockTime.Should().BeNull(); resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.Details.Should().ContainSingle(); GetTransactionDetailsModel detailsReceivingWallet = resultReceivingWallet.Details.Single(); detailsReceivingWallet.Address.Should().Be(unusedaddresses.Single()); detailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); detailsReceivingWallet.Fee.Should().BeNull(); detailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); detailsReceivingWallet.OutputIndex.Should().Be(1); } }
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 async Task GetTransactionOnTransactionSentFromMultipleOutputsAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "98000002" }).ToList(), }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; // Mine so that we make sure the node is up to date. TestHelper.MineBlocks(sendingNode, 1); // Get the block that was mined. string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("consensus/getbestblockhash") .GetJsonAsync <string>(); BlockModel blockModelAtTip = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = lastBlockHash, outputJson = true }) .GetJsonAsync <BlockModel>(); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000); resultSendingWallet.Fee.Should().Be((decimal) - 0.0001); resultSendingWallet.Confirmations.Should().Be(1); resultSendingWallet.Isgenerated.Should().BeNull(); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().Be(uint256.Parse(blockModelAtTip.Hash)); resultSendingWallet.BlockIndex.Should().Be(1); resultSendingWallet.BlockTime.Should().Be(blockModelAtTip.Time); resultSendingWallet.TimeReceived.Should().BeLessOrEqualTo(blockModelAtTip.Time); resultSendingWallet.TransactionTime.Should().Be(((PosTransaction)trx).Time); resultSendingWallet.Details.Count.Should().Be(1); GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single(); detailsSendingWallet.Address.Should().Be(unusedaddresses.Single()); detailsSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000); detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001); detailsSendingWallet.OutputIndex.Should().Be(1); } }
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));
public async Task GetTransactionOnTransactionReceivedToMultipleAddressesAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 2 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList() }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; // Mine and sync so that we make sure the receiving node is up to date. TestHelper.MineBlocks(sendingNode, 1); TestHelper.WaitForNodeToSync(sendingNode, receivingNode); // Get the block that was mined. string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("consensus/getbestblockhash") .GetJsonAsync <string>(); BlockModel tip = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = lastBlockHash, outputJson = true }) .GetJsonAsync <BlockModel>(); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcReceivingNode = receivingNode.CreateRPCClient(); RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 2.00000000); //resultSendingWallet.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. resultSendingWallet.Confirmations.Should().Be(1); resultSendingWallet.Isgenerated.Should().BeNull(); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash)); resultSendingWallet.BlockIndex.Should().Be(1); resultSendingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds()); resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultSendingWallet.TransactionTime.Should().Be(trx.Time); resultSendingWallet.Details.Count.Should().Be(2); GetTransactionDetailsModel detailsSendingWalletFirstRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.First()); detailsSendingWalletFirstRecipient.Address.Should().Be(unusedaddresses.First()); detailsSendingWalletFirstRecipient.Amount.Should().Be((decimal) - 1.00000000); //detailsSendingWalletFirstRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. detailsSendingWalletFirstRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWalletFirstRecipient.OutputIndex.Should().Be(1); // Output at index 0 contains the change. GetTransactionDetailsModel detailsSendingWalletSecondRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.Last()); detailsSendingWalletSecondRecipient.Address.Should().Be(unusedaddresses.Last()); detailsSendingWalletSecondRecipient.Amount.Should().Be((decimal) - 1.00000000); //detailsSendingWalletSecondRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. detailsSendingWalletSecondRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWalletSecondRecipient.OutputIndex.Should().Be(2); // Checking receiver. GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>(); resultReceivingWallet.Amount.Should().Be((decimal)2.00000000); resultReceivingWallet.Fee.Should().BeNull(); resultReceivingWallet.Confirmations.Should().Be(1); resultReceivingWallet.Isgenerated.Should().BeNull(); resultReceivingWallet.TransactionId.Should().Be(txId); resultReceivingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash)); resultReceivingWallet.BlockIndex.Should().Be(1); resultReceivingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds()); resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.Details.Count.Should().Be(2); GetTransactionDetailsModel firstDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.First()); firstDetailsReceivingWallet.Address.Should().Be(unusedaddresses.First()); firstDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); firstDetailsReceivingWallet.Fee.Should().BeNull(); firstDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); firstDetailsReceivingWallet.OutputIndex.Should().Be(1); // Output at index 0 contains the change. GetTransactionDetailsModel secondDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.Last()); secondDetailsReceivingWallet.Address.Should().Be(unusedaddresses.Last()); secondDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); secondDetailsReceivingWallet.Fee.Should().BeNull(); secondDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); secondDetailsReceivingWallet.OutputIndex.Should().Be(2); } }