public IBitcoinBasedTransaction SignP2PkSwapRefundTx(BitcoinBasedCurrency currency) { const int paymentQty = 1_0000_0000; var paymentTx = CreateP2PkSwapPaymentTx(currency); var paymentTxOutputs = paymentTx.Outputs.Where(o => o.Value == paymentQty).ToArray(); var lockTime = DateTimeOffset.Now.AddHours(12); const int amount = 9999_0000; const int fee = 1_0000; // change = 0; var refundTx = BitcoinBasedCommon.CreatePaymentTx( currency: Common.BtcTestNet, outputs: paymentTxOutputs, from: Common.Alice.PubKey, to: Common.Alice.PubKey, amount: amount, fee: fee, lockTime: lockTime ); var sigHash = new uint256(refundTx.GetSignatureHash(paymentTxOutputs.First())); var aliceSign = Common.Alice.Sign(sigHash, SigHash.All); var bobSign = Common.Bob.Sign(sigHash, SigHash.All); var refundScript = BitcoinBasedSwapTemplate.GenerateSwapRefund(aliceSign, bobSign); refundTx.NonStandardSign(refundScript, paymentTxOutputs.First()); Assert.True(refundTx.Verify(paymentTxOutputs)); return(refundTx); }
public IBitcoinBasedTransaction SignHtlcP2PkhScriptSwapRedeemTx(BitcoinBasedCurrency currency) { const int paymentQty = 1_0000_0000; var(paymentTx, redeemScript) = CreateHtlcP2PkhScriptSwapPaymentTx(currency); var paymentTxOutputs = paymentTx.Outputs .Where(o => o.Value == paymentQty) .ToArray(); const int amount = 9999_0000; const int fee = 1_0000; var redeemTx = currency.CreatePaymentTx( unspentOutputs: paymentTxOutputs, destinationAddress: Common.Bob.PubKey.GetAddress(currency), changeAddress: Common.Bob.PubKey.GetAddress(currency), amount: amount, fee: fee, lockTime: DateTimeOffset.MinValue); var sigHash = new uint256(redeemTx.GetSignatureHash(new Script(redeemScript), paymentTxOutputs.First())); var scriptSig = BitcoinBasedSwapTemplate.GenerateP2PkhSwapRedeemForP2Sh( sig: Common.Bob.Sign(sigHash, SigHash.All).ToBytes(), pubKey: Common.Bob.PubKey.ToBytes(), secret: Common.Secret, redeemScript: redeemScript); redeemTx.NonStandardSign(scriptSig, paymentTxOutputs.First()); Assert.True(redeemTx.Verify(paymentTxOutputs)); return(redeemTx); }
public virtual IBitcoinBasedTransaction CreateHtlcP2PkhSwapPaymentTx( IEnumerable <ITxOutput> unspentOutputs, string aliceRefundAddress, string bobAddress, DateTimeOffset lockTime, byte[] secretHash, int secretSize, long amount, long fee) { var coins = unspentOutputs .Cast <BitcoinBasedTxOutput>() .Select(o => o.Coin); var swap = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapPayment( aliceRefundAddress: aliceRefundAddress, bobAddress: bobAddress, lockTimeStamp: lockTime.ToUnixTimeSeconds(), secretHash: secretHash, secretSize: secretSize, expectedNetwork: Network); var change = BitcoinAddress.Create(aliceRefundAddress, Network) .ScriptPubKey; return(BitcoinBasedTransaction.CreateTransaction( currency: this, coins: coins, destination: swap, change: change, amount: amount, fee: fee)); }
public IBitcoinBasedTransaction SignHtlcP2PkhSwapRedeemTxByBob(BitcoinBasedCurrency currency) { const int paymentQty = 3000; var paymentTx = CreateHtlcP2PkhSwapPaymentTxAlice2Bob(currency); var paymentTxOutputs = paymentTx.Outputs .Where(o => o.Value == paymentQty) .ToArray(); const int amount = 2000; const int fee = 1000; var redeemTx = currency.CreatePaymentTx( unspentOutputs: paymentTxOutputs, destinationAddress: Common.Bob.PubKey.GetAddress(currency), changeAddress: Common.Bob.PubKey.GetAddress(currency), amount: amount, fee: fee); var sigHash = new uint256(redeemTx.GetSignatureHash(paymentTxOutputs.First())); var bobSign = Common.Bob.Sign(sigHash, SigHash.All).ToBytes(); var bobPubKey = Common.Bob.PubKey.ToBytes(); var redeemScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapRedeem(bobSign, bobPubKey, Common.Secret); redeemTx.NonStandardSign(redeemScript, paymentTxOutputs.First()); Assert.True(redeemTx.Verify(paymentTxOutputs)); return(redeemTx); }
public virtual IBitcoinBasedTransaction CreateP2PkhSwapPaymentTx( IEnumerable <ITxOutput> unspentOutputs, byte[] aliceRefundPubKey, byte[] bobRefundPubKey, string bobAddress, byte[] secretHash, long amount, long fee) { var coins = unspentOutputs .Cast <BitcoinBasedTxOutput>() .Select(o => o.Coin); var alicePubKey = new PubKey(aliceRefundPubKey); var swap = BitcoinBasedSwapTemplate.GenerateP2PkhSwapPayment( aliceRefundPubKey: aliceRefundPubKey, bobRefundPubKey: bobRefundPubKey, bobAddress: bobAddress, secretHash: secretHash, expectedNetwork: Network); return(BitcoinBasedTransaction.CreateTransaction( currency: this, coins: coins, destination: swap, change: alicePubKey.Hash.ScriptPubKey, amount: amount, fee: fee)); }
public async Task <IBitcoinBasedTransaction> SignRefundTxAsync( IBitcoinBasedTransaction refundTx, IBitcoinBasedTransaction paymentTx, WalletAddress refundAddress) { var tx = refundTx.Clone(); if (tx.Inputs.Length != 1) { Log.Error("Refund transaction has zero or more than one input"); return(null); } var spentOutput = paymentTx.Outputs.FirstOrDefault(o => o.IsSwapPayment); if (spentOutput == null) { Log.Error("Payment transaction hasn't swap output"); return(null); } // firstly check, if transaction is already signed if (tx.Verify(spentOutput)) { return(tx); } // clean any signature, if exists tx.NonStandardSign(Script.Empty, 0); var sigHash = tx.GetSignatureHash(spentOutput); var signature = await Account.Wallet .SignHashAsync( hash : sigHash, address : refundAddress) .ConfigureAwait(false); if (signature == null) { Log.Error("Refund transaction signature error"); return(null); } var refundScript = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefund( aliceRefundSig: signature, aliceRefundPubKey: refundAddress.PublicKeyBytes()); tx.NonStandardSign(refundScript, spentOutput); if (!tx.Verify(spentOutput, out var errors)) { Log.Error("Refund transaction verify errors: {errors}", errors); return(null); } return(tx); }
public async Task <IBitcoinBasedTransaction> SignHtlcSwapRedeemForP2ShTxAsync( IBitcoinBasedTransaction redeemTx, IBitcoinBasedTransaction paymentTx, WalletAddress redeemAddress, byte[] secret, byte[] redeemScript) { var tx = redeemTx.Clone(); var spentOutput = paymentTx.Outputs .Cast <BitcoinBasedTxOutput>() .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript)); if (spentOutput == null) { Log.Error("Payment transaction hasn't pay to script hash output for redeem script"); return(null); } var sigHash = tx.GetSignatureHash(new Script(redeemScript), spentOutput); var signature = await Account.Wallet .SignHashAsync( hash : sigHash, address : redeemAddress, currency : Account.Currencies.GetByName(Account.Currency)) .ConfigureAwait(false); if (signature == null) { Log.Error("Redeem transaction signature error"); return(null); } var redeemScriptSig = BitcoinBasedSwapTemplate.GenerateP2PkhSwapRedeemForP2Sh( sig: signature, pubKey: redeemAddress.PublicKeyBytes(), secret: secret, redeemScript: redeemScript); tx.NonStandardSign(redeemScriptSig, spentOutput); if (!tx.Verify(spentOutput, out var errors, Account.Config)) { Log.Error("Redeem transaction verify errors: {errors}", errors); return(null); } return(tx); }
public void ExtractSecretTest() { var redeemScript = new Script(SwapRedeemScript); Assert.NotNull(redeemScript); var secret = BitcoinBasedSwapTemplate.ExtractSecretFromP2PkhSwapRedeem(redeemScript); Assert.NotNull(secret); var secretHash = Convert.ToBase64String(CurrencySwap.CreateSwapSecretHash160(secret)); Assert.Equal(SwapSecretHash, secretHash); }
public async Task <IBitcoinBasedTransaction> SignRedeemTxAsync( IBitcoinBasedTransaction redeemTx, IBitcoinBasedTransaction paymentTx, WalletAddress redeemAddress, byte[] secret) { var tx = redeemTx.Clone(); var spentOutput = paymentTx.Outputs.FirstOrDefault(o => o.IsSwapPayment); if (spentOutput == null) { Log.Error("Payment transaction hasn't swap output"); return(null); } var sigHash = tx.GetSignatureHash(spentOutput); var signature = await Account.Wallet .SignHashAsync( hash : sigHash, address : redeemAddress) .ConfigureAwait(false); if (signature == null) { Log.Error("Redeem transaction signature error"); return(null); } var redeemScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapRedeem( sig: signature, pubKey: redeemAddress.PublicKeyBytes(), secret: secret); tx.NonStandardSign(redeemScript, spentOutput); if (!tx.Verify(spentOutput, out var errors)) { Log.Error("Redeem transaction verify errors: {errors}", errors); return(null); } return(tx); }
public IBitcoinBasedTransaction SignHtlcP2PkhScriptSwapRefundTx(BitcoinBasedConfig currency) { const int paymentQty = 1_0000_0000; var(paymentTx, redeemScriptBytes) = CreateHtlcP2PkhScriptSwapPaymentTx(currency); var paymentTxOutputs = paymentTx.Outputs.Where(o => o.Value == paymentQty).ToArray(); var lockTime = DateTimeOffset.UtcNow.AddHours(1); const int amount = 9999_0000; const int fee = 1_0000; // change = 0; var redeemScript = new Script(redeemScriptBytes); var refundTx = BitcoinBasedCommon.CreatePaymentTx( currency: Common.BtcTestNet, outputs: paymentTxOutputs, from: Common.Alice.PubKey, to: Common.Alice.PubKey, amount: amount, fee: fee, lockTime: lockTime, knownRedeems: redeemScript ); var sigHash = new uint256(refundTx.GetSignatureHash(redeemScript, paymentTxOutputs.First())); var aliceSign = Common.Alice.Sign(sigHash, SigHash.All); var refundScript = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefundForP2Sh( aliceRefundSig: aliceSign.ToBytes(), aliceRefundPubKey: Common.Alice.PubKey.ToBytes(), redeemScript: redeemScriptBytes); refundTx.NonStandardSign(refundScript, paymentTxOutputs.First()); Assert.True(refundTx.Verify(paymentTxOutputs, currency)); return(refundTx); }
public static Task StartSwapSpentControlAsync( Swap swap, CurrencyConfig currency, DateTime refundTimeUtc, TimeSpan interval, Func <Swap, ITxPoint, CancellationToken, Task> completionHandler = null, Func <Swap, CancellationToken, Task> refundTimeReachedHandler = null, CancellationToken cancellationToken = default) { return(Task.Run(async() => { try { var bitcoinBased = (BitcoinBasedConfig)currency; var side = swap.Symbol .OrderSideForBuyCurrency(swap.PurchasedCurrency); var requiredAmount = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier); var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount); var lockTimeInSeconds = swap.IsInitiator ? CurrencySwap.DefaultInitiatorLockTimeInSeconds : CurrencySwap.DefaultAcceptorLockTimeInSeconds; var refundTimeUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)) .ToUnixTimeSeconds(); var redeemScript = swap.RefundAddress == null && swap.RedeemScript != null ? new Script(Convert.FromBase64String(swap.RedeemScript)) : BitcoinBasedSwapTemplate .GenerateHtlcP2PkhSwapPayment( aliceRefundAddress: swap.RefundAddress, bobAddress: swap.PartyAddress, lockTimeStamp: refundTimeUtcInSec, secretHash: swap.SecretHash, secretSize: CurrencySwap.DefaultSecretSize, expectedNetwork: bitcoinBased.Network); var swapOutput = ((IBitcoinBasedTransaction)swap.PaymentTx) .Outputs .Cast <BitcoinBasedTxOutput>() .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript) && o.Value >= requiredAmountInSatoshi); if (swapOutput == null) { throw new InternalException( code: Errors.SwapError, description: "Payment tx have not swap output"); } while (!cancellationToken.IsCancellationRequested) { Log.Debug("Output spent control for {@currency} swap {@swapId}", currency.Name, swap.Id); var result = await currency .GetSpentPointAsync( hash: swap.PaymentTxId, index: swapOutput.Index, cancellationToken: cancellationToken) .ConfigureAwait(false); if (result != null && !result.HasError) { if (result.Value != null) { await completionHandler.Invoke(swap, result.Value, cancellationToken) .ConfigureAwait(false); break; } } if (DateTime.UtcNow >= refundTimeUtc) { await refundTimeReachedHandler.Invoke(swap, cancellationToken) .ConfigureAwait(false); break; } await Task.Delay(interval, cancellationToken) .ConfigureAwait(false); } } catch (OperationCanceledException) { Log.Debug("StartSwapSpentControlAsync canceled."); } catch (Exception e) { Log.Error(e, "StartSwapSpentControlAsync error"); } }, cancellationToken)); }
public async Task <IBitcoinBasedTransaction> SignHtlcSwapRefundForP2ShTxAsync( IBitcoinBasedTransaction refundTx, IBitcoinBasedTransaction paymentTx, WalletAddress refundAddress, byte[] redeemScript) { var tx = refundTx.Clone(); if (tx.Inputs.Length != 1) { Log.Error("Refund transaction has zero or more than one input"); return(null); } var spentOutput = paymentTx.Outputs .Cast <BitcoinBasedTxOutput>() .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript)); if (spentOutput == null) { Log.Error("Payment transaction hasn't pay to script hash output for redeem script"); return(null); } // firstly check, if transaction is already signed if (tx.Verify(spentOutput, Account.Config)) { return(tx); } // clean any signature, if exists tx.NonStandardSign(Script.Empty, 0); var sigHash = tx.GetSignatureHash(new Script(redeemScript), spentOutput); var signature = await Account.Wallet .SignHashAsync( hash : sigHash, address : refundAddress, currency : Account.Currencies.GetByName(Account.Currency)) .ConfigureAwait(false); if (signature == null) { Log.Error("Refund transaction signature error"); return(null); } var refundScriptSig = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefundForP2Sh( aliceRefundSig: signature, aliceRefundPubKey: refundAddress.PublicKeyBytes(), redeemScript: redeemScript); tx.NonStandardSign(refundScriptSig, spentOutput); if (!tx.Verify(spentOutput, out var errors, Account.Config)) { Log.Error("Refund transaction verify errors: {errors}", errors); return(null); } return(tx); }
public async override Task RefundAsync( Swap swap, CancellationToken cancellationToken = default) { if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast)) { _ = TrackTransactionConfirmationAsync( swap: swap, currency: Currencies.GetByName(swap.SoldCurrency), 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); var refundAddress = await _account .GetAddressAsync(swap.RefundAddress) .ConfigureAwait(false); var currency = Currencies.Get <BitcoinBasedConfig>(Currency); var redeemScript = BitcoinBasedSwapTemplate .GenerateHtlcP2PkhSwapPayment( aliceRefundAddress: refundAddress.Address, bobAddress: swap.PartyAddress, lockTimeStamp: lockTime.ToUnixTimeSeconds(), secretHash: swap.SecretHash, secretSize: DefaultSecretSize, expectedNetwork: currency.Network) .ToBytes(); swap.RefundTx = await CreateRefundTxAsync( swap : swap, paymentTx : (IBitcoinBasedTransaction)swap.PaymentTx, refundAddress : refundAddress.Address, lockTime : lockTime, redeemScript : redeemScript) .ConfigureAwait(false); swap.RefundTx = await SignRefundTxAsync( swap : swap, refundTx : (IBitcoinBasedTransaction)swap.RefundTx, paymentTx : (IBitcoinBasedTransaction)swap.PaymentTx, refundAddress : refundAddress, redeemScript : redeemScript) .ConfigureAwait(false); swap.StateFlags |= SwapStateFlags.IsRefundSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken) .ConfigureAwait(false); _ = swap.RefundTx.ForceBroadcast( blockchainApi: Config.BlockchainApi, swap: swap, interval: ForceRefundInterval, completionHandler: RefundBroadcastEventHandler, cancellationToken: cancellationToken); }
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); }
public async Task <IBitcoinBasedTransaction> CreateSwapPaymentTxAsync( IEnumerable <BitcoinBasedTxOutput> fromOutputs, long amount, string refundAddress, string toAddress, DateTimeOffset lockTime, byte[] secretHash, int secretSize, BitcoinBasedConfig currencyConfig, CancellationToken cancellationToken = default) { var availableAmountInSatoshi = fromOutputs.Sum(o => o.Value); if (availableAmountInSatoshi <= amount) { throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}"); } var feeRate = await currencyConfig .GetFeeRateAsync() .ConfigureAwait(false); var lockScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapPayment( aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTimeStamp: lockTime.ToUnixTimeSeconds(), secretHash: secretHash, secretSize: secretSize, expectedNetwork: currencyConfig.Network); var feeInSatoshi = 0L; var inputsToSign = fromOutputs .Select(o => new BitcoinInputToSign { Output = o }); var destinations = new BitcoinDestination[] { new BitcoinDestination { Script = lockScript.PaymentScript, AmountInSatoshi = amount } }; var txParams = await BitcoinTransactionParams.SelectTransactionParamsByFeeRateAsync( availableInputs : inputsToSign, destinations : destinations, changeAddress : refundAddress, feeRate : feeRate, currencyConfig : currencyConfig, cancellationToken : cancellationToken) .ConfigureAwait(false); if (txParams == null) // can't create tx with required feeRate => let's try to change feeRate and try again { var maxFeeInSatoshi = availableAmountInSatoshi - amount; var(txSize, txSizeWithChange) = BitcoinTransactionParams.CalculateTxSize( inputsCount: fromOutputs.Count(), inputsSize: inputsToSign.Sum(i => i.SizeWithSignature()), outputsCount: destinations.Length, outputsSize: destinations.Sum(d => d.Size()), witnessCount: fromOutputs.Sum(o => o.IsSegWit ? 1 : 0), changeOutputSize: BitcoinTransactionParams.CalculateChangeOutputSize( changeAddress: refundAddress, network: currencyConfig.Network)); var estimatedFeeRate = maxFeeInSatoshi / txSizeWithChange; if (Math.Abs(feeRate - estimatedFeeRate) / feeRate > MaxFeeRateChangePercent) { throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}. Probably feeRate has changed a lot."); } feeInSatoshi = maxFeeInSatoshi; } else { feeInSatoshi = (long)txParams.FeeInSatoshi; } var tx = currencyConfig .CreateHtlcP2PkhScriptSwapPaymentTx( unspentOutputs: txParams?.InputsToSign.Select(i => i.Output) ?? fromOutputs, aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTime: lockTime, secretHash: secretHash, secretSize: secretSize, amount: amount, fee: feeInSatoshi, redeemScript: out _); return(tx); }
public static async Task <Result <IBlockchainTransaction> > TryToFindPaymentAsync( Swap swap, CurrencyConfig currency, Side side, string toAddress, string refundAddress, long refundTimeStamp, string redeemScriptBase64 = null, CancellationToken cancellationToken = default) { try { Log.Debug("BitcoinBased: try to find payment tx"); var bitcoinBased = (BitcoinBasedConfig)currency; var requiredAmount = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier); var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount); var redeemScript = refundAddress == null && redeemScriptBase64 != null ? new Script(Convert.FromBase64String(redeemScriptBase64)) : BitcoinBasedSwapTemplate .GenerateHtlcP2PkhSwapPayment( aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTimeStamp: refundTimeStamp, secretHash: swap.SecretHash, secretSize: CurrencySwap.DefaultSecretSize, expectedNetwork: bitcoinBased.Network); var redeemScriptAddress = redeemScript .PaymentScript .GetDestinationAddress(bitcoinBased.Network) .ToString(); var api = bitcoinBased.BlockchainApi as BitcoinBasedBlockchainApi; var outputsResult = await api .GetOutputsAsync(redeemScriptAddress, null, cancellationToken) .ConfigureAwait(false); if (outputsResult == null) { return(new Error(Errors.RequestError, $"Connection error while getting outputs for {redeemScriptAddress} address")); } if (outputsResult.HasError) { return(outputsResult.Error); } foreach (var output in outputsResult.Value) { var o = output as BitcoinBasedTxOutput; var outputScriptHex = o.Coin.TxOut.ScriptPubKey.ToHex(); if (redeemScript.PaymentScript.ToHex() != outputScriptHex) { continue; } if (o.Value < requiredAmountInSatoshi) { continue; } var txResult = await api .GetTransactionAsync(o.TxId, cancellationToken) .ConfigureAwait(false); if (txResult == null) { return(new Error(Errors.RequestError, $"Connection error while getting tx {o.TxId}")); } if (txResult.HasError) { return(txResult.Error); } if (txResult.Value == null) { continue; } return(txResult.Value as BitcoinBasedTransaction); } return(new Result <IBlockchainTransaction>((IBitcoinBasedTransaction)null)); } catch (Exception e) { Log.Error(e, "BitcoinBased swap initiated control task error"); return(new Error(Errors.InternalError, e.Message)); } }