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);
        }
Example #2
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);
        }
Example #3
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);
        }
        public override async Task RedeemAsync(ClientSwap swap)
        {
            Log.Debug("Create redeem for swap {@swapId}", swap.Id);

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

            if (walletAddress == null)
            {
                Log.Error("Insufficient funds for redeem");
                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;
            }

            swap.RedeemTx = redeemTx;
            swap.SetRedeemSigned();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRedeemSigned);

            await BroadcastTxAsync(swap, redeemTx)
            .ConfigureAwait(false);

            swap.RedeemTx = redeemTx;
            swap.SetRedeemBroadcast();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRedeemBroadcast);

            TaskPerformer.EnqueueTask(new TransactionConfirmationCheckTask
            {
                Currency        = Currency,
                Swap            = swap,
                Interval        = DefaultConfirmationCheckInterval,
                TxId            = redeemTx.Id,
                CompleteHandler = RedeemConfirmedEventHandler
            });
        }
        public override async Task RedeemForPartyAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            var eth = Eth;

            if (swap.IsInitiator)
            {
                var partyRedeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - PartyRedeemTimeReserve;

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

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

            var walletAddress = (await _account
                                 .GetUnspentAddressesAsync(
                                     toAddress: null, // todo: get participant address
                                     amount: 0,
                                     fee: 0,
                                     feePrice: 0,
                                     feeUsagePolicy: FeeUsagePolicy.EstimatedFee,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress,
                                     transactionType: BlockchainTransactionType.SwapRedeem,
                                     cancellationToken: cancellationToken)
                                 .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 nonceResult = await EthereumNonceManager.Instance
                              .GetNonceAsync(eth, walletAddress.Address, 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     = Atomex.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 = 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);
        }
        public override async Task RedeemAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            var eth = Eth;

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

            if (!secretResult.HasError && secretResult.Value != null)
            {
                RedeemConfirmedEventHandler(swap, null, cancellationToken);
                return;
            }

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast))
            {
                // redeem already broadcast
                TrackTransactionConfirmationAsync(
                    swap: swap,
                    currency: eth,
                    txId: swap.RedeemTx.Id,
                    confirmationHandler: RedeemConfirmedEventHandler,
                    cancellationToken: cancellationToken)
                .FireAndForget();

                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 walletAddress = (await _account
                                 .GetUnspentAddressesAsync(
                                     toAddress: swap.ToAddress,
                                     amount: 0,
                                     fee: 0,
                                     feePrice: 0,
                                     feeUsagePolicy: FeeUsagePolicy.EstimatedFee,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress,
                                     transactionType: BlockchainTransactionType.SwapRedeem,
                                     cancellationToken: cancellationToken)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

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

            var nonceResult = await EthereumNonceManager.Instance
                              .GetNonceAsync(eth, walletAddress.Address, 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     = Atomex.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 = 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;
            RaiseSwapUpdated(swap, SwapStateFlags.IsRedeemSigned);

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

            swap.RedeemTx    = redeemTx;
            swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast;
            RaiseSwapUpdated(swap, SwapStateFlags.IsRedeemBroadcast);

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