private static async Task CanChangeCircuitWithinSameHttpClientAsync() { var requestUri = "https://api.ipify.org/"; IPAddress torIp; IPAddress changedIp; // 1. Get TOR IP var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); var gotIp = IPAddress.TryParse(content.Replace("\n", ""), out torIp); Assert.True(gotIp); // 2. Change TOR IP var controlPortClient = new ControlPort.Client(Shared.HostAddress, Shared.ControlPort, Shared.ControlPortPassword); await controlPortClient.ChangeCircuitAsync().ConfigureAwait(false); // 3. Get changed TOR IP content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); gotIp = IPAddress.TryParse(content.Replace("\n", ""), out changedIp); Assert.True(gotIp); } Assert.NotEqual(changedIp, torIp); }
public async Task CanRequestDifferentWithSameHandlerAsync() { using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { HttpResponseMessage message = await httpClient.GetAsync("http://api.qbit.ninja/whatisit/what%20is%20my%20future").ConfigureAwait(false); var content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); IPAddress ip; message = await httpClient.GetAsync("http://icanhazip.com/").ConfigureAwait(false); content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); var gotIp = IPAddress.TryParse(content.Replace("\n", ""), out ip); Assert.True(gotIp); message = await httpClient.GetAsync("http://api.qbit.ninja/whatisit/what%20is%20my%20future").ConfigureAwait(false); content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } }
public async Task CanReuseHandlerAsync() { // YOU HAVE TO SET THE HTTP CLIENT NOT TO DISPOSE THE HANDLER var requestUri = "http://api.qbit.ninja/whatisit/what%20is%20my%20future"; var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var httpClient = new HttpClient(handler, disposeHandler: false)) { HttpResponseMessage message = await httpClient.GetAsync(requestUri).ConfigureAwait(false); var content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } using (var httpClient = new HttpClient(handler)) { HttpResponseMessage message = await httpClient.GetAsync(requestUri).ConfigureAwait(false); var content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } using (var httpClient = new HttpClient(handler)) { HttpResponseMessage message = await httpClient.GetAsync(requestUri).ConfigureAwait(false); var content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } }
private static async Task TorIpIsNotTheRealOneAsync() { var requestUri = "http://icanhazip.com/"; IPAddress realIp; IPAddress torIp; // 1. Get real IP using (var httpClient = new HttpClient()) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); var gotIp = IPAddress.TryParse(content.Replace("\n", ""), out realIp); Assert.True(gotIp); } // 2. Get TOR IP using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); var gotIp = IPAddress.TryParse(content.Replace("\n", ""), out torIp); Assert.True(gotIp); } Assert.NotEqual(realIp, torIp); }
public async Task CanDoRequestManyDifferentAsync() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var client = new HttpClient(handler)) { await QBitTestAsync(client, 10, alterRequests : true).ConfigureAwait(false); } }
public async Task CanRequestInRowAsync() { var firstRequest = "http://api.qbit.ninja/transactions/38d4cfeb57d6685753b7a3b3534c3cb576c34ca7344cd4582f9613ebf0c2b02a?format=json&headeronly=true"; using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { await(await httpClient.GetAsync(firstRequest).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); await(await httpClient.GetAsync("http://api.qbit.ninja/balances/15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe?unspentonly=true").ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); await(await httpClient.GetAsync("http://api.qbit.ninja/balances/akEBcY5k1dn2yeEdFnTMwdhVbHxtgHb6GGi?from=tip&until=336000").ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); } }
public async Task TestMicrosoftNCSI() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var client = new HttpClient(handler)) { var response = await client.GetAsync("http://www.msftncsi.com/ncsi.txt").ConfigureAwait(false); var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "Microsoft NCSI"); } }
public async Task CanRequestChunkEncodedAsync() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var client = new HttpClient(handler)) { var response = await client.GetAsync("https://jigsaw.w3.org/HTTP/ChunkedScript").ConfigureAwait(false); var content = await response.Content.ReadAsStringAsync(); Assert.Equal(1000, Regex.Matches(content, "01234567890123456789012345678901234567890123456789012345678901234567890").Count); } }
public async Task CanDoBasicRequestAsync() { var requestUri = "http://api.qbit.ninja/whatisit/what%20is%20my%20future"; using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { HttpResponseMessage message = await httpClient.GetAsync(requestUri).ConfigureAwait(false); var content = await message.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } }
public async Task CanRequestGzipEncoded() { using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) { var client = new QBitNinjaClient(Network.Main); client.SetHttpMessageHandler(handler); var response = await client.GetBlock(new QBitNinja.Client.Models.BlockFeature(new uint256("0000000000000000004e24d06073aef7a5313d4ea83a5c105b3cadd0d38cc1f0")), true).ConfigureAwait(false); Assert.Equal(474010, response.AdditionalInformation.Height); Assert.Equal(null, response.Block); Assert.Equal(null, response.ExtendedInformation); } }
public async Task CanRequestOnionAsync() { var requestUri = "http://msydqstlz2kzerdg.onion/"; using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); Assert.True(content.Contains("Learn more about Ahmia and its team")); } }
public async Task CanDoHttpsAsync() { var requestUri = "https://slack.com/api/api.test"; using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); Assert.Equal(content, "{\"ok\":true}"); } }
public async Task CanDoHttpsRequest1Async() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort, ignoreSslCertification: true); using (var client = new HttpClient(handler)) { var request = "https://api.qbit.ninja/whatisit/what%20is%20my%20future"; var res = await client.GetAsync(request).ConfigureAwait(false); var content = await res.Content.ReadAsStringAsync().ConfigureAwait(false); Assert.Equal(content, "\"Good question Holmes !\""); } }
public async Task CanDoHttpsRequestManyAsync() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort, ignoreSslCertification: true); using (var client = new HttpClient(handler)) { var contents = await QBitTestAsync(client, 15, https : true).ConfigureAwait(false); foreach (var content in contents) { Assert.Equal(content, "\"Good question Holmes !\""); } } }
public async Task CanDoRequest2Async() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var client = new HttpClient(handler)) { var contents = await QBitTestAsync(client, 2).ConfigureAwait(false); foreach (var content in contents) { Assert.Equal(content, "\"Good question Holmes !\""); } } }
public async Task CanRequestOnionAsync() { var requestUri = "http://bitmixer2whesjgj.onion/order.php?addr1=16HGUokcXuJXn9yiV6uQ4N3umAWteE2cRR&pr1=33&time1=8&addr2=1F1Afwxr2xrs3ZQpf6ifqfNMxJWZt2JupK&pr2=67&time2=16&bitcode=AcOw&fee=0.6523"; using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort)) using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync() .ConfigureAwait(false); Assert.Equal(content, "error=Invalid Bitcode"); } }
private static async Task <IPAddress> GetTorIpAsync(string requestUri) { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); IPAddress torIp; using (var httpClient = new HttpClient(handler)) { var content = await(await httpClient.GetAsync(requestUri).ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); var gotIp = IPAddress.TryParse(content.Replace("\n", ""), out torIp); Assert.True(gotIp); } return(torIp); }
public async Task CanRequestInRowHttpsAsync() { using (var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort, ignoreSslCertification: true)) { for (int i = 0; i < 2; i++) { using (var httpClient = new HttpClient(handler, disposeHandler: false)) { await(await httpClient.GetAsync( "https://api.qbit.ninja/transactions/38d4cfeb57d6685753b7a3b3534c3cb576c34ca7344cd4582f9613ebf0c2b02a?format=json&headeronly=true") .ConfigureAwait(false)).Content.ReadAsStringAsync().ConfigureAwait(false); } } } }
public async Task CanRequestChunkEncodedAsync() { var handler = new SocksPortHandler(Shared.HostAddress, Shared.SocksPort); using (var client = new HttpClient(handler)) { var response = await client.GetAsync("https://jigsaw.w3.org/HTTP/ChunkedScript").ConfigureAwait(false); var content = await response.Content.ReadAsStringAsync(); Assert.Equal(1000, Regex.Matches(content, "01234567890123456789012345678901234567890123456789012345678901234567890").Count); var tumbleBitResponse = await client.GetAsync("http://testnet.ntumblebit.metaco.com/api/v1/tumblers/0/parameters").ConfigureAwait(false); var tumbleBitContent = await tumbleBitResponse.Content.ReadAsStringAsync(); Assert.True(tumbleBitContent.Contains("TestNet")); Assert.True(tumbleBitContent.Contains("fakePuzzleCount")); Assert.True(tumbleBitContent.Contains("30820124020100300d06092a864886f70d01010105000482010e3082010a0282010100b520935292dd6ff1e6d69af2a6936bb0bb52681ec6e700b2b256cc88e80a1264c4d1390b5e37dc3540c0069680df10ffd4e16b3511264488b9f7e27eb74e4fdf97c3f18b331a2aa33541b6e2b6fbad8ebf9b2799e14af0d5b327260f162c84b16c08fdfb0730a4dac956116b6e200b33cbcdf19b270250e820c5aec8f9dcc224b5cb08f2a1a4adb583a4d70c76a252492f1b0da6e89f7d586c12f426dd1e5a9843b542eea760eb89c4a2e44cb3b1f4815866d9150b6c2c9bdf5d0e99ece9ac6df09cd13e43dc02dad19fc828f5f737b2ac9e3318ee2374bfd1b70da4884e807c6150a0ceeca20a1a62814dad7408a542f5865d7b5b3f0c1bfd8878372514dca10203010001")); Assert.True(tumbleBitContent.Contains("realTransactionCount")); Assert.True(tumbleBitContent.Contains("denomination")); Assert.True(tumbleBitContent.Contains("fakeFormat")); } }
public override HttpMessageHandler CreateHttpHandler() { SocksPortHandler handler = new SocksPortHandler(Proxy); return(handler); }
public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); services.AddMvc(option => option.EnableEndpointRouting = false); services.AddControllers() .AddNewtonsoftJson(); services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { License = new Microsoft.OpenApi.Models.OpenApiLicense { Name = "Attribution-NonCommercial-NoDerivatives 4.0 International", Url = new Uri("https://raw.githubusercontent.com/tangramproject/Tangram.Vector/initial/LICENSE") }, Title = "Tangram Membership HTTP API", Version = "v1", Description = "Backend services.", TermsOfService = new Uri("https://tangrams.io/legal/"), Contact = new Microsoft.OpenApi.Models.OpenApiContact { Email = "*****@*****.**", Url = new Uri("https://tangrams.io/about-tangram/team/") } }); }); services.AddHttpContextAccessor(); services.AddOptions(); services.AddSingleton <IOnionServiceClientConfiguration, OnionServiceClientConfiguration>(); services.AddHttpClient <IOnionServiceClient, OnionServiceClient>(); services.AddSingleton(sp => { var logger = sp.GetService <ILogger <Startup> >(); //var onionStarted = sp.GetService<IOnionServiceClient>() // .IsTorStartedAsync() // .GetAwaiter() // .GetResult(); //while(!onionStarted) //{ // logger.LogWarning("Unable to verify Tor is started... retrying in a few seconds"); // Thread.Sleep(5000); // onionStarted = sp.GetService<IOnionServiceClient>() // .IsTorStartedAsync() // .GetAwaiter() // .GetResult(); //} //logger.LogInformation("Tor is started... configuring Socks Port Handler"); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; var onionServiceClientConfiguration = sp.GetService <IOnionServiceClientConfiguration>(); var handler = new SocksPortHandler(onionServiceClientConfiguration.SocksHost, onionServiceClientConfiguration.SocksPort); return(handler); }); services.AddHttpClient <ITorClient, TorClient>() //.ConfigurePrimaryHttpMessageHandler( // p => p.GetRequiredService<SocksPortHandler>() //) .SetHandlerLifetime(TimeSpan.FromMinutes(5)); services.AddSingleton <ISwimNode, SwimNode>(sp => { //var onionServiceClientDetails = sp.GetService<IOnionServiceClient>() // .GetHiddenServiceDetailsAsync() // .GetAwaiter() // .GetResult(); var publicIp = GetPublicHostIp(); var publicPort = GetPublicHostPort(); return(new SwimNode($"http://{publicIp}:{publicPort}")); }); services.AddSingleton <ISwimProtocolProvider, SwimProtocolProvider>(); services.AddSingleton <ISwimProtocol, FailureDetection>(); services.AddHostedService <FailureDetection>(); }
public Client(IConfiguration apiRestSection, ILogger logger, SocksPortHandler socksPortHandler) { this.apiRestSection = apiRestSection.GetSection(Constant.ApiGateway); this.logger = logger; this.socksPortHandler = socksPortHandler; }
public Client(ILogger logger, SocksPortHandler socksPortHandler) { this.logger = logger; this.socksPortHandler = socksPortHandler; }
private static async Task MainAsync(IReadOnlyList <string> args) { //args = new string[] { "help" }; //args = new string[] { "generate-wallet" }; //args = new string[] { "generate-wallet", "wallet-file=test2.json" }; ////math super cool donate beach mobile sunny web board kingdom bacon crisp ////no password //args = new string[] { "recover-wallet", "wallet-file=test5.json" }; //args = new string[] { "show-balances"}; //args = new string[] { "receive" }; //args = new string[] { "send","btc=1", "address=mqjVoPiXtLdBdxdqQzWvFSMSBv93swPUUH", "wallet-file=MoliWallet.json" }; //args = new string[] { "send", "btc=0.1", "address=mkpC5HFC8QHbJbuwajYLDkwPoqcftMU1ga" }; //args = new string[] { "send", "btc=all", "address=mzz63n3n89KVeHQXRqJEVsQX8MZj5zeqCw", "wallet-file=test4.json" }; // Load config file // It also creates it with default settings if doesn't exist Config.Load(); // Configure QBitNinjaClient _qBitClient = new QBitNinjaClient(Config.Network); _httpClient = new HttpClient(); if (Config.UseTor) { var torHandler = new SocksPortHandler(Config.TorHost, Config.TorSocksPort, ignoreSslCertification: true); // ignoreSslCertification needed for linux, until QBit or DotNetTor fixes its issues _qBitClient.SetHttpMessageHandler(torHandler); _httpClient = new HttpClient(torHandler); } if (args.Count == 0) { DisplayHelp(); Exit(color: ConsoleColor.Green); } var command = args[0]; if (!Commands.Contains(command)) { WriteLine("Wrong command is specified."); DisplayHelp(); } foreach (var arg in args.Skip(1).Where(arg => !arg.Contains('='))) { Exit($"Wrong argument format specified: {arg}"); } #region HelpCommand if (command == "help") { AssertArgumentsLenght(args.Count, 1, 1); DisplayHelp(); } #endregion HelpCommand #region GenerateWalletCommand if (command == "generate-wallet") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); AssertWalletNotExists(walletFilePath); string pw; string pwConf; do { // 1. Get password from user WriteLine("Choose a password:"******"Confirm password:"******"Passwords do not match. Try again!"); } } while (pw != pwConf); // 3. Create wallet string mnemonic; Safe.Create(out mnemonic, pw, walletFilePath, Config.Network); // If no exception thrown the wallet is successfully created. WriteLine(); WriteLine("Wallet is successfully created."); WriteLine($"Wallet file: {walletFilePath}"); // 4. Display mnemonic WriteLine(); WriteLine("Write down the following mnemonic words."); WriteLine("With the mnemonic words AND your password you can recover this wallet by using the recover-wallet command."); WriteLine(); WriteLine("-------"); WriteLine(mnemonic); WriteLine("-------"); } #endregion GenerateWalletCommand #region RecoverWalletCommand if (command == "recover-wallet") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); AssertWalletNotExists(walletFilePath); WriteLine($"Your software is configured using the Bitcoin {Config.Network} network."); WriteLine("Provide your mnemonic words, separated by spaces:"); var mnemonic = ReadLine(); AssertCorrectMnemonicFormat(mnemonic); WriteLine("Provide your password. Please note the wallet cannot check if your password is correct or not. If you provide a wrong password a wallet will be recovered with your provided mnemonic AND password pair:"); var password = PasswordConsole.ReadPassword(); Safe.Recover(mnemonic, password, walletFilePath, Config.Network); // If no exception thrown the wallet is successfully recovered. WriteLine(); WriteLine("Wallet is successfully recovered."); WriteLine($"Wallet file: {walletFilePath}"); } #endregion RecoverWalletCommand #region ShowBalancesCommand if (command == "show-balances") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); Safe safe = DecryptWalletByAskingForPassword(walletFilePath); if (Config.ConnectionType == ConnectionType.Http) { await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false); // 0. Query all operations, grouped by addresses Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false); // 1. Get all address history record with a wrapper class var addressHistoryRecords = new List <AddressHistoryRecord>(); foreach (var elem in operationsPerAddresses) { foreach (BalanceOperation op in elem.Value) { addressHistoryRecords.Add(new AddressHistoryRecord(elem.Key, op)); } } // 2. Calculate wallet balances Money confirmedWalletBalance; Money unconfirmedWalletBalance; GetBalances(addressHistoryRecords, out confirmedWalletBalance, out unconfirmedWalletBalance); // 3. Group all address history records by addresses var addressHistoryRecordsPerAddresses = new Dictionary <BitcoinAddress, HashSet <AddressHistoryRecord> >(); foreach (BitcoinAddress address in operationsPerAddresses.Keys) { var recs = new HashSet <AddressHistoryRecord>(); foreach (AddressHistoryRecord record in addressHistoryRecords) { if (record.Address == address) { recs.Add(record); } } addressHistoryRecordsPerAddresses.Add(address, recs); } // 4. Calculate address balances WriteLine(); WriteLine("---------------------------------------------------------------------------"); WriteLine(@"Address Confirmed Unconfirmed"); WriteLine("---------------------------------------------------------------------------"); foreach (var elem in addressHistoryRecordsPerAddresses) { Money confirmedBalance; Money unconfirmedBalance; GetBalances(elem.Value, out confirmedBalance, out unconfirmedBalance); if (confirmedBalance != Money.Zero || unconfirmedBalance != Money.Zero) { WriteLine($@"{elem.Key.ToWif()} {confirmedBalance.ToDecimal(MoneyUnit.BTC):0.#############################} {unconfirmedBalance.ToDecimal(MoneyUnit.BTC):0.#############################}"); } } WriteLine("---------------------------------------------------------------------------"); WriteLine($"Confirmed Wallet Balance: {confirmedWalletBalance.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); WriteLine($"Unconfirmed Wallet Balance: {unconfirmedWalletBalance.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); WriteLine("---------------------------------------------------------------------------"); } else if (Config.ConnectionType == ConnectionType.FullNode) { throw new NotImplementedException(); } else { Exit("Invalid connection type."); } } #endregion ShowBalancesCommand #region ShowHistoryCommand if (command == "show-history") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); Safe safe = DecryptWalletByAskingForPassword(walletFilePath); if (Config.ConnectionType == ConnectionType.Http) { await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false); // 0. Query all operations, grouped our used safe addresses Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false); WriteLine(); WriteLine("---------------------------------------------------------------------------"); WriteLine(@"Date Amount Confirmed Transaction Id"); WriteLine("---------------------------------------------------------------------------"); Dictionary <uint256, List <BalanceOperation> > operationsPerTransactions = GetOperationsPerTransactions(operationsPerAddresses); // 3. Create history records from the transactions // History records is arbitrary data we want to show to the user var txHistoryRecords = new List <Tuple <DateTimeOffset, Money, int, uint256> >(); foreach (var elem in operationsPerTransactions) { var amount = Money.Zero; foreach (var op in elem.Value) { amount += op.Amount; } var firstOp = elem.Value.First(); txHistoryRecords .Add(new Tuple <DateTimeOffset, Money, int, uint256>( firstOp.FirstSeen, amount, firstOp.Confirmations, elem.Key)); } // 4. Order the records by confirmations and time (Simply time does not work, because of a QBitNinja issue) var orderedTxHistoryRecords = txHistoryRecords .OrderByDescending(x => x.Item3) // Confirmations .ThenBy(x => x.Item1); // FirstSeen foreach (var record in orderedTxHistoryRecords) { // Item2 is the Amount if (record.Item2 > 0) { ForegroundColor = ConsoleColor.Green; } else if (record.Item2 < 0) { ForegroundColor = ConsoleColor.DarkGreen; } WriteLine($@"{record.Item1.DateTime} {record.Item2} {record.Item3 > 0} {record.Item4}"); ResetColor(); } } else if (Config.ConnectionType == ConnectionType.FullNode) { throw new NotImplementedException(); } else { Exit("Invalid connection type."); } } #endregion ShowHistoryCommand #region ShowExtKeys if (command == "show-extkey") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); Safe safe = DecryptWalletByAskingForPassword(walletFilePath); WriteLine($"ExtKey: {safe.BitcoinExtKey}"); WriteLine($"Network: {safe.Network}"); } if (command == "show-extpubkey") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); Safe safe = DecryptWalletByAskingForPassword(walletFilePath); WriteLine($"ExtPubKey: {safe.BitcoinExtPubKey}"); WriteLine($"Network: {safe.Network}"); } #endregion ShowExtKeys #region ReceiveCommand if (command == "receive") { AssertArgumentsLenght(args.Count, 1, 2); var walletFilePath = GetWalletFilePath(args); Safe safe = DecryptWalletByAskingForPassword(walletFilePath); if (Config.ConnectionType == ConnectionType.Http) { await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false); Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerReceiveAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, 7, Safe.HdPathType.Receive).ConfigureAwait(false); WriteLine("---------------------------------------------------------------------------"); WriteLine("Unused Receive Addresses"); WriteLine("---------------------------------------------------------------------------"); foreach (var elem in operationsPerReceiveAddresses) { if (elem.Value.Count == 0) { WriteLine($"{elem.Key.ToWif()}"); } } } else if (Config.ConnectionType == ConnectionType.FullNode) { throw new NotImplementedException(); } else { Exit("Invalid connection type."); } } #endregion ReceiveCommand #region SendCommand if (command == "send") { await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false); AssertArgumentsLenght(args.Count, 3, 4); var walletFilePath = GetWalletFilePath(args); BitcoinAddress addressToSend; try { addressToSend = BitcoinAddress.Create(GetArgumentValue(args, argName: "address", required: true), Config.Network); } catch (Exception ex) { Exit(ex.ToString()); throw; } Safe safe = DecryptWalletByAskingForPassword(walletFilePath); if (Config.ConnectionType == ConnectionType.Http) { Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false); // 1. Gather all the not empty private keys WriteLine("Finding not empty private keys..."); var operationsPerNotEmptyPrivateKeys = new Dictionary <BitcoinExtKey, List <BalanceOperation> >(); foreach (var elem in operationsPerAddresses) { var balance = Money.Zero; foreach (var op in elem.Value) { balance += op.Amount; } if (balance > Money.Zero) { var secret = safe.FindPrivateKey(elem.Key); operationsPerNotEmptyPrivateKeys.Add(secret, elem.Value); } } // 2. Get the script pubkey of the change. WriteLine("Select change address..."); Script changeScriptPubKey = null; Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerChangeAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, minUnusedKeys : 1, hdPathType : Safe.HdPathType.Change).ConfigureAwait(false); foreach (var elem in operationsPerChangeAddresses) { if (elem.Value.Count == 0) { changeScriptPubKey = safe.FindPrivateKey(elem.Key).ScriptPubKey; } } if (changeScriptPubKey == null) { throw new ArgumentNullException(); } // 3. Gather coins can be spend WriteLine("Gathering unspent coins..."); Dictionary <Coin, bool> unspentCoins = await GetUnspentCoinsAsync(operationsPerNotEmptyPrivateKeys.Keys, _qBitClient).ConfigureAwait(false); // 4. How much money we can spend? var availableAmount = Money.Zero; var unconfirmedAvailableAmount = Money.Zero; foreach (var elem in unspentCoins) { // If can spend unconfirmed add all if (Config.CanSpendUnconfirmed) { availableAmount += elem.Key.Amount; if (!elem.Value) { unconfirmedAvailableAmount += elem.Key.Amount; } } // else only add confirmed ones else { if (elem.Value) { availableAmount += elem.Key.Amount; } } } // 5. Get and calculate fee WriteLine("Calculating dynamic transaction fee..."); Money feePerBytes = null; try { feePerBytes = await QueryFeePerBytesAsync().ConfigureAwait(false); } catch (Exception ex) { WriteLine(ex.Message); Exit("Couldn't calculate transaction fee, try it again later."); } int inNum; string amountString = GetArgumentValue(args, argName: "btc", required: true); if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase)) { inNum = unspentCoins.Count; } else { const int expectedMinTxSize = 1 * 148 + 2 * 34 + 10 - 1; inNum = SelectCoinsToSpend(unspentCoins, ParseBtcString(amountString) + feePerBytes * expectedMinTxSize).Count; } const int outNum = 2; // 1 address to send + 1 for change var estimatedTxSize = inNum * 148 + outNum * 34 + 10 + inNum; // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending WriteLine($"Estimated tx size: {estimatedTxSize} bytes"); Money fee = feePerBytes * estimatedTxSize; WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); // 6. How much to spend? Money amountToSend = null; if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase)) { amountToSend = availableAmount; amountToSend -= fee; } else { amountToSend = ParseBtcString(amountString); } // 7. Do some checks if (amountToSend < Money.Zero || availableAmount < amountToSend + fee) { Exit("Not enough coins."); } decimal feePc = Math.Round((100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC)); if (feePc > 1) { WriteLine(); WriteLine($"The transaction fee is {feePc:0.#}% of your transaction amount."); WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); ConsoleKey response = GetYesNoAnswerFromUser(); if (response == ConsoleKey.N) { Exit("User interruption."); } } var confirmedAvailableAmount = availableAmount - unconfirmedAvailableAmount; var totalOutAmount = amountToSend + fee; if (confirmedAvailableAmount < totalOutAmount) { var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount; WriteLine(); WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC):0.#############################} unconfirmed btc."); ConsoleKey response = GetYesNoAnswerFromUser(); if (response == ConsoleKey.N) { Exit("User interruption."); } } // 8. Select coins WriteLine("Selecting coins..."); HashSet <Coin> coinsToSpend = SelectCoinsToSpend(unspentCoins, totalOutAmount); // 9. Get signing keys var signingKeys = new HashSet <ISecret>(); foreach (var coin in coinsToSpend) { foreach (var elem in operationsPerNotEmptyPrivateKeys) { if (elem.Key.ScriptPubKey == coin.ScriptPubKey) { signingKeys.Add(elem.Key); } } } // 10. Build the transaction WriteLine("Signing transaction..."); var builder = new TransactionBuilder(); var tx = builder .AddCoins(coinsToSpend) .AddKeys(signingKeys.ToArray()) .Send(addressToSend, amountToSend) .SetChange(changeScriptPubKey) .SendFees(fee) .BuildTransaction(true); if (!builder.Verify(tx)) { Exit("Couldn't build the transaction."); } WriteLine($"Transaction Id: {tx.GetHash()}"); // QBit's success response is buggy so let's check manually, too BroadcastResponse broadcastResponse; var success = false; var tried = 0; const int maxTry = 7; do { tried++; WriteLine($"Try broadcasting transaction... ({tried})"); broadcastResponse = await _qBitClient.Broadcast(tx).ConfigureAwait(false); var getTxResp = await _qBitClient.GetTransaction(tx.GetHash()).ConfigureAwait(false); if (getTxResp != null) { success = true; break; } else { await Task.Delay(3000).ConfigureAwait(false); } } while (tried < maxTry); if (!success) { if (broadcastResponse.Error != null) { // Try broadcasting with smartbit if QBit fails (QBit issue) if (broadcastResponse.Error.ErrorCode == NBitcoin.Protocol.RejectCode.INVALID && broadcastResponse.Error.Reason == "Unknown") { WriteLine("Try broadcasting transaction with smartbit..."); var post = "https://testnet-api.smartbit.com.au/v1/blockchain/pushtx"; if (Config.Network == Network.Main) { post = "https://api.smartbit.com.au/v1/blockchain/pushtx"; } var content = new StringContent(new JObject(new JProperty("hex", tx.ToHex())).ToString(), Encoding.UTF8, "application/json"); var resp = await _httpClient.PostAsync(post, content).ConfigureAwait(false); var json = JObject.Parse(await resp.Content.ReadAsStringAsync().ConfigureAwait(false)); if (json.Value <bool>("success")) { Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green); } else { WriteLine($"Error code: {json["error"].Value<string>("code")} Reason: {json["error"].Value<string>("message")}"); } } else { WriteLine($"Error code: {broadcastResponse.Error.ErrorCode} Reason: {broadcastResponse.Error.Reason}"); } } Exit("The transaction might not have been successfully broadcasted. Please check the Transaction ID in a block explorer.", ConsoleColor.Blue); } Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green); } else if (Config.ConnectionType == ConnectionType.FullNode) { throw new NotImplementedException(); } else { Exit("Invalid connection type."); } } #endregion SendCommand Exit(color: ConsoleColor.Green); }