public static async Task <BitcoinTransactionParams> SelectTransactionParamsByFeeRateAsync(
            IEnumerable <BitcoinBasedTxOutput> availableOutputs,
            string to,
            decimal amount,
            decimal feeRate,
            BitcoinBasedAccount account,
            CancellationToken cancellationToken = default)
        {
            var config = account.Config;

            var freeInternalAddress = await account
                                      .GetFreeInternalAddressAsync()
                                      .ConfigureAwait(false);

            return(await SelectTransactionParamsByFeeRateAsync(
                       availableInputs : availableOutputs.Select(o => new BitcoinInputToSign {
                Output = o
            }),
                       destinations : new BitcoinDestination[]
            {
                new BitcoinDestination
                {
                    AmountInSatoshi = config.CoinToSatoshi(amount),
                    Script = BitcoinAddress.Create(to, config.Network).ScriptPubKey,
                }
            },
                       changeAddress : freeInternalAddress.Address,
                       feeRate : feeRate,
                       currencyConfig : config,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
Exemple #2
0
        public override async Task RedeemAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Redeem for swap {@swap}.", swap.Id);

            var currency = Currencies.GetByName(swap.PurchasedCurrency);

            var needReplaceTx = false;

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast))
            {
                Log.Debug("Check redeem confirmation for swap {@swap}.", swap.Id);

                // redeem already broadcast
                var result = await currency
                             .IsTransactionConfirmed(
                    txId : swap.RedeemTx.Id,
                    cancellationToken : cancellationToken)
                             .ConfigureAwait(false);

                if (result == null)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Result is null.");
                    return;
                }
                else if (result.HasError && result.Error.Code == (int)HttpStatusCode.NotFound)
                {
                    // probably the transaction was deleted by miners
                    Log.Debug("Probably the transaction {@tx} was deleted by miners.", swap.RedeemTx.Id);
                    needReplaceTx = true;
                }
                else if (result.HasError)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Code: {@code}. Description: {@description}.",
                              result.Error.Code,
                              result.Error.Description);
                    return;
                }
                else if (result.Value.IsConfirmed) // tx already confirmed
                {
                    Log.Debug("Transaction {@tx} is already confirmed.", swap.RedeemTx.Id);
                    RedeemConfirmedEventHandler(swap, result.Value.Transaction, cancellationToken);
                    return;
                }

                var currentTimeUtc = DateTime.UtcNow;

                var creationTimeUtc = swap.RedeemTx.CreationTime != null
                    ? swap.RedeemTx.CreationTime.Value.ToUniversalTime()
                    : swap.TimeStamp.ToUniversalTime();

                var difference = currentTimeUtc - creationTimeUtc;

                Log.Debug("Currenct time: {@current}, creation time: {@now}, difference: {@diff}",
                          currentTimeUtc,
                          creationTimeUtc,
                          difference);

                // check transaction creation time and try replacing it with a higher fee
                if (difference >= TimeSpan.FromHours(4))
                {
                    needReplaceTx = true;
                }

                if (!needReplaceTx)
                {
                    TrackTransactionConfirmationAsync(
                        swap: swap,
                        currency: currency,
                        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;
                }
            }

            var redeemAddress = await _account
                                .GetFreeInternalAddressAsync()
                                .ConfigureAwait(false);

            var partyRedeemScript = Convert.FromBase64String(swap.PartyRedeemScript);

            // create redeem tx
            swap.RedeemTx = await CreateRedeemTxAsync(
                swap : swap,
                paymentTx : (IBitcoinBasedTransaction)swap.PartyPaymentTx,
                redeemAddress : redeemAddress.Address,
                redeemScript : partyRedeemScript,
                increaseSequenceNumber : needReplaceTx)
                            .ConfigureAwait(false);

            var toAddress = await _account
                            .GetAddressAsync(currency.Name, swap.ToAddress, cancellationToken)
                            .ConfigureAwait(false);

            // sign redeem tx
            swap.RedeemTx = await SignRedeemTxAsync(
                swap : swap,
                redeemTx : (IBitcoinBasedTransaction)swap.RedeemTx,
                paymentTx : (IBitcoinBasedTransaction)swap.PartyPaymentTx,
                redeemAddress : toAddress,
                redeemScript : partyRedeemScript)
                            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRedeemSigned;
            RaiseSwapUpdated(swap, SwapStateFlags.IsRedeemSigned);

            // broadcast redeem tx
            await BroadcastRedeemAsync(
                swap : swap,
                redeemTx : swap.RedeemTx,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

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

            // add new unconfirmed transaction
            await _account
            .UpsertTransactionAsync(
                tx : swap.RedeemTx,
                updateBalance : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            TrackTransactionConfirmationAsync(
                swap: swap,
                currency: currency,
                txId: swap.RedeemTx.Id,
                confirmationHandler: RedeemConfirmedEventHandler,
                cancellationToken: cancellationToken)
            .FireAndForget();
        }
        public override async Task RedeemAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Redeem for swap {@swap}.", swap.Id);

            var currency = Currencies.GetByName(swap.PurchasedCurrency);

            var needReplaceTx = false;

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast))
            {
                Log.Debug("Check redeem confirmation for swap {@swap}.", swap.Id);

                // redeem already broadcast
                var result = await currency
                             .IsTransactionConfirmed(
                    txId : swap.RedeemTx.Id,
                    cancellationToken : cancellationToken)
                             .ConfigureAwait(false);

                if (result == null)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Result is null.");
                    return;
                }
                else if (result.HasError && result.Error.Code == (int)HttpStatusCode.NotFound)
                {
                    // probably the transaction was deleted by miners
                    Log.Debug("Probably the transaction {@tx} was deleted by miners.", swap.RedeemTx.Id);
                    needReplaceTx = true;
                }
                else if (result.HasError)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Code: {@code}. Description: {@description}.",
                              result.Error.Code,
                              result.Error.Description);

                    return;
                }
                else if (result.Value.IsConfirmed) // tx already confirmed
                {
                    Log.Debug("Transaction {@tx} is already confirmed.", swap.RedeemTx.Id);

                    await RedeemConfirmedEventHandler(swap, result.Value.Transaction, cancellationToken)
                    .ConfigureAwait(false);

                    return;
                }

                var currentTimeUtc = DateTime.UtcNow;

                var creationTimeUtc = swap.RedeemTx.CreationTime != null
                    ? swap.RedeemTx.CreationTime.Value.ToUniversalTime()
                    : swap.TimeStamp.ToUniversalTime();

                var difference = currentTimeUtc - creationTimeUtc;

                Log.Debug("Currenct time: {@current}, creation time: {@now}, difference: {@diff}",
                          currentTimeUtc,
                          creationTimeUtc,
                          difference);

                // check transaction creation time and try replacing it with a higher fee
                if (difference >= TimeSpan.FromHours(4))
                {
                    needReplaceTx = true;
                }

                if (!needReplaceTx)
                {
                    _ = TrackTransactionConfirmationAsync(
                        swap: swap,
                        currency: currency,
                        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;
                }
            }

            var redeemAddress = await _account
                                .GetFreeInternalAddressAsync()
                                .ConfigureAwait(false);

            var lockTimeInSeconds = swap.IsInitiator
                ? DefaultAcceptorLockTimeInSeconds
                : DefaultInitiatorLockTimeInSeconds;

            var refundTimeUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds))
                                     .ToUnixTimeSeconds();

            var bitcoinBased = (BitcoinBasedConfig)currency;

            var partyRedeemScript = swap.PartyRefundAddress == null && swap.PartyRedeemScript != null
                ? new Script(Convert.FromBase64String(swap.PartyRedeemScript))
                : BitcoinBasedSwapTemplate
                                    .GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: swap.PartyRefundAddress,
                bobAddress: swap.ToAddress,
                lockTimeStamp: refundTimeUtcInSec,
                secretHash: swap.SecretHash,
                secretSize: DefaultSecretSize,
                expectedNetwork: bitcoinBased.Network);

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

            // get party payment
            var partyPaymentResult = await BitcoinBasedSwapInitiatedHelper
                                     .TryToFindPaymentAsync(
                swap : swap,
                currency : currency,
                side : side,
                toAddress : swap.ToAddress,
                refundAddress : swap.PartyRefundAddress,
                refundTimeStamp : refundTimeUtcInSec,
                redeemScriptBase64 : swap.PartyRedeemScript,
                cancellationToken : cancellationToken)
                                     .ConfigureAwait(false);

            if (partyPaymentResult == null || partyPaymentResult.HasError || partyPaymentResult.Value == null)
            {
                Log.Error($"BitcoinBased: can't get party payment for swap {swap.Id}");
                return;
            }

            // create redeem tx
            swap.RedeemTx = await CreateRedeemTxAsync(
                swap : swap,
                paymentTx : (IBitcoinBasedTransaction)partyPaymentResult.Value,
                redeemAddress : redeemAddress.Address,
                redeemScript : partyRedeemScript.ToBytes(),
                increaseSequenceNumber : needReplaceTx)
                            .ConfigureAwait(false);

            var toAddress = await _account
                            .GetAddressAsync(currency.Name, swap.ToAddress, cancellationToken)
                            .ConfigureAwait(false);

            // sign redeem tx
            swap.RedeemTx = await SignRedeemTxAsync(
                swap : swap,
                redeemTx : (IBitcoinBasedTransaction)swap.RedeemTx,
                paymentTx : (IBitcoinBasedTransaction)partyPaymentResult.Value,
                redeemAddress : toAddress,
                redeemScript : partyRedeemScript.ToBytes())
                            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRedeemSigned;

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

            // broadcast redeem tx
            await BroadcastRedeemAsync(
                swap : swap,
                redeemTx : swap.RedeemTx,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast;

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

            // add new unconfirmed transaction
            await _account
            .UpsertTransactionAsync(
                tx : swap.RedeemTx,
                updateBalance : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            _ = TrackTransactionConfirmationAsync(
                swap: swap,
                currency: currency,
                dataRepository: _account.DataRepository,
                txId: swap.RedeemTx.Id,
                confirmationHandler: RedeemConfirmedEventHandler,
                cancellationToken: cancellationToken);
        }