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)); }