private Task ScanContractAsync(
            string address,
            string contractAddress,
            TokenContractWithMetadata contract,
            CancellationToken cancellationToken = default)
        {
            return(Task.Run(async() =>
            {
                var tezosConfig = _tezosAccount.Config;

                var bcdSettings = tezosConfig.BcdApiSettings;

                var bcdApi = new BcdApi(bcdSettings);

                var contractType = contract
                                   ?.GetContractType() ?? "FA2";

                var tokenBalancesResult = await bcdApi
                                          .GetTokenBalancesAsync(
                    address: address,
                    contractAddress: contractAddress,
                    offset: 0,
                    count: bcdSettings.MaxTokensPerUpdate,
                    cancellationToken: cancellationToken)
                                          .ConfigureAwait(false);

                if (tokenBalancesResult.HasError)
                {
                    Log.Error("Error while scan tokens balance for " +
                              $"contract: {contractAddress} and " +
                              $"address: {address}. " +
                              $"Code: {tokenBalancesResult.Error.Code}. " +
                              $"Description: {tokenBalancesResult.Error.Description}");

                    return;
                }

                var localTokenAddresses = (await _tezosAccount.DataRepository
                                           .GetTezosTokenAddressesAsync(address, contractAddress)
                                           .ConfigureAwait(false))
                                          .ToList();

                var tokenBalanceDict = tokenBalancesResult.Value
                                       .GroupBy(tb => UniqueTokenId(contractType, tb.Contract, tb.TokenId))
                                       .ToDictionary(
                    g => g.Key,
                    g => g.First());

                foreach (var localAddress in localTokenAddresses)
                {
                    var uniqueTokenId = UniqueTokenId(
                        localAddress.Currency,
                        localAddress.TokenBalance.Contract,
                        localAddress.TokenBalance.TokenId);

                    if (tokenBalanceDict.TryGetValue(uniqueTokenId, out var tb))
                    {
                        localAddress.Balance = tb.GetTokenBalance();
                        localAddress.TokenBalance = tb;

                        tokenBalanceDict.Remove(uniqueTokenId);
                    }
                    else
                    {
                        localAddress.Balance = 0;
                        localAddress.TokenBalance.Balance = "0";
                    }
                }

                var xtzAddress = await _tezosAccount
                                 .GetAddressAsync(address, cancellationToken)
                                 .ConfigureAwait(false);

                var newTokenAddresses = tokenBalanceDict.Values
                                        .Select(tb => new WalletAddress
                {
                    Address = address,
                    Balance = tb.GetTokenBalance(),
                    Currency = contractType,
                    KeyIndex = xtzAddress.KeyIndex,
                    KeyType = xtzAddress.KeyType,
                    HasActivity = true,
                    TokenBalance = tb
                });

                localTokenAddresses.AddRange(newTokenAddresses);

                if (localTokenAddresses.Any())
                {
                    await _tezosAccount.DataRepository
                    .UpsertTezosTokenAddressesAsync(localTokenAddresses)
                    .ConfigureAwait(false);
                }

                // scan transfers

                foreach (var localAddress in localTokenAddresses)
                {
                    var transfersResult = await bcdApi
                                          .GetTokenTransfers(
                        address: localAddress.Address,
                        contract: localAddress.TokenBalance.Contract,
                        tokenId: localAddress.TokenBalance.TokenId,
                        count: bcdSettings.MaxTransfersPerUpdate,
                        cancellationToken: cancellationToken)
                                          .ConfigureAwait(false);

                    if (transfersResult.HasError)
                    {
                        Log.Error($"Error while get transfers for " +
                                  $"address: {localAddress.Address}, " +
                                  $"contract: {localAddress.TokenBalance.Contract} and " +
                                  $"token id: {localAddress.TokenBalance.TokenId}. " +
                                  $"Code: {transfersResult.Error.Code}. " +
                                  $"Description: {transfersResult.Error.Description}.");

                        return;
                    }

                    // todo: fix 'self' transfers
                    //transfersResult.Value.ForEach(t =>
                    //{
                    foreach (var t in transfersResult.Value)
                    {
                        t.Currency = contractType;

                        if (localAddress.Address == t.From)
                        {
                            t.Type |= BlockchainTransactionType.Output;
                        }
                        else
                        {
                            var fromAddress = await _tezosAccount
                                              .GetAddressAsync(t.From, cancellationToken)
                                              .ConfigureAwait(false);

                            if (fromAddress != null)
                            {
                                t.Type |= BlockchainTransactionType.Output;
                            }
                        }

                        if (localAddress.Address == t.To)
                        {
                            t.Type |= BlockchainTransactionType.Input;
                        }
                        else
                        {
                            var toAddress = await _tezosAccount
                                            .GetAddressAsync(t.To, cancellationToken)
                                            .ConfigureAwait(false);

                            if (toAddress != null)
                            {
                                t.Type |= BlockchainTransactionType.Input;
                            }
                        }
                    }

                    if (transfersResult?.Value?.Any() ?? false)
                    {
                        await _tezosAccount.DataRepository
                        .UpsertTezosTokenTransfersAsync(transfersResult.Value)
                        .ConfigureAwait(false);
                    }
                }
            }, cancellationToken));
        }