Use PaymentUrlBuilder for ensuring proper formatting of BIP21 addresses (#2723)

This commit is contained in:
Nicolas Dorier
2021-07-30 18:47:02 +09:00
committed by GitHub
parent 4c57405945
commit a9da79cc58
7 changed files with 56 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
#if ALTCOINS #if ALTCOINS
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BTCPayServer.Common;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
using NBXplorer.Models; using NBXplorer.Models;
@@ -33,13 +34,15 @@ namespace BTCPayServer
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId)); output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
} }
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue) public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{ {
//precision 0: 10 = 0.00000010 //precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000 //precision 2: 10 = 0.00001000
//precision 8: 10 = 10 //precision 8: 10 = 10
var money = cryptoInfoDue is null? null: new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC); var money = cryptoInfoDue is null ? null : new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}{(money is null? "?": "&")}assetid={AssetId}"; var builder = base.GenerateBIP21(cryptoInfoAddress, money);
builder.QueryParams.Add("assetid", AssetId.ToString());
return builder;
} }
} }
} }

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using BTCPayServer.Common;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
using NBXplorer.Models; using NBXplorer.Models;
@@ -121,9 +122,15 @@ namespace BTCPayServer
}); });
} }
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue) public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{ {
return $"{UriScheme}:{cryptoInfoAddress}{(cryptoInfoDue is null? string.Empty: $"?amount={cryptoInfoDue.ToString(false, true)}")}"; var builder = new PaymentUrlBuilder(UriScheme);
builder.Host = cryptoInfoAddress;
if (cryptoInfoDue != null && cryptoInfoDue != Money.Zero)
{
builder.QueryParams.Add("amount", cryptoInfoDue.ToString(false, true));
}
return builder;
} }
public virtual List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet) public virtual List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)

View File

@@ -0,0 +1,30 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BTCPayServer.Common
{
public class PaymentUrlBuilder
{
public PaymentUrlBuilder(string uriScheme)
{
UriScheme = uriScheme;
}
public string UriScheme { get; set; }
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
public string? Host { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder($"{UriScheme}:{Host}");
if (QueryParams.Count != 0)
{
var parts = QueryParams.Select(q => Uri.EscapeDataString(q.Key) + "=" + System.Web.NBitcoin.HttpUtility.UrlEncode(q.Value))
.ToArray();
builder.Append($"?{string.Join('&', parts)}");
}
return builder.ToString();
}
}
}

View File

@@ -127,17 +127,16 @@ namespace BTCPayServer.Controllers.GreenField
return BadRequest(); return BadRequest();
} }
var bip21 = network.GenerateBIP21(kpi.Address.ToString(), null); var bip21 = network.GenerateBIP21(kpi.Address?.ToString(), null);
var allowedPayjoin = derivationScheme.IsHotWallet && Store.GetStoreBlob().PayJoinEnabled; var allowedPayjoin = derivationScheme.IsHotWallet && Store.GetStoreBlob().PayJoinEnabled;
if (allowedPayjoin) if (allowedPayjoin)
{ {
bip21 += bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey, Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new { cryptoCode })));
$"?{PayjoinClient.BIP21EndpointKey}={Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new {cryptoCode}))}";
} }
return Ok(new OnChainWalletAddressData() return Ok(new OnChainWalletAddressData()
{ {
Address = kpi.Address.ToString(), Address = kpi.Address?.ToString(),
PaymentLink = bip21, PaymentLink = bip21.ToString(),
KeyPath = kpi.KeyPath KeyPath = kpi.KeyPath
}); });
} }

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Common;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders; using BTCPayServer.ModelBinders;
@@ -285,7 +286,7 @@ namespace BTCPayServer.Controllers
continue; continue;
} }
var blob = payout.GetBlob(_jsonSerializerSettings); var blob = payout.GetBlob(_jsonSerializerSettings);
bip21.Add(network.GenerateBIP21(payout.Destination, new Money(blob.CryptoAmount.Value, MoneyUnit.BTC))); bip21.Add(network.GenerateBIP21(payout.Destination, new Money(blob.CryptoAmount.Value, MoneyUnit.BTC)).ToString());
} }
if(bip21.Any()) if(bip21.Any())

View File

@@ -368,18 +368,17 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
var address = _walletReceiveService.Get(walletId)?.Address; var address = _walletReceiveService.Get(walletId)?.Address;
var allowedPayjoin = paymentMethod.IsHotWallet && CurrentStore.GetStoreBlob().PayJoinEnabled; var allowedPayjoin = paymentMethod.IsHotWallet && CurrentStore.GetStoreBlob().PayJoinEnabled;
var bip21 = address is null ? null : network.GenerateBIP21(address.ToString(), null); var bip21 = network.GenerateBIP21(address?.ToString(), null);
if (allowedPayjoin) if (allowedPayjoin)
{ {
bip21 += bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey, Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new { walletId.CryptoCode })));
$"?{PayjoinClient.BIP21EndpointKey}={Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new {walletId.CryptoCode}))}";
} }
return View(new WalletReceiveViewModel() return View(new WalletReceiveViewModel()
{ {
CryptoCode = walletId.CryptoCode, CryptoCode = walletId.CryptoCode,
Address = address?.ToString(), Address = address?.ToString(),
CryptoImage = GetImage(paymentMethod.PaymentId, network), CryptoImage = GetImage(paymentMethod.PaymentId, network),
PaymentLink = bip21 PaymentLink = bip21.ToString()
}); });
} }

View File

@@ -79,9 +79,9 @@ namespace BTCPayServer.Payments
if ((paymentMethodDetails as BitcoinLikeOnChainPaymentMethod)?.PayjoinEnabled is true && serverUri != null) if ((paymentMethodDetails as BitcoinLikeOnChainPaymentMethod)?.PayjoinEnabled is true && serverUri != null)
{ {
bip21 += $"&{PayjoinClient.BIP21EndpointKey}={serverUri.WithTrailingSlash()}{network.CryptoCode}/{PayjoinClient.BIP21EndpointKey}"; bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey, $"{serverUri.WithTrailingSlash()}{network.CryptoCode}/{PayjoinClient.BIP21EndpointKey}");
} }
return bip21; return bip21.ToString();
} }
public override string InvoiceViewPaymentPartialName { get; } = "Bitcoin/ViewBitcoinLikePaymentData"; public override string InvoiceViewPaymentPartialName { get; } = "Bitcoin/ViewBitcoinLikePaymentData";