public Task ScanContractAsync( string address, string contractAddress, CancellationToken cancellationToken = default) { return(Task.Run(async() => { var tezosConfig = _tezosAccount.Config; var bcdSettings = tezosConfig.BcdApiSettings; var bcdApi = new BcdApi(bcdSettings); var tokenContractsResult = await bcdApi .GetTokenContractsAsync(address, cancellationToken) .ConfigureAwait(false); if (tokenContractsResult.HasError) { Log.Error($"Error while get token contracts for " + $"address: {address}. " + $"Code: {tokenContractsResult.Error.Code}. " + $"Description: {tokenContractsResult.Error.Description}."); return; } // upsert contracts if (tokenContractsResult.Value.Any()) { await _tezosAccount.DataRepository .UpsertTezosTokenContractsAsync(tokenContractsResult.Value.Values) .ConfigureAwait(false); } var contractWithMetadata = tokenContractsResult.Value.TryGetValue(contractAddress, out var contract) ? contract : null; await ScanContractAsync( address, contractAddress, contractWithMetadata, cancellationToken) .ConfigureAwait(false); }, cancellationToken)); }
public Task ScanAsync( string address, CancellationToken cancellationToken = default) { return(Task.Run(async() => { var tezosConfig = _tezosAccount.Config; var bcdSettings = tezosConfig.BcdApiSettings; var bcdApi = new BcdApi(bcdSettings); var tokenContractsResult = await bcdApi .GetTokenContractsAsync(address, cancellationToken) .ConfigureAwait(false); if (tokenContractsResult.HasError) { Log.Error($"Error while get token contracts for " + $"address: {address}. " + $"Code: {tokenContractsResult.Error.Code}. " + $"Description: {tokenContractsResult.Error.Description}."); return; } // upsert contracts if (tokenContractsResult.Value.Any()) { await _tezosAccount.DataRepository .UpsertTezosTokenContractsAsync(tokenContractsResult.Value.Values) .ConfigureAwait(false); } // contracts from local db var contracts = (await _tezosAccount.DataRepository .GetTezosTokenAddressesAsync(address) .ConfigureAwait(false)) .Select(a => a.TokenBalance.Contract) .ToList(); // add contracts from network if (tokenContractsResult.Value.Any()) { contracts.AddRange(tokenContractsResult.Value.Keys); } contracts = contracts .Distinct() .ToList(); // scan by address and contract foreach (var contractAddress in contracts) { var contractWithMetadata = tokenContractsResult.Value.TryGetValue(contractAddress, out var contract) ? contract : null; await ScanContractAsync( address, contractAddress, contractWithMetadata, cancellationToken) .ConfigureAwait(false); } }, cancellationToken)); }
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)); }
public static Task <IEnumerable <BalanceError> > CheckBalancesAsync( IAccount account, IEnumerable <WalletAddress> addresses, CancellationToken cancellationToken = default) { return(Task.Run(async() => { try { var errors = new List <BalanceError>(); foreach (var address in addresses) { var actualBalance = 0m; if (Currencies.IsTezosToken(address.Currency)) { // tezos tokens var xtzConfig = account.Currencies.Get <TezosConfig>("XTZ"); var fa12Config = account.Currencies.Get <Fa12Config>(address.Currency); var bcdSettings = xtzConfig.BcdApiSettings; var bcdApi = new BcdApi(bcdSettings); var balanceResult = await bcdApi .GetTokenBalancesAsync( address: address.Address, contractAddress: fa12Config.TokenContractAddress, cancellationToken: cancellationToken) .ConfigureAwait(false); if (balanceResult == null || balanceResult.HasError || balanceResult.Value == null || !balanceResult.Value.Any()) { errors.Add(new BalanceError { Type = BalanceErrorType.FailedToGet, Address = address.Address, LocalBalance = address.AvailableBalance(), }); continue; } actualBalance = balanceResult.Value.First().GetTokenBalance(); } else if (Currencies.IsEthereumToken(address.Currency)) { // ethereum tokens var erc20 = account.Currencies .Get <Erc20Config>(address.Currency); var api = erc20.BlockchainApi as IEthereumBlockchainApi; var balanceResult = await api .TryGetErc20BalanceAsync( address: address.Address, contractAddress: erc20.ERC20ContractAddress, cancellationToken: cancellationToken) .ConfigureAwait(false); if (balanceResult == null || balanceResult.HasError) { errors.Add(new BalanceError { Type = BalanceErrorType.FailedToGet, Address = address.Address, LocalBalance = address.AvailableBalance(), }); continue; } actualBalance = erc20.TokenDigitsToTokens(balanceResult.Value); } else { var api = account.Currencies .GetByName(address.Currency) .BlockchainApi; var balanceResult = await api .TryGetBalanceAsync( address: address.Address, cancellationToken: cancellationToken) .ConfigureAwait(false); if (balanceResult == null || balanceResult.HasError) { errors.Add(new BalanceError { Type = BalanceErrorType.FailedToGet, Address = address.Address, LocalBalance = address.AvailableBalance(), }); continue; } actualBalance = balanceResult.Value; } if (actualBalance < address.AvailableBalance()) { errors.Add(new BalanceError { Type = BalanceErrorType.LessThanExpected, Address = address.Address, LocalBalance = address.AvailableBalance(), ActualBalance = actualBalance }); } else if (actualBalance > address.AvailableBalance() && Currencies.IsBitcoinBased(address.Currency)) { errors.Add(new BalanceError { Type = BalanceErrorType.MoreThanExpected, Address = address.Address, LocalBalance = address.AvailableBalance(), ActualBalance = actualBalance }); } } return errors; } catch (Exception e) { Log.Error(e, "Balance check error"); } return addresses.Select(a => new BalanceError { Type = BalanceErrorType.FailedToGet, Address = a.Address, LocalBalance = a.AvailableBalance() }); }, cancellationToken)); }