2022-02-05 19:19:42 -08:00
#nullable enable
2020-06-28 21:44:35 -05:00
using System ;
2018-02-26 00:48:12 +09:00
using System.Linq ;
2020-06-28 17:55:27 +09:00
using System.Threading ;
2018-02-26 00:48:12 +09:00
using System.Threading.Tasks ;
2022-02-21 15:46:43 +01:00
using BTCPayServer.Abstractions.Constants ;
2022-03-02 18:28:12 +01:00
using BTCPayServer.Abstractions.Extensions ;
2022-01-19 03:52:05 +01:00
using BTCPayServer.Configuration ;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Data ;
using BTCPayServer.Lightning ;
2021-10-25 08:18:02 +02:00
using BTCPayServer.Logging ;
2022-01-19 03:52:05 +01:00
using BTCPayServer.Models ;
2018-02-26 00:48:12 +09:00
using BTCPayServer.Models.StoreViewModels ;
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Lightning ;
2020-06-28 17:55:27 +09:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.DependencyInjection ;
2018-02-26 00:48:12 +09:00
namespace BTCPayServer.Controllers
{
2022-01-07 12:32:00 +09:00
public partial class UIStoresController
2018-02-26 00:48:12 +09:00
{
2022-01-19 03:52:05 +01:00
private readonly ExternalServiceTypes [ ] _externalServiceTypes =
{
ExternalServiceTypes . Spark ,
ExternalServiceTypes . RTL ,
ExternalServiceTypes . ThunderHub
} ;
private readonly string [ ] _externalServiceNames =
{
"Lightning Terminal"
} ;
2021-03-31 13:23:36 +02:00
[HttpGet("{storeId}/lightning/{cryptoCode}")]
2022-05-24 13:18:16 +09:00
public IActionResult Lightning ( string storeId , string cryptoCode )
2022-01-19 03:52:05 +01:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
var vm = new LightningViewModel
{
CryptoCode = cryptoCode ,
StoreId = storeId
} ;
2022-05-24 13:18:16 +09:00
SetExistingValues ( store , vm ) ;
2022-01-19 03:52:05 +01:00
if ( vm . LightningNodeType = = LightningNodeType . Internal )
{
var services = _externalServiceOptions . Value . ExternalServices . ToList ( )
. Where ( service = > _externalServiceTypes . Contains ( service . Type ) )
2022-05-04 13:10:01 +02:00
. Select ( async service = >
2022-01-19 03:52:05 +01:00
{
2022-05-04 13:10:01 +02:00
var model = new AdditionalServiceViewModel
{
DisplayName = service . DisplayName ,
ServiceName = service . ServiceName ,
CryptoCode = service . CryptoCode ,
Type = service . Type . ToString ( )
} ;
try
{
model . Link = await GetServiceLink ( service ) ;
}
catch ( Exception exception )
{
model . Error = exception . Message ;
}
return model ;
2022-01-19 03:52:05 +01:00
} )
2022-04-11 10:49:28 +02:00
. Select ( t = > t . Result )
2022-01-19 03:52:05 +01:00
. ToList ( ) ;
// other services
foreach ( ( string key , Uri value ) in _externalServiceOptions . Value . OtherExternalServices )
{
if ( _externalServiceNames . Contains ( key ) )
{
services . Add ( new AdditionalServiceViewModel
{
DisplayName = key ,
ServiceName = key ,
Type = key . Replace ( " " , "" ) ,
Link = Request . GetAbsoluteUriNoPathBase ( value ) . AbsoluteUri
} ) ;
}
}
vm . Services = services ;
}
return View ( vm ) ;
}
2022-04-11 10:49:28 +02:00
2022-01-19 03:52:05 +01:00
[HttpGet("{storeId}/lightning/{cryptoCode}/setup")]
2022-05-24 13:18:16 +09:00
public IActionResult SetupLightningNode ( string storeId , string cryptoCode )
2018-02-26 00:48:12 +09:00
{
2018-04-30 02:33:42 +09:00
var store = HttpContext . GetStoreData ( ) ;
2018-02-26 00:48:12 +09:00
if ( store = = null )
return NotFound ( ) ;
2021-03-31 13:23:36 +02:00
var vm = new LightningNodeViewModel
2018-07-27 13:37:16 +02:00
{
CryptoCode = cryptoCode ,
2019-01-07 09:52:27 +01:00
StoreId = storeId
2018-07-27 13:37:16 +02:00
} ;
2022-05-24 13:18:16 +09:00
SetExistingValues ( store , vm ) ;
2018-02-26 00:48:12 +09:00
return View ( vm ) ;
}
2022-01-19 03:52:05 +01:00
[HttpPost("{storeId}/lightning/{cryptoCode}/setup")]
2021-04-19 16:21:50 +02:00
public async Task < IActionResult > SetupLightningNode ( string storeId , LightningNodeViewModel vm , string command , string cryptoCode )
2018-02-26 00:48:12 +09:00
{
2018-03-21 02:48:11 +09:00
vm . CryptoCode = cryptoCode ;
2018-04-30 02:33:42 +09:00
var store = HttpContext . GetStoreData ( ) ;
2018-02-26 00:48:12 +09:00
if ( store = = null )
return NotFound ( ) ;
2018-03-21 02:09:25 +09:00
2022-05-24 13:18:16 +09:00
vm . CanUseInternalNode = CanUseInternalLightning ( ) ;
2021-03-31 13:23:36 +02:00
2022-02-05 19:19:42 -08:00
if ( vm . CryptoCode = = null )
2018-02-26 00:48:12 +09:00
{
2018-03-21 02:48:11 +09:00
ModelState . AddModelError ( nameof ( vm . CryptoCode ) , "Invalid network" ) ;
2018-02-26 00:48:12 +09:00
return View ( vm ) ;
}
2022-02-05 19:19:42 -08:00
var network = _ExplorerProvider . GetNetwork ( vm . CryptoCode ) ;
2021-03-31 13:23:36 +02:00
var paymentMethodId = new PaymentMethodId ( network . CryptoCode , PaymentTypes . LightningLike ) ;
2021-04-16 15:31:09 +02:00
2022-02-05 19:19:42 -08:00
LightningSupportedPaymentMethod ? paymentMethod = null ;
2021-03-31 13:23:36 +02:00
if ( vm . LightningNodeType = = LightningNodeType . Internal )
2021-03-02 11:11:58 +09:00
{
2022-05-24 13:18:16 +09:00
if ( ! CanUseInternalLightning ( ) )
2021-03-02 11:11:58 +09:00
{
2021-03-31 13:23:36 +02:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "You are not authorized to use the internal lightning node" ) ;
2021-03-02 11:11:58 +09:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
paymentMethod = new LightningSupportedPaymentMethod
2021-03-02 11:11:58 +09:00
{
2021-10-29 08:25:43 +02:00
CryptoCode = paymentMethodId . CryptoCode
2021-03-02 11:11:58 +09:00
} ;
paymentMethod . SetInternalNode ( ) ;
}
2021-03-31 13:23:36 +02:00
else
2018-02-26 00:48:12 +09:00
{
2021-03-31 13:23:36 +02:00
if ( string . IsNullOrEmpty ( vm . ConnectionString ) )
{
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "Please provide a connection string" ) ;
return View ( vm ) ;
}
2018-07-01 15:45:08 +09:00
if ( ! LightningConnectionString . TryParse ( vm . ConnectionString , false , out var connectionString , out var error ) )
2018-02-26 00:48:12 +09:00
{
2018-07-01 15:45:08 +09:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , $"Invalid URL ({error})" ) ;
2018-02-26 00:48:12 +09:00
return View ( vm ) ;
}
2020-06-28 17:55:27 +09:00
if ( connectionString . ConnectionType = = LightningConnectionType . LndGRPC )
2018-07-23 11:11:00 +09:00
{
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , $"BTCPay does not support gRPC connections" ) ;
return View ( vm ) ;
}
2021-03-02 11:11:58 +09:00
if ( ! User . IsInRole ( Roles . ServerAdmin ) & & ! connectionString . IsSafe ( ) )
2018-07-10 12:46:04 +09:00
{
2021-03-31 13:23:36 +02:00
ModelState . AddModelError ( nameof ( vm . ConnectionString ) , "You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'." ) ;
2018-02-26 18:58:02 +09:00
return View ( vm ) ;
}
2021-03-31 13:23:36 +02:00
paymentMethod = new LightningSupportedPaymentMethod
2018-02-26 00:48:12 +09:00
{
2021-10-29 08:25:43 +02:00
CryptoCode = paymentMethodId . CryptoCode
2018-02-26 00:48:12 +09:00
} ;
2018-03-21 00:31:19 +09:00
paymentMethod . SetLightningUrl ( connectionString ) ;
2018-02-26 00:48:12 +09:00
}
2018-06-23 22:03:51 -05:00
2018-07-27 13:37:16 +02:00
switch ( command )
2018-02-26 00:48:12 +09:00
{
2018-07-27 13:37:16 +02:00
case "save" :
2021-10-25 08:18:02 +02:00
var lnurl = new PaymentMethodId ( vm . CryptoCode , PaymentTypes . LNURLPay ) ;
2019-05-09 16:11:09 +09:00
store . SetSupportedPaymentMethod ( paymentMethodId , paymentMethod ) ;
2022-01-25 04:17:39 +01:00
store . SetSupportedPaymentMethod ( lnurl , new LNURLPaySupportedPaymentMethod ( )
{
CryptoCode = vm . CryptoCode ,
UseBech32Scheme = true ,
EnableForStandardInvoices = false ,
LUD12Enabled = false
} ) ;
2021-10-25 08:18:02 +02:00
2018-07-27 13:37:16 +02:00
await _Repo . UpdateStore ( store ) ;
2021-04-20 09:09:32 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning node updated." ;
2022-01-19 12:58:02 +01:00
return RedirectToAction ( nameof ( LightningSettings ) , new { storeId , cryptoCode } ) ;
2021-03-31 13:23:36 +02:00
2018-07-27 13:37:16 +02:00
case "test" :
2019-05-29 09:43:50 +00:00
var handler = _ServiceProvider . GetRequiredService < LightningLikePaymentHandler > ( ) ;
2018-07-27 13:37:16 +02:00
try
2018-04-09 16:25:31 +09:00
{
2021-11-04 23:26:29 +09:00
var info = await handler . GetNodeInfo ( paymentMethod , network , new InvoiceLogs ( ) , Request . IsOnion ( ) , true ) ;
2018-07-27 13:37:16 +02:00
if ( ! vm . SkipPortTest )
2018-04-09 16:25:31 +09:00
{
2021-03-31 13:23:36 +02:00
using var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 20 ) ) ;
2021-09-23 13:36:42 +02:00
await handler . TestConnection ( info . First ( ) , cts . Token ) ;
2018-04-09 16:25:31 +09:00
}
2021-09-23 13:36:42 +02:00
TempData [ WellKnownTempData . SuccessMessage ] = $"Connection to the Lightning node successful. Your node address: {info.First()}" ;
2018-07-27 13:37:16 +02:00
}
catch ( Exception ex )
{
2019-10-31 12:29:59 +09:00
TempData [ WellKnownTempData . ErrorMessage ] = ex . Message ;
2018-07-27 13:37:16 +02:00
return View ( vm ) ;
2018-04-09 16:25:31 +09:00
}
2018-02-26 00:48:12 +09:00
return View ( vm ) ;
2021-03-31 13:23:36 +02:00
2018-07-27 13:37:16 +02:00
default :
return View ( vm ) ;
2018-02-26 00:48:12 +09:00
}
}
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
[HttpGet("{storeId}/lightning/{cryptoCode}/settings")]
2022-05-24 13:18:16 +09:00
public IActionResult LightningSettings ( string storeId , string cryptoCode )
2021-10-29 08:25:43 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
var storeBlob = store . GetStoreBlob ( ) ;
2022-01-19 12:58:02 +01:00
var excludeFilters = storeBlob . GetExcludedPaymentMethods ( ) ;
var lightning = GetExistingLightningSupportedPaymentMethod ( cryptoCode , store ) ;
2022-02-05 19:19:42 -08:00
if ( lightning = = null )
{
TempData [ WellKnownTempData . ErrorMessage ] = $"You need to connect to a Lightning node before adjusting its settings." ;
return RedirectToAction ( nameof ( SetupLightningNode ) , new { storeId , cryptoCode } ) ;
}
2021-10-29 08:25:43 +02:00
var vm = new LightningSettingsViewModel
{
CryptoCode = cryptoCode ,
StoreId = storeId ,
2022-01-19 12:58:02 +01:00
Enabled = ! excludeFilters . Match ( lightning . PaymentId ) ,
2021-10-29 08:25:43 +02:00
LightningDescriptionTemplate = storeBlob . LightningDescriptionTemplate ,
LightningAmountInSatoshi = storeBlob . LightningAmountInSatoshi ,
LightningPrivateRouteHints = storeBlob . LightningPrivateRouteHints ,
OnChainWithLnInvoiceFallback = storeBlob . OnChainWithLnInvoiceFallback
} ;
2022-05-24 13:18:16 +09:00
SetExistingValues ( store , vm ) ;
2021-12-31 16:59:02 +09:00
2022-02-05 19:19:42 -08:00
if ( lightning ! = null )
2021-10-29 08:25:43 +02:00
{
vm . DisableBolt11PaymentMethod = lightning . DisableBOLT11PaymentOption ;
}
var lnurl = GetExistingLNURLSupportedPaymentMethod ( vm . CryptoCode , store ) ;
if ( lnurl ! = null )
{
vm . LNURLEnabled = ! store . GetStoreBlob ( ) . GetExcludedPaymentMethods ( ) . Match ( lnurl . PaymentId ) ;
vm . LNURLBech32Mode = lnurl . UseBech32Scheme ;
vm . LNURLStandardInvoiceEnabled = lnurl . EnableForStandardInvoices ;
vm . LUD12Enabled = lnurl . LUD12Enabled ;
vm . DisableBolt11PaymentMethod =
vm . LNURLEnabled & & vm . LNURLStandardInvoiceEnabled & & vm . DisableBolt11PaymentMethod ;
}
else
{
//disable by default for now
//vm.LNURLEnabled = !lnSet;
vm . DisableBolt11PaymentMethod = false ;
}
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
return View ( vm ) ;
}
[HttpPost("{storeId}/lightning/{cryptoCode}/settings")]
public async Task < IActionResult > LightningSettings ( LightningSettingsViewModel vm )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
2022-02-05 19:19:42 -08:00
if ( vm . CryptoCode = = null )
2021-10-29 08:25:43 +02:00
{
ModelState . AddModelError ( nameof ( vm . CryptoCode ) , "Invalid network" ) ;
return View ( vm ) ;
}
2021-12-31 16:59:02 +09:00
2022-02-05 19:19:42 -08:00
var network = _ExplorerProvider . GetNetwork ( vm . CryptoCode ) ;
2021-10-29 08:25:43 +02:00
var needUpdate = false ;
var blob = store . GetStoreBlob ( ) ;
blob . LightningDescriptionTemplate = vm . LightningDescriptionTemplate ? ? string . Empty ;
blob . LightningAmountInSatoshi = vm . LightningAmountInSatoshi ;
blob . LightningPrivateRouteHints = vm . LightningPrivateRouteHints ;
blob . OnChainWithLnInvoiceFallback = vm . OnChainWithLnInvoiceFallback ;
var disableBolt11PaymentMethod =
vm . LNURLEnabled & & vm . LNURLStandardInvoiceEnabled & & vm . DisableBolt11PaymentMethod ;
var lnurlId = new PaymentMethodId ( vm . CryptoCode , PaymentTypes . LNURLPay ) ;
blob . SetExcluded ( lnurlId , ! vm . LNURLEnabled ) ;
var lightning = GetExistingLightningSupportedPaymentMethod ( vm . CryptoCode , store ) ;
2022-02-05 19:19:42 -08:00
// Going to mark "lightning" as non-null here assuming that if we are POSTing here it's because we have a Lightning Node set-up
if ( lightning ! . DisableBOLT11PaymentOption ! = disableBolt11PaymentMethod )
2021-10-29 08:25:43 +02:00
{
needUpdate = true ;
lightning . DisableBOLT11PaymentOption = disableBolt11PaymentMethod ;
store . SetSupportedPaymentMethod ( lightning ) ;
}
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
var lnurl = GetExistingLNURLSupportedPaymentMethod ( vm . CryptoCode , store ) ;
2021-10-29 11:27:36 +02:00
if ( lnurl is null | | (
2021-12-31 16:59:02 +09:00
lnurl . EnableForStandardInvoices ! = vm . LNURLStandardInvoiceEnabled | |
lnurl . UseBech32Scheme ! = vm . LNURLBech32Mode | |
2021-10-29 08:25:43 +02:00
lnurl . LUD12Enabled ! = vm . LUD12Enabled ) )
{
needUpdate = true ;
}
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
store . SetSupportedPaymentMethod ( new LNURLPaySupportedPaymentMethod
{
CryptoCode = vm . CryptoCode ,
EnableForStandardInvoices = vm . LNURLStandardInvoiceEnabled ,
UseBech32Scheme = vm . LNURLBech32Mode ,
LUD12Enabled = vm . LUD12Enabled
} ) ;
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
if ( store . SetStoreBlob ( blob ) )
{
needUpdate = true ;
}
2021-12-31 16:59:02 +09:00
2021-10-29 08:25:43 +02:00
if ( needUpdate )
{
await _Repo . UpdateStore ( store ) ;
2022-01-19 12:58:02 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning settings successfully updated." ;
2021-10-29 08:25:43 +02:00
}
2022-01-19 12:58:02 +01:00
return RedirectToAction ( nameof ( LightningSettings ) , new { vm . StoreId , vm . CryptoCode } ) ;
2021-10-29 08:25:43 +02:00
}
2021-03-31 13:23:36 +02:00
2021-04-16 15:31:09 +02:00
[HttpPost("{storeId}/lightning/{cryptoCode}/status")]
public async Task < IActionResult > SetLightningNodeEnabled ( string storeId , string cryptoCode , bool enabled )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
return NotFound ( ) ;
2022-02-05 19:19:42 -08:00
if ( cryptoCode = = null )
2021-04-16 15:31:09 +02:00
return NotFound ( ) ;
2022-02-05 19:19:42 -08:00
var network = _ExplorerProvider . GetNetwork ( cryptoCode ) ;
2021-04-16 15:31:09 +02:00
var lightning = GetExistingLightningSupportedPaymentMethod ( cryptoCode , store ) ;
if ( lightning = = null )
return NotFound ( ) ;
2021-04-20 09:10:23 +02:00
var paymentMethodId = new PaymentMethodId ( network . CryptoCode , PaymentTypes . LightningLike ) ;
var storeBlob = store . GetStoreBlob ( ) ;
storeBlob . SetExcluded ( paymentMethodId , ! enabled ) ;
2021-11-08 08:51:21 +01:00
if ( ! enabled )
{
storeBlob . SetExcluded ( new PaymentMethodId ( network . CryptoCode , PaymentTypes . LNURLPay ) , true ) ;
}
2021-04-20 09:10:23 +02:00
store . SetStoreBlob ( storeBlob ) ;
await _Repo . UpdateStore ( store ) ;
TempData [ WellKnownTempData . SuccessMessage ] = $"{network.CryptoCode} Lightning payments are now {(enabled ? " enabled " : " disabled ")} for this store." ;
2021-04-16 15:31:09 +02:00
2022-01-19 12:58:02 +01:00
return RedirectToAction ( nameof ( LightningSettings ) , new { storeId , cryptoCode } ) ;
2021-04-16 15:31:09 +02:00
}
2022-05-24 13:18:16 +09:00
private bool CanUseInternalLightning ( )
2018-02-26 18:58:02 +09:00
{
2022-05-24 13:18:16 +09:00
return User . IsInRole ( Roles . ServerAdmin ) | | _policiesSettings . AllowLightningInternalNodeForAll ;
2018-02-26 18:58:02 +09:00
}
2021-03-31 13:23:36 +02:00
2022-05-24 13:18:16 +09:00
private void SetExistingValues ( StoreData store , LightningNodeViewModel vm )
2021-03-31 13:23:36 +02:00
{
2022-05-24 13:18:16 +09:00
vm . CanUseInternalNode = CanUseInternalLightning ( ) ;
2021-03-31 13:23:36 +02:00
var lightning = GetExistingLightningSupportedPaymentMethod ( vm . CryptoCode , store ) ;
2021-10-25 08:18:02 +02:00
2022-02-05 19:19:42 -08:00
if ( lightning ! = null )
2021-03-31 13:23:36 +02:00
{
vm . LightningNodeType = lightning . IsInternalNode ? LightningNodeType . Internal : LightningNodeType . Custom ;
vm . ConnectionString = lightning . GetDisplayableConnectionString ( ) ;
}
2021-04-19 16:21:50 +02:00
else
{
vm . LightningNodeType = vm . CanUseInternalNode ? LightningNodeType . Internal : LightningNodeType . Custom ;
}
2021-03-31 13:23:36 +02:00
}
2022-02-05 19:19:42 -08:00
private LightningSupportedPaymentMethod ? GetExistingLightningSupportedPaymentMethod ( string cryptoCode , StoreData store )
2021-03-31 13:23:36 +02:00
{
var id = new PaymentMethodId ( cryptoCode , PaymentTypes . LightningLike ) ;
var existing = store . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < LightningSupportedPaymentMethod > ( )
. FirstOrDefault ( d = > d . PaymentId = = id ) ;
return existing ;
}
2022-04-11 10:49:28 +02:00
2022-02-05 19:19:42 -08:00
private LNURLPaySupportedPaymentMethod ? GetExistingLNURLSupportedPaymentMethod ( string cryptoCode , StoreData store )
2021-10-25 08:18:02 +02:00
{
var id = new PaymentMethodId ( cryptoCode , PaymentTypes . LNURLPay ) ;
var existing = store . GetSupportedPaymentMethods ( _NetworkProvider )
. OfType < LNURLPaySupportedPaymentMethod > ( )
. FirstOrDefault ( d = > d . PaymentId = = id ) ;
return existing ;
}
2022-04-11 10:49:28 +02:00
private async Task < string > GetServiceLink ( ExternalService service )
{
var connectionString = await service . ConnectionString . Expand ( Request . GetAbsoluteUriNoPathBase ( ) , service . Type , _BtcpayServerOptions . NetworkType ) ;
var tokenParam = service . Type = = ExternalServiceTypes . ThunderHub ? "token" : "access-key" ;
return $"{connectionString.Server}?{tokenParam}={connectionString.AccessKey}" ;
}
2018-02-26 00:48:12 +09:00
}
}