2024-05-06 14:33:44 +02:00
using System ;
using System.Diagnostics.CodeAnalysis ;
2024-05-08 12:16:25 +02:00
using System.Linq ;
2024-05-06 14:33:44 +02:00
using System.Threading ;
2024-05-08 12:16:25 +02:00
using System.Threading.Channels ;
2024-05-06 14:33:44 +02:00
using System.Threading.Tasks ;
using BTCPayApp.CommonServer ;
2024-05-06 16:13:57 +02:00
using BTCPayServer.Client.Models ;
2024-05-06 14:33:44 +02:00
using BTCPayServer.Controllers ;
using BTCPayServer.Lightning ;
using Microsoft.AspNetCore.SignalR ;
using NBitcoin ;
2024-05-08 12:16:25 +02:00
using LightningPayment = BTCPayApp . CommonServer . LightningPayment ;
2024-05-06 14:33:44 +02:00
namespace BTCPayServer.App ;
public class BTCPayAppLightningConnectionStringHandler : ILightningConnectionStringHandler
{
private readonly IHubContext < BTCPayAppHub , IBTCPayAppHubClient > _hubContext ;
private readonly BTCPayAppState _appState ;
private readonly DefaultHubLifetimeManager < BTCPayAppHub > _lifetimeManager ;
public BTCPayAppLightningConnectionStringHandler ( IHubContext < BTCPayAppHub , IBTCPayAppHubClient > hubContext , BTCPayAppState appState )
{
_hubContext = hubContext ;
_appState = appState ;
}
public ILightningClient Create ( string connectionString , Network network , [ UnscopedRef ] out string error )
{
var kv = LightningConnectionStringHelper . ExtractValues ( connectionString , out var type ) ;
if ( type ! = "app" )
{
error = null ;
return null ;
}
if ( ! kv . TryGetValue ( "group" , out var key ) )
{
error = $"The key 'group' is mandatory for app connection strings" ;
return null ;
}
if ( ! _appState . GroupToConnectionId . TryGetValue ( key , out var connectionId ) )
{
error = $"The group {key} is not connected" ;
return null ;
}
error = null ;
2024-05-08 12:16:25 +02:00
return new BTCPayAppLightningClient ( _hubContext , _appState , key , network ) ;
2024-05-06 14:33:44 +02:00
}
}
public class BTCPayAppLightningClient : ILightningClient
{
private readonly IHubContext < BTCPayAppHub , IBTCPayAppHubClient > _hubContext ;
private readonly BTCPayAppState _appState ;
private readonly string _key ;
2024-05-08 12:16:25 +02:00
private readonly Network _network ;
2024-05-06 14:33:44 +02:00
2024-05-08 12:16:25 +02:00
public BTCPayAppLightningClient ( IHubContext < BTCPayAppHub , IBTCPayAppHubClient > hubContext , BTCPayAppState appState , string key , Network network )
2024-05-06 14:33:44 +02:00
{
_hubContext = hubContext ;
_appState = appState ;
_key = key ;
2024-05-08 12:16:25 +02:00
_network = network ;
2024-05-06 14:33:44 +02:00
}
public override string ToString ( )
{
return $"type=app;group={_key}" ;
}
public IBTCPayAppHubClient HubClient = > _appState . GroupToConnectionId . TryGetValue ( _key , out var connId ) ? _hubContext . Clients . Client ( connId ) : throw new InvalidOperationException ( "Connection not found" ) ;
public async Task < LightningInvoice > GetInvoice ( string invoiceId , CancellationToken cancellation = new CancellationToken ( ) )
{
return await GetInvoice ( uint256 . Parse ( invoiceId ) , cancellation ) ;
}
public async Task < LightningInvoice > GetInvoice ( uint256 paymentHash , CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-08 12:16:25 +02:00
var lp = await HubClient . GetLightningInvoice ( paymentHash . ToString ( ) ) ;
return ToLightningInvoice ( lp , _network ) ;
2024-05-06 14:33:44 +02:00
}
public async Task < LightningInvoice [ ] > ListInvoices ( CancellationToken cancellation = new CancellationToken ( ) )
{
return await ListInvoices ( new ListInvoicesParams ( ) , cancellation ) ;
}
public async Task < LightningInvoice [ ] > ListInvoices ( ListInvoicesParams request , CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-08 12:16:25 +02:00
var invs = await HubClient . GetLightningInvoices ( request ) ;
return invs . Select ( i = > ToLightningInvoice ( i , _network ) ) . ToArray ( ) ;
2024-05-06 14:33:44 +02:00
}
2024-05-08 12:16:25 +02:00
public async Task < Lightning . LightningPayment > GetPayment ( string paymentHash , CancellationToken cancellation = new CancellationToken ( ) )
2024-05-06 14:33:44 +02:00
{
2024-05-08 12:16:25 +02:00
return ToLightningPayment ( await HubClient . GetLightningPayment ( paymentHash ) ) ;
}
private static Lightning . LightningPayment ToLightningPayment ( LightningPayment lightningPayment )
{
return new Lightning . LightningPayment ( )
{
Id = lightningPayment . PaymentHash ,
Amount = LightMoney . MilliSatoshis ( lightningPayment . Value ) ,
PaymentHash = lightningPayment . PaymentHash ,
Preimage = lightningPayment . Preimage ,
BOLT11 = lightningPayment . PaymentRequests . FirstOrDefault ( ) ,
Status = lightningPayment . Status
} ;
2024-05-06 14:33:44 +02:00
}
2024-05-08 12:16:25 +02:00
public async Task < Lightning . LightningPayment [ ] > ListPayments ( CancellationToken cancellation = new CancellationToken ( ) )
2024-05-06 14:33:44 +02:00
{
return await ListPayments ( new ListPaymentsParams ( ) , cancellation ) ;
}
2024-05-08 12:16:25 +02:00
public async Task < Lightning . LightningPayment [ ] > ListPayments ( ListPaymentsParams request , CancellationToken cancellation = new CancellationToken ( ) )
2024-05-06 14:33:44 +02:00
{
2024-05-08 12:16:25 +02:00
var invs = await HubClient . GetLightningPayments ( request ) ;
return invs . Select ( ToLightningPayment ) . ToArray ( ) ;
2024-05-06 14:33:44 +02:00
}
public async Task < LightningInvoice > CreateInvoice ( LightMoney amount , string description , TimeSpan expiry ,
CancellationToken cancellation = new CancellationToken ( ) )
{
return await CreateInvoice ( new CreateInvoiceParams ( amount , description , expiry ) , cancellation ) ;
}
public async Task < LightningInvoice > CreateInvoice ( CreateInvoiceParams createInvoiceRequest , CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-06 16:13:57 +02:00
var lp = await HubClient . CreateInvoice ( new CreateLightningInvoiceRequest ( createInvoiceRequest . Amount , createInvoiceRequest . Description , createInvoiceRequest . Expiry )
{
DescriptionHashOnly = createInvoiceRequest . DescriptionHashOnly ,
PrivateRouteHints = createInvoiceRequest . PrivateRouteHints ,
} ) ;
2024-05-08 12:16:25 +02:00
return ToLightningInvoice ( lp , _network ) ;
2024-05-06 14:33:44 +02:00
}
2024-05-08 12:16:25 +02:00
private static LightningInvoice ToLightningInvoice ( LightningPayment lightningPayment , Network _network )
{
var paymenRequest = BOLT11PaymentRequest . Parse ( lightningPayment . PaymentRequests . First ( ) , _network ) ;
return new LightningInvoice ( )
{
Id = lightningPayment . PaymentHash ,
Amount = LightMoney . MilliSatoshis ( lightningPayment . Value ) ,
PaymentHash = lightningPayment . PaymentHash ,
Preimage = lightningPayment . Preimage ,
BOLT11 = lightningPayment . PaymentRequests . FirstOrDefault ( ) ,
Status = lightningPayment . Status = = LightningPaymentStatus . Complete ? LightningInvoiceStatus . Paid : paymenRequest . ExpiryDate < DateTimeOffset . UtcNow ? LightningInvoiceStatus . Expired : LightningInvoiceStatus . Unpaid
} ;
}
2024-05-06 16:13:57 +02:00
2024-05-06 14:33:44 +02:00
public async Task < ILightningInvoiceListener > Listen ( CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-24 12:11:56 +02:00
return new Listener ( _appState , _network , _key ) ;
2024-05-08 12:16:25 +02:00
}
public class Listener : ILightningInvoiceListener
{
private readonly BTCPayAppState _btcPayAppState ;
private readonly Network _network ;
2024-05-24 12:11:56 +02:00
private readonly string _key ;
2024-05-08 12:16:25 +02:00
private readonly Channel < LightningPayment > _channel = Channel . CreateUnbounded < LightningPayment > ( ) ;
2024-05-24 12:11:56 +02:00
private readonly CancellationTokenSource _cts ;
2024-05-08 12:16:25 +02:00
2024-05-24 12:11:56 +02:00
public Listener ( BTCPayAppState btcPayAppState , Network network , string key )
2024-05-08 12:16:25 +02:00
{
_btcPayAppState = btcPayAppState ;
2024-05-24 12:11:56 +02:00
btcPayAppState . GroupRemoved + = BtcPayAppStateOnGroupRemoved ;
2024-05-08 12:16:25 +02:00
_network = network ;
2024-05-24 12:11:56 +02:00
_key = key ;
_cts = new CancellationTokenSource ( ) ;
2024-05-08 12:16:25 +02:00
_btcPayAppState . OnPaymentUpdate + = BtcPayAppStateOnOnPaymentUpdate ;
}
2024-05-24 12:11:56 +02:00
private void BtcPayAppStateOnGroupRemoved ( object sender , string e )
{
if ( e = = _key )
_channel . Writer . Complete ( ) ;
}
2024-05-08 12:16:25 +02:00
private void BtcPayAppStateOnOnPaymentUpdate ( object sender , ( string , LightningPayment ) e )
{
2024-05-24 12:11:56 +02:00
if ( e . Item1 ! = _key )
return ;
2024-05-08 12:16:25 +02:00
_channel . Writer . TryWrite ( e . Item2 ) ;
}
public void Dispose ( )
{
2024-05-24 12:11:56 +02:00
_cts ? . Cancel ( ) ;
2024-05-08 12:16:25 +02:00
_btcPayAppState . OnPaymentUpdate - = BtcPayAppStateOnOnPaymentUpdate ;
2024-05-24 12:11:56 +02:00
_btcPayAppState . GroupRemoved - = BtcPayAppStateOnGroupRemoved ;
_channel . Writer . TryComplete ( ) ;
2024-05-08 12:16:25 +02:00
}
public async Task < LightningInvoice > WaitInvoice ( CancellationToken cancellation )
{
2024-05-24 12:11:56 +02:00
return ToLightningInvoice ( await _channel . Reader . ReadAsync ( CancellationTokenSource . CreateLinkedTokenSource ( cancellation , _cts . Token ) . Token ) , _network ) ;
2024-05-08 12:16:25 +02:00
}
2024-05-06 14:33:44 +02:00
}
public async Task < LightningNodeInformation > GetInfo ( CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-08 12:16:25 +02:00
throw new NotSupportedException ( ) ;
2024-05-06 14:33:44 +02:00
}
public async Task < LightningNodeBalance > GetBalance ( CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
public async Task < PayResponse > Pay ( PayInvoiceParams payParams , CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-24 12:11:56 +02:00
return await Pay ( null , payParams , cancellation ) ;
2024-05-06 14:33:44 +02:00
}
public async Task < PayResponse > Pay ( string bolt11 , PayInvoiceParams payParams , CancellationToken cancellation = new CancellationToken ( ) )
{
2024-05-24 12:11:56 +02:00
return await HubClient . PayInvoice ( bolt11 , payParams . Amount ? . MilliSatoshi ) ;
2024-05-06 14:33:44 +02:00
}
public async Task < PayResponse > Pay ( string bolt11 , CancellationToken cancellation = new CancellationToken ( ) )
{
return await Pay ( bolt11 , new PayInvoiceParams ( ) , cancellation ) ;
}
public async Task < OpenChannelResponse > OpenChannel ( OpenChannelRequest openChannelRequest , CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
public async Task < BitcoinAddress > GetDepositAddress ( CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
public async Task < ConnectionResult > ConnectTo ( NodeInfo nodeInfo , CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
public async Task CancelInvoice ( string invoiceId , CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
public async Task < LightningChannel [ ] > ListChannels ( CancellationToken cancellation = new CancellationToken ( ) )
{
throw new NotImplementedException ( ) ;
}
}