diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj index 033cfee25..f3dca2308 100644 --- a/BTCPayServer.Client/BTCPayServer.Client.csproj +++ b/BTCPayServer.Client/BTCPayServer.Client.csproj @@ -27,7 +27,7 @@ - + diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 61e20e3e9..2a6529f65 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -493,7 +493,7 @@ namespace BTCPayServer.Tests var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri); await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode)); await client.CreateInvoiceAsync( - new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true }, + new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true }, NBitpayClient.Facade.Merchant); client = new NBitpayClient.Bitpay(new Key(), s.ServerUri); @@ -503,7 +503,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("ApprovePairing")).Click(); await client.CreateInvoiceAsync( - new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true }, + new NBitpayClient.Invoice() { Price = 1.000000012m, Currency = "USD", FullNotifications = true }, NBitpayClient.Facade.Merchant); s.Driver.Navigate().GoToUrl(s.Link("/api-tokens")); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index d725edf19..3666bccbf 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -3004,15 +3004,31 @@ namespace BTCPayServer.Tests [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] + [Trait("Lightning", "Lightning")] public async Task CanCreateStrangeInvoice() { using (var tester = ServerTester.Create()) { + tester.ActivateLightning(); await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + user.GrantAccess(true); user.RegisterDerivationScheme("BTC"); + DateTimeOffset expiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(21); + + // This should fail, the amount is too low to be above the dust limit of bitcoin + var ex = Assert.Throws(() => user.BitPay.CreateInvoice( + new Invoice() + { + Price = 0.000000012m, + Currency = "USD", + FullNotifications = true, + ExpirationTime = expiration + }, Facade.Merchant)); + Assert.Contains("dust threshold", ex.Message); + await user.RegisterLightningNodeAsync("BTC"); + var invoice1 = user.BitPay.CreateInvoice( new Invoice() { @@ -3021,6 +3037,7 @@ namespace BTCPayServer.Tests FullNotifications = true, ExpirationTime = expiration }, Facade.Merchant); + Assert.Equal(expiration.ToUnixTimeSeconds(), invoice1.ExpirationTime.ToUnixTimeSeconds()); var invoice2 = user.BitPay.CreateInvoice(new Invoice() { Price = 0.000000019m, Currency = "USD" }, Facade.Merchant); diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index dd86838f1..ee5c6d52e 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -1,4 +1,4 @@ - + diff --git a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs index 057f19c59..a840af0ca 100644 --- a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs @@ -136,7 +136,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination) { txout.ScriptPubKey = bitcoinLikeClaimDestination.Address.ScriptPubKey; - return Task.FromResult(txout.GetDustThreshold(new FeeRate(1.0m)).ToDecimal(MoneyUnit.BTC)); + return Task.FromResult(txout.GetDustThreshold().ToDecimal(MoneyUnit.BTC)); } return Task.FromResult(0m); diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs index a2d99c0ec..3f0db280e 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs @@ -190,6 +190,15 @@ namespace BTCPayServer.Payments.Bitcoin } var reserved = await prepare.ReserveAddress; + if (paymentMethod.ParentEntity.Type != InvoiceType.TopUp) + { + var txOut = network.NBitcoinNetwork.Consensus.ConsensusFactory.CreateTxOut(); + txOut.ScriptPubKey = reserved.Address.ScriptPubKey; + var dust = txOut.GetDustThreshold(); + var amount = paymentMethod.Calculate().Due; + if (amount < dust) + throw new PaymentMethodUnavailableException("Amount below the dust threshold. For amounts of this size, it is recommended to enable an off-chain (Lightning) payment method"); + } onchainMethod.DepositAddress = reserved.Address.ToString(); onchainMethod.KeyPath = reserved.KeyPath; onchainMethod.PayjoinEnabled = blob.PayJoinEnabled && diff --git a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs index c90e4c311..808edeb87 100644 --- a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs +++ b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs @@ -424,7 +424,7 @@ namespace BTCPayServer.Payments.PayJoin { var outputContribution = Money.Min(additionalFee, -due); outputContribution = Money.Min(outputContribution, - newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold(minRelayTxFee)); + newTx.Outputs[i].Value - newTx.Outputs[i].GetDustThreshold()); newTx.Outputs[i].Value -= outputContribution; additionalFee -= outputContribution; due += outputContribution; @@ -437,7 +437,7 @@ namespace BTCPayServer.Payments.PayJoin { var outputContribution = Money.Min(additionalFee, feeOutput.Value); outputContribution = Money.Min(outputContribution, - feeOutput.Value - feeOutput.GetDustThreshold(minRelayTxFee)); + feeOutput.Value - feeOutput.GetDustThreshold()); outputContribution = Money.Min(outputContribution, allowedSenderFeeContribution); feeOutput.Value -= outputContribution; additionalFee -= outputContribution;