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));
        }
示例#4
0
        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));
        }