Exemple #1
0
        public virtual IUserDataClient Subscribe <TEventArgs>(string listenKey, IBinanceApiUser user, Action <TEventArgs> callback)
            where TEventArgs : UserDataEventArgs
        {
            Logger?.LogDebug($"{nameof(UserDataClient)}.{nameof(Subscribe)}: \"{listenKey}\" (callback: {(callback == null ? "no" : "yes")}).  [thread: {Thread.CurrentThread.ManagedThreadId}]");

            var type = typeof(TEventArgs);

            if (type == typeof(AccountUpdateEventArgs))
            {
                // ReSharper disable once InconsistentlySynchronizedField
                Subscribe(listenKey, user, callback as Action <AccountUpdateEventArgs>, _accountUpdateSubscribers);
            }
            else if (type == typeof(OrderUpdateEventArgs))
            {
                // ReSharper disable once InconsistentlySynchronizedField
                Subscribe(listenKey, user, callback as Action <OrderUpdateEventArgs>, _orderUpdateSubscribers);
            }
            else if (type == typeof(AccountTradeUpdateEventArgs))
            {
                // ReSharper disable once InconsistentlySynchronizedField
                Subscribe(listenKey, user, callback as Action <AccountTradeUpdateEventArgs>, _accountTradeUpdateSubscribers);
            }
            else
            {
                Subscribe(listenKey, user, callback as Action <UserDataEventArgs>, null);
            }

            return(this);
        }
Exemple #2
0
        public virtual Order Deserialize(string json, IBinanceApiUser user)
        {
            Throw.IfNullOrWhiteSpace(json, nameof(json));
            Throw.IfNull(user, nameof(user));

            return(FillOrder(new Order(user), JObject.Parse(json)));
        }
Exemple #3
0
        public async Task SubscribeAsync(IBinanceApiUser user, Action <UserDataEventArgs> callback, CancellationToken token = default)
        {
            Throw.IfNull(user, nameof(user));

            if (_listenKeys.ContainsKey(user))
            {
                throw new InvalidOperationException($"{nameof(UserDataWebSocketClient)}: Already subscribed to user.");
            }

            try
            {
                _listenKeys[user] = await _api.UserStreamStartAsync(user, token)
                                    .ConfigureAwait(false);

                if (string.IsNullOrWhiteSpace(_listenKeys[user]))
                {
                    throw new Exception($"{nameof(IUserDataWebSocketClient)}: Failed to get listen key from API.");
                }

                SubscribeTo(_listenKeys[user], callback);
            }
            catch (OperationCanceledException) { }
            catch (Exception e)
            {
                if (!token.IsCancellationRequested)
                {
                    Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}.{nameof(SubscribeAsync)}");
                    throw;
                }
            }
        }
Exemple #4
0
        private void Subscribe <TEventArgs>(string listenKey, IBinanceApiUser user, Action <TEventArgs> callback, IDictionary <string, IList <Action <TEventArgs> > > subscribers)
            where TEventArgs : UserDataEventArgs
        {
            Throw.IfNullOrWhiteSpace(listenKey, nameof(listenKey));
            Throw.IfNull(user, nameof(user));

            // Subscribe callback (if provided) to listen key.
            if (subscribers == null)
            {
                SubscribeStream(listenKey, callback as Action <UserDataEventArgs>);
            }
            else
            {
                lock (_sync)
                {
                    SubscribeStream(listenKey, callback, subscribers);
                }
            }

            // If listen key is new.
            // ReSharper disable once InvertIf
            if (!Users.ContainsKey(listenKey))
            {
                // If a listen key exists with user.
                if (Users.Any(_ => _.Value.Equals(user)))
                {
                    throw new InvalidOperationException($"{nameof(UserDataClient)}.{nameof(Subscribe)}: A listen key is already subscribed for this user.");
                }

                // Add listen key and user (for stream event handling).
                Users[listenKey] = user;
            }
        }
        public Task SubscribeAsync(IBinanceApiUser user, Action<AccountInfoCacheEventArgs> callback, CancellationToken token = default)
        {
            Throw.IfNull(user, nameof(user));

            base.LinkTo(Client, callback);

            return Client.SubscribeAsync(user, ClientCallback, token);
        }
        public async Task SubscribeAsync <TEventArgs>(IBinanceApiUser user, Action <TEventArgs> callback, CancellationToken token = default)
            where TEventArgs : UserDataEventArgs
        {
            var listenKey = await _streamControl.OpenStreamAsync(user, token)
                            .ConfigureAwait(false);

            HandleSubscribe(() => Client.Subscribe(listenKey, user, callback));
        }
Exemple #7
0
        public virtual IEnumerable <Order> DeserializeMany(string json, IBinanceApiUser user)
        {
            Throw.IfNullOrWhiteSpace(json, nameof(json));
            Throw.IfNull(user, nameof(user));

            return(JArray.Parse(json)
                   .Select(item => FillOrder(new Order(user), item))
                   .ToArray());
        }
Exemple #8
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="oldListenKey"></param>
        /// <param name="newListenKey"></param>
        public UserDataListenKeyUpdateEventArgs(IBinanceApiUser user, string oldListenKey, string newListenKey)
        {
            Throw.IfNull(user, nameof(user));
            Throw.IfNullOrWhiteSpace(oldListenKey, nameof(oldListenKey));

            User         = user;
            OldListenKey = oldListenKey;
            NewListenKey = newListenKey; // can be null.
        }
Exemple #9
0
        public virtual async Task SubscribeAsync(IBinanceApiUser user, Action <UserDataEventArgs> callback, CancellationToken token)
        {
            Throw.IfNull(user, nameof(user));

            if (!token.CanBeCanceled)
            {
                throw new ArgumentException("Token must be capable of being in the canceled state.", nameof(token));
            }

            token.ThrowIfCancellationRequested();

            User = user;

            if (IsSubscribed)
            {
                throw new InvalidOperationException($"{nameof(UserDataWebSocketClient)} is already subscribed to a user.");
            }

            try
            {
                _listenKey = await _api.UserStreamStartAsync(user, token)
                             .ConfigureAwait(false);

                if (string.IsNullOrWhiteSpace(_listenKey))
                {
                    throw new Exception($"{nameof(IUserDataWebSocketClient)}: Failed to get listen key from API.");
                }

                var period = _options?.KeepAliveTimerPeriod ?? KeepAliveTimerPeriodDefault;
                period = Math.Min(Math.Max(period, KeepAliveTimerPeriodMin), KeepAliveTimerPeriodMax);

                _keepAliveTimer = new Timer(OnKeepAliveTimer, token, period, period);

                try
                {
                    await SubscribeToAsync(_listenKey, callback, token)
                    .ConfigureAwait(false);
                }
                finally
                {
                    _keepAliveTimer.Dispose();

                    await _api.UserStreamCloseAsync(User, _listenKey, CancellationToken.None)
                    .ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException) { }
            catch (Exception e)
            {
                if (!token.IsCancellationRequested)
                {
                    Logger?.LogError(e, $"{nameof(UserDataWebSocketClient)}.{nameof(SubscribeAsync)}");
                    throw;
                }
            }
        }
Exemple #10
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="cache"></param>
        /// <param name="user"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task StreamAsync(this IAccountInfoCache cache, IBinanceApiUser user, Action <AccountInfoCacheEventArgs> callback, CancellationToken token)
        {
            Throw.IfNull(cache, nameof(cache));

            await cache.SubscribeAsync(user, callback, token)
            .ConfigureAwait(false);

            await cache.Client.WebSocket.StreamAsync(token)
            .ConfigureAwait(false);
        }
Exemple #11
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="callback"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task StreamAsync(this IUserDataWebSocketClient client, IBinanceApiUser user, Action <UserDataEventArgs> callback, CancellationToken token)
        {
            Throw.IfNull(client, nameof(client));

            await client.SubscribeAsync(user, callback, token)
            .ConfigureAwait(false);

            await client.WebSocket.StreamAsync(token)
            .ConfigureAwait(false);
        }
Exemple #12
0
        public void Subscribe(string listenKey, IBinanceApiUser user, Action <AccountInfoCacheEventArgs> callback)
        {
            Throw.IfNullOrWhiteSpace(listenKey, nameof(listenKey));
            Throw.IfNull(user, nameof(user));

            _listenKey = listenKey;
            _user      = user;

            OnSubscribe(callback);
            SubscribeToClient();
        }
Exemple #13
0
        public override void Subscribe(string listenKey, IBinanceApiUser user, Action <UserDataEventArgs> callback)
        {
            Throw.IfNull(user, nameof(user));

            // Ensure only one user is subscribed.
            if (ListenKeys.Count > 0 && !ListenKeys.Single().Value.Equals(user))
            {
                throw new InvalidOperationException($"{nameof(SingleUserDataWebSocketClient)}: Can only subscribe to a single a user.");
            }

            base.Subscribe(listenKey, user, callback);
        }
Exemple #14
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="commissions">The account commissions.</param>
        /// <param name="status">The account status.</param>
        /// <param name="time">The update time.</param>
        /// <param name="balances">The account balances.</param>
        public AccountInfo(IBinanceApiUser user, AccountCommissions commissions, AccountStatus status, DateTime time, IEnumerable <AccountBalance> balances = null)
        {
            Throw.IfNull(user, nameof(user));
            Throw.IfNull(commissions, nameof(commissions));
            Throw.IfNull(status, nameof(status));

            User        = user;
            Commissions = commissions;
            Status      = status;
            Time        = time;

            Balances = balances ?? new AccountBalance[] { };
        }
        public async Task UnsubscribeAsync <TEventArgs>(IBinanceApiUser user, Action <TEventArgs> callback, CancellationToken token = default)
            where TEventArgs : UserDataEventArgs
        {
            var listenKey = await _streamControl.GetStreamNameAsync(user, token)
                            .ConfigureAwait(false);

            HandleUnsubscribe(() => Client.Unsubscribe(listenKey, callback));

            if (callback == null || !Controller.Stream.ProvidedStreams.Contains(listenKey))
            {
                await _streamControl.CloseStreamAsync(user, token)
                .ConfigureAwait(false);
            }
        }
Exemple #16
0
        public async Task SignAsync(BinanceHttpRequest request, IBinanceApiUser user, CancellationToken token = default)
        {
            Throw.IfNull(request, nameof(request));
            Throw.IfNull(user, nameof(user));

            var timestamp = TimestampProvider != null
                ? await TimestampProvider.GetTimestampAsync(this, token).ConfigureAwait(false)
                : DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

            request.AddParameter("timestamp", timestamp);

            var signature = user.Sign(request.TotalParams);

            request.AddParameter("signature", signature);
        }
Exemple #17
0
        public void Subscribe(string listenKey, IBinanceApiUser user, Action <AccountInfoCacheEventArgs> callback)
        {
            Throw.IfNullOrWhiteSpace(listenKey, nameof(listenKey));
            Throw.IfNull(user, nameof(user));

            if (_listenKey != null)
            {
                throw new InvalidOperationException($"{nameof(AccountInfoCache)}.{nameof(Subscribe)}: Already subscribed to a (user) listen key: \"{_listenKey}\"");
            }

            _listenKey = listenKey;
            _user      = user;

            OnSubscribe(callback);
            SubscribeToClient();
        }
Exemple #18
0
        public override IJsonClient Unsubscribe()
        {
            if (_listenKey == null)
            {
                return(this);
            }

            UnsubscribeFromClient();
            OnUnsubscribe();

            AccountInfo = null;

            _listenKey = default;
            _user      = default;

            return(this);
        }
Exemple #19
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="commissions">The account commissions.</param>
        /// <param name="status">The account status.</param>
        /// <param name="updateTime">The update time.</param>
        /// <param name="balances">The account balances.</param>
        public AccountInfo(IBinanceApiUser user, AccountCommissions commissions, AccountStatus status, long updateTime, IEnumerable <AccountBalance> balances = null)
        {
            Throw.IfNull(user, nameof(user));
            Throw.IfNull(commissions, nameof(commissions));
            Throw.IfNull(status, nameof(status));

            if (updateTime <= 0)
            {
                throw new ArgumentException($"{nameof(AccountInfo)}: timestamp must be greater than 0.", nameof(updateTime));
            }

            User        = user;
            Commissions = commissions;
            Status      = status;
            Timestamp   = updateTime;

            Balances = balances ?? new AccountBalance[] { };
        }
        public async Task SubscribeAsync(IBinanceApiUser user, Action <AccountInfoCacheEventArgs> callback, CancellationToken token)
        {
            Throw.IfNull(user, nameof(user));

            if (!token.CanBeCanceled)
            {
                throw new ArgumentException("Token must be capable of being in the canceled state.", nameof(token));
            }

            token.ThrowIfCancellationRequested();

            Token = token;

            LinkTo(Client, callback);

            try
            {
                await Client.SubscribeAsync(user, token)
                .ConfigureAwait(false);
            }
            finally { UnLink(); }
        }
        public virtual void Subscribe(string listenKey, IBinanceApiUser user, Action <UserDataEventArgs> callback)
        {
            Throw.IfNullOrWhiteSpace(listenKey, nameof(listenKey));
            Throw.IfNull(user, nameof(user));

            Logger?.LogDebug($"{nameof(UserDataWebSocketClient)}.{nameof(Subscribe)}: \"{listenKey}\" (callback: {(callback == null ? "no" : "yes")}).  [thread: {Thread.CurrentThread.ManagedThreadId}]");

            // Subscribe callback (if provided) to listen key.
            SubscribeStream(listenKey, callback);

            // If listen key is new.
            // ReSharper disable once InvertIf
            if (!ListenKeys.ContainsKey(listenKey))
            {
                // If a listen key exists with user.
                if (ListenKeys.Any(_ => _.Value.Equals(user)))
                {
                    throw new InvalidOperationException($"{nameof(UserDataWebSocketClient)}.{nameof(Subscribe)}: A listen key is already subscribed for this user.");
                }

                // Add listen key and user (for stream event handling).
                ListenKeys[listenKey] = user;
            }
        }
Exemple #22
0
        public static async Task Main(string[] args)
        {
            // Un-comment to run...
            //await AccountBalancesExample.ExampleMain(args);
            //await AccountBalancesExample.AdvancedExampleMain(args);
            //await MinimalWithDependencyInjection.ExampleMain(args);
            //await MinimalWithoutDependencyInjection.ExampleMain(args);
            //await OrderBookCacheAccountBalanceExample.AdvancedExampleMain(args);
            //await ReadMeExample.ExampleMain(args);


            var cts = new CancellationTokenSource();

            try
            {
                // Load configuration.
                Configuration = new ConfigurationBuilder()
                                .SetBasePath(Directory.GetCurrentDirectory())
                                .AddJsonFile("appsettings.json", true, false)
                                .AddUserSecrets <Program>() // for access to API key and secret.
                                .Build();

                // Configure services.
                ServiceProvider = new ServiceCollection()
                                                                             // ReSharper disable once ArgumentsStyleLiteral
                                  .AddBinance(useSingleCombinedStream: true) // add default Binance services.

                                                                             // Use alternative, low-level, web socket client implementation.
                                                                             //.AddTransient<IWebSocketClient, WebSocket4NetClient>()
                                                                             //.AddTransient<IWebSocketClient, WebSocketSharpClient>()

                                  .AddOptions()
                                  .Configure <BinanceApiOptions>(Configuration.GetSection("ApiOptions"))

                                  .AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace))
                                  .BuildServiceProvider();

                // Configure logging.
                ServiceProvider
                .GetService <ILoggerFactory>()
                .AddConsole(Configuration.GetSection("Logging:Console"))
                .AddFile(Configuration.GetSection("Logging:File"));
                // NOTE: Using ":" requires Microsoft.Extensions.Configuration.Binder.

                var apiKey = Configuration["BinanceApiKey"]                       // user secrets configuration.
                             ?? Configuration.GetSection("User")["ApiKey"];       // appsettings.json configuration.

                var apiSecret = Configuration["BinanceApiSecret"]                 // user secrets configuration.
                                ?? Configuration.GetSection("User")["ApiSecret"]; // appsettings.json configuration.

                if (string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(apiSecret))
                {
                    PrintApiNotice();
                }

                if (!string.IsNullOrEmpty(apiKey))
                {
                    User = ServiceProvider
                           .GetService <IBinanceApiUserProvider>()
                           .CreateUser(apiKey, apiSecret);
                }

                Api = ServiceProvider.GetService <IBinanceApi>();

                ClientManager = ServiceProvider.GetService <IBinanceWebSocketClientManager>();

                ClientManager.Error += (s, e) =>
                {
                    lock (ConsoleSync)
                    {
                        Console.WriteLine();
                        Console.WriteLine($"! Client Manager Error: \"{e.Exception.Message}\"");
                        Console.WriteLine();
                    }
                };

                UserDataManager = ServiceProvider.GetService <IUserDataWebSocketManager>();

                // Instantiate all assembly command handlers.
                foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
                {
                    if (typeof(IHandleCommand).IsAssignableFrom(type) && !type.IsAbstract)
                    {
                        CommandHandlers.Add((IHandleCommand)Activator.CreateInstance(type));
                    }
                }

                await SuperLoopAsync(cts.Token);
            }
            catch (Exception e)
            {
                lock (ConsoleSync)
                {
                    Console.WriteLine($"! FAIL: \"{e.Message}\"");
                    if (e.InnerException != null)
                    {
                        Console.WriteLine($"  -> Exception: \"{e.InnerException.Message}\"");
                    }
                }
            }
            finally
            {
                await DisableLiveTaskAsync();

                cts.Cancel();
                cts.Dispose();

                User?.Dispose();

                ClientManager?.Dispose();

                lock (ConsoleSync)
                {
                    Console.WriteLine();
                    Console.WriteLine("  ...press any key to close window.");
                    Console.ReadKey(true);
                }
            }
        }
Exemple #23
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="user">The user (required).</param>
 public TakeProfitLimitOrder(IBinanceApiUser user)
     : base(user)
 {
 }
Exemple #24
0
 public MarketOrder(IBinanceApiUser user)
     : base(user)
 {
 }
Exemple #25
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="cache"></param>
 /// <param name="user"></param>
 /// <param name="token"></param>
 /// <returns></returns>
 public static Task StreamAsync(this IAccountInfoCache cache, IBinanceApiUser user, CancellationToken token)
 => StreamAsync(cache, user, null, token);
        /// <summary>
        /// Start a new user data stream.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static Task <string> UserStreamStartAsync(this IBinanceHttpClient client, IBinanceApiUser user, CancellationToken token = default)
        {
            Throw.IfNull(user, nameof(user));

            return(UserStreamStartAsync(client, user.ApiKey, token));
        }
        /// <summary>
        /// Get the account status.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task <string> GetAccountStatusAsync(this IBinanceHttpClient client, IBinanceApiUser user, CancellationToken token = default)
        {
            Throw.IfNull(client, nameof(client));

            var request = new BinanceHttpRequest("/wapi/v3/accountStatus.html")
            {
                ApiKey = user.ApiKey
            };

            await client.SignAsync(request, user, token)
            .ConfigureAwait(false);

            return(await client.GetAsync(request, token)
                   .ConfigureAwait(false));
        }
        /// <summary>
        /// Get the deposit address for an asset.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="asset"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task <string> GetDepositAddressAsync(this IBinanceHttpClient client, IBinanceApiUser user, string asset, CancellationToken token = default)
        {
            Throw.IfNull(client, nameof(client));
            Throw.IfNullOrWhiteSpace(asset, nameof(asset));

            var request = new BinanceHttpRequest("/wapi/v3/depositAddress.html")
            {
                ApiKey = user.ApiKey
            };

            request.AddParameter("asset", asset.FormatSymbol());

            await client.SignAsync(request, user, token)
            .ConfigureAwait(false);

            return(await client.GetAsync(request, token)
                   .ConfigureAwait(false));
        }
        /// <summary>
        /// Get the withdrawal history.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="asset"></param>
        /// <param name="status"></param>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <param name="recvWindow"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task <string> GetWithdrawalsAsync(this IBinanceHttpClient client, IBinanceApiUser user, string asset = null, WithdrawalStatus?status = null, long startTime = default, long endTime = default, long recvWindow = default, CancellationToken token = default)
        {
            Throw.IfNull(client, nameof(client));
            Throw.IfNull(user, nameof(user));

            if (recvWindow <= 0)
            {
                recvWindow = client.Options.RecvWindowDefault ?? 0;
            }

            var request = new BinanceHttpRequest("/wapi/v3/withdrawHistory.html")
            {
                ApiKey = user.ApiKey
            };

            if (!string.IsNullOrWhiteSpace(asset))
            {
                request.AddParameter("asset", asset.FormatSymbol());
            }

            if (status.HasValue)
            {
                request.AddParameter("status", (int)status);
            }

            if (startTime > 0)
            {
                request.AddParameter("startTime", startTime);
            }

            if (endTime > 0)
            {
                request.AddParameter("endTime", endTime);
            }

            if (recvWindow > 0)
            {
                request.AddParameter("recvWindow", recvWindow);
            }

            await client.SignAsync(request, user, token)
            .ConfigureAwait(false);

            return(await client.GetAsync(request, token)
                   .ConfigureAwait(false));
        }
        /// <summary>
        /// Submit a withdraw request.
        /// </summary>
        /// <param name="client"></param>
        /// <param name="user"></param>
        /// <param name="asset"></param>
        /// <param name="address"></param>
        /// <param name="addressTag"></param>
        /// <param name="amount"></param>
        /// <param name="name">A description of the address (optional).</param>
        /// <param name="recvWindow"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static async Task <string> WithdrawAsync(this IBinanceHttpClient client, IBinanceApiUser user, string asset, string address, string addressTag, decimal amount, string name = null, long recvWindow = default, CancellationToken token = default)
        {
            Throw.IfNull(client, nameof(client));
            Throw.IfNull(user, nameof(user));
            Throw.IfNullOrWhiteSpace(asset, nameof(asset));
            Throw.IfNullOrWhiteSpace(address, nameof(address));

            if (amount <= 0)
            {
                throw new ArgumentException("Withdraw amount must be greater than 0.", nameof(amount));
            }

            if (recvWindow <= 0)
            {
                recvWindow = client.Options.RecvWindowDefault ?? 0;
            }

            var request = new BinanceHttpRequest("/wapi/v3/withdraw.html")
            {
                ApiKey = user.ApiKey
            };

            request.AddParameter("asset", asset.FormatSymbol());
            request.AddParameter("address", address);
            request.AddParameter("amount", amount);

            if (!string.IsNullOrWhiteSpace(addressTag))
            {
                request.AddParameter("addressTag", addressTag);
            }

            if (!string.IsNullOrWhiteSpace(name))
            {
                request.AddParameter("name", name);
            }

            if (recvWindow > 0)
            {
                request.AddParameter("recvWindow", recvWindow);
            }

            await client.SignAsync(request, user, token)
            .ConfigureAwait(false);

            return(await client.PostAsync(request, token)
                   .ConfigureAwait(false));
        }