Exemplo n.º 1
0
        private async Task BroadcastTxAsync(
            ClientSwap swap,
            EthereumTransaction tx,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var txId = await Eth.BlockchainApi
                       .BroadcastAsync(tx, cancellationToken)
                       .ConfigureAwait(false);

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            // account new unconfirmed transaction
            await Account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : true,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            // todo: transaction receipt status control
        }
Exemplo n.º 2
0
        private async Task BroadcastTxAsync(
            Swap swap,
            EthereumTransaction tx,
            CancellationToken cancellationToken = default,
            bool updateBalance          = true,
            bool notifyIfUnconfirmed    = true,
            bool notifyIfBalanceUpdated = true)
        {
            var broadcastResult = await Erc20.BlockchainApi
                                  .BroadcastAsync(tx, cancellationToken)
                                  .ConfigureAwait(false);

            if (broadcastResult.HasError)
            {
                throw new InternalException(broadcastResult.Error);
            }

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            if (tx.Type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                tx = tx.ParseERC20Input();
            }

            // account new unconfirmed transaction
            await Erc20Account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : updateBalance,
                notifyIfUnconfirmed : notifyIfUnconfirmed,
                notifyIfBalanceUpdated : notifyIfBalanceUpdated,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            var ethTx = tx.Clone();

            ethTx.Currency = Eth;
            ethTx.Amount   = 0;
            ethTx.Type     = BlockchainTransactionType.Output | (ethTx.Type.HasFlag(BlockchainTransactionType.TokenApprove)
                ? BlockchainTransactionType.TokenCall
                : BlockchainTransactionType.SwapCall);

            await EthereumAccount
            .UpsertTransactionAsync(
                tx : ethTx,
                updateBalance : updateBalance,
                notifyIfUnconfirmed : notifyIfUnconfirmed,
                notifyIfBalanceUpdated : notifyIfBalanceUpdated,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            // todo: transaction receipt status control
        }
Exemplo n.º 3
0
        private async Task BroadcastTxAsync(
            Swap swap,
            EthereumTransaction tx,
            CancellationToken cancellationToken = default)
        {
            var broadcastResult = await EthConfig.BlockchainApi
                                  .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (broadcastResult.HasError)
            {
                throw new InternalException(broadcastResult.Error);
            }

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            // account new unconfirmed transaction
            await _account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : true,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);
        }
        public static decimal GetAmount(
            EthereumTransaction tx,
            Erc20Config erc20Config)
        {
            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.SwapRedeem) ||
                tx.Type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                result += erc20Config.TokenDigitsToTokens(tx.Amount);
            }
            else
            {
                if (tx.Type.HasFlag(BlockchainTransactionType.Input))
                {
                    result += erc20Config.TokenDigitsToTokens(tx.Amount);
                }
                if (tx.Type.HasFlag(BlockchainTransactionType.Output))
                {
                    result += -erc20Config.TokenDigitsToTokens(tx.Amount);
                }
            }

            tx.InternalTxs?.ForEach(t => result += GetAmount(t, erc20Config));

            return(result);
        }
Exemplo n.º 5
0
        public static decimal GetAmount(EthereumTransaction tx)
        {
            var Erc20 = tx.Currency as EthereumTokens.ERC20;

            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.SwapRedeem) ||
                tx.Type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                result += Erc20.TokenDigitsToTokens(tx.Amount);
            }
            else
            {
                if (tx.Type.HasFlag(BlockchainTransactionType.Input))
                {
                    result += Erc20.TokenDigitsToTokens(tx.Amount);
                }
                if (tx.Type.HasFlag(BlockchainTransactionType.Output))
                {
                    result += -Erc20.TokenDigitsToTokens(tx.Amount);
                }
            }

            tx.InternalTxs?.ForEach(t => result += GetAmount(t));

            return(result);
        }
Exemplo n.º 6
0
        public static EthereumTransaction ParseERC20TransactionType(
            this EthereumTransaction transaction)
        {
            if (transaction.Input == "0x")
            {
                return(transaction);
            }

            if (transaction.Currency.Name == "ETH")
            {
                if (transaction.IsERC20TransferTransaction() ||
                    transaction.IsERC20ApproveTransaction())
                {
                    transaction.Type |= BlockchainTransactionType.TokenCall;
                }
                else if (transaction.IsERC20InitiateTransaction() ||
                         transaction.IsERC20RedeemTransaction() ||
                         transaction.IsERC20RefundTransaction())
                {
                    transaction.Type |= BlockchainTransactionType.SwapCall;
                }
            }

            return(transaction);
        }
Exemplo n.º 7
0
        public static EthereumTransaction ParseERC20AddInput(
            this EthereumTransaction transaction)
        {
            var input = transaction.Input.Substring(transaction.Input.Length % InputItemSizeInHex);

            transaction.Amount = new HexBigInteger(input.Substring(InputItemSizeInHex * 1, InputItemSizeInHex)).Value;

            return(transaction);
        }
Exemplo n.º 8
0
        public override async Task PartyRedeemAsync(ClientSwap swap)
        {
            Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id);

            var walletAddress = (await Account
                                 .GetUnspentAddressesAsync(
                                     currency: Currency,
                                     amount: 0,
                                     fee: Eth.RedeemGasLimit,
                                     feePrice: Eth.GasPriceInGwei,
                                     isFeePerTransaction: false,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

            if (walletAddress == null)
            {
                Log.Error("Insufficient balance for party redeem. Cannot find the address containing the required amount of funds.");
                return;
            }

            var nonce = await EthereumNonceManager.Instance
                        .GetNonce(Eth, walletAddress.Address)
                        .ConfigureAwait(false);

            var message = new RedeemFunctionMessage
            {
                FromAddress  = walletAddress.Address,
                HashedSecret = swap.SecretHash,
                Secret       = swap.Secret,
                Nonce        = nonce,
                GasPrice     = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei),
            };

            message.Gas = await EstimateGasAsync(message, new BigInteger(Eth.RedeemGasLimit))
                          .ConfigureAwait(false);

            var txInput = message.CreateTransactionInput(Eth.SwapContractAddress);

            var redeemTx = new EthereumTransaction(Eth, txInput)
            {
                Type = EthereumTransaction.OutputTransaction
            };

            var signResult = await SignTransactionAsync(redeemTx)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            await BroadcastTxAsync(swap, redeemTx)
            .ConfigureAwait(false);
        }
Exemplo n.º 9
0
        public static EthereumTransaction ParseERC20TransferInput(
            this EthereumTransaction transaction)
        {
            var input = transaction.Input.Substring(transaction.Input.Length % InputItemSizeInHex);

            transaction.To     = $"0x{input.Substring(InputItemSizeInHex - AddressLengthInHex, AddressLengthInHex)}";
            transaction.Amount = new HexBigInteger(input.Substring(InputItemSizeInHex, InputItemSizeInHex)).Value;

            return(transaction);
        }
Exemplo n.º 10
0
        public async Task <long> StartAsync(string hash, long userId, EthereumTransactionEntityType entityType, long entityId, EthereumTransactionType transactionType)
        {
            var transaction = new EthereumTransaction(userId, hash, _clock.UtcNow, entityType, entityId, transactionType);

            _repository.Add(transaction);

            await _repository.SaveChangesAsync();

            return(transaction.Id);
        }
Exemplo n.º 11
0
 public EthereumTransactionViewModel(EthereumTransaction tx, EthereumConfig ethereumConfig)
     : base(tx, ethereumConfig, GetAmount(tx), GetFee(tx))
 {
     From       = tx.From;
     To         = tx.To;
     GasPrice   = EthereumConfig.WeiToGwei((decimal)tx.GasPrice);
     GasLimit   = (decimal)tx.GasLimit;
     GasUsed    = (decimal)tx.GasUsed;
     Fee        = EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice);
     IsInternal = tx.IsInternal;
 }
        public void MapTransactionToDton_EmptySource_ShouldSucceed()
        {
            var mapper = new EthereumTransactionMapper();
            var source = new EthereumTransaction();

            // act
            var actual = mapper.Map(source);

            // assert
            Assert.NotNull(actual);
        }
 public EthereumERC20TransactionViewModel(
     EthereumTransaction tx,
     Erc20Config erc20Config)
     : base(tx, erc20Config, GetAmount(tx, erc20Config), 0)
 {
     From       = tx.From;
     To         = tx.To;
     GasPrice   = EthereumConfig.WeiToGwei((decimal)tx.GasPrice);
     GasLimit   = (decimal)tx.GasLimit;
     GasUsed    = (decimal)tx.GasUsed;
     IsInternal = tx.IsInternal;
 }
Exemplo n.º 14
0
        public static EthereumTransaction TransformTransferEvent(
            this EtherScanApi.ContractEvent contractEvent,
            string address,
            EthereumTokens.ERC20 erc20,
            long lastBlockNumber)
        {
            if (!contractEvent.IsERC20TransferEvent())
            {
                return(null);
            }

            var transferEvent = contractEvent.ParseERC20TransferEvent();

            var tx = new EthereumTransaction() //todo: make a refactoring
            {
                Currency = erc20,
                Id       = contractEvent.HexTransactionHash,

                Type = transferEvent.From == address
                    ? transferEvent.To == erc20.SwapContractAddress.ToLowerInvariant()   //todo: change to erc20.SwapContractAddress after currencies.json update
                        ? BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
                        : BlockchainTransactionType.Output
                    : BlockchainTransactionType.Input,               //todo: recognize redeem&refund
                State        = BlockchainTransactionState.Confirmed, //todo: check if true in 100% cases
                CreationTime = contractEvent.HexTimeStamp.Substring(PrefixOffset).FromHexString(),

                From   = transferEvent.From,
                To     = transferEvent.To,
                Amount = transferEvent.Value.ToHexBigInteger(),
                ////Nonce
                GasPrice = new HexBigInteger(contractEvent.HexGasPrice).Value,
                ////GasLimit
                GasLimit      = new HexBigInteger(contractEvent.HexGasUsed).Value,
                ReceiptStatus = true,
                IsInternal    = transferEvent.From == erc20.SwapContractAddress.ToLowerInvariant() ||
                                transferEvent.To == erc20.SwapContractAddress.ToLowerInvariant(),
                InternalIndex = 0,
                BlockInfo     = new BlockInfo
                {
                    Confirmations = 1 + (int)(lastBlockNumber - long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber)),
                    //    //Confirmations = txReceipt.Status != null
                    //    //? (int)txReceipt.Status.Value
                    //    //: 0,
                    //    //BlockHash = tx.BlockHash,
                    BlockHeight = long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber),
                    //    //BlockTime = blockTimeStamp,
                    //    //FirstSeen = blockTimeStamp
                }
            };

            return(tx);
        }
Exemplo n.º 15
0
        private static decimal GetFee(EthereumTransaction tx)
        {
            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.Output))
            {
                result += EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice);
            }

            tx.InternalTxs?.ForEach(t => result += GetFee(t));

            return(result);
        }
 public static EthereumTransactionResponse Create(EthereumTransaction transaction)
 {
     return(new EthereumTransactionResponse
     {
         Id = transaction.Id,
         UserId = transaction.UserId,
         Hash = transaction.Hash,
         EntityId = transaction.EntityId,
         EntityType = transaction.EntityType,
         TransactionType = transaction.TransactionType,
         Status = transaction.Status,
         Created = transaction.Created
     });
 }
        public void MapTransactionToDto_BlockNumber_ShouldSucceed()
        {
            var mapper = new EthereumTransactionMapper();
            var source = new EthereumTransaction
            {
                BlockNumber = "12345"
            };

            // act
            var actual = mapper.Map(source);

            // assert
            Assert.NotNull(actual);
            Assert.Equal("12345", actual.BlockNumber);
        }
Exemplo n.º 18
0
        private async Task <bool> SignTransactionAsync(
            EthereumTransaction tx,
            CancellationToken cancellationToken = default)
        {
            var walletAddress = await _account
                                .GetAddressAsync(
                address : tx.From,
                cancellationToken : cancellationToken)
                                .ConfigureAwait(false);

            return(await _account.Wallet
                   .SignAsync(
                       tx : tx,
                       address : walletAddress,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
Exemplo n.º 19
0
        public static EthereumTransaction TransformApprovalEvent(
            this EtherScanApi.ContractEvent contractEvent,
            EthereumTokens.ERC20 erc20,
            long lastBlockNumber)
        {
            if (!contractEvent.IsERC20ApprovalEvent())
            {
                return(null);
            }

            var approvalEvent = contractEvent.ParseERC20ApprovalEvent();

            var tx = new EthereumTransaction() //todo: make a refactoring
            {
                Currency     = erc20,
                Id           = contractEvent.HexTransactionHash,
                Type         = BlockchainTransactionType.Output | BlockchainTransactionType.TokenApprove,
                State        = BlockchainTransactionState.Confirmed, //todo: check if true in 100% cases
                CreationTime = contractEvent.HexTimeStamp.Substring(PrefixOffset).FromHexString(),

                From   = approvalEvent.Owner,
                To     = approvalEvent.Spender,
                Amount = 0,
                ////Nonce
                GasPrice = new HexBigInteger(contractEvent.HexGasPrice).Value,
                ////GasLimit
                GasLimit      = new HexBigInteger(contractEvent.HexGasUsed).Value,
                ReceiptStatus = true,
                IsInternal    = false,
                InternalIndex = 0,
                BlockInfo     = new BlockInfo
                {
                    Confirmations = 1 + (int)(lastBlockNumber - long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber)),
                    //    //Confirmations = txReceipt.Status != null
                    //    //? (int)txReceipt.Status.Value
                    //    //: 0,
                    //    //BlockHash = tx.BlockHash,
                    BlockHeight = long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber),
                    //    //BlockTime = blockTimeStamp,
                    //    //FirstSeen = blockTimeStamp
                }
            };

            return(tx);
        }
Exemplo n.º 20
0
        public EthereumERC20TransactionViewModel(EthereumTransaction tx)
            : base(tx, GetAmount(tx), 0)
        {
            From       = tx.From;
            To         = tx.To;
            GasPrice   = Ethereum.WeiToGwei((decimal)tx.GasPrice);
            GasLimit   = (decimal)tx.GasLimit;
            GasUsed    = (decimal)tx.GasUsed;
            IsInternal = tx.IsInternal;

            if (Amount <= 0)
            {
                Alias = tx.To;
            }

            if (Amount > 0)
            {
                Alias = tx.From;
            }
        }
        public EthereumTransactionDto Map(EthereumTransaction source)
        {
            var result = new EthereumTransactionDto();

            if (source == null)
            {
                return(result);
            }

            result.BlockHash   = source.BlockHash;
            result.BlockNumber = source.BlockNumber;
            result.Gas         = source.Gas;
            result.Hash        = source.Hash;
            result.From        = source.From;
            result.To          = source.To;
            //result.Value = Convert.ToInt64(source.Value, 16);
            result.Value = source.Value;

            return(result);
        }
Exemplo n.º 22
0
        public static EthereumTransaction ParseERC20Input(
            this EthereumTransaction transaction)
        {
            if (transaction.Input == "0x")
            {
                return(transaction);
            }

            if (transaction.IsERC20TransferTransaction())
            {
                return(transaction.ParseERC20TransferInput());
            }
            else if (transaction.IsERC20InitiateTransaction())
            {
                return(transaction.ParseERC20InitiateInput());
            }
            else if (transaction.IsERC20AddTransaction())
            {
                return(transaction.ParseERC20AddInput());
            }

            return(transaction);
        }
Exemplo n.º 23
0
        public async void AddEthereumTransactionTest()
        {
            var repository = new LiteDbAccountDataRepository(
                pathToDb: PathToDb,
                password: Password,
                currencies: Common.CurrenciesTestNet,
                network: Network.TestNet);

            var id = "abcdefgh";

            var tx = new EthereumTransaction
            {
                Id          = id,
                Currency    = Common.EthTestNet.Name,
                InternalTxs = new List <EthereumTransaction>
                {
                    new EthereumTransaction {
                        Currency = Common.EthTestNet.Name
                    }
                }
            };

            var result = await repository
                         .UpsertTransactionAsync(tx)
                         .ConfigureAwait(false);

            Assert.True(result);

            var readTx = await repository
                         .GetTransactionByIdAsync(Common.EthTestNet.Name, id, Common.EthTestNet.TransactionType)
                         .ConfigureAwait(false) as EthereumTransaction;

            Assert.NotNull(readTx);
            Assert.NotNull(readTx.InternalTxs);
            Assert.Equal(id, readTx.Id);
        }
Exemplo n.º 24
0
        public async Task <Error> SendAsync(
            string from,
            string to,
            decimal amount,
            decimal gasLimit,
            decimal gasPrice,
            bool useDefaultFee = false,
            CancellationToken cancellationToken = default)
        {
            //if (from == to)
            //    return new Error(
            //        code: Errors.SendingAndReceivingAddressesAreSame,
            //        description: "Sending and receiving addresses are the same.");

            var ethConfig = EthConfig;

            if (useDefaultFee)
            {
                gasLimit = GasLimitByType(BlockchainTransactionType.Output);

                gasPrice = Math.Floor(await ethConfig
                                      .GetGasPriceAsync(cancellationToken)
                                      .ConfigureAwait(false));
            }

            var addressFeeUsage = await CalculateFundsUsageAsync(
                from : from,
                amount : amount,
                fee : gasLimit,
                feePrice : gasPrice,
                cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (addressFeeUsage == null)
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

            if (gasLimit < ethConfig.GasLimit)
            {
                return(new Error(
                           code: Errors.InsufficientGas,
                           description: "Insufficient gas"));
            }

            Log.Debug("Try to send {@amount} ETH with fee {@fee} from address {@address} with available balance {@balance}",
                      addressFeeUsage.UsedAmount,
                      addressFeeUsage.UsedFee,
                      addressFeeUsage.WalletAddress.Address,
                      addressFeeUsage.WalletAddress.AvailableBalance());

            // lock address to prevent nonce races
            using var addressLock = await AddressLocker
                                    .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken)
                                    .ConfigureAwait(false);

            var nonceAsyncResult = await EthereumNonceManager.Instance
                                   .GetNonceAsync(ethConfig, addressFeeUsage.WalletAddress.Address)
                                   .ConfigureAwait(false);

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

            var tx = new EthereumTransaction
            {
                Currency     = ethConfig.Name,
                Type         = BlockchainTransactionType.Output,
                CreationTime = DateTime.UtcNow,
                To           = to.ToLowerInvariant(),
                Amount       = EthereumConfig.EthToWei(addressFeeUsage.UsedAmount),
                Nonce        = nonceAsyncResult.Value,
                GasPrice     = new BigInteger(EthereumConfig.GweiToWei(gasPrice)),
                GasLimit     = new BigInteger(gasLimit),
            };

            var signResult = await Wallet
                             .SignAsync(tx, addressFeeUsage.WalletAddress, ethConfig, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                return(new Error(
                           code: Errors.TransactionSigningError,
                           description: "Transaction signing error"));
            }

            if (!tx.Verify(ethConfig))
            {
                return(new Error(
                           code: Errors.TransactionVerificationError,
                           description: "Transaction verification error"));
            }

            var broadcastResult = await ethConfig.BlockchainApi
                                  .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

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

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                return(new Error(
                           code: Errors.TransactionBroadcastError,
                           description: "Transaction Id is null"));
            }

            Log.Debug("Transaction successfully sent with txId: {@id}", txId);

            await UpsertTransactionAsync(
                tx : tx,
                updateBalance : false,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : false,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            _ = UpdateBalanceAsync(cancellationToken);

            return(null);
        }
Exemplo n.º 25
0
        public override async Task RefundAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            var ethConfig = EthConfig;

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) &&
                swap.RefundTx != null &&
                swap.RefundTx.CreationTime != null &&
                swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(20) > DateTime.UtcNow)
            {
                _ = TrackTransactionConfirmationAsync(
                    swap: swap,
                    currency: ethConfig,
                    dataRepository: _account.DataRepository,
                    txId: swap.RefundTx.Id,
                    confirmationHandler: RefundConfirmedEventHandler,
                    cancellationToken: cancellationToken);

                return;
            }

            var lockTimeInSeconds = swap.IsInitiator
                ? DefaultInitiatorLockTimeInSeconds
                : DefaultAcceptorLockTimeInSeconds;

            var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds);

            await RefundTimeDelayAsync(lockTime, cancellationToken)
            .ConfigureAwait(false);

            // check swap initiation
            try
            {
                var txResult = await EthereumSwapInitiatedHelper
                               .TryToFindPaymentAsync(swap, ethConfig, cancellationToken)
                               .ConfigureAwait(false);

                if (!txResult.HasError && txResult.Value == null)
                {
                    // swap not initiated and must be canceled
                    swap.StateFlags |= SwapStateFlags.IsCanceled;

                    await UpdateSwapAsync(swap, SwapStateFlags.IsCanceled, cancellationToken)
                    .ConfigureAwait(false);

                    return;
                }
            }
            catch (Exception e)
            {
                Log.Error(e, $"Can't check {swap.Id} swap initiation for ETH");
            }

            Log.Debug("Create refund for swap {@swap}", swap.Id);

            var gasPrice = await ethConfig
                           .GetGasPriceAsync(cancellationToken)
                           .ConfigureAwait(false);

            var walletAddress = await _account
                                .GetAddressAsync(swap.FromAddress, cancellationToken)
                                .ConfigureAwait(false);

            if (walletAddress == null)
            {
                Log.Error("Can't get address {@address} for refund from local db", swap.FromAddress);
                return;
            }

            var feeInEth = ethConfig.GetFeeAmount(ethConfig.RefundGasLimit, gasPrice);

            if (walletAddress.Balance < feeInEth)
            {
                Log.Error("Insufficient funds for refund");
                return;
            }

            EthereumTransaction refundTx;

            try
            {
                await EthereumAccount.AddressLocker
                .LockAsync(walletAddress.Address, cancellationToken)
                .ConfigureAwait(false);

                var nonceResult = await EthereumNonceManager.Instance
                                  .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken)
                                  .ConfigureAwait(false);

                if (nonceResult.HasError)
                {
                    Log.Error("Nonce getting error with code {@code} and description {@description}",
                              nonceResult.Error.Code,
                              nonceResult.Error.Description);

                    return;
                }

                var message = new RefundFunctionMessage
                {
                    FromAddress  = walletAddress.Address,
                    HashedSecret = swap.SecretHash,
                    GasPrice     = EthereumConfig.GweiToWei(gasPrice),
                    Nonce        = nonceResult.Value,
                };

                message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RefundGasLimit))
                              .ConfigureAwait(false);

                var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress);

                refundTx = new EthereumTransaction(ethConfig.Name, txInput)
                {
                    Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund
                };

                var signResult = await SignTransactionAsync(refundTx, cancellationToken)
                                 .ConfigureAwait(false);

                if (!signResult)
                {
                    Log.Error("Transaction signing error");
                    return;
                }

                swap.RefundTx    = refundTx;
                swap.StateFlags |= SwapStateFlags.IsRefundSigned;

                await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken)
                .ConfigureAwait(false);

                await BroadcastTxAsync(swap, refundTx, cancellationToken)
                .ConfigureAwait(false);
            }
            catch
            {
                throw;
            }
            finally
            {
                EthereumAccount.AddressLocker.Unlock(walletAddress.Address);
            }

            swap.RefundTx    = refundTx;
            swap.StateFlags |= SwapStateFlags.IsRefundBroadcast;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRefundBroadcast, cancellationToken)
            .ConfigureAwait(false);

            _ = TrackTransactionConfirmationAsync(
                swap: swap,
                currency: ethConfig,
                dataRepository: _account.DataRepository,
                txId: refundTx.Id,
                confirmationHandler: RefundConfirmedEventHandler,
                cancellationToken: cancellationToken);
        }
Exemplo n.º 26
0
        public override async Task RedeemForPartyAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            if (swap.IsInitiator)
            {
                var partyRedeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - PartyRedeemTimeReserve;

                if (DateTime.UtcNow > partyRedeemDeadline)
                {
                    Log.Error("Party redeem deadline reached for swap {@swap}", swap.Id);
                    return;
                }
            }

            Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id);

            var ethConfig = EthConfig;

            var gasPrice = await ethConfig
                           .GetGasPriceAsync(cancellationToken)
                           .ConfigureAwait(false);

            var walletAddress = await _account
                                .GetAddressAsync(swap.FromAddress, cancellationToken)
                                .ConfigureAwait(false);

            if (walletAddress == null)
            {
                Log.Error("Can't get address {@address} for redeem for party from local db", swap.FromAddress);
                return;
            }

            var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice);

            if (walletAddress.Balance < feeInEth)
            {
                Log.Error("Insufficient funds for redeem for party");
                return;
            }

            using var addressLock = await EthereumAccount.AddressLocker
                                    .GetLockAsync(walletAddress.Address, cancellationToken)
                                    .ConfigureAwait(false);

            var nonceResult = await EthereumNonceManager.Instance
                              .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken)
                              .ConfigureAwait(false);

            if (nonceResult.HasError)
            {
                Log.Error("Nonce getting error with code {@code} and description {@description}",
                          nonceResult.Error.Code,
                          nonceResult.Error.Description);

                return;
            }

            var message = new RedeemFunctionMessage
            {
                FromAddress  = walletAddress.Address,
                HashedSecret = swap.SecretHash,
                Secret       = swap.Secret,
                Nonce        = nonceResult.Value,
                GasPrice     = EthereumConfig.GweiToWei(gasPrice),
            };

            message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit))
                          .ConfigureAwait(false);

            var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress);

            var redeemTx = new EthereumTransaction(ethConfig.Name, txInput)
            {
                Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem
            };

            var signResult = await SignTransactionAsync(redeemTx, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            await BroadcastTxAsync(swap, redeemTx, cancellationToken)
            .ConfigureAwait(false);
        }
Exemplo n.º 27
0
        public override async Task RedeemAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            var ethConfig = EthConfig;

            var secretResult = await EthereumSwapRedeemedHelper
                               .IsRedeemedAsync(
                swap : swap,
                currency : ethConfig,
                attempts : MaxRedeemCheckAttempts,
                attemptIntervalInSec : RedeemCheckAttemptIntervalInSec,
                cancellationToken : cancellationToken)
                               .ConfigureAwait(false);

            if (!secretResult.HasError && secretResult.Value != null)
            {
                await RedeemConfirmedEventHandler(swap, null, cancellationToken)
                .ConfigureAwait(false);

                return;
            }

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast) &&
                swap.RedeemTx != null &&
                swap.RedeemTx.CreationTime != null &&
                swap.RedeemTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(30) > DateTime.UtcNow)
            {
                // redeem already broadcast
                _ = TrackTransactionConfirmationAsync(
                    swap: swap,
                    currency: ethConfig,
                    dataRepository: _account.DataRepository,
                    txId: swap.RedeemTx.Id,
                    confirmationHandler: RedeemConfirmedEventHandler,
                    cancellationToken: cancellationToken);

                return;
            }

            if (swap.IsInitiator)
            {
                var redeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - RedeemTimeReserve;

                if (DateTime.UtcNow > redeemDeadline)
                {
                    Log.Error("Redeem dedline reached for swap {@swap}", swap.Id);
                    return;
                }
            }

            Log.Debug("Create redeem for swap {@swapId}", swap.Id);

            var gasPrice = await ethConfig
                           .GetGasPriceAsync(cancellationToken)
                           .ConfigureAwait(false);

            var walletAddress = await _account
                                .GetAddressAsync(swap.RedeemFromAddress, cancellationToken)
                                .ConfigureAwait(false);

            if (walletAddress == null)
            {
                Log.Error("Can't get address {@address} for redeem from local db", swap.RedeemFromAddress);
                return;
            }

            var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice);

            if (walletAddress.Balance < feeInEth)
            {
                Log.Error("Insufficient funds for redeem");
                return;
            }

            EthereumTransaction redeemTx;

            try
            {
                await EthereumAccount.AddressLocker
                .LockAsync(walletAddress.Address, cancellationToken)
                .ConfigureAwait(false);

                var nonceResult = await EthereumNonceManager.Instance
                                  .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

                if (nonceResult.HasError)
                {
                    Log.Error("Nonce getting error with code {@code} and description {@description}",
                              nonceResult.Error.Code,
                              nonceResult.Error.Description);

                    return;
                }

                var message = new RedeemFunctionMessage
                {
                    FromAddress  = walletAddress.Address,
                    HashedSecret = swap.SecretHash,
                    Secret       = swap.Secret,
                    Nonce        = nonceResult.Value,
                    GasPrice     = EthereumConfig.GweiToWei(gasPrice),
                };

                message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit))
                              .ConfigureAwait(false);

                var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress);

                redeemTx = new EthereumTransaction(ethConfig.Name, txInput)
                {
                    Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem
                };

                var signResult = await SignTransactionAsync(redeemTx, cancellationToken)
                                 .ConfigureAwait(false);

                if (!signResult)
                {
                    Log.Error("Transaction signing error");
                    return;
                }

                swap.RedeemTx    = redeemTx;
                swap.StateFlags |= SwapStateFlags.IsRedeemSigned;

                await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemSigned, cancellationToken)
                .ConfigureAwait(false);

                await BroadcastTxAsync(swap, redeemTx, cancellationToken)
                .ConfigureAwait(false);
            }
            catch
            {
                throw;
            }
            finally
            {
                EthereumAccount.AddressLocker.Unlock(walletAddress.Address);
            }

            swap.RedeemTx    = redeemTx;
            swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemBroadcast, cancellationToken)
            .ConfigureAwait(false);

            _ = TrackTransactionConfirmationAsync(
                swap: swap,
                currency: ethConfig,
                dataRepository: _account.DataRepository,
                txId: redeemTx.Id,
                confirmationHandler: RedeemConfirmedEventHandler,
                cancellationToken: cancellationToken);
        }
Exemplo n.º 28
0
        public async Task <Error> SendAsync(
            string from,
            string to,
            decimal amount,
            decimal gasLimit   = 0,
            decimal gasPrice   = 0,
            bool useDefaultFee = false,
            CancellationToken cancellationToken = default)
        {
            //if (from == to)
            //    return new Error(
            //        code: Errors.SendingAndReceivingAddressesAreSame,
            //        description: "Sending and receiving addresses are the same.");

            var erc20Config = Erc20Config;

            if (useDefaultFee)
            {
                gasLimit = GasLimitByType(BlockchainTransactionType.Output);

                gasPrice = await erc20Config
                           .GetGasPriceAsync()
                           .ConfigureAwait(false);
            }

            var addressFeeUsage = await SelectUnspentAddressesAsync(
                from : from,
                amount : amount,
                fee : gasLimit,
                feePrice : gasPrice,
                cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (addressFeeUsage == null)
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

            if (gasLimit < erc20Config.TransferGasLimit)
            {
                return(new Error(
                           code: Errors.InsufficientGas,
                           description: "Insufficient gas"));
            }

            var feeAmount = erc20Config.GetFeeAmount(gasLimit, gasPrice);

            Log.Debug("Fee per transaction {@feePerTransaction}. Fee Amount {@feeAmount}",
                      gasLimit,
                      feeAmount);

            Log.Debug("Send {@amount} of {@currency} from address {@address} with available balance {@balance}",
                      addressFeeUsage.UsedAmount,
                      erc20Config.Name,
                      addressFeeUsage.WalletAddress.Address,
                      addressFeeUsage.WalletAddress.AvailableBalance());

            using var addressLock = await EthereumAccount.AddressLocker
                                    .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken)
                                    .ConfigureAwait(false);

            var nonceResult = await EthereumNonceManager.Instance
                              .GetNonceAsync(EthConfig, addressFeeUsage.WalletAddress.Address)
                              .ConfigureAwait(false);

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

            TransactionInput txInput;

            var message = new ERC20TransferFunctionMessage
            {
                To          = to.ToLowerInvariant(),
                Value       = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount),
                FromAddress = addressFeeUsage.WalletAddress.Address,
                Gas         = new BigInteger(gasLimit),
                GasPrice    = new BigInteger(EthereumConfig.GweiToWei(gasPrice)),
                Nonce       = nonceResult.Value
            };

            txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress);

            var tx = new EthereumTransaction(erc20Config.Name, txInput)
            {
                Type = BlockchainTransactionType.Output
            };

            var signResult = await Wallet
                             .SignAsync(tx, addressFeeUsage.WalletAddress, erc20Config, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                return(new Error(
                           code: Errors.TransactionSigningError,
                           description: "Transaction signing error"));
            }

            if (!tx.Verify(erc20Config))
            {
                return(new Error(
                           code: Errors.TransactionVerificationError,
                           description: "Transaction verification error"));
            }

            var broadcastResult = await erc20Config.BlockchainApi
                                  .BroadcastAsync(tx, cancellationToken)
                                  .ConfigureAwait(false);

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

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                return(new Error(
                           code: Errors.TransactionBroadcastError,
                           description: "Transaction Id is null"));
            }

            Log.Debug("Transaction successfully sent with txId: {@id}", txId);

            tx.Amount = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount);
            tx.To     = to.ToLowerInvariant();

            await UpsertTransactionAsync(
                tx : tx,
                updateBalance : false,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : false,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            var ethTx = tx.Clone();

            ethTx.Currency = EthConfig.Name;
            ethTx.Amount   = 0;
            ethTx.Type     = BlockchainTransactionType.TokenCall;

            await UpsertTransactionAsync(
                tx : ethTx,
                updateBalance : false,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : false,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            _ = UpdateBalanceAsync(cancellationToken);

            return(null);
        }
Exemplo n.º 29
0
        private async Task RefundAsync(
            ClientSwap swap,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Log.Debug("Create refund for swap {@swap}", swap.Id);

            var walletAddress = (await Account.GetUnspentAddressesAsync(
                                     currency: Currency,
                                     amount: 0, // todo: account storage fee
                                     fee: Eth.RefundGasLimit,
                                     feePrice: Eth.GasPriceInGwei,
                                     isFeePerTransaction: true,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

            if (walletAddress == null)
            {
                Log.Error("Insufficient funds for refund");
                return;
            }

            var nonce = await EthereumNonceManager.Instance
                        .GetNonce(Eth, walletAddress.Address)
                        .ConfigureAwait(false);

            var message = new RefundFunctionMessage
            {
                FromAddress  = walletAddress.Address,
                HashedSecret = swap.SecretHash,
                GasPrice     = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei),
                Nonce        = nonce,
            };

            message.Gas = await EstimateGasAsync(message, new BigInteger(Eth.RefundGasLimit))
                          .ConfigureAwait(false);

            var txInput = message.CreateTransactionInput(Eth.SwapContractAddress);

            var refundTx = new EthereumTransaction(Eth, txInput)
            {
                Type = EthereumTransaction.OutputTransaction
            };

            var signResult = await SignTransactionAsync(refundTx, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            swap.RefundTx = refundTx;
            swap.SetRefundSigned();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundSigned);

            await BroadcastTxAsync(swap, refundTx, cancellationToken)
            .ConfigureAwait(false);

            swap.RefundTx = refundTx;
            swap.SetRefundBroadcast();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundBroadcast);

            TaskPerformer.EnqueueTask(new TransactionConfirmationCheckTask
            {
                Currency        = Currency,
                Swap            = swap,
                Interval        = DefaultConfirmationCheckInterval,
                TxId            = refundTx.Id,
                CompleteHandler = RefundConfirmedEventHandler
            });
        }
Exemplo n.º 30
0
        public override async Task RefundAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            var eth = Eth;

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) &&
                swap.RefundTx != null &&
                swap.RefundTx.CreationTime != null &&
                swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(5) > DateTime.UtcNow)
            {
                TrackTransactionConfirmationAsync(
                    swap: swap,
                    currency: eth,
                    txId: swap.RefundTx.Id,
                    confirmationHandler: RefundConfirmedEventHandler,
                    cancellationToken: cancellationToken)
                .FireAndForget();

                return;
            }

            Log.Debug("Create refund for swap {@swap}", swap.Id);

            var walletAddress = (await _account
                                 .GetUnspentAddressesAsync(
                                     toAddress: null, // get refund address
                                     amount: 0,
                                     fee: 0,
                                     feePrice: 0,
                                     feeUsagePolicy: FeeUsagePolicy.EstimatedFee,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress,
                                     transactionType: BlockchainTransactionType.SwapRefund,
                                     cancellationToken: cancellationToken)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

            if (walletAddress == null)
            {
                Log.Error("Insufficient funds for refund");
                return;
            }

            var nonceResult = await EthereumNonceManager.Instance
                              .GetNonceAsync(eth, walletAddress.Address)
                              .ConfigureAwait(false);

            if (nonceResult.HasError)
            {
                Log.Error("Nonce getting error with code {@code} and description {@description}",
                          nonceResult.Error.Code,
                          nonceResult.Error.Description);

                return;
            }

            var message = new RefundFunctionMessage
            {
                FromAddress  = walletAddress.Address,
                HashedSecret = swap.SecretHash,
                GasPrice     = Atomex.Ethereum.GweiToWei(eth.GasPriceInGwei),
                Nonce        = nonceResult.Value,
            };

            message.Gas = await EstimateGasAsync(message, new BigInteger(eth.RefundGasLimit))
                          .ConfigureAwait(false);

            var txInput = message.CreateTransactionInput(eth.SwapContractAddress);

            var refundTx = new EthereumTransaction(eth, txInput)
            {
                Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund
            };

            var signResult = await SignTransactionAsync(refundTx, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            swap.RefundTx    = refundTx;
            swap.StateFlags |= SwapStateFlags.IsRefundSigned;
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundSigned);

            await BroadcastTxAsync(swap, refundTx, cancellationToken)
            .ConfigureAwait(false);

            swap.RefundTx    = refundTx;
            swap.StateFlags |= SwapStateFlags.IsRefundBroadcast;
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundBroadcast);

            TrackTransactionConfirmationAsync(
                swap: swap,
                currency: eth,
                txId: refundTx.Id,
                confirmationHandler: RefundConfirmedEventHandler,
                cancellationToken: cancellationToken)
            .FireAndForget();
        }