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)); }
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); }