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 PayAsync( Swap swap, CancellationToken cancellationToken = default) { if (swap.IsAcceptor && !swap.HasPartyPayment) { Log.Debug("Acceptor is not ready to broadcast {@currency} payment tx for swap {@swap}", Currency, swap.Id); return; } if (!await CheckPayRelevanceAsync(swap, cancellationToken)) { return; } var lockTimeInSeconds = swap.IsInitiator ? DefaultInitiatorLockTimeInSeconds : DefaultAcceptorLockTimeInSeconds; var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds); var refundAddress = await _account .GetAddressAsync(swap.RefundAddress) .ConfigureAwait(false); swap.PaymentTx = await CreatePaymentTxAsync( swap : swap, refundAddress : refundAddress.Address, lockTime : lockTime) .ConfigureAwait(false); swap.PaymentTx = await SignPaymentTxAsync( swap : swap, paymentTx : (IBitcoinBasedTransaction)swap.PaymentTx) .ConfigureAwait(false); swap.StateFlags |= SwapStateFlags.IsPaymentSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsPaymentSigned) .ConfigureAwait(false); Log.Debug("Broadcast {@currency} payment tx for swap {@swap}", Currency, swap.Id); var currency = Currencies.GetByName(swap.SoldCurrency); // broadcast payment transaction var broadcastResult = await currency.BlockchainApi .TryBroadcastAsync(swap.PaymentTx, cancellationToken : cancellationToken) .ConfigureAwait(false); if (broadcastResult.HasError) { Log.Error("Error while broadcast {@currency} transaction. Code: {@code}. Description: {@description}", currency.Name, broadcastResult.Error.Code, broadcastResult.Error.Description); return; } var txId = broadcastResult.Value; swap.PaymentTxId = txId ?? throw new Exception("Transaction Id is null"); swap.StateFlags |= SwapStateFlags.IsPaymentBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsPaymentBroadcast, cancellationToken) .ConfigureAwait(false); Log.Debug("{@currency} payment txId {@id} for swap {@swap}", currency.Name, txId, swap.Id); // account new unconfirmed transaction await _account .UpsertTransactionAsync( tx : swap.PaymentTx, updateBalance : true, notifyIfUnconfirmed : true, cancellationToken : cancellationToken) .ConfigureAwait(false); }