コード例 #1
0
        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()));
            }
        }
コード例 #2
0
        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()));
            }
        }
コード例 #3
0
        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);
            }
        }
コード例 #4
0
        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()));
            }
        }
コード例 #5
0
        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()));
            }
        }
コード例 #6
0
        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);
        }
コード例 #7
0
        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()));
            }
        }
コード例 #8
0
        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()));
            }
        }
コード例 #9
0
        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);
            }
        }
コード例 #10
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));
 }
コード例 #12
0
        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);
            }
        }
コード例 #13
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);
            }
        }
コード例 #14
0
        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"));
            }
        }
コード例 #15
0
        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;
 }
コード例 #17
0
        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);
            }
        }
コード例 #18
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));
        }
コード例 #19
0
        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);
            }
        }
コード例 #20
0
        public override async Task <WalletBuildTransactionModel> OfflineSignRequest(OfflineSignRequest request, CancellationToken cancellationToken)
        {
            return(await Task.Run(() =>
            {
                Transaction unsignedTransaction = this.network.CreateTransaction(request.UnsignedTransaction);

                uint256 originalTxId = unsignedTransaction.GetHash();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                return builtTransactionModel;
            }, cancellationToken));
コード例 #21
0
        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);
            }
        }