示例#1
0
        public async void GetRedeemedEventTest()
        {
            var api = new EtherScanApi(Common.EthTestNet);

            var eventSignatureHash = EventSignatureExtractor.GetSignatureHash <RedeemedEventDTO>();

            var eventsAsyncResult = await api.GetContractEventsAsync(
                address : "0x527d1049837edf5f99c287a41a87702686082bf8",
                fromBlock : Common.EthTestNet.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : eventSignatureHash,
                topic1 : "0x7ca4344b5d8e624917b6b0cee015bab65397349062ec2fcdbaebc25d5e1cbb4d")
                                    .ConfigureAwait(false);

            Assert.False(eventsAsyncResult.HasError);

            var events = eventsAsyncResult.Value?.ToList();

            Assert.NotNull(events);
            Assert.Single(events);
            Assert.True(events.First().IsRedeemedEvent());

            var redeemedEvent = events.First().ParseRedeemedEvent();

            Assert.NotNull(redeemedEvent);
            Assert.Equal("7ca4344b5d8e624917b6b0cee015bab65397349062ec2fcdbaebc25d5e1cbb4d", redeemedEvent.HashedSecret.ToHexString());
            Assert.Equal("f53c77f812c16243d3cdffc48fc4fd5a1f36541db9409b112a0e56436fc7fa35", redeemedEvent.Secret.ToHexString());
        }
示例#2
0
        public async void GetAddedEventTest()
        {
            var api = new EtherScanApi(Common.EthTestNet);

            var eventSignatureHash = EventSignatureExtractor.GetSignatureHash <AddedEventDTO>();

            var eventsAsyncResult = await api.GetContractEventsAsync(
                address : "0x527d1049837edf5f99c287a41a87702686082bf8",
                fromBlock : Common.EthTestNet.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : eventSignatureHash,
                topic1 : "0xbe51acca480dba043159355d597e39744ad7140d325f6cb3c1554db6b33947d6")
                                    .ConfigureAwait(false);

            Assert.False(eventsAsyncResult.HasError);

            var events = eventsAsyncResult.Value?.ToList();

            Assert.NotNull(events);
            Assert.Single(events);
            Assert.True(events.First().IsAddedEvent());

            var addedEvent = events.First().ParseAddedEvent();

            Assert.NotNull(addedEvent);
            Assert.Equal("be51acca480dba043159355d597e39744ad7140d325f6cb3c1554db6b33947d6", addedEvent.HashedSecret.ToHexString());
            Assert.Equal("0xe4aec93f3c0807b66b3fd043623e21dbbb0a3a82", addedEvent.Initiator);
            Assert.Equal(289637150000000000, (long)addedEvent.Value);
        }
示例#3
0
        public async void GetInitiatedEventTest()
        {
            var api = new EtherScanApi(Common.EthTestNet);

            var eventSignatureHash = EventSignatureExtractor.GetSignatureHash <InitiatedEventDTO>();

            var eventsAsyncResult = await api.GetContractEventsAsync(
                address : "0x527d1049837edf5f99c287a41a87702686082bf8",
                fromBlock : Common.EthTestNet.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : eventSignatureHash,
                topic1 : "0x87639bcb4d5e61e52398acb13181ddec825744f8fd90a3f8efa68c129a968d0f")
                                    .ConfigureAwait(false);

            Assert.False(eventsAsyncResult.HasError);

            var events = eventsAsyncResult.Value?.ToList();

            Assert.NotNull(events);
            Assert.Single(events);
            Assert.True(events.First().IsInitiatedEvent());

            var initiatedEvent = events.First().ParseInitiatedEvent();

            Assert.NotNull(initiatedEvent);
            Assert.Equal("87639bcb4d5e61e52398acb13181ddec825744f8fd90a3f8efa68c129a968d0f", initiatedEvent.HashedSecret.ToHexString());
            Assert.Equal("0xcd38a31acf4db224a20052acb1934c993680b3e2", initiatedEvent.Participant);
            Assert.Equal("0x981c7251a11a1614d4c70c0f3507bbda54808065", initiatedEvent.Initiator);
            Assert.Equal(1569198635, (long)initiatedEvent.RefundTimestamp);
            Assert.Equal(27645001080510000, (long)initiatedEvent.Value);
            Assert.Equal(0, (long)initiatedEvent.RedeemFee);
        }
示例#4
0
        public async void GetBalanceAsyncTest()
        {
            var api = new EtherScanApi(Common.EthTestNet, Chain.Ropsten);

            var balance = await api
                          .GetBalanceAsync("0xe4aec93f3c0807b66b3fd043623e21dbbb0a3a82")
                          .ConfigureAwait(false);
        }
示例#5
0
        public async void GetBalanceAsyncTest()
        {
            var api = new EtherScanApi(Common.EthTestNet);

            var asyncResult = await api
                              .GetBalanceAsync("0xe4aec93f3c0807b66b3fd043623e21dbbb0a3a82")
                              .ConfigureAwait(false);

            Assert.False(asyncResult.HasError);
        }
示例#6
0
        public static async Task <BigInteger> GetTransferValue(
            Currency currency,
            string from,
            string to,
            string blockNumber,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum ERC20: check transfer event");

                var erc20 = (EthereumTokens.ERC20)currency;

                var api = new EtherScanApi(erc20);

                var transferEventsResult = await api
                                           .GetContractEventsAsync(
                    address : erc20.ERC20ContractAddress,
                    fromBlock : (ulong)new HexBigInteger(blockNumber).Value,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <ERC20TransferEventDTO>(),
                    topic1 : "0x000000000000000000000000" + from,
                    topic2 : "0x000000000000000000000000" + to,
                    cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                if (transferEventsResult == null)
                {
                    return(0);
                }

                if (transferEventsResult.HasError)
                {
                    return(0);
                }

                var events = transferEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(0);
                }

                var transferEvent = events.Last().ParseERC20TransferEvent(); //todo: get last

                return(transferEvent.Value);
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum ERC20 get transfer value task error");

                return(0);
            }
        }
示例#7
0
        public static async Task <Result <byte[]> > IsRedeemedAsync(
            Swap swap,
            Currency currency,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum: check redeem event");

                var ethereum = (Atomex.Ethereum)currency;

                var api = new EtherScanApi(ethereum);

                var redeemEventsResult = await api
                                         .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <RedeemedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                         .ConfigureAwait(false);

                if (redeemEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} redeem events"));
                }

                if (redeemEventsResult.HasError)
                {
                    return(redeemEventsResult.Error);
                }

                var events = redeemEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return((byte[])null);
                }

                var secret = events.First().ParseRedeemedEvent().Secret;

                Log.Debug("Redeem event received with secret {@secret}", Convert.ToBase64String(secret));

                return(secret);
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum redeem control task error");

                return(new Error(Errors.InternalError, e.Message));
            }
        }
        public static async Task <Result <bool> > IsRefundedAsync(
            Swap swap,
            CurrencyConfig currency,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum: check refund event");

                var ethereum = (Atomex.EthereumConfig)currency;

                var api = new EtherScanApi(ethereum);

                var refundEventsResult = await api
                                         .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <RefundedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                         .ConfigureAwait(false);

                if (refundEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} refund event"));
                }

                if (refundEventsResult.HasError)
                {
                    return(refundEventsResult.Error);
                }

                var events = refundEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                Log.Debug("Refund event received for swap {@swap}", swap.Id);

                return(true);
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum refund control task error");

                return(new Error(Errors.InternalError, e.Message));
            }
        }
示例#9
0
        public static async Task <Result <bool> > IsInitiatedAsync(
            Swap swap,
            Currency currency,
            long lockTimeInSec,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum ERC20: check initiated event");

                var erc20 = (EthereumTokens.ERC20)currency;

                var side = swap.Symbol
                           .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                           .Opposite();

                var refundTimeStamp                   = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSec)).ToUnixTimeSeconds();
                var requiredAmountInERC20             = AmountHelper.QtyToAmount(side, swap.Qty, swap.Price, erc20.DigitsMultiplier);
                var requiredAmountInDecimals          = erc20.TokensToTokenDigits(requiredAmountInERC20);
                var receivedAmountInDecimals          = new BigInteger(0);
                var requiredRewardForRedeemInDecimals = swap.IsAcceptor
                    ? erc20.TokensToTokenDigits(swap.RewardForRedeem)
                    : 0;

                var api = new EtherScanApi(erc20);

                var initiateEventsResult = await api
                                           .GetContractEventsAsync(
                    address : erc20.SwapContractAddress,
                    fromBlock : erc20.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <ERC20InitiatedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    topic2 : "0x000000000000000000000000" + erc20.ERC20ContractAddress.Substring(2),     //??
                    topic3 : "0x000000000000000000000000" + swap.ToAddress.Substring(2),
                    cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                if (initiateEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while trying to get contract {erc20.SwapContractAddress} initiate event"));
                }

                if (initiateEventsResult.HasError)
                {
                    return(initiateEventsResult.Error);
                }

                var events = initiateEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                var contractInitEvent = events.Last();

                var initiatedEvent = contractInitEvent.ParseERC20InitiatedEvent();

                if (initiatedEvent.RefundTimestamp != refundTimeStamp)
                {
                    Log.Debug(
                        "Invalid refund time in initiated event. Expected value is {@expected}, actual is {@actual}",
                        refundTimeStamp,
                        (long)initiatedEvent.RefundTimestamp);

                    return(new Error(
                               code: Errors.InvalidRefundLockTime,
                               description: $"Invalid refund time in initiated event. Expected value is {refundTimeStamp}, actual is {(long)initiatedEvent.RefundTimestamp}"));
                }

                if (initiatedEvent.Countdown != lockTimeInSec)  //todo: use it
                {
                    Log.Debug(
                        "Invalid countdown in initiated event. Expected value is {@expected}, actual is {@actual}",
                        lockTimeInSec,
                        (long)initiatedEvent.Countdown);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid countdown in initiated event. Expected value is {lockTimeInSec}, actual is {(long)initiatedEvent.Countdown}"));
                }

                if (initiatedEvent.RedeemFee != requiredRewardForRedeemInDecimals)
                {
                    Log.Debug(
                        "Invalid redeem fee in initiated event. Expected value is {@expected}, actual is {@actual}",
                        requiredRewardForRedeemInDecimals,
                        (long)initiatedEvent.RedeemFee);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid redeem fee in initiated event. Expected value is {requiredRewardForRedeemInDecimals}, actual is {(long)initiatedEvent.RedeemFee}"));
                }

                if (!initiatedEvent.Active)
                {
                    Log.Debug(
                        "Invalid active value in initiated event. Expected value is {@expected}, actual is {@actual}",
                        true,
                        initiatedEvent.Active);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid active value in initiated event. Expected value is {true}, actual is {initiatedEvent.Active}"));
                }

                var erc20TransferValue = await GetTransferValue(
                    currency : currency,
                    from : initiatedEvent.Initiator.Substring(2),
                    to : erc20.SwapContractAddress.Substring(2),
                    blockNumber : contractInitEvent.HexBlockNumber,
                    cancellationToken : cancellationToken)
                                         .ConfigureAwait(false);

                if (erc20TransferValue != initiatedEvent.Value + initiatedEvent.RedeemFee)
                {
                    Log.Debug(
                        "Invalid transfer value in erc20 initiated event. Expected value is {@expected}, actual is {@actual}",
                        initiatedEvent.Value,
                        erc20TransferValue);

                    return(new Error(
                               code: Errors.InvalidSwapPaymentTx,
                               description: $"Invalid transfer value in erc20 initiated event. Expected value is {initiatedEvent.Value}, actual is {initiatedEvent.Active}"));
                }

                receivedAmountInDecimals = initiatedEvent.Value;

                if (receivedAmountInDecimals >= requiredAmountInDecimals - requiredRewardForRedeemInDecimals)
                {
                    return(true);
                }

                Log.Debug(
                    "Ethereum ERC20 value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                    (decimal)(requiredAmountInDecimals - requiredRewardForRedeemInDecimals),
                    (decimal)initiatedEvent.Value);

                var addEventsResult = await api
                                      .GetContractEventsAsync(
                    address : erc20.SwapContractAddress,
                    fromBlock : erc20.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <ERC20AddedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                if (addEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while trying to get contract {erc20.SwapContractAddress} add event"));
                }

                if (addEventsResult.HasError)
                {
                    return(addEventsResult.Error);
                }

                events = addEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                foreach (var @event in events.Select(e => e.ParseERC20AddedEvent()))
                {
                    erc20TransferValue = await GetTransferValue(
                        currency : currency,
                        from : @event.Initiator.Substring(2),
                        to : erc20.SwapContractAddress.Substring(2),
                        blockNumber : contractInitEvent.HexBlockNumber,
                        cancellationToken : cancellationToken)
                                         .ConfigureAwait(false);

                    if (erc20TransferValue != @event.Value - receivedAmountInDecimals)
                    {
                        Log.Debug(
                            "Invalid transfer value in added event. Expected value is {@expected}, actual is {@actual}",
                            @event.Value - receivedAmountInDecimals,
                            erc20TransferValue);

                        return(new Error(
                                   code: Errors.InvalidSwapPaymentTx,
                                   description: $"Invalid transfer value in initiated event. Expected value is {@event.Value - receivedAmountInDecimals}, actual is {erc20TransferValue}"));
                    }

                    receivedAmountInDecimals = @event.Value;

                    if (receivedAmountInDecimals >= requiredAmountInDecimals - requiredRewardForRedeemInDecimals)
                    {
                        return(true);
                    }

                    Log.Debug(
                        "Ethereum ERC20 value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                        requiredAmountInDecimals - requiredRewardForRedeemInDecimals,
                        (long)@event.Value);
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum ERC20 swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }

            return(false);
        }
        public async Task ScanAsync(
            bool skipUsed = false,
            CancellationToken cancellationToken = default)
        {
            var currency = Currency;

            var scanParams = new[]
            {
//               new {Chain = HdKeyStorage.NonHdKeysChain, LookAhead = 0},
                new { Chain = Bip44.Internal, LookAhead = InternalLookAhead },
                new { Chain = Bip44.External, LookAhead = ExternalLookAhead },
            };

            var txs = new List <EthereumTransaction>();

            var api = new EtherScanApi(currency);

            var lastBlockNumberResult = await api
                                        .GetBlockNumber()
                                        .ConfigureAwait(false);

            if (lastBlockNumberResult.HasError)
            {
                Log.Error(
                    "Error while getting last block number with code {@code} and description {@description}",
                    lastBlockNumberResult.Error.Code,
                    lastBlockNumberResult.Error.Description);

                return;
            }

            var lastBlockNumber = lastBlockNumberResult.Value;

            if (lastBlockNumber <= 0)
            {
                Log.Error("Error in block number {@lastBlockNumber}", lastBlockNumber);
                return;
            }

            foreach (var param in scanParams)
            {
                var freeKeysCount = 0;
                var index         = 0u;

                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    var walletAddress = await Account
                                        .DivideAddressAsync(
                        account : Bip44.DefaultAccount,
                        chain : param.Chain,
                        index : index,
                        keyType : CurrencyConfig.StandardKey)
                                        .ConfigureAwait(false);

                    if (walletAddress == null)
                    {
                        break;
                    }

                    Log.Debug(
                        "Scan transactions for {@name} address {@chain}:{@index}:{@address}",
                        currency.Name,
                        param.Chain,
                        index,
                        walletAddress.Address);

                    var events = await GetERC20EventsAsync(walletAddress.Address, cancellationToken);

                    if (events == null || !events.Any())
                    {
                        var ethereumAddress = await EthereumAccount
                                              .GetAddressAsync(walletAddress.Address, cancellationToken)
                                              .ConfigureAwait(false);

                        if (ethereumAddress != null && ethereumAddress.HasActivity)
                        {
                            freeKeysCount = 0;
                            index++;
                            continue;
                        }

                        freeKeysCount++;

                        if (freeKeysCount >= param.LookAhead)
                        {
                            Log.Debug("{@lookAhead} free keys found. Chain scan completed", param.LookAhead);
                            break;
                        }
                    }
                    else // address has activity
                    {
                        freeKeysCount = 0;

                        foreach (var ev in events)
                        {
                            var tx = new EthereumTransaction();

                            if (ev.IsERC20ApprovalEvent())
                            {
                                tx = ev.TransformApprovalEvent(currency, lastBlockNumber);
                            }
                            else if (ev.IsERC20TransferEvent())
                            {
                                tx = ev.TransformTransferEvent(walletAddress.Address, currency, lastBlockNumber);
                            }

                            if (tx != null)
                            {
                                txs.Add(tx);
                            }
                        }
                    }

                    index++;
                }
            }

            if (txs.Any())
            {
                await UpsertTransactionsAsync(txs)
                .ConfigureAwait(false);
            }

            await Account
            .UpdateBalanceAsync(cancellationToken : cancellationToken)
            .ConfigureAwait(false);
        }
        private async Task <List <ContractEvent> > GetERC20EventsAsync(
            string address,
            CancellationToken cancellationToken = default)
        {
            var currency = Currency;
            var api      = new EtherScanApi(currency);

            var approveEventsResult = await api
                                      .GetContractEventsAsync(
                address : currency.ERC20ContractAddress,
                fromBlock : currency.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : EventSignatureExtractor.GetSignatureHash <ERC20ApprovalEventDTO>(),
                topic1 : "0x000000000000000000000000" + address.Substring(2),
                topic2 : "0x000000000000000000000000" + currency.SwapContractAddress.Substring(2),
                cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

            if (approveEventsResult == null)
            {
                Log.Error("Connection error while get approve events");
                return(null);
            }

            if (approveEventsResult.HasError)
            {
                Log.Error(
                    "Error while scan address transactions for {@address} with code {@code} and description {@description}",
                    address,
                    approveEventsResult.Error.Code,
                    approveEventsResult.Error.Description);

                return(null);
            }

            var outEventsResult = await api
                                  .GetContractEventsAsync(
                address : currency.ERC20ContractAddress,
                fromBlock : currency.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : EventSignatureExtractor.GetSignatureHash <ERC20TransferEventDTO>(),
                topic1 : "0x000000000000000000000000" + address.Substring(2),
                topic2 : null,
                cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (outEventsResult == null)
            {
                Log.Error("Connection error while get output events");
                return(null);
            }

            if (outEventsResult.HasError)
            {
                Log.Error(
                    "Error while scan address transactions for {@address} with code {@code} and description {@description}",
                    address,
                    outEventsResult.Error.Code,
                    outEventsResult.Error.Description);

                return(null);
            }

            var inEventsResult = await api
                                 .GetContractEventsAsync(
                address : currency.ERC20ContractAddress,
                fromBlock : currency.SwapContractBlockNumber,
                toBlock : ulong.MaxValue,
                topic0 : EventSignatureExtractor.GetSignatureHash <ERC20TransferEventDTO>(),
                topic1 : null,
                topic2 : "0x000000000000000000000000" + address.Substring(2),
                cancellationToken : cancellationToken)
                                 .ConfigureAwait(false);

            if (inEventsResult == null)
            {
                Log.Error("Connection error while get input events");
                return(null);
            }

            if (inEventsResult.HasError)
            {
                Log.Error(
                    "Error while scan address transactions for {@address} with code {@code} and description {@description}",
                    address,
                    inEventsResult.Error.Code,
                    inEventsResult.Error.Description);

                return(null);
            }

            var events = approveEventsResult.Value?
                         .Concat(outEventsResult.Value?.Concat(inEventsResult.Value))
                         .ToList();

            if (events == null || !events.Any()) // address without activity
            {
                return(null);
            }

            return(events);
        }
        public async Task ScanAsync(
            string address,
            CancellationToken cancellationToken = default)
        {
            var currency = Currency;

            Log.Debug("Scan transactions for {@currency} address {@address}",
                      Currency.Name,
                      address);

            var txs = new List <EthereumTransaction>();
            var api = new EtherScanApi(currency);

            var lastBlockNumberResult = await api
                                        .GetBlockNumber()
                                        .ConfigureAwait(false);

            if (lastBlockNumberResult == null)
            {
                Log.Error("Connection error while get block number");
                return;
            }

            if (lastBlockNumberResult.HasError)
            {
                Log.Error(
                    "Error while getting last block number with code {@code} and description {@description}",
                    lastBlockNumberResult.Error.Code,
                    lastBlockNumberResult.Error.Description);

                return;
            }

            var lastBlockNumber = lastBlockNumberResult.Value;

            if (lastBlockNumber <= 0)
            {
                Log.Error(
                    "Error in block number {@lastBlockNumber}",
                    lastBlockNumber);

                return;
            }

            var events = await GetERC20EventsAsync(address, cancellationToken)
                         .ConfigureAwait(false);

            if (events == null || !events.Any()) // address without activity
            {
                return;
            }

            foreach (var ev in events)
            {
                var tx = new EthereumTransaction();

                if (ev.IsERC20ApprovalEvent())
                {
                    tx = ev.TransformApprovalEvent(currency, lastBlockNumber);
                }
                else if (ev.IsERC20TransferEvent())
                {
                    tx = ev.TransformTransferEvent(address, currency, lastBlockNumber);
                }

                if (tx != null)
                {
                    txs.Add(tx);
                }
            }

            if (txs.Any())
            {
                await UpsertTransactionsAsync(txs)
                .ConfigureAwait(false);
            }

            await Account
            .UpdateBalanceAsync(address : address, cancellationToken : cancellationToken)
            .ConfigureAwait(false);
        }
        public static async Task <Result <bool> > IsInitiatedAsync(
            Swap swap,
            Currency currency,
            long refundTimeStamp,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum: check initiated event");

                var ethereum = (Atomex.Ethereum)currency;

                var sideOpposite = swap.Symbol
                                   .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                                   .Opposite();

                var requiredAmountInEth          = AmountHelper.QtyToAmount(sideOpposite, swap.Qty, swap.Price, ethereum.DigitsMultiplier);
                var requiredAmountInWei          = Atomex.Ethereum.EthToWei(requiredAmountInEth);
                var requiredRewardForRedeemInWei = Atomex.Ethereum.EthToWei(swap.RewardForRedeem);


                var api = new EtherScanApi(ethereum);

                var initiateEventsResult = await api
                                           .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <InitiatedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    topic2 : "0x000000000000000000000000" + swap.ToAddress.Substring(2),
                    cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                if (initiateEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} initiate event"));
                }

                if (initiateEventsResult.HasError)
                {
                    return(initiateEventsResult.Error);
                }

                var events = initiateEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                var initiatedEvent = events.First().ParseInitiatedEvent();

                if (initiatedEvent.Value >= requiredAmountInWei - requiredRewardForRedeemInWei)
                {
                    if (initiatedEvent.RefundTimestamp != refundTimeStamp)
                    {
                        Log.Debug(
                            "Invalid refund time in initiated event. Expected value is {@expected}, actual is {@actual}",
                            refundTimeStamp,
                            (long)initiatedEvent.RefundTimestamp);

                        return(new Error(
                                   code: Errors.InvalidRefundLockTime,
                                   description: $"Invalid refund time in initiated event. Expected value is {refundTimeStamp}, actual is {(long)initiatedEvent.RefundTimestamp}"));
                    }

                    if (swap.IsAcceptor)
                    {
                        if (initiatedEvent.RedeemFee != requiredRewardForRedeemInWei)
                        {
                            Log.Debug(
                                "Invalid redeem fee in initiated event. Expected value is {@expected}, actual is {@actual}",
                                requiredRewardForRedeemInWei,
                                (long)initiatedEvent.RedeemFee);

                            return(new Error(
                                       code: Errors.InvalidRewardForRedeem,
                                       description: $"Invalid redeem fee in initiated event. Expected value is {requiredRewardForRedeemInWei}, actual is {(long)initiatedEvent.RedeemFee}"));
                        }
                    }

                    return(true);
                }

                Log.Debug(
                    "Eth value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                    (decimal)(requiredAmountInWei - requiredRewardForRedeemInWei),
                    (decimal)initiatedEvent.Value);

                var addEventsResult = await api
                                      .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <AddedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                if (addEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} add event"));
                }

                if (addEventsResult.HasError)
                {
                    return(addEventsResult.Error);
                }

                events = addEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                foreach (var @event in events.Select(e => e.ParseAddedEvent()))
                {
                    if (@event.Value >= requiredAmountInWei - requiredRewardForRedeemInWei)
                    {
                        return(true);
                    }

                    Log.Debug(
                        "Eth value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                        requiredAmountInWei - requiredRewardForRedeemInWei,
                        (long)@event.Value);
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }

            return(false);
        }