This commit is contained in:
Kukks
2024-10-10 11:32:00 +02:00
parent c768d7000b
commit 9b90e10b66
108 changed files with 757 additions and 1350 deletions

View File

@@ -83,8 +83,8 @@ Global
{B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B19C9F52-DC47-466D-8B5C-2D202B7B003F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD9635BB-C70E-4676-BB04-900D51B01666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD9635BB-C70E-4676-BB04-900D51B01666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD9635BB-C70E-4676-BB04-900D51B01666}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD9635BB-C70E-4676-BB04-900D51B01666}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD9635BB-C70E-4676-BB04-900D51B01666}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD9635BB-C70E-4676-BB04-900D51B01666}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -99,48 +99,48 @@ Global
{8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {8F158B88-0FEE-44FF-8552-7C0F17D5C508}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Release|Any CPU.Build.0 = Release|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Release|Any CPU.Build.0 = Release|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {DF85EFA4-0EF5-4A99-853F-E6F9C88E3F8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Release|Any CPU.ActiveCfg = Release|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Release|Any CPU.Build.0 = Release|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Release|Any CPU.Build.0 = Release|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {2C5C4DF9-BA1F-4671-9F24-B22D7C9C3D21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Release|Any CPU.Build.0 = Release|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Release|Any CPU.Build.0 = Release|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {D7E7309D-C4F4-496A-B2C8-BC5D3991B9C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Release|Any CPU.Build.0 = Release|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Release|Any CPU.Build.0 = Release|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {3F2E0BA0-9EA7-490F-894D-F9703F35B174}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Release|Any CPU.ActiveCfg = Release|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Release|Any CPU.Build.0 = Release|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Release|Any CPU.Build.0 = Release|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6295533A-F941-40CA-B889-FE6C0432ED53}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {6295533A-F941-40CA-B889-FE6C0432ED53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58863D86-3C78-4BEC-ACB6-2F82CC141210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58863D86-3C78-4BEC-ACB6-2F82CC141210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58863D86-3C78-4BEC-ACB6-2F82CC141210}.Debug|Any CPU.Build.0 = Debug|Any CPU {58863D86-3C78-4BEC-ACB6-2F82CC141210}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58863D86-3C78-4BEC-ACB6-2F82CC141210}.Release|Any CPU.ActiveCfg = Release|Any CPU {58863D86-3C78-4BEC-ACB6-2F82CC141210}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -155,8 +155,8 @@ Global
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -1,7 +1,21 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayCoinjoinCoinSelector_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayCoinjoinCoinSelector_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayWallet_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayWallet_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABreezEvent_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19b2ba64aa6844efb3312f86dbfe1b9652000_003F63_003F83f9cdbb_003FBreezEvent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABreezSdkLiquidMethods_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4dbc7b5c57d04ec8a063235af9eb203a3fc00_003F7e_003F6a527844_003FBreezSdkLiquidMethods_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABreez_002ESdk_002ELiquid_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fmetadata_003F4dbc7b5c57d04ec8a063235af9eb203a3fc00_003F58_003F0584d9dc_003FBreez_002ESdk_002ELiquid_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABreez_002ESdk_002ELiquid_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fmetadata_003F4dbc7b5c57d04ec8a063235af9eb203a3fc00_003F58_003F0584d9dc_003FBreez_002ESdk_002ELiquid_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACallSiteFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff7e838f3c047ee76488fa4c2b9b1bffb2b33b04a2780de4664de937e024e97_003FCallSiteFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbSet_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F4b21fc3f8a3abb5d25f06c622276fbec0fd3932f169cc6af520bf5abc6a46b4_003FDbSet_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FLocal_003FSymbols_003Fsrc_003Fdotnet_003Fruntime_003F08338fcaa5c9b9a8190abb99222fed12aaba956c_003Fsrc_003Flibraries_003FSystem_002EPrivate_002ECoreLib_003Fsrc_003FSystem_003FRuntime_003FExceptionServices_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANostrClientPool_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F758d2546736f41ac5ee3998f9abad1f58b82254824f2532c89ddf667ed74_003FNostrClientPool_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANostrEventTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff2e2e45db36b5d75d466cd5c6134e5ce566eface233da2ec472ac018f2eb16_003FNostrEventTag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2a3105e7355248488912e34f9ff5cbebca718_003F8b_003Fd6fc9706_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullReferenceException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc092928882a17a51a62a367685d90a0a613eb91fb453b9f50147e19c1f3ff40_003FNullReferenceException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APaymentDetails_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4dbc7b5c57d04ec8a063235af9eb203a3fc00_003Fc6_003Fb76207e1_003FPaymentDetails_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APayment_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4dbc7b5c57d04ec8a063235af9eb203a3fc00_003F5b_003F56fb0a4a_003FPayment_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AToCollection_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd47ee1ab214a7674f381fb716d273cc4bb64441375b47417e9dcd5ec96f89_003FToCollection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWhere_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fevilk_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F767db1e17d404f86c3c73c54c6b20b5ff9ec43b79ef47681ae34b40a6852cf9_003FWhere_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ACoinsView_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ACoinsView_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ASmartCoin_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ASmartCoin_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -50,6 +64,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -10,7 +10,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Bitcoin Whitepaper</Product> <Product>Bitcoin Whitepaper</Product>
<Description>This makes the Bitcoin whitepaper available on your BTCPay Server.</Description> <Description>This makes the Bitcoin whitepaper available on your BTCPay Server.</Description>
<Version>1.0.4</Version> <Version>1.0.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -7,7 +7,7 @@ namespace BTCPayServer.Plugins.BitcoinWhitepaper
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
} }
} }

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Blink</Product> <Product>Blink</Product>
<Description>Blink Lightning support</Description> <Description>Blink Lightning support</Description>
<Version>1.0.8</Version> <Version>1.0.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RootNamespace>BTCPayServer.Plugins.Blink</RootNamespace> <RootNamespace>BTCPayServer.Plugins.Blink</RootNamespace>
</PropertyGroup> </PropertyGroup>

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.Blink
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"} new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Breez / Greenlight</Product> <Product>Breez / Greenlight</Product>
<Description>Lightweight lightning baby!</Description> <Description>Lightweight lightning baby!</Description>
<Version>1.0.8</Version> <Version>1.0.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
@@ -34,7 +34,7 @@
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" /> <ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Breez.Sdk" Version="0.5.2" /> <PackageReference Include="Breez.Sdk" Version="0.6.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Breez.Sdk; using Breez.Sdk;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
@@ -12,13 +11,13 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
@@ -26,15 +25,19 @@ namespace BTCPayServer.Plugins.Breez;
[Route("plugins/{storeId}/Breez")] [Route("plugins/{storeId}/Breez")]
public class BreezController : Controller public class BreezController : Controller
{ {
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BreezService _breezService; private readonly BreezService _breezService;
private readonly BTCPayWalletProvider _btcWalletProvider; private readonly BTCPayWalletProvider _btcWalletProvider;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
public BreezController(BTCPayNetworkProvider btcPayNetworkProvider, public BreezController(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayNetworkProvider btcPayNetworkProvider,
BreezService breezService, BreezService breezService,
BTCPayWalletProvider btcWalletProvider, StoreRepository storeRepository) BTCPayWalletProvider btcWalletProvider, StoreRepository storeRepository)
{ {
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_breezService = breezService; _breezService = breezService;
_btcWalletProvider = btcWalletProvider; _btcWalletProvider = btcWalletProvider;
@@ -113,7 +116,7 @@ public class BreezController : Controller
if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase)) if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase))
{ {
var store = ControllerContext.HttpContext.GetStoreData() var store = ControllerContext.HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); .GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
var res = await _btcWalletProvider.GetWallet(storeId) var res = await _btcWalletProvider.GetWallet(storeId)
.ReserveAddressAsync(storeId, store.AccountDerivation, "Breez"); .ReserveAddressAsync(storeId, store.AccountDerivation, "Breez");
address = res.Address.ToString(); address = res.Address.ToString();
@@ -264,7 +267,7 @@ public class BreezController : Controller
if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase)) if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase))
{ {
var store = ControllerContext.HttpContext.GetStoreData() var store = ControllerContext.HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); .GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
var res = await _btcWalletProvider.GetWallet(storeId) var res = await _btcWalletProvider.GetWallet(storeId)
.ReserveAddressAsync(storeId, store.AccountDerivation, "Breez"); .ReserveAddressAsync(storeId, store.AccountDerivation, "Breez");
address = res.Address.ToString(); address = res.Address.ToString();
@@ -342,10 +345,8 @@ public class BreezController : Controller
public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings) public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings)
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider).OfType<LightningSupportedPaymentMethod>() var pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
.FirstOrDefault(method => var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
method.PaymentId.PaymentType == LightningPaymentType.Instance &&
method.PaymentId.CryptoCode == "BTC");
if (command == "clear") if (command == "clear")
{ {
@@ -355,7 +356,7 @@ public class BreezController : Controller
var isStoreSetToThisMicro = existing?.GetExternalLightningUrl() == client?.ToString(); var isStoreSetToThisMicro = existing?.GetExternalLightningUrl() == client?.ToString();
if (client is not null && isStoreSetToThisMicro) if (client is not null && isStoreSetToThisMicro)
{ {
store.SetSupportedPaymentMethod(existing.PaymentId, null); store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null);
await _storeRepository.UpdateStore(store); await _storeRepository.UpdateStore(store);
} }
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Configure), new {storeId});
@@ -422,18 +423,13 @@ public class BreezController : Controller
if(existing is null) if(existing is null)
{ {
existing = new LightningSupportedPaymentMethod()
{ existing = new LightningPaymentMethodConfig();
CryptoCode = "BTC"
};
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
existing.SetLightningUrl(client); existing.SetLightningUrl(client);
store.SetSupportedPaymentMethod(existing); store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], existing);
var lnurl = new LNURLPaySupportedPaymentMethod() var lnurlPMI = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
{ store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[lnurlPMI], new LNURLPaymentMethodConfig());
CryptoCode = "BTC",
};
store.SetSupportedPaymentMethod(lnurl);
await _storeRepository.UpdateStore(store); await _storeRepository.UpdateStore(store);
} }

View File

@@ -252,7 +252,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
OffchainBalance = new OffchainBalance() OffchainBalance = new OffchainBalance()
{ {
Local = LightMoney.MilliSatoshis(ni.channelsBalanceMsat), Local = LightMoney.MilliSatoshis(ni.channelsBalanceMsat),
Remote = LightMoney.MilliSatoshis(ni.inboundLiquidityMsats), Remote = LightMoney.MilliSatoshis(ni.totalInboundLiquidityMsats),
} }
}; };
} }
@@ -275,7 +275,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
} }
else else
{ {
result = Sdk.SendPayment(new SendPaymentRequest(bolt11, (ulong?) payParams.Amount?.MilliSatoshi)); result = Sdk.SendPayment(new SendPaymentRequest(bolt11,false, (ulong?) payParams.Amount?.MilliSatoshi));
} }
var details = result.payment.details as PaymentDetails.Ln; var details = result.payment.details as PaymentDetails.Ln;

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.Breez
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.13.2" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -11,7 +11,9 @@ using BTCPayServer.Events;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NBitcoin; using NBitcoin;
@@ -22,7 +24,8 @@ public class BreezService:EventHostedServiceBase
{ {
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly IOptions<DataDirectories> _dataDirectories; private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly IServiceProvider _serviceProvider;
private PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary => _serviceProvider.GetRequiredService<PaymentMethodHandlerDictionary>();
private readonly ILogger _logger; private readonly ILogger _logger;
private Dictionary<string, BreezSettings> _settings; private Dictionary<string, BreezSettings> _settings;
private Dictionary<string, BreezLightningClient> _clients = new(); private Dictionary<string, BreezLightningClient> _clients = new();
@@ -31,12 +34,12 @@ public class BreezService:EventHostedServiceBase
EventAggregator eventAggregator, EventAggregator eventAggregator,
StoreRepository storeRepository, StoreRepository storeRepository,
IOptions<DataDirectories> dataDirectories, IOptions<DataDirectories> dataDirectories,
BTCPayNetworkProvider btcPayNetworkProvider, IServiceProvider serviceProvider,
ILogger<BreezService> logger) : base(eventAggregator, logger) ILogger<BreezService> logger) : base(eventAggregator, logger)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_dataDirectories = dataDirectories; _dataDirectories = dataDirectories;
_btcPayNetworkProvider = btcPayNetworkProvider; _serviceProvider = serviceProvider;
_logger = logger; _logger = logger;
} }
@@ -133,13 +136,13 @@ public class BreezService:EventHostedServiceBase
{ {
_settings.Remove(storeId, out var oldSettings ); _settings.Remove(storeId, out var oldSettings );
var data = await _storeRepository.FindStore(storeId); var data = await _storeRepository.FindStore(storeId);
var existing = data?.GetSupportedPaymentMethods(_btcPayNetworkProvider) var pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
.OfType<LightningSupportedPaymentMethod>().FirstOrDefault(method => var existing =
method.CryptoCode == "BTC" && method.PaymentId.PaymentType == LightningPaymentType.Instance); data?.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
var isBreez = existing?.GetExternalLightningUrl() == $"type=breez;key={oldSettings.PaymentKey}"; var isBreez = existing?.GetExternalLightningUrl() == $"type=breez;key={oldSettings.PaymentKey}";
if (isBreez) if (isBreez)
{ {
data.SetSupportedPaymentMethod(new PaymentMethodId("BTC", LightningPaymentType.Instance), null ); data.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null );
await _storeRepository.UpdateStore(data); await _storeRepository.UpdateStore(data);
} }
Directory.Delete(GetWorkDir(storeId), true); Directory.Delete(GetWorkDir(storeId), true);

View File

@@ -1,7 +1,5 @@
@using Breez.Sdk @using Breez.Sdk
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client @using BTCPayServer.Client
@using BTCPayServer.Components.QRCode @using BTCPayServer.Components.QRCode
@using BTCPayServer.Components.TruncateCenter @using BTCPayServer.Components.TruncateCenter
@@ -10,13 +8,15 @@
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin @using NBitcoin
@inject BreezService BreezService @inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject TransactionLinkProviders TransactionLinkProviders @inject TransactionLinkProviders TransactionLinkProviders
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{ @{
ViewData.SetActivePage("Breez", "Swap In", "SwapIn"); ViewData.SetActivePage("Breez", "Swap In", "SwapIn");
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
string storeId = Model switch string storeId = Model switch
{ {
string s => s, string s => s,
@@ -38,10 +38,9 @@
} }
var refundables = sdk.ListRefundables(); var refundables = sdk.ListRefundables();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var ni = sdk.NodeInfo(); var ni = sdk.NodeInfo();
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
var pmi = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
} }
<datalist id="fees"> <datalist id="fees">

View File

@@ -1,18 +1,18 @@
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.AspNetCore.Routing @using Microsoft.AspNetCore.Routing
@model string @model string
@inject BreezService BreezService @inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider @inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{ @{
var storeId = Context.GetImplicitStoreId(); var storeId = Context.GetImplicitStoreId();
var address = Context.GetRouteValue("address").ToString(); var address = Context.GetRouteValue("address").ToString();
ViewData.SetActivePage("Breez", "Create Swapin Refund", "SwapIn"); ViewData.SetActivePage("Breez", "Create Swapin Refund", "SwapIn");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var sdk = BreezService.GetClient(storeId)?.Sdk; var sdk = BreezService.GetClient(storeId)?.Sdk;
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
} }

View File

@@ -1,11 +1,12 @@
@using Breez.Sdk @using Breez.Sdk
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Models.StoreViewModels @using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService @inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider @inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{ @{
@@ -29,7 +30,7 @@
if (sdk is null) if (sdk is null)
return; return;
var inProgressSwaps = sdk.InProgressReverseSwaps(); var inProgressSwaps = sdk.InProgressReverseSwaps();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
var swapOutRec = sdk.FetchReverseSwapFees(new ReverseSwapFeesRequest()); var swapOutRec = sdk.FetchReverseSwapFees(new ReverseSwapFeesRequest());
} }

View File

@@ -2,9 +2,10 @@
@using BTCPayServer.Models.StoreViewModels @using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService @inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider @inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{ @{
Layout = "_Layout"; Layout = "_Layout";
ViewData.SetActivePage("Breez", "Sweep", "Sweep"); ViewData.SetActivePage("Breez", "Sweep", "Sweep");
@@ -24,7 +25,7 @@
var sdk = BreezService.GetClient(storeId)?.Sdk; var sdk = BreezService.GetClient(storeId)?.Sdk;
if (sdk is null) if (sdk is null)
return; return;
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
} }
<datalist id="fees"> <datalist id="fees">

View File

@@ -1,10 +0,0 @@
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "_Nav";
}
<style>
#mainContent > section {
padding: 3rem !important;
}
</style>
@RenderBody()

View File

@@ -1,41 +0,0 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService
@{
var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var client = BreezService.GetClient(storeId);
var sdk = client?.Sdk;
}
<div class="sticky-header-setup"></div>
<div class="sticky-header mb-l">
<h2 class="mt-1 mb-2 mb-lg-4">Breez / Greenlight</h2>
@if (sdk is not null)
{
<nav id="SectionNav">
<div class="nav">
<a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a>
<a permission="@Policies.CanViewStoreSettings" asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a>
<a permission="@Policies.CanCreateInvoice" asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap In</a>
<a permission="@Policies.CanModifyStoreSettings" asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Out</a>
<a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
@if (client.Events.Any())
{
<a permission="@Policies.CanViewStoreSettings" asp-action="Logs" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Logs")">Logs</a>
}
</div>
</nav>
}
</div>

View File

@@ -1,15 +1,28 @@
@using Breez.Sdk
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client @using BTCPayServer.Client
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@inject IScopeProvider ScopeProvider @inject IScopeProvider ScopeProvider
@inject BreezService BreezService
@{ @{
var storeId = ScopeProvider.GetCurrentStoreId(); var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var active = @ViewData.IsActivePage("Breez");
var client = string.IsNullOrEmpty(active) ? null : BreezService.GetClient(storeId);
var sdk = client?.Sdk;
} }
@if (!string.IsNullOrEmpty(storeId)) @if (!string.IsNullOrEmpty(storeId))
{ {
<li class="nav-item"> <li class="nav-item">
<a permission="@Policies.CanViewStoreSettings" asp-controller="Breez" asp-action="Index" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez")" id="Nav-Breez"> <a permission="@Policies.CanViewStoreSettings" asp-controller="Breez" asp-action="Index" asp-route-storeId="@storeId" class="nav-link @active" id="Nav-Breez">
<svg style="width: 15px; margin-left: 3px; margin-right: 7px;" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg style="width: 15px; margin-left: 3px; margin-right: 7px;" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
@@ -24,4 +37,31 @@
<span>Breez</span> <span>Breez</span>
</a> </a>
</li> </li>
@if (sdk is not null)
{
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanCreateInvoice" asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap In</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanModifyStoreSettings" asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Out</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
</li>
@if (client.Events.Any())
{
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Logs" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Logs")">Logs</a>
</li>
}
}
} }

View File

@@ -103,7 +103,7 @@
<span class="text-secondary fw-semibold currency">BTC receivable</span> <span class="text-secondary fw-semibold currency">BTC receivable</span>
</div> </div>
<div class="balance d-flex align-items-baseline gap-1"> <div class="balance d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.inboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.inboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)</h3> <h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.totalInboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.totalInboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)</h3>
<span class="text-secondary fw-semibold currency">BTC inbound liquidity</span> <span class="text-secondary fw-semibold currency">BTC inbound liquidity</span>
</div> </div>
<div class="balance d-flex align-items-baseline gap-1"> <div class="balance d-flex align-items-baseline gap-1">

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Bringin</Product> <Product>Bringin</Product>
<Description>Euro Offramp</Description> <Description>Euro Offramp</Description>
<Version>1.0.1</Version> <Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
@@ -41,6 +41,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" /> <PackageReference Include="AsyncKeyedLock" Version="7.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -11,7 +11,10 @@ public class BringinPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"} new()
{
Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"
}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -17,6 +17,7 @@ using BTCPayServer.Events;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -139,13 +140,15 @@ public class BringinService : EventHostedServiceBase
invoiceEvent when bringinStoreSettings.Enabled: invoiceEvent when bringinStoreSettings.Enabled:
var pmPayments = invoiceEvent.Invoice.GetPayments("BTC", true) var pmPayments = invoiceEvent.Invoice.GetPayments("BTC", true)
.GroupBy(payment => payment.GetPaymentMethodId()); .GroupBy(payment => payment.PaymentMethodId);
var update = false; var update = false;
foreach (var pmPayment in pmPayments) foreach (var pmPayment in pmPayments)
{ {
var methodId = pmPayment.Key; var methodId = pmPayment.Key;
if (methodId.PaymentType == PaymentTypes.LNURLPay) if (methodId == PaymentTypes.LNURL.GetPaymentMethodId("BTC"))
methodId = new PaymentMethodId(methodId.CryptoCode, PaymentTypes.LightningLike); {
methodId = PaymentTypes.LN.GetPaymentMethodId("BTC");
}
if (!bringinStoreSettings.MethodSettings.TryGetValue(methodId.ToString(), if (!bringinStoreSettings.MethodSettings.TryGetValue(methodId.ToString(),
out var methodSettings)) out var methodSettings))
{ {
@@ -153,7 +156,7 @@ public class BringinService : EventHostedServiceBase
} }
methodSettings.CurrentBalance += methodSettings.CurrentBalance +=
pmPayment.Sum(payment => payment.GetCryptoPaymentData().GetValue()); pmPayment.Sum(payment => payment.Value);
update = true; update = true;
} }
@@ -190,14 +193,14 @@ public class BringinService : EventHostedServiceBase
foreach (var methodSetting in bringinStoreSetting.MethodSettings) foreach (var methodSetting in bringinStoreSetting.MethodSettings)
{ {
var pmi = PaymentMethodId.TryParse(methodSetting.Key); var pmi = PayoutMethodId.TryParse(methodSetting.Key);
if (pmi is null) if (pmi is null)
{ {
continue; continue;
} }
var isOnChain = PayoutTypes.CHAIN.GetPayoutMethodId("BTC") == pmi;
// if there is a pending payout, try and cancel it if this is onchain as we want to save needless tx fees // if there is a pending payout, try and cancel it if this is onchain as we want to save needless tx fees
if (methodSetting.Value.PendingPayouts.Count > 0 && pmi.PaymentType == BitcoinPaymentType.Instance) if (methodSetting.Value.PendingPayouts.Count > 0 && isOnChain)
{ {
var cancelResult = await _pullPaymentHostedService.Cancel( var cancelResult = await _pullPaymentHostedService.Cancel(
new PullPaymentHostedService.CancelRequest(methodSetting.Value.PendingPayouts.ToArray(), new PullPaymentHostedService.CancelRequest(methodSetting.Value.PendingPayouts.ToArray(),
@@ -252,16 +255,16 @@ public class BringinService : EventHostedServiceBase
return result; return result;
} }
public async Task<string?> CreateOrder(string storeId, PaymentMethodId paymentMethodId, Money amountBtc, bool payout) public async Task<string?> CreateOrder(string storeId, PayoutMethodId paymentMethodId, Money amountBtc, bool payout)
{ {
if (SupportedMethods.All(supportedMethod => supportedMethod.PaymentMethod != paymentMethodId)) if (SupportedMethods.All(supportedMethod => supportedMethod.PayoutMethod != paymentMethodId))
{ {
throw new NotSupportedException($"{paymentMethodId.ToPrettyString()} Payment method not supported"); throw new NotSupportedException($"{paymentMethodId} Payment method not supported");
} }
var settings = _settings[storeId]; var settings = _settings[storeId];
var bringinClient = settings.CreateClient(_httpClientFactory, _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode).NBitcoinNetwork); var bringinClient = settings.CreateClient(_httpClientFactory, _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), CancellationToken.None); var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), CancellationToken.None);
@@ -270,7 +273,7 @@ public class BringinService : EventHostedServiceBase
var supportedMethod = SupportedMethods.First(supportedMethod => supportedMethod.PaymentMethod == paymentMethodId); var supportedMethod = SupportedMethods.First(supportedMethod => supportedMethod.PayoutMethod == paymentMethodId);
//check if amount is enough //check if amount is enough
if (supportedMethod.FiatMinimumAmount > 0) if (supportedMethod.FiatMinimumAmount > 0)
{ {
@@ -297,13 +300,13 @@ public class BringinService : EventHostedServiceBase
{ {
return order.Invoice?? order.DepositAddress; return order.Invoice?? order.DepositAddress;
} }
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode); var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var destination = !string.IsNullOrEmpty(order.Invoice)? (IClaimDestination) new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)): var destination = !string.IsNullOrEmpty(order.Invoice)? (IClaimDestination) new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)):
new AddressClaimDestination(BitcoinAddress.Create(order.DepositAddress, network.NBitcoinNetwork)); new AddressClaimDestination(BitcoinAddress.Create(order.DepositAddress, network.NBitcoinNetwork));
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest() var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
{ {
PaymentMethodId = paymentMethodId, PayoutMethodId = paymentMethodId,
StoreId = storeId, StoreId = storeId,
Destination = destination, Destination = destination,
Value = orderMoney.ToUnit(MoneyUnit.BTC), Value = orderMoney.ToUnit(MoneyUnit.BTC),
@@ -363,17 +366,17 @@ public class BringinService : EventHostedServiceBase
{ {
case PayoutState.Completed: case PayoutState.Completed:
// remove from pending payouts list in a setting // remove from pending payouts list in a setting
return _settings[payout.StoreDataId].MethodSettings[payout.GetPaymentMethodId().ToString()] return _settings[payout.StoreDataId].MethodSettings[payout.GetPayoutMethodId().ToString()]
.PendingPayouts.Remove(payout.Id); .PendingPayouts.Remove(payout.Id);
case PayoutState.Cancelled: case PayoutState.Cancelled:
// remove from pending payouts list in a setting and add to a balance // remove from pending payouts list in a setting and add to a balance
var result = _settings[payout.StoreDataId].MethodSettings[payout.GetPaymentMethodId().ToString()] var result = _settings[payout.StoreDataId].MethodSettings[payout.GetPayoutMethodId().ToString()]
.PendingPayouts.Remove(payout.Id); .PendingPayouts.Remove(payout.Id);
var pmi = payout.GetPaymentMethodId(); var pmi = payout.GetPayoutMethodId();
if (_settings[payout.StoreDataId].MethodSettings if (_settings[payout.StoreDataId].MethodSettings
.TryGetValue(pmi.ToString(), out var methodSettings)) .TryGetValue(pmi.ToString(), out var methodSettings))
{ {
methodSettings.CurrentBalance += payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Amount; methodSettings.CurrentBalance += payout.Amount ?? payout.OriginalAmount;
result = true; result = true;
} }
@@ -389,12 +392,12 @@ public class BringinService : EventHostedServiceBase
return _settings.TryGetValue(storeId, out var bringinStoreSettings) ? bringinStoreSettings : null; return _settings.TryGetValue(storeId, out var bringinStoreSettings) ? bringinStoreSettings : null;
} }
public record SupportedMethodOptions(PaymentMethodId PaymentMethod, bool FiatMinimum, decimal FiatMinimumAmount, string bringinMethod); public record SupportedMethodOptions(PayoutMethodId PayoutMethod, bool FiatMinimum, decimal FiatMinimumAmount, string bringinMethod);
public static readonly SupportedMethodOptions[] SupportedMethods = new[] public static readonly SupportedMethodOptions[] SupportedMethods = new[]
{ {
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 22, "LIGHTNING"), new SupportedMethodOptions(PayoutTypes.LN.GetPayoutMethodId("BTC"), true, 22, "LIGHTNING"),
new SupportedMethodOptions(new PaymentMethodId("BTC", BitcoinPaymentType.Instance), true, 22, "ON_CHAIN"), new SupportedMethodOptions(PayoutTypes.CHAIN.GetPayoutMethodId("BTC"), true, 22, "ON_CHAIN"),
}; };
private ConcurrentDictionary<string, (IDisposable, BringinStoreSettings, DateTimeOffset Expiry)> _editModes = new(); private ConcurrentDictionary<string, (IDisposable, BringinStoreSettings, DateTimeOffset Expiry)> _editModes = new();
@@ -412,9 +415,9 @@ public class BringinService : EventHostedServiceBase
// add or remove any missing methods in result // add or remove any missing methods in result
foreach (var supportedMethod in SupportedMethods) foreach (var supportedMethod in SupportedMethods)
{ {
if (!result.MethodSettings.ContainsKey(supportedMethod.PaymentMethod.ToString())) if (!result.MethodSettings.ContainsKey(supportedMethod.PayoutMethod.ToString()))
{ {
result.MethodSettings.Add(supportedMethod.PaymentMethod.ToString(), result.MethodSettings.Add(supportedMethod.PayoutMethod.ToString(),
new BringinStoreSettings.PaymentMethodSettings() new BringinStoreSettings.PaymentMethodSettings()
{ {
FiatThreshold = supportedMethod.FiatMinimum, FiatThreshold = supportedMethod.FiatMinimum,
@@ -424,7 +427,7 @@ public class BringinService : EventHostedServiceBase
} }
result.MethodSettings = result.MethodSettings.Where(pair => result.MethodSettings = result.MethodSettings.Where(pair =>
SupportedMethods.Any(supportedMethod => supportedMethod.PaymentMethod.ToString() == pair.Key)) SupportedMethods.Any(supportedMethod => supportedMethod.PayoutMethod.ToString() == pair.Key))
.ToDictionary(pair => pair.Key, pair => pair.Value); .ToDictionary(pair => pair.Key, pair => pair.Value);
isNew = true; isNew = true;
return (storeLock, result, DateTimeOffset.Now.AddMinutes(5)); return (storeLock, result, DateTimeOffset.Now.AddMinutes(5));

View File

@@ -4,7 +4,9 @@
@using BTCPayServer.Data @using BTCPayServer.Data
@using BTCPayServer.Payments @using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors @using BTCPayServer.PayoutProcessors
@using BTCPayServer.Payouts
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Services.Invoices
@using BTCPayServer.Services.Stores @using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Http @using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Routing @using Microsoft.AspNetCore.Routing
@@ -25,6 +27,7 @@
[Inject] private IHttpClientFactory HttpClientFactory { get; set; } [Inject] private IHttpClientFactory HttpClientFactory { get; set; }
[Inject] private PayoutProcessorService PayoutProcessorService { get; set; } [Inject] private PayoutProcessorService PayoutProcessorService { get; set; }
[Inject] private IAuthorizationService AuthorizationService { get; set; } [Inject] private IAuthorizationService AuthorizationService { get; set; }
[Inject] private PayoutMethodHandlerDictionary PayoutMethodHandlerDictionary { get; set; }
[Parameter] public string StoreId { get; set; } [Parameter] public string StoreId { get; set; }
private decimal? LastFiatBalance { get; set; } private decimal? LastFiatBalance { get; set; }
private DateTimeOffset? LastDataFetch { get; set; } private DateTimeOffset? LastDataFetch { get; set; }
@@ -79,12 +82,13 @@
PmiLink = $"A payout processor has not been configured for this payment method. Payouts generated by Bringin will not be automatically handled. <a href=\"{LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "ConfigureStorePayoutProcessors", "UIPayoutProcessors", new {StoreId})}\">Configure now</a>"; PmiLink = $"A payout processor has not been configured for this payment method. Payouts generated by Bringin will not be automatically handled. <a href=\"{LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "ConfigureStorePayoutProcessors", "UIPayoutProcessors", new {StoreId})}\">Configure now</a>";
_callbackLink = LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "Callback", "Bringin", new {StoreId}); _callbackLink = LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "Callback", "Bringin", new {StoreId});
_settings = BringinService.IsInEditMode(StoreId) ? await BringinService.Update(StoreId) : await BringinService.Get(StoreId); _settings = BringinService.IsInEditMode(StoreId) ? await BringinService.Update(StoreId) : await BringinService.Get(StoreId);
_pms = (await StoreRepository.FindStore(StoreId)).GetSupportedPaymentMethods(BTCPayNetworkProvider).Select(method => method.PaymentId).Where(id => id.CryptoCode == "BTC").ToArray(); var store = await StoreRepository.FindStore(StoreId);
_pms = PayoutMethodHandlerDictionary.GetSupportedPayoutMethods(store);
_pps = (await PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery() _pps = (await PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
{ {
Stores = new[] {StoreId}, Stores = new[] {StoreId},
PaymentMethods = _pms.Select(p => p.ToString()).ToArray() PayoutMethods = _pms.Select(p => p).ToArray()
})).Select(data => PaymentMethodId.TryParse(data.PaymentMethod)).Where(id => id is not null).ToArray(); })).Select(data => PayoutMethodId.TryParse(data.PayoutMethodId)).Where(id => id is not null).ToArray();
EditMode = BringinService.IsInEditMode(StoreId); EditMode = BringinService.IsInEditMode(StoreId);
IsLoaded = true; IsLoaded = true;
_ = FetchBalanceAndRate(); _ = FetchBalanceAndRate();
@@ -148,8 +152,8 @@
} }
private bool _saving; private bool _saving;
private PaymentMethodId[] _pms; private HashSet<PayoutMethodId> _pms;
private PaymentMethodId[] _pps; private PayoutMethodId[] _pps;
private bool _apiKeyError; private bool _apiKeyError;
private async Task Save() private async Task Save()
@@ -269,7 +273,7 @@
_saving = true; _saving = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
var pm = PaymentMethodId.TryParse(ManualOrderPaymentMethod); var pm = PayoutMethodId.TryParse(ManualOrderPaymentMethod);
if (pm is null) if (pm is null)
{ {
SaveError = "Invalid payment method"; SaveError = "Invalid payment method";
@@ -391,8 +395,8 @@
} }
else if (_manualOrder) else if (_manualOrder)
{ {
var items = new List<PaymentMethodId>(); var items = new List<PayoutMethodId>();
items.AddRange(BringinService.SupportedMethods.Where(s => _pms.Contains(s.PaymentMethod)).Select(s => s.PaymentMethod)); items.AddRange(BringinService.SupportedMethods.Where(s => _pms.Contains(s.PayoutMethod)).Select(s => s.PayoutMethod));
<div class="row"> <div class="row">
@@ -422,7 +426,7 @@
<option value="">Select a payment method</option> <option value="">Select a payment method</option>
@foreach (var opt in items) @foreach (var opt in items)
{ {
<option value="@opt.ToString()">@opt.ToPrettyString()</option> <option value="@opt.ToString()">@opt</option>
} }
</select> </select>
</div> </div>
@@ -472,7 +476,7 @@
@foreach (var method in _settings.MethodSettings) @foreach (var method in _settings.MethodSettings)
{ {
var pmi = PaymentMethodId.TryParse(method.Key); var pmi = PayoutMethodId.TryParse(method.Key);
if (pmi is null) if (pmi is null)
continue; continue;
if (!_pms.Contains(pmi)) if (!_pms.Contains(pmi))
@@ -480,7 +484,7 @@
<hr class=""/> <hr class=""/>
<div class="store-number"> <div class="store-number">
<header> <header>
<h6>@pmi.ToPrettyString()</h6> <h6>@pmi</h6>
</header> </header>
@if (LastFiatRate is null || !method.Value.FiatThreshold) @if (LastFiatRate is null || !method.Value.FiatThreshold)
@@ -504,7 +508,7 @@
<span class="text-secondary"> (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached.</span> <span class="text-secondary"> (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached.</span>
@if (method.Value.CurrentBalance > 0) @if (method.Value.CurrentBalance > 0)
{ {
<button class="btn btn-link" @onclick="() => ResetBalance(pmi)" disabled="@_saving">Reset balance</button> <button class="btn btn-link" @onclick="() => ResetBalance(PaymentMethodId.Parse(pmi.ToString()))" disabled="@_saving">Reset balance</button>
//Clear balance //Clear balance
} }
</div> </div>
@@ -579,9 +583,9 @@
var pmId = PaymentMethodId.TryParse(method.Key); var pmId = PaymentMethodId.TryParse(method.Key);
if (pmId is null) if (pmId is null)
continue; continue;
var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PaymentMethod.ToString() == method.Key); var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PayoutMethod.ToString() == method.Key);
<div class="col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? $"col-xl-6 {(i == 0 ? "border-end" : "")}" : "")"> <div class="col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? $"col-xl-6 {(i == 0 ? "border-end" : "")}" : "")">
<h5 class=" border-bottom-0 text-muted mb-4">@pmId.ToPrettyString()</h5> <h5 class=" border-bottom-0 text-muted mb-4">@pmId</h5>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<label class="form-label">Percentage</label> <label class="form-label">Percentage</label>
@@ -644,15 +648,16 @@
<tr> <tr>
<td>@tx.CreatedAt.ToTimeAgo()</td> <td>@tx.CreatedAt.ToTimeAgo()</td>
<td>@tx.SubType.ToHumanReadable()</td> <td>@tx.SubType.ToHumanReadable()</td>
<td> <td>@tx.Status.ToHumanReadable()</td>
@tx.Status.ToHumanReadable()
</td>
<td class="amount-col"> <td class="amount-col">
<span data-sensitive>@(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC): tx.SourceAmount)@tx.SourceCurrency -> @tx.DestinationAmount @tx.DestinationCurrency </span> <span data-sensitive>
@(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC) : tx.SourceAmount) @tx.SourceCurrency
-> @tx.DestinationAmount @tx.DestinationCurrency
</span>
</td> </td>
</tr> </tr>
} }
</tbody> </tbody>s
</table> </table>
</div> </div>
} }

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Data Erasure</Product> <Product>Data Erasure</Product>
<Description>Allows you to erase user data from invoices after a period of time.</Description> <Description>Allows you to erase user data from invoices after a period of time.</Description>
<Version>1.0.2</Version> <Version>1.0.3</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -12,6 +12,7 @@ namespace BTCPayServer.Plugins.DataErasure
public class DataErasureController : Controller public class DataErasureController : Controller
{ {
private readonly DataErasureService _dataErasureService; private readonly DataErasureService _dataErasureService;
public DataErasureController(DataErasureService dataErasureService) public DataErasureController(DataErasureService dataErasureService)
{ {
_dataErasureService = dataErasureService; _dataErasureService = dataErasureService;
@@ -31,7 +32,8 @@ namespace BTCPayServer.Plugins.DataErasure
{ {
if (_dataErasureService.IsRunning) if (_dataErasureService.IsRunning)
{ {
TempData["ErrorMessage"] = "Data erasure is currently running and cannot be changed. Please try again later."; TempData["ErrorMessage"] =
"Data erasure is currently running and cannot be changed. Please try again later.";
} }
@@ -46,7 +48,11 @@ namespace BTCPayServer.Plugins.DataErasure
switch (command) switch (command)
{ {
case "cleardate":
await _dataErasureService.Set(storeId, vm, true);
TempData["SuccessMessage"] = "Data erasure settings modified and date cleared";
return RedirectToAction(nameof(Update), new {storeId});
case "save": case "save":
await _dataErasureService.Set(storeId, vm); await _dataErasureService.Set(storeId, vm);
TempData["SuccessMessage"] = "Data erasure settings modified"; TempData["SuccessMessage"] = "Data erasure settings modified";

View File

@@ -9,7 +9,7 @@ namespace BTCPayServer.Plugins.DataErasure
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -14,13 +15,15 @@ namespace BTCPayServer.Plugins.DataErasure
private readonly IStoreRepository _storeRepository; private readonly IStoreRepository _storeRepository;
private readonly ILogger<DataErasureService> _logger; private readonly ILogger<DataErasureService> _logger;
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private readonly ApplicationDbContextFactory _dbContextFactory;
public DataErasureService(IStoreRepository storeRepository, ILogger<DataErasureService> logger, public DataErasureService(IStoreRepository storeRepository, ILogger<DataErasureService> logger,
InvoiceRepository invoiceRepository) InvoiceRepository invoiceRepository, ApplicationDbContextFactory dbContextFactory)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_logger = logger; _logger = logger;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_dbContextFactory = dbContextFactory;
} }
public async Task<DataErasureSettings> Get(string storeId) public async Task<DataErasureSettings> Get(string storeId)
@@ -29,13 +32,16 @@ namespace BTCPayServer.Plugins.DataErasure
nameof(DataErasureSettings)); nameof(DataErasureSettings));
} }
public async Task Set(string storeId, DataErasureSettings settings) public async Task Set(string storeId, DataErasureSettings settings, bool clearDate = false)
{ {
_cts?.Cancel();
await _runningLock.WaitAsync(); await _runningLock.WaitAsync();
var existing = await Get(storeId); var existing = await Get(storeId);
settings.LastRunCutoff = existing?.LastRunCutoff; settings.LastRunCutoff = clearDate? null: existing?.LastRunCutoff;
await SetCore(storeId, settings); await SetCore(storeId, settings);
_runningLock.Release(); _runningLock.Release();
_cts = new CancellationTokenSource();
_ = Run();
} }
private async Task SetCore(string storeId, DataErasureSettings settings) private async Task SetCore(string storeId, DataErasureSettings settings)
@@ -46,11 +52,11 @@ namespace BTCPayServer.Plugins.DataErasure
public bool IsRunning { get; private set; } public bool IsRunning { get; private set; }
private readonly SemaphoreSlim _runningLock = new(1, 1); private readonly SemaphoreSlim _runningLock = new(1, 1);
private async Task Run(CancellationToken cancellationToken) private async Task Run()
{ {
while (!cancellationToken.IsCancellationRequested) while (!_cts.IsCancellationRequested)
{ {
await _runningLock.WaitAsync(cancellationToken); await _runningLock.WaitAsync(_cts.Token);
IsRunning = true; IsRunning = true;
@@ -58,9 +64,20 @@ namespace BTCPayServer.Plugins.DataErasure
await _storeRepository.GetSettingsAsync<DataErasureSettings>(nameof(DataErasureSettings)); await _storeRepository.GetSettingsAsync<DataErasureSettings>(nameof(DataErasureSettings));
foreach (var setting in settings.Where(setting => setting.Value.Enabled)) foreach (var setting in settings.Where(setting => setting.Value.Enabled))
{ {
var skip = 0;
var count = 0; var count = 0;
var cutoffDate = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(setting.Value.DaysToKeep)); var cutoffDate = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(setting.Value.DaysToKeep));
if (setting.Value.EntirelyEraseInvoice)
{
await using var db = _dbContextFactory.CreateContext();
db.Invoices.RemoveRange(db.Invoices.Where(i => i.StoreDataId == setting.Key && i.Created < cutoffDate && (setting.Value.LastRunCutoff == null || i.Created > setting.Value.LastRunCutoff)));
count = await db.SaveChangesAsync(_cts.Token);
}
else
{
var skip = 0;
while (true) while (true)
{ {
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery() var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery()
@@ -70,7 +87,10 @@ namespace BTCPayServer.Plugins.DataErasure
StoreId = new[] {setting.Key}, StoreId = new[] {setting.Key},
Skip = skip, Skip = skip,
Take = 100 Take = 100
}); }, _cts.Token);
foreach (var invoice in invoices) foreach (var invoice in invoices)
{ {
//replace all buyer info with "erased" //replace all buyer info with "erased"
@@ -104,6 +124,8 @@ namespace BTCPayServer.Plugins.DataErasure
skip += 100; skip += 100;
} }
}
if (count > 0) if (count > 0)
_logger.LogInformation($"Erased {count} invoice data for store {setting.Key}"); _logger.LogInformation($"Erased {count} invoice data for store {setting.Key}");
setting.Value.LastRunCutoff = cutoffDate; setting.Value.LastRunCutoff = cutoffDate;
@@ -114,18 +136,30 @@ namespace BTCPayServer.Plugins.DataErasure
_runningLock.Release(); _runningLock.Release();
await Task.Delay(TimeSpan.FromDays(1), cancellationToken); await Task.Delay(TimeSpan.FromHours(1), _cts.Token);
}
try
{
_runningLock.Release();
}
catch (Exception e)
{
} }
} }
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
_ = Run(cancellationToken); _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_ = Run();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
_cts?.Cancel();
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@@ -7,5 +7,7 @@ namespace BTCPayServer.Plugins.DataErasure
public bool Enabled { get; set; } public bool Enabled { get; set; }
public int DaysToKeep { get; set; } public int DaysToKeep { get; set; }
public DateTimeOffset? LastRunCutoff { get; set; } public DateTimeOffset? LastRunCutoff { get; set; }
public bool EntirelyEraseInvoice { get; set; }
} }
} }

View File

@@ -36,9 +36,24 @@
<input asp-for="DaysToKeep" type="number" class="form-control"/> <input asp-for="DaysToKeep" type="number" class="form-control"/>
</div> </div>
<div class="form-group">
<div class="d-flex align-items-center">
<input asp-for="EntirelyEraseInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="EntirelyEraseInvoice" class="form-label mb-0 me-1">Remove entire invoice from records (instead of just buyer data)</label>
</div>
<div class="alert alert-warning">
<p>
Deleting entire invoices may cause issues with integrations and accounting. Only use this option if you are sure you want to remove the invoice entirely.
</p>
</div>
</div>
@if (Model.LastRunCutoff != null) @if (Model.LastRunCutoff != null)
{ {
<div>Cleared data up to @Model.LastRunCutoff.Value.ToString("g")</div> <div>Cleared data up to @Model.LastRunCutoff.Value.ToString("g")</div>
<div class="form-group">
<button name="command" type="submit" value="cleardate" class="btn btn-danger">Start over</button>
</div>
} }
</div> </div>

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Dynamic Rate Limit</Product> <Product>Dynamic Rate Limit</Product>
<Description>Allows you to override the default rate limiting.</Description> <Description>Allows you to override the default rate limiting.</Description>
<Version>1.0.1</Version> <Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -10,7 +10,7 @@ public class DynamicRateLimitsPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {

View File

@@ -1,11 +1,6 @@
@using BTCPayServer.Plugins.DynamicRateLimits @using BTCPayServer.Plugins.DynamicRateLimits
@model DynamicRateLimitSettings @model DynamicRateLimitSettings
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIServer/_Nav";
}
<h2 class="mb-4">Rate limit configuration</h2> <h2 class="mb-4">Rate limit configuration</h2>
<form method="post"> <form method="post">

View File

@@ -1,7 +1,13 @@
@using BTCPayServer.Client
@using BTCPayServer.Plugins.DynamicRateLimits @using BTCPayServer.Plugins.DynamicRateLimits
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{ @{
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null && var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
nameof(DynamicRatesLimiterController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase); nameof(DynamicRatesLimiterController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase);
} }
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicRatesLimiter">Rate Limits</a> <a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicRatesLimiter">Rate Limits</a>
</li>

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Dynamic Reports</Product> <Product>Dynamic Reports</Product>
<Description>Allows you to create custom reports using SQL.</Description> <Description>Allows you to create custom reports using SQL.</Description>
<Version>1.0.1</Version> <Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -10,7 +10,7 @@ public class DynamicReportsPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.13.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {

View File

@@ -21,19 +21,15 @@ public class PostgresReportProvider : ReportProvider
public DynamicReportsSettings.DynamicReportSetting Setting { get; set; } public DynamicReportsSettings.DynamicReportSetting Setting { get; set; }
private readonly ApplicationDbContextFactory _dbContextFactory; private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly IOptions<DatabaseOptions> _options;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
public PostgresReportProvider( ApplicationDbContextFactory dbContextFactory, public PostgresReportProvider( ApplicationDbContextFactory dbContextFactory, IHttpContextAccessor httpContextAccessor)
IOptions<DatabaseOptions> options, IHttpContextAccessor httpContextAccessor)
{ {
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_options = options;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
} }
public override bool IsAvailable() public override bool IsAvailable()
{ {
return _options.Value.DatabaseType == DatabaseType.Postgres && return Setting.AllowForNonAdmins || _httpContextAccessor.HttpContext?.User.IsInRole(Roles.ServerAdmin) is true;
Setting.AllowForNonAdmins || _httpContextAccessor.HttpContext?.User.IsInRole(Roles.ServerAdmin) is true;
} }
public override async Task Query(QueryContext queryContext, CancellationToken cancellation) public override async Task Query(QueryContext queryContext, CancellationToken cancellation)
{ {

View File

@@ -10,8 +10,6 @@
@inject DynamicReportService DynamicReportService @inject DynamicReportService DynamicReportService
@{ @{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIServer/_Nav";
var storeId = ScopeProvider.GetCurrentStoreId(); var storeId = ScopeProvider.GetCurrentStoreId();
var reportName = Context.Request.Query["reportName"].ToString(); var reportName = Context.Request.Query["reportName"].ToString();
reportName = string.IsNullOrEmpty(reportName) ? null : reportName; reportName = string.IsNullOrEmpty(reportName) ? null : reportName;

View File

@@ -1,8 +1,15 @@
@using BTCPayServer.Client
@using BTCPayServer.Plugins.DynamicReports @using BTCPayServer.Plugins.DynamicReports
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{ @{
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null && var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
nameof(DynamicReportsController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase); nameof(DynamicReportsController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase);
} }
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicReports">Dynamic Reports</a> <a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicReports">Dynamic Reports</a>
</li>

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>File Seller</Product> <Product>File Seller</Product>
<Description>Allows you to sell files through the point of sale/crowdfund apps.</Description> <Description>Allows you to sell files through the point of sale/crowdfund apps.</Description>
<Version>1.0.4</Version> <Version>1.0.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -9,8 +9,9 @@ public class FileSellerPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddHostedService<FileSellerService>(); applicationBuilder.AddHostedService<FileSellerService>();

View File

@@ -6,16 +6,16 @@
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@inject UserService UserService @inject UserService UserService
@{ @{
var userId = UserManager.GetUserId(User); var user = await UserManager.GetUserAsync(User);
var files = (await StoredFileRepository.GetFiles(new StoredFileRepository.FilesQuery() var files = (await StoredFileRepository.GetFiles(new StoredFileRepository.FilesQuery()
{ {
UserIds = await UserService.IsAdminUser(userId) ? Array.Empty<string>() : new[] {userId}, UserIds = await UserService.IsAdminUser(user) ? Array.Empty<string>() : new[] {user.Id},
})).Select(file => new SelectListItem(file.FileName, file.Id)).Prepend(new SelectListItem("No file", "")); })).Select(file => new SelectListItem(file.FileName, file.Id)).Prepend(new SelectListItem("No file", ""));
} }
<template v-if="editingItem"> <template v-if="editingItem">
<div class="form-group"> <div class="form-group">
<label class="form-label">Downloadable file</label> <label class="form-label">Downloadable file</label>
<select :value="editingItem['file'] || ''" asp-items="files" class="form-select w-auto" v-on:change=" if(event.target.value) editingItem['file']= event.target.value; else delete editingItem['file'];"></select> <select :value="editingItem['file'] || ''" asp-items="files" class="form-select w-auto" v-on:change="if(event.target.value) Vue.set(editingItem, 'file', event.target.value); else Vue.delete(editingItem, 'file');"></select>
<span class="form-text">If a file is selected, when a user buys this item, a download link is generated in the payment receipt once the invoice is settled. <a target="_blank" asp-action="Files" asp-controller="UIServer">Upload files here</a></span> <span class="form-text">If a file is selected, when a user buys this item, a download link is generated in the payment receipt once the invoice is settled. <a target="_blank" asp-action="Files" asp-controller="UIServer">Upload files here</a></span>
</div> </div>
</template> </template>

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>FixedFloat</Product> <Product>FixedFloat</Product>
<Description>Allows you to embed a FixedFloat conversion screen to allow customers to pay with altcoins.</Description> <Description>Allows you to embed a FixedFloat conversion screen to allow customers to pay with altcoins.</Description>
<Version>1.1.7</Version> <Version>1.1.8</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -9,29 +9,16 @@ namespace BTCPayServer.Plugins.FixedFloat
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddSingleton<FixedFloatService>(); applicationBuilder.AddSingleton<FixedFloatService>();
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/FixedFloatNav", applicationBuilder.AddUIExtension("store-integrations-nav", "FixedFloat/FixedFloatNav");
"store-integrations-nav")); applicationBuilder.AddUIExtension("checkout-payment-method", "FixedFloat/CheckoutPaymentMethodExtension");
// Checkout v2 applicationBuilder.AddUIExtension("checkout-payment", "FixedFloat/CheckoutPaymentExtension");
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutPaymentMethodExtension",
"checkout-payment-method"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutPaymentExtension",
"checkout-payment"));
// Checkout Classic
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutContentExtension",
"checkout-bitcoin-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutContentExtension",
"checkout-lightning-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutTabExtension",
"checkout-bitcoin-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutTabExtension",
"checkout-lightning-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutEnd",
"checkout-end"));
base.Execute(applicationBuilder); base.Execute(applicationBuilder);
} }
} }

View File

@@ -20,47 +20,61 @@ namespace BTCPayServer.Plugins.FixedFloat
{ {
{"AAVEETH", "Aave (ERC20)"}, {"AAVEETH", "Aave (ERC20)"},
{"ADA", "Cardano"}, {"ADA", "Cardano"},
{"ADABSC", "Cardano (BEP20)"},
{"APT", "Aptos"},
{"ATOM", "Cosmos"}, {"ATOM", "Cosmos"},
{"AVAX", "Avalanche (C-Chain)"}, {"AVAX", "Avalanche (C-Chain)"},
{"BAT", "Basic Attention (ERC20)"}, {"BAT", "Basic Attention (ERC20)"},
{"BCH", "Bitcoin Cash"},
{"BNB", "BNB Beacon Chain (BEP2)"},
{"BSC", "BNB Smart Chain (BEP20)"}, {"BSC", "BNB Smart Chain (BEP20)"},
{"WBNBBSC", "Wrapped BNB (BEP20)"},
{"BTC", "Bitcoin"}, {"BTC", "Bitcoin"},
{"BTCBSC", "Bitcoin (BEP20)"},
{"BTCLN", "Bitcoin (Lightning)"}, {"BTCLN", "Bitcoin (Lightning)"},
{"BTT", "BitTorrent"}, {"BTT", "BitTorrent"},
{"BUSD", "Binance USD (BEP2)"},
{"BUSDBSC", "Binance USD (BEP20)"},
{"BUSDETH", "Binance USD (ERC20)"},
{"CAKE", "PancakeSwap (BEP20)"}, {"CAKE", "PancakeSwap (BEP20)"},
{"DAIBSC", "DAI (BEP20)"},
{"DAIETH", "DAI (ERC20)"}, {"DAIETH", "DAI (ERC20)"},
{"DAIMATIC", "DAI (Polygon)"},
{"DASH", "Dash"},
{"DOGE", "Dogecoin"}, {"DOGE", "Dogecoin"},
{"DOT", "Polkadot"}, {"DOT", "Polkadot"},
{"EOS", "EOS"}, {"EOS", "EOS"},
{"ETC", "Ethereum Classic"},
{"ETH", "Ethereum"}, {"ETH", "Ethereum"},
{"ETHARBITRUM", "Ethereum (Arbitrum)"},
{"ETHBASE", "Ethereum (Base)"},
{"ETHZKSYNC", "Ethereum (ZkSync)"},
{"WETHARBITRUM", "Wrapped ETH (Arbitrum)"},
{"WETHETH", "Wrapped ETH (ERC20)"},
{"FTM", "Fantom"}, {"FTM", "Fantom"},
{"KCS", "KuCoin Token"},
{"LINK", "Chainlink (ERC20)"}, {"LINK", "Chainlink (ERC20)"},
{"LTC", "Litecoin"}, {"LTC", "Litecoin"},
{"MANAETH", "Decentraland (ERC20)"}, {"MANAETH", "Decentraland (ERC20)"},
{"MATIC", "Polygon"},
{"MATICETH", "Polygon (ERC20)"},
{"MKR", "Maker (ERC20)"}, {"MKR", "Maker (ERC20)"},
{"PAXGETH", "PAX Gold (ERC20)"},
{"POL", "Polygon"},
{"POLETH", "Polygon (ERC20)"},
{"SHIB", "SHIBA INU (ERC20)"}, {"SHIB", "SHIBA INU (ERC20)"},
{"SHIBBSC", "SHIBA INU (BEP20)"},
{"SOL", "Solana"}, {"SOL", "Solana"},
{"WSOL", "Wrapped SOL (Solana)"},
{"TON", "Toncoin"}, {"TON", "Toncoin"},
{"TRX", "Tron"}, {"TRX", "Tron"},
{"TUSD", "TrueUSD (ERC20)"}, {"TUSD", "TrueUSD (ERC20)"},
{"TWT", "Trust Wallet Token (BEP2)"},
{"TWTBSC", "Trust Wallet Token (BEP20)"}, {"TWTBSC", "Trust Wallet Token (BEP20)"},
{"USDCARBITRUM", "USD Coin (Arbitrum)"},
{"USDCBSC", "USD Coin (BEP20)"},
{"USDCETH", "USD Coin (ERC20)"}, {"USDCETH", "USD Coin (ERC20)"},
{"USDCMATIC", "USD Coin (Polygon)"},
{"USDCSOL", "USD Coin (Solana)"}, {"USDCSOL", "USD Coin (Solana)"},
{"USDCTRC", "USD Coin (TRC20)"},
{"USDP", "Pax Dollar (ERC20)"}, {"USDP", "Pax Dollar (ERC20)"},
{"USDT", "Tether (ERC20)"}, {"USDT", "Tether (ERC20)"},
{"USDTBSC", "Tether (BEP20)"},
{"USDTMATIC", "Tether (Polygon)"},
{"USDTSOL", "Tether (Solana)"}, {"USDTSOL", "Tether (Solana)"},
{"USDTTRC", "Tether (TRC20)"}, {"USDTTRC", "Tether (TRC20)"},
{"VET", "VeChain"}, {"VET", "VeChain"},
{"XLM", "Stellar Lumens"},
{"XMR", "Monero"}, {"XMR", "Monero"},
{"XRP", "Ripple"}, {"XRP", "Ripple"},
{"XTZ", "Tezos"}, {"XTZ", "Tezos"},
@@ -68,8 +82,8 @@ namespace BTCPayServer.Plugins.FixedFloat
{"ZRX", "0x (ERC20)"} {"ZRX", "0x (ERC20)"}
}; };
public static List<SelectListItem> AllowedSendingOptionsList => AllowedSendingOptions.Select(o => new SelectListItem(o.Value, o.Key)).ToList();
public static List<SelectListItem> AllowedSendingOptionsList =>
AllowedSendingOptions.Select(o => new SelectListItem(o.Value, o.Key)).ToList();
} }
} }

View File

@@ -2,15 +2,17 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Data @using BTCPayServer.Data
@using BTCPayServer.Plugins.FixedFloat @using BTCPayServer.Plugins.FixedFloat
@using BTCPayServer.Services.Invoices
@using BTCPayServer.Services.Stores @using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.FixedFloat.FixedFloatSettings @model BTCPayServer.Plugins.FixedFloat.FixedFloatSettings
@inject BTCPayNetworkProvider BTCPayNetworkProvider @inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{ @{
ViewData.SetActivePage("FixedFloat", "FixedFloat", "FixedFloat"); ViewData.SetActivePage("FixedFloat", "FixedFloat", "FixedFloat");
var store = Context.GetStoreData(); var store = Context.GetStoreData();
var allowedPaymentMethods = store.GetEnabledPaymentIds(BTCPayNetworkProvider) var allowedPaymentMethods = store.GetEnabledPaymentIds()
.Select(pmi => new SelectListItem(pmi.ToPrettyString(), pmi.ToString())) .Select(pmi => new SelectListItem(pmi.ToString(), pmi.ToString()))
.Prepend(new SelectListItem("Any", "")); .Prepend(new SelectListItem("Any", ""));
} }

View File

@@ -1,19 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@inject FixedFloatService FixedFloatService
@{
var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<div id="FixedFloat" class="bp-view payment manual-flow" style="padding:0" :class="{ active: currentTab == 'undefined' || currentTab == 'FixedFloat' }">
<fixed-float inline-template
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.btcDue * (1 + (@settings.AmountMarkupPercentage / 100)) "
:to-currency-address="srvModel.btcAddress">
<iframe :src="url" style="min-height:600px;width:100%;border:none" allowtransparency="true"></iframe>
</fixed-float>
</div>
}
}

View File

@@ -1,12 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@using Newtonsoft.Json
@using Newtonsoft.Json.Linq
@inject FixedFloatService FixedFloatService
@{
var storeId = ((JObject)JObject.Parse(JsonConvert.SerializeObject(Model)))["StoreId"].Value<string>();
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<script src="~/Resources/js/fixedFloatComponent.js"></script>
}
}

View File

@@ -1,10 +1,15 @@
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Payments
@using BTCPayServer.Plugins.FixedFloat @using BTCPayServer.Plugins.FixedFloat
@inject FixedFloatService FixedFloatService @inject FixedFloatService FixedFloatService
@model BTCPayServer.Models.InvoicingModels.PaymentModel @model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{ @{
var storeId = Model.StoreId; var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId); var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
var preferredTargetPaymentMethodId = string.IsNullOrEmpty(settings?.PreferredTargetPaymentMethodId) ? null : Model.AvailableCryptos.Any(crypto => crypto.PaymentMethodId == settings.PreferredTargetPaymentMethodId) ? settings.PreferredTargetPaymentMethodId : null; var preferredTargetPaymentMethodId =
string.IsNullOrEmpty(settings?.PreferredTargetPaymentMethodId) ? null :
Model.AvailablePaymentMethods.Any(crypto => crypto.PaymentMethodId.ToString() == PaymentMethodId.TryParse(settings.PreferredTargetPaymentMethodId)?.ToString()) ?
settings.PreferredTargetPaymentMethodId : null;
} }
@if (settings?.Enabled is true) @if (settings?.Enabled is true)
{ {
@@ -41,7 +46,7 @@
const result = this.$parent.paymentMethodId === "FixedFloat"; const result = this.$parent.paymentMethodId === "FixedFloat";
if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){ if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC"){ if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC-CHAIN"){
return result; return result;
} }
this.$parent.paymentMethodId = this.preferredToCurrency; this.$parent.paymentMethodId = this.preferredToCurrency;
@@ -53,7 +58,7 @@
return result; return result;
}, },
lightning () { lightning () {
if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC"){ if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC-CHAIN"){
return null; return null;
} }
const index = this.model.invoiceBitcoinUrl.indexOf("lightning="); const index = this.model.invoiceBitcoinUrl.indexOf("lightning=");
@@ -63,9 +68,9 @@
return this.model.invoiceBitcoinUrl.slice(index + "lightning=".length); return this.model.invoiceBitcoinUrl.slice(index + "lightning=".length);
}, },
url () { url () {
debugger;
const address= this.lightning || this.model.btcAddress; const address= this.lightning || this.model.address;
return "https://widget.fixedfloat.com/?" + return "https://widget.ff.io/?" +
`to=${this.settleMethodId}` + `to=${this.settleMethodId}` +
"&lockReceive=true&ref=fkbyt39c" + "&lockReceive=true&ref=fkbyt39c" +
`&address=${address}` + `&address=${address}` +
@@ -76,9 +81,9 @@
return this.model.paymentMethodId; return this.model.paymentMethodId;
}, },
settleMethodId () { settleMethodId () {
return this.currency.endsWith('LightningLike') || this.currency.endsWith('LNURLPay') || this.lightning return this.currency.endsWith('LN') || this.currency.endsWith('LNURL') || this.lightning
? 'BTCLN' ? 'BTCLN'
: this.currency.replace('_BTCLike', '').replace('_MoneroLike', '').replace('_ZcashLike', '').toUpperCase(); : this.currency.replace('-CHAIN', '').replace('_CHAIN', '').toUpperCase();
}, },
explicitQuery (){ explicitQuery (){
const isExplicit = !!this.explicitId; const isExplicit = !!this.explicitId;
@@ -91,7 +96,7 @@
: `&lockType=true&hideType=true&lockAmount=true&toAmount=${this.amountDue}`; : `&lockType=true&hideType=true&lockAmount=true&toAmount=${this.amountDue}`;
}, },
amountDue () { amountDue () {
return this.model.btcDue * (1 + (markupPercentage / 100)); return this.model.due * (1 + (markupPercentage / 100));
} }
} }
}); });

View File

@@ -1,5 +1,5 @@
@using BTCPayServer.Plugins.FixedFloat @using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel @model BTCPayServer.Models.InvoicingModels.CheckoutModel
@inject FixedFloatService FixedFloatService @inject FixedFloatService FixedFloatService
@{ @{
const string id = "FixedFloat"; const string id = "FixedFloat";

View File

@@ -1,13 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@inject FixedFloatService FixedFloatService
@{
var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<div class="payment-tabs__tab py-0" id="FixedFloat-tab" v-on:click="switchTab('FixedFloat')" v-bind:class="{ 'active': currentTab == 'FixedFloat'}">
<span>{{$t("Altcoins (FixedFloat)")}}</span>
</div>
}
}

View File

@@ -1,43 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<!-- Plugin specific properties -->
<PropertyGroup>
<Product>LDK</Product>
<Description>The way lightning's meant to be</Description>
<Version>1.0.0</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<PreserveCompilationContext>false</PreserveCompilationContext>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<!-- This will make sure that referencing BTCPayServer doesn't put any artifact in the published directory -->
<ItemDefinitionGroup>
<ProjectReference>
<Properties>StaticWebAssetsEnabled=false</Properties>
<Private>false</Private>
<ExcludeAssets>runtime;native;build;buildTransitive;contentFiles</ExcludeAssets>
</ProjectReference>
</ItemDefinitionGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**" />
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="org.ldk" Version="0.0.118-alpha1" />
</ItemGroup>
</Project>

View File

@@ -1,232 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Runtime.Internal.Util;
using BTCPayServer;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Services;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NBitcoin;
using NBXplorer;
using org.ldk.enums;
using org.ldk.structs;
using enums_Network = org.ldk.enums.Network;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Logger = org.ldk.structs.Logger;
using Network = NBitcoin.Network;
using OutPoint = org.ldk.structs.OutPoint;
using Path = System.IO.Path;
public class LDKService : IHostedService, PersistInterface, BroadcasterInterfaceInterface, FeeEstimatorInterface, EventHandlerInterface, LoggerInterface, FilterInterface
{
private readonly ILogger<LDKService> _logger;
private readonly IFeeProviderFactory _feeProviderFactory;
private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayNetwork _network;
private readonly ExplorerClient _explorerClient;
private readonly string _workDir;
private readonly enums_Network _ldkNetwork;
private readonly Logger _ldklogger;
private readonly FeeEstimator _ldkfeeEstimator;
private readonly BroadcasterInterface _ldkbroadcaster;
private readonly Persist _ldkpersist;
private readonly Filter _ldkfilter;
private readonly NetworkGraph _ldkNetworkGraph;
private readonly ChainMonitor _ldkChainMonitor;
public LDKService(BTCPayNetworkProvider btcPayNetworkProvider,
ExplorerClientProvider explorerClientProvider,
ILogger<LDKService> logger,
IFeeProviderFactory feeProviderFactory,
IOptions<DataDirectories> dataDirectories)
{
_logger = logger;
_feeProviderFactory = feeProviderFactory;
_dataDirectories = dataDirectories;
_network = btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
_explorerClient = explorerClientProvider.GetExplorerClient(_network);
_workDir = GetWorkDir();
Directory.CreateDirectory(_workDir);
_ldkNetwork = GetLdkNetwork(_network.NBitcoinNetwork);
_ldklogger = Logger.new_impl(this);
_ldkfeeEstimator = FeeEstimator.new_impl(this);
_ldkbroadcaster = BroadcasterInterface.new_impl(this);
_ldkpersist = Persist.new_impl(this);
_ldkfilter = Filter.new_impl(this);
_ldkNetworkGraph = NetworkGraph.of(_ldkNetwork, _ldklogger);
_ldkChainMonitor = ChainMonitor.of( Option_FilterZ.Option_FilterZ_Some.some(_ldkfilter), _ldkbroadcaster, _ldklogger, _ldkfeeEstimator, _ldkpersist);
}
private static enums_Network GetLdkNetwork(Network network)
{
enums_Network? ldkNetwork = null;
if (network.ChainName == ChainName.Mainnet)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Bitcoin;
else if (network.ChainName == ChainName.Testnet)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Testnet;
else if (network.ChainName == ChainName.Regtest)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Regtest;
return ldkNetwork ?? throw new NotSupportedException();
}
private string GetWorkDir()
{
var dir = _dataDirectories.Value.DataDir;
return Path.Combine(dir, "Plugins", "LDK");
}
public async Task StartAsync(CancellationToken cancellationToken)
{
}
public async Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public int get_est_sat_per_1000_weight(ConfirmationTarget confirmation_target)
{
var feeProvider = _feeProviderFactory.CreateFeeProvider(_network);
var targetBlocks = confirmation_target switch
{
ConfirmationTarget.LDKConfirmationTarget_OnChainSweep => 30, // High priority (10-50 blocks)
ConfirmationTarget
.LDKConfirmationTarget_MaxAllowedNonAnchorChannelRemoteFee =>
20, // Moderate to high priority (small multiple of high-priority estimate)
ConfirmationTarget
.LDKConfirmationTarget_MinAllowedAnchorChannelRemoteFee =>
12, // Moderate priority (long-term mempool minimum or medium-priority)
ConfirmationTarget
.LDKConfirmationTarget_MinAllowedNonAnchorChannelRemoteFee =>
12, // Moderate priority (medium-priority feerate)
ConfirmationTarget.LDKConfirmationTarget_AnchorChannelFee => 6, // Lower priority (can be bumped later)
ConfirmationTarget
.LDKConfirmationTarget_NonAnchorChannelFee => 20, // Moderate to high priority (high-priority feerate)
ConfirmationTarget.LDKConfirmationTarget_ChannelCloseMinimum => 144, // Within a day or so (144-250 blocks)
_ => throw new ArgumentOutOfRangeException(nameof(confirmation_target), confirmation_target, null)
};
return (int) Math.Max(253, feeProvider.GetFeeRateAsync(targetBlocks).GetAwaiter().GetResult().FeePerK.Satoshi);
}
public void log(Record record)
{
var level = record.get_level() switch
{
Level.LDKLevel_Trace => LogLevel.Trace,
Level.LDKLevel_Debug => LogLevel.Debug,
Level.LDKLevel_Info => LogLevel.Information,
Level.LDKLevel_Warn => LogLevel.Warning,
Level.LDKLevel_Error => LogLevel.Error,
Level.LDKLevel_Gossip => LogLevel.Trace,
};
_logger.Log(level, $"[{record.get_module_path()}] {record.get_args()}");
}
public void broadcast_transactions(byte[][] txs)
{
foreach (var tx in txs)
{
var loadedTx = Transaction.Load(tx, _explorerClient.Network.NBitcoinNetwork);
_explorerClient.Broadcast(loadedTx);
}
}
public ChannelMonitorUpdateStatus persist_new_channel(OutPoint channel_id, ChannelMonitor data,
MonitorUpdateId update_id)
{
var name = Convert.ToHexString(channel_id.write());
File.WriteAllBytes(Path.Combine(_workDir, name), data.write());
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed;
}
public ChannelMonitorUpdateStatus update_persisted_channel(OutPoint channel_id, ChannelMonitorUpdate update,
ChannelMonitor data, MonitorUpdateId update_id)
{
var name = Convert.ToHexString(channel_id.write());
File.WriteAllBytes(Path.Combine(_workDir, name), data.write());
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed;
}
public void handle_event(Event _event)
{
switch (_event)
{
case Event.Event_BumpTransaction eventBumpTransaction:
switch (eventBumpTransaction.bump_transaction)
{
case BumpTransactionEvent.BumpTransactionEvent_ChannelClose bumpTransactionEventChannelClose:
break;
case BumpTransactionEvent.BumpTransactionEvent_HTLCResolution bumpTransactionEventHtlcResolution:
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
case Event.Event_ChannelClosed eventChannelClosed:
break;
case Event.Event_ChannelPending eventChannelPending:
break;
case Event.Event_ChannelReady eventChannelReady:
break;
case Event.Event_DiscardFunding eventDiscardFunding:
break;
case Event.Event_FundingGenerationReady eventFundingGenerationReady:
break;
case Event.Event_HTLCHandlingFailed eventHtlcHandlingFailed:
break;
case Event.Event_HTLCIntercepted eventHtlcIntercepted:
break;
case Event.Event_InvoiceRequestFailed eventInvoiceRequestFailed:
break;
case Event.Event_OpenChannelRequest eventOpenChannelRequest:
break;
case Event.Event_PaymentClaimable eventPaymentClaimable:
break;
case Event.Event_PaymentClaimed eventPaymentClaimed:
break;
case Event.Event_PaymentFailed eventPaymentFailed:
break;
case Event.Event_PaymentForwarded eventPaymentForwarded:
break;
case Event.Event_PaymentPathFailed eventPaymentPathFailed:
break;
case Event.Event_PaymentPathSuccessful eventPaymentPathSuccessful:
break;
case Event.Event_PaymentSent eventPaymentSent:
break;
case Event.Event_PendingHTLCsForwardable eventPendingHtlCsForwardable:
break;
case Event.Event_ProbeFailed eventProbeFailed:
break;
case Event.Event_ProbeSuccessful eventProbeSuccessful:
break;
case Event.Event_SpendableOutputs eventSpendableOutputs:
break;
default:
throw new ArgumentOutOfRangeException(nameof(_event));
}
}
public void register_tx(byte[] txid, byte[] script_pubkey)
{
throw new NotImplementedException();
}
public void register_output(WatchedOutput output)
{
throw new NotImplementedException();
}
}

View File

@@ -12,7 +12,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Liquid+</Product> <Product>Liquid+</Product>
<Description>Enhanced support for the liquid network.</Description> <Description>Enhanced support for the liquid network.</Description>
<Version>1.1.4</Version> <Version>1.1.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>

View File

@@ -10,7 +10,10 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Common; using BTCPayServer.Common;
using BTCPayServer.Data;
using BTCPayServer.Plugins.Altcoins; using BTCPayServer.Plugins.Altcoins;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -25,20 +28,24 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public class StoreLiquidController : Controller public class StoreLiquidController : Controller
{ {
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BTCPayServerClient _client;
private readonly IExplorerClientProvider _explorerClientProvider; private readonly IExplorerClientProvider _explorerClientProvider;
public StoreLiquidController(BTCPayNetworkProvider btcPayNetworkProvider, public StoreLiquidController(PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayServerClient client, IExplorerClientProvider explorerClientProvider) StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider,
IExplorerClientProvider explorerClientProvider)
{ {
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_client = client;
_explorerClientProvider = explorerClientProvider; _explorerClientProvider = explorerClientProvider;
} }
[HttpGet("stores/{storeId}/liquid")] [HttpGet("stores/{storeId}/liquid")]
public async Task<IActionResult> GenerateLiquidScript(string storeId, Dictionary<string, BitcoinExtKey> bitcoinExtKeys = null) public async Task<IActionResult> GenerateLiquidScript(string storeId,
Dictionary<string, BitcoinExtKey> bitcoinExtKeys = null)
{ {
Dictionary<string, string> generated = new Dictionary<string, string>(); Dictionary<string, string> generated = new Dictionary<string, string>();
var allNetworks = _btcPayNetworkProvider.GetAll().OfType<ElementsBTCPayNetwork>() var allNetworks = _btcPayNetworkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
@@ -49,12 +56,19 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
.Distinct(); .Distinct();
Dictionary<string, BitcoinExtKey> privKeys = bitcoinExtKeys ?? new Dictionary<string, BitcoinExtKey>(); Dictionary<string, BitcoinExtKey> privKeys = bitcoinExtKeys ?? new Dictionary<string, BitcoinExtKey>();
var store = await _storeRepository.FindStore(storeId);
var pms = store
.GetPaymentMethodConfigs<DerivationSchemeSettings>(_paymentMethodHandlerDictionary)
.Select(pair => (PaymentMethodId: pair.Key, DerivationSchemeSettings: pair.Value,
CryptoCode: pair.Key.ToString().Split("-")[0])).ToArray();
var paymentMethods = (await _client.GetStoreOnChainPaymentMethods(storeId)) var paymentMethodsGroupedByNetworkCode =
pms
.Where(settings => allNetworkCodes.Contains(settings.CryptoCode)) .Where(settings => allNetworkCodes.Contains(settings.CryptoCode))
.GroupBy(data => _btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(data.CryptoCode).NetworkCryptoCode); .GroupBy(data =>
_btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(data.CryptoCode).NetworkCryptoCode);
if (paymentMethods.Any() is false) if (paymentMethodsGroupedByNetworkCode.Any() is false)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
@@ -64,7 +78,7 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
return View(new GenerateLiquidImportScripts()); return View(new GenerateLiquidImportScripts());
} }
foreach (var der in paymentMethods) foreach (var der in paymentMethodsGroupedByNetworkCode)
{ {
var network = _btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(der.Key); var network = _btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(der.Key);
var nbxnet = network.NBXplorerNetwork; var nbxnet = network.NBXplorerNetwork;
@@ -79,12 +93,12 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
generated.Add(der.Key, sb.ToString()); generated.Add(der.Key, sb.ToString());
continue; continue;
} }
var derivationSchemesForNetwork = der.GroupBy(data => data.DerivationScheme);
var derivationSchemesForNetwork = der.GroupBy(data => data.DerivationSchemeSettings);
foreach (var paymentMethodDerivationScheme in derivationSchemesForNetwork) foreach (var paymentMethodDerivationScheme in derivationSchemesForNetwork)
{ {
var derivatonScheme = var derivatonScheme = paymentMethodDerivationScheme.Key.AccountDerivation;
nbxnet.DerivationStrategyFactory.Parse(paymentMethodDerivationScheme.Key);
var sameWalletCryptoCodes = paymentMethodDerivationScheme.Select(data => data.CryptoCode).ToArray(); var sameWalletCryptoCodes = paymentMethodDerivationScheme.Select(data => data.CryptoCode).ToArray();
var matchedExistingKey = privKeys.Where(pair => sameWalletCryptoCodes.Contains(pair.Key)); var matchedExistingKey = privKeys.Where(pair => sameWalletCryptoCodes.Contains(pair.Key));
BitcoinExtKey key = null; BitcoinExtKey key = null;
@@ -94,14 +108,12 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
} }
else else
{ {
key = await explorerClient.GetMetadataAsync<BitcoinExtKey>(derivatonScheme, key = await explorerClient.GetMetadataAsync<BitcoinExtKey>(derivatonScheme,
WellknownMetadataKeys.AccountHDKey); WellknownMetadataKeys.AccountHDKey);
} }
if (key != null) if (key != null)
{ {
foreach (var paymentMethodData in paymentMethodDerivationScheme) foreach (var paymentMethodData in paymentMethodDerivationScheme)
{ {
privKeys.TryAdd(paymentMethodData.CryptoCode, key); privKeys.TryAdd(paymentMethodData.CryptoCode, key);
@@ -139,14 +151,14 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
{ {
sb.AppendLine("elements-cli stop"); sb.AppendLine("elements-cli stop");
sb.AppendLine("elementsd -rescan"); sb.AppendLine("elementsd -rescan");
} }
generated.Add(der.Key, sb.ToString()); generated.Add(der.Key, sb.ToString());
} }
return View(new GenerateLiquidImportScripts() return View(new GenerateLiquidImportScripts()
{ {
Wallets = paymentMethods.SelectMany(settings => Wallets = paymentMethodsGroupedByNetworkCode.SelectMany(settings =>
settings.Select(data => settings.Select(data =>
new GenerateLiquidImportScripts.GenerateLiquidImportScriptWalletKeyVm() new GenerateLiquidImportScripts.GenerateLiquidImportScriptWalletKeyVm()
{ {
@@ -199,10 +211,9 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
continue; continue;
} }
var der = HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, wallet.CryptoCode).AccountDerivation;
var der = n.NBXplorerNetwork.DerivationStrategyFactory.Parse(
(await _client.GetStoreOnChainPaymentMethod(storeId, wallet.CryptoCode)).DerivationScheme);
if (der.GetExtPubKeys().Count() > 1) if (der.GetExtPubKeys().Count() > 1)
{ {
vm.AddModelError(scripts => scripts.Wallets[index].ManualKey, "cannot handle multsig", this); vm.AddModelError(scripts => scripts.Wallets[index].ManualKey, "cannot handle multsig", this);
@@ -250,6 +261,7 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
return await GenerateLiquidScript(storeId, privKeys); return await GenerateLiquidScript(storeId, privKeys);
} }
public class GenerateLiquidImportScripts public class GenerateLiquidImportScripts
{ {
public class GenerateLiquidImportScriptWalletKeyVm public class GenerateLiquidImportScriptWalletKeyVm
@@ -266,6 +278,7 @@ public class GenerateLiquidImportScripts
} }
} }
} }
namespace XX namespace XX
{ {
public static class ModelStateExtensions public static class ModelStateExtensions
@@ -275,7 +288,9 @@ namespace XX
string message, string message,
ControllerBase controller) ControllerBase controller)
{ {
var provider = (ModelExpressionProvider)controller.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider)); var provider =
(ModelExpressionProvider) controller.HttpContext.RequestServices.GetService(
typeof(ModelExpressionProvider));
var key = provider.GetExpressionText(ex); var key = provider.GetExpressionText(ex);
controller.ModelState.AddModelError(key, message); controller.ModelState.AddModelError(key, message);
} }

View File

@@ -22,7 +22,7 @@ namespace BTCPayServer.Plugins.LiquidPlus
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"} new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
@@ -31,11 +31,11 @@ namespace BTCPayServer.Plugins.LiquidPlus
if (services.BootstrapServices.GetRequiredService<NBXplorerNetworkProvider>() if (services.BootstrapServices.GetRequiredService<NBXplorerNetworkProvider>()
.GetFromCryptoCode("LBTC") is null || !services.BootstrapServices.GetRequiredService<SelectedChains>().Contains("LBTC")) .GetFromCryptoCode("LBTC") is null || !services.BootstrapServices.GetRequiredService<SelectedChains>().Contains("LBTC"))
return; return;
services.AddSingleton<IUIExtension>(new UIExtension("LiquidNav", "store-integrations-nav")); services.AddUIExtension("store-integrations-nav", "LiquidNav");
services.AddSingleton<IUIExtension>(new UIExtension("OnChainWalletSetupLiquidExtension", services.AddUIExtension("onchain-wallet-setup-post-body", "OnChainWalletSetupLiquidExtension");
"onchain-wallet-setup-post-body")); services.AddUIExtension("server-nav", "CustomLiquidAssetsNavExtension");
services.AddSingleton<IUIExtension>(new UIExtension("CustomLiquidAssetsNavExtension", "server-nav")); services.AddUIExtension("store-nav", "StoreNavLiquidExtension");
services.AddSingleton<IUIExtension>(new UIExtension("StoreNavLiquidExtension", "store-nav"));
services.AddSingleton<CustomLiquidAssetsRepository>(); services.AddSingleton<CustomLiquidAssetsRepository>();
@@ -53,12 +53,11 @@ namespace BTCPayServer.Plugins.LiquidPlus
CryptoCode: "LBTC" CryptoCode: "LBTC"
}) })
.ImplementationInstance; .ImplementationInstance;
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("LBTC");
var tlProvider = (TransactionLinkProviders.Entry) services.Single(descriptor => var tlProvider = (TransactionLinkProviders.Entry) services.Single(descriptor =>
descriptor.ServiceType == typeof(TransactionLinkProviders.Entry) && descriptor.ServiceType == typeof(TransactionLinkProviders.Entry) &&
descriptor.ImplementationInstance is TransactionLinkProviders.Entry descriptor.ImplementationInstance is TransactionLinkProviders.Entry entry && entry.PaymentMethodId == pmi)
{
PaymentMethodId: {CryptoCode: "LBTC"}
})
.ImplementationInstance; .ImplementationInstance;
settings.Items.ForEach(configuration => settings.Items.ForEach(configuration =>
@@ -80,7 +79,6 @@ namespace BTCPayServer.Plugins.LiquidPlus
NetworkCryptoCode = template.NetworkCryptoCode, NetworkCryptoCode = template.NetworkCryptoCode,
DefaultSettings = template.DefaultSettings, DefaultSettings = template.DefaultSettings,
ElectrumMapping = template.ElectrumMapping, ElectrumMapping = template.ElectrumMapping,
BlockExplorerLink = template.BlockExplorerLink,
ReadonlyWallet = template.ReadonlyWallet, ReadonlyWallet = template.ReadonlyWallet,
SupportLightning = false, SupportLightning = false,
SupportPayJoin = false, SupportPayJoin = false,
@@ -92,7 +90,7 @@ namespace BTCPayServer.Plugins.LiquidPlus
VaultSupported = template.VaultSupported, VaultSupported = template.VaultSupported,
MaxTrackedConfirmation = template.MaxTrackedConfirmation, MaxTrackedConfirmation = template.MaxTrackedConfirmation,
SupportRBF = template.SupportRBF SupportRBF = template.SupportRBF
}).AddTransactionLinkProvider(new PaymentMethodId(code, PaymentTypes.BTCLike), tlProvider.Provider); }).AddTransactionLinkProvider(code, tlProvider.Provider);
}); });
} }
} }

View File

@@ -34,7 +34,7 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\**" /> <EmbeddedResource Include="Resources\**" />
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" /> <ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
<PackageReference Include="AsyncKeyedLock" Version="6.4.2" /> <PackageReference Include="AsyncKeyedLock" Version="7.0.1" />
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" /> <PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" />
<PackageReference Include="Laraue.EfCoreTriggers.PostgreSql" Version="8.0.3" /> <PackageReference Include="Laraue.EfCoreTriggers.PostgreSql" Version="8.0.3" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.10.0" /> <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.10.0" />

View File

@@ -1,23 +1,29 @@
using BTCPayServer.Abstractions.Contracts; using System;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using Laraue.EfCoreTriggers.PostgreSql.Extensions; using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace BTCPayServer.Plugins.MicroNode; namespace BTCPayServer.Plugins.MicroNode;
public class MicroNodeContextFactory : BaseDbContextFactory<MicroNodeContext> public class MicroNodeContextFactory : BaseDbContextFactory<MicroNodeContext>
{ {
public MicroNodeContextFactory(IOptions<DatabaseOptions> options) : base(options, "BTCPayServer.Plugins.MicroNode") private readonly ILoggerFactory _loggerFactory;
public MicroNodeContextFactory(IOptions<DatabaseOptions> options, ILoggerFactory loggerFactory) : base(options, "BTCPayServer.Plugins.MicroNode")
{ {
_loggerFactory = loggerFactory;
} }
public override MicroNodeContext CreateContext() public override MicroNodeContext CreateContext(Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null)
{ {
var builder = new DbContextOptionsBuilder<MicroNodeContext>(); var builder = new DbContextOptionsBuilder<MicroNodeContext>();
ConfigureBuilder(builder); builder.UseLoggerFactory(_loggerFactory);
builder.UsePostgreSqlTriggers(); builder.AddInterceptors(MigrationInterceptor.Instance);
ConfigureBuilder(builder, npgsqlOptionsAction);
return new MicroNodeContext(builder.Options); return new MicroNodeContext(builder.Options);
} }
} }

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -17,14 +18,17 @@ namespace BTCPayServer.Plugins.MicroNode;
[Route("plugins/micronode")] [Route("plugins/micronode")]
public class MicroNodeController : Controller public class MicroNodeController : Controller
{ {
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly MicroNodeService _microNodeService; private readonly MicroNodeService _microNodeService;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _networkProvider; private readonly BTCPayNetworkProvider _networkProvider;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
public MicroNodeController(MicroNodeService microNodeService, StoreRepository storeRepository, public MicroNodeController(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,MicroNodeService microNodeService, StoreRepository storeRepository,
BTCPayNetworkProvider networkProvider, IAuthorizationService authorizationService) BTCPayNetworkProvider networkProvider, IAuthorizationService authorizationService)
{ {
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_microNodeService = microNodeService; _microNodeService = microNodeService;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_networkProvider = networkProvider; _networkProvider = networkProvider;
@@ -66,11 +70,8 @@ public class MicroNodeController : Controller
ModelState.AddModelError("masterStoreId", "Master cannot be the same as this store"); ModelState.AddModelError("masterStoreId", "Master cannot be the same as this store");
return View(settings); return View(settings);
} }
var pmi = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
var existing = store.GetSupportedPaymentMethods(_networkProvider).OfType<LightningSupportedPaymentMethod>() var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
.FirstOrDefault(method =>
method.PaymentId.PaymentType == LightningPaymentType.Instance &&
method.PaymentId.CryptoCode == network.CryptoCode);
var isSet = settings?.Key is not null; var isSet = settings?.Key is not null;
settings ??= new MicroNodeStoreSettings(); settings ??= new MicroNodeStoreSettings();
settings.Key ??= Guid.NewGuid().ToString(); settings.Key ??= Guid.NewGuid().ToString();
@@ -118,12 +119,10 @@ public class MicroNodeController : Controller
} }
existing ??= new LightningSupportedPaymentMethod() existing ??= new();
{
CryptoCode = "BTC"
};
existing.SetLightningUrl(mlc); existing.SetLightningUrl(mlc);
store.SetSupportedPaymentMethod(existing);
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], existing);
await _microNodeService.Set(storeId, settings, masterStoreId); await _microNodeService.Set(storeId, settings, masterStoreId);
@@ -145,7 +144,7 @@ public class MicroNodeController : Controller
if (isStoreSetToThisMicro) if (isStoreSetToThisMicro)
{ {
store.SetSupportedPaymentMethod(existing.PaymentId, null); store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null);
await _storeRepository.UpdateStore(store); await _storeRepository.UpdateStore(store);
} }

View File

@@ -11,7 +11,7 @@ public class MicroNodePlugin:BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new () { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" } new () { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
}; };

View File

@@ -15,7 +15,9 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -34,6 +36,7 @@ public class MicroNodeService : EventHostedServiceBase
private readonly ILogger<MicroNodeService> _logger; private readonly ILogger<MicroNodeService> _logger;
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions; private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private static readonly ConcurrentDictionary<string, long> ExpectedCounter = new(); private static readonly ConcurrentDictionary<string, long> ExpectedCounter = new();
private readonly TaskCompletionSource _init = new(); private readonly TaskCompletionSource _init = new();
private Dictionary<string, MicroNodeSettings> _ownerSettings; private Dictionary<string, MicroNodeSettings> _ownerSettings;
@@ -53,6 +56,7 @@ public class MicroNodeService : EventHostedServiceBase
EventAggregator eventAggregator, EventAggregator eventAggregator,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
IOptions<LightningNetworkOptions> lightningNetworkOptions, IOptions<LightningNetworkOptions> lightningNetworkOptions,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
IServiceProvider serviceProvider) : base(eventAggregator, logger) IServiceProvider serviceProvider) : base(eventAggregator, logger)
{ {
_network = btcPayNetworkProvider.BTC; _network = btcPayNetworkProvider.BTC;
@@ -63,6 +67,7 @@ public class MicroNodeService : EventHostedServiceBase
_logger = logger; _logger = logger;
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
_lightningNetworkOptions = lightningNetworkOptions; _lightningNetworkOptions = lightningNetworkOptions;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
@@ -124,17 +129,20 @@ public class MicroNodeService : EventHostedServiceBase
{ {
var b = payout.GetBlob(_btcPayNetworkJsonSerializerSettings); var b = payout.GetBlob(_btcPayNetworkJsonSerializerSettings);
List<MicroTransaction> res = new(); List<MicroTransaction> res =
res.Add(new MicroTransaction() [
new()
{ {
Id = payout.Id, Id = payout.Id,
AccountId = key, AccountId = key,
Amount = -LightMoney.Coins(b.CryptoAmount.Value).MilliSatoshi, Amount = -LightMoney.Coins(payout.Amount!.Value).MilliSatoshi,
Accounted = payout.State != PayoutState.Cancelled, Accounted = payout.State != PayoutState.Cancelled,
Active = payout.State is PayoutState.AwaitingApproval or PayoutState.AwaitingPayment Active = payout.State is PayoutState.AwaitingApproval or PayoutState.AwaitingPayment
or PayoutState.InProgress, or PayoutState.InProgress,
Type = "Payout" Type = "Payout"
}); }
];
if (b.Metadata?.TryGetValue("Fee", out var microNode) is true && microNode.Value<decimal>() is { } payoutFee) if (b.Metadata?.TryGetValue("Fee", out var microNode) is true && microNode.Value<decimal>() is { } payoutFee)
{ {
@@ -230,7 +238,7 @@ public class MicroNodeService : EventHostedServiceBase
await using var ctx = _microNodeContextFactory.CreateContext(); await using var ctx = _microNodeContextFactory.CreateContext();
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
var account = await ctx.MicroAccounts.FindAsync(key); var account = await ctx.MicroAccounts.FindAsync(key, cancellation);
if (account is null) if (account is null)
{ {
return null; return null;
@@ -336,9 +344,9 @@ public class MicroNodeService : EventHostedServiceBase
return null; return null;
} }
var lightningConnectionString = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) var pmi = PaymentTypes.LN.GetPaymentMethodId(_network.CryptoCode);
.OfType<LightningSupportedPaymentMethod>() var lightningConnectionString = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary)
.FirstOrDefault(method => method.CryptoCode == _network.CryptoCode)?.CreateLightningClient(_network, ?.CreateLightningClient(_network,
_lightningNetworkOptions.Value, _serviceProvider.GetService<LightningClientFactoryService>()); _lightningNetworkOptions.Value, _serviceProvider.GetService<LightningClientFactoryService>());
return lightningConnectionString; return lightningConnectionString;
} }
@@ -616,7 +624,7 @@ public class MicroNodeService : EventHostedServiceBase
StoreId = masterClients.Key, StoreId = masterClients.Key,
Destination = new LNURLPayClaimDestinaton(destination), Destination = new LNURLPayClaimDestinaton(destination),
PreApprove = true, PreApprove = true,
PaymentMethodId = new PaymentMethodId("BTC", LightningPaymentType.Instance), PayoutMethodId = PayoutTypes.LN.GetPayoutMethodId("BTC"),
Metadata = JObject.FromObject(new Metadata = JObject.FromObject(new
{ {
Source = $"MicroNode on store {storeId.Key}" Source = $"MicroNode on store {storeId.Key}"
@@ -667,7 +675,7 @@ public class MicroNodeService : EventHostedServiceBase
await base.StopAsync(cancellationToken); await base.StopAsync(cancellationToken);
} }
private ConcurrentDictionary<string, string> _keyToMasterStoreId = new(); private readonly ConcurrentDictionary<string, string> _keyToMasterStoreId = new();
public async Task Set(string storeId, MicroNodeStoreSettings? settings, string? masterStoreId = null) public async Task Set(string storeId, MicroNodeStoreSettings? settings, string? masterStoreId = null)
{ {

View File

@@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Nostr</Product> <Product>Nostr</Product>
<Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description> <Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description>
<Version>1.1.13</Version> <Version>1.1.14</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Plugins.NIP05
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"} new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -32,7 +32,7 @@
<div class="col-xl-8 col-xxl-constrain"> <div class="col-xl-8 col-xxl-constrain">
<div class="form-group"> <div class="form-group">
<div class="form-group pt-2"> <div class="form-group pt-2">
<label asp-for="Name (does not need to be same as for a lightning address)" class="form-label"></label> <label asp-for="Name" class="form-label">Name (does not need to be same as for a lightning address)</label>
<div class="input-group"> <div class="input-group">
<input asp-for="Name" class="form-control"/> <input asp-for="Name" class="form-control"/>
<span class="input-group-text">@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span> <span class="input-group-text">@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span>

View File

@@ -5,11 +5,9 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Events; using BTCPayServer.Events;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -24,6 +22,7 @@ public class Zapper : IHostedService
{ {
record PendingZapEvent(string[] relays, NostrEvent nostrEvent); record PendingZapEvent(string[] relays, NostrEvent nostrEvent);
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly Nip5Controller _nip5Controller; private readonly Nip5Controller _nip5Controller;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
@@ -51,7 +50,9 @@ public class Zapper : IHostedService
} }
public Zapper(EventAggregator eventAggregator, public Zapper(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
EventAggregator eventAggregator,
Nip5Controller nip5Controller, Nip5Controller nip5Controller,
IMemoryCache memoryCache, IMemoryCache memoryCache,
ILogger<Zapper> logger, ILogger<Zapper> logger,
@@ -59,6 +60,7 @@ public class Zapper : IHostedService
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
NostrClientPool nostrClientPool) NostrClientPool nostrClientPool)
{ {
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_nip5Controller = nip5Controller; _nip5Controller = nip5Controller;
_memoryCache = memoryCache; _memoryCache = memoryCache;
@@ -150,7 +152,8 @@ public class Zapper : IHostedService
{ {
if (arg.EventCode != InvoiceEventCode.Completed && arg.EventCode != InvoiceEventCode.MarkedCompleted) if (arg.EventCode != InvoiceEventCode.Completed && arg.EventCode != InvoiceEventCode.MarkedCompleted)
return; return;
var pm = arg.Invoice.GetPaymentMethod(new PaymentMethodId("BTC", LNURLPayPaymentType.Instance)); var pmi = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
var pm = arg.Invoice.GetPaymentPrompt(pmi);
if (pm is null) if (pm is null)
{ {
return; return;
@@ -160,7 +163,6 @@ public class Zapper : IHostedService
return; return;
} }
var pmd = (LNURLPayPaymentMethodDetails) pm.GetPaymentMethodDetails();
var settings = await GetSettings(); var settings = await GetSettings();
var zapRequestEvent = JsonSerializer.Deserialize<NostrEvent>(zapRequest); var zapRequestEvent = JsonSerializer.Deserialize<NostrEvent>(zapRequest);
@@ -174,7 +176,7 @@ public class Zapper : IHostedService
new NostrEventTag new NostrEventTag
{ {
TagIdentifier = "bolt11", TagIdentifier = "bolt11",
Data = new() {pmd.BOLT11} Data = new() {pm.Destination}
}, },
new NostrEventTag() new NostrEventTag()

View File

@@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Prism</Product> <Product>Prism</Product>
<Description>Automated value splits for Bitcoin.</Description> <Description>Automated value splits for Bitcoin.</Description>
<Version>1.2.8</Version> <Version>1.2.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -4,6 +4,7 @@
@using BTCPayServer.HostedServices @using BTCPayServer.HostedServices
@using BTCPayServer.Payments @using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors @using BTCPayServer.PayoutProcessors
@using BTCPayServer.Payouts
@using Microsoft.AspNetCore.Http @using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Routing @using Microsoft.AspNetCore.Routing
@using Microsoft.Extensions.Logging @using Microsoft.Extensions.Logging
@@ -28,7 +29,7 @@ else
{ {
<datalist id="users"> <datalist id="users">
<option value="*">Catch-all lightning payments made against invoices in your store (excluding when other prisms are configured that capture those payments.)</option> <option value="*">Catch-all lightning payments made against invoices in your store (excluding when other prisms are configured that capture those payments.)</option>
<option value="*@BitcoinPaymentType.Instance.ToStringNormalized()">Catch-all on-chain payments made against invoices in your store</option> <option value="*@PaymentTypes.CHAIN.ToString()">Catch-all on-chain payments made against invoices in your store</option>
<option value="*All">Catch-all any payments made against invoices in your store</option> <option value="*All">Catch-all any payments made against invoices in your store</option>
@foreach (var user in Users) @foreach (var user in Users)
@@ -201,8 +202,8 @@ else
public bool Loading { get; set; } = true; public bool Loading { get; set; } = true;
public List<LightningAddressData> Users { get; set; } = new(); public List<LightningAddressData> Users { get; set; } = new();
public PaymentMethodId pmi { get; set; } = new("BTC", LightningPaymentType.Instance); public PayoutMethodId pmi { get; set; } = PayoutTypes.LN.GetPayoutMethodId("BTC");
public PaymentMethodId pmichain { get; set; } = new("BTC", PaymentTypes.BTCLike); public PayoutMethodId pmichain { get; set; } = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
public bool NoPayoutProcessors { get; set; } public bool NoPayoutProcessors { get; set; }
private string PrismEditButtonsFilter { get; set; } private string PrismEditButtonsFilter { get; set; }
@@ -224,7 +225,7 @@ else
var fetchProcessors = PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery() var fetchProcessors = PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
{ {
Stores = new[] {StoreId}, Stores = new[] {StoreId},
PaymentMethods = new[] {pmi.ToString(), pmichain.ToString()} PayoutMethods = new[] {pmi, pmichain}
}); });
var tasks = new Task[] var tasks = new Task[]
@@ -243,8 +244,8 @@ else
EditContext.OnFieldChanged += FieldChanged; EditContext.OnFieldChanged += FieldChanged;
SatBreaker.PrismUpdated += SatBreakerOnPrismUpdated; SatBreaker.PrismUpdated += SatBreakerOnPrismUpdated;
//set NoPayoutProcessors to true if there are no configured payout processores for pmi and pmichain //set NoPayoutProcessors to true if there are no configured payout processores for pmi and pmichain
NoPayoutProcessors = PayoutProcessorFactories.Any(factory => factory.GetSupportedPaymentMethods().Contains(pmi)) && (await fetchProcessors).All(data => NoPayoutProcessors = PayoutProcessorFactories.Any(factory => factory.GetSupportedPayoutMethods().Contains(pmi)) && (await fetchProcessors).All(data =>
!new[] {pmi, pmichain}.Contains(data.GetPaymentMethodId())); !new[] {pmi, pmichain}.Contains(data.GetPayoutMethodId()));
Loading = false; Loading = false;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts;
namespace BTCPayServer.Plugins.Prism; namespace BTCPayServer.Plugins.Prism;
@@ -19,7 +20,7 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult() return Task.FromResult<object>(new PrismDestinationValidationResult()
{ {
Success = true, Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.LNURLPay) PayoutMethodId = PayoutTypes.LN.GetPayoutMethodId("BTC")
}); });
} }
catch (Exception e) catch (Exception e)
@@ -30,7 +31,7 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult() return Task.FromResult<object>(new PrismDestinationValidationResult()
{ {
Success = true, Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.LNURLPay) PayoutMethodId =PayoutTypes.LN.GetPayoutMethodId("BTC")
}); });
} }
catch (Exception) catch (Exception)
@@ -45,5 +46,5 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
public class PrismDestinationValidationResult public class PrismDestinationValidationResult
{ {
public bool Success { get; set; } public bool Success { get; set; }
public PaymentMethodId PaymentMethod { get; set; } public PayoutMethodId PayoutMethodId { get; set; }
} }

View File

@@ -1,10 +1,9 @@
using System; using System;
using System.Collections;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payouts;
using NBitcoin; using NBitcoin;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
@@ -35,7 +34,7 @@ public class OnChainPrismClaimCreate : IPluginHookFilter
{ {
claimRequest.Destination = claimRequest.Destination =
new AddressClaimDestination(BitcoinAddress.Create(destStr, network.NBitcoinNetwork)); new AddressClaimDestination(BitcoinAddress.Create(destStr, network.NBitcoinNetwork));
claimRequest.PaymentMethodId = new PaymentMethodId("BTC", BitcoinPaymentType.Instance); claimRequest.PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
return args; return args;
} }
catch (Exception) catch (Exception)
@@ -48,7 +47,7 @@ public class OnChainPrismClaimCreate : IPluginHookFilter
claimRequest.Destination = claimRequest.Destination =
new AddressClaimDestination(add.Address); new AddressClaimDestination(add.Address);
claimRequest.PaymentMethodId = new PaymentMethodId("BTC", BitcoinPaymentType.Instance); claimRequest.PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
} }
catch (Exception exception) catch (Exception exception)
{ {

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
@@ -34,7 +35,7 @@ public class OnChainPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult() return Task.FromResult<object>(new PrismDestinationValidationResult()
{ {
Success = true, Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike) PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC")
}); });
} }
catch (Exception e) catch (Exception e)
@@ -46,7 +47,7 @@ public class OnChainPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult() return Task.FromResult<object>(new PrismDestinationValidationResult()
{ {
Success = true, Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike) PayoutMethodId =PayoutTypes.CHAIN.GetPayoutMethodId("BTC")
}); });
} }
catch (Exception) catch (Exception)

View File

@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -30,7 +31,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
var parts = args1.ToLowerInvariant().Split(":", StringSplitOptions.RemoveEmptyEntries); var parts = args1.ToLowerInvariant().Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "general_fund"; var project = "general_fund";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike); var paymentMethod = PayoutTypes.LN.GetPayoutMethodId("BTC");
if (parts.Length > 1) if (parts.Length > 1)
{ {
project = parts[1]; project = parts[1];
@@ -38,12 +39,11 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
if (parts.Length > 2) if (parts.Length > 2)
{ {
paymentMethod = PaymentMethodId.Parse(parts[2]); paymentMethod = PayoutMethodId.Parse(parts[2]);
} }
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod); if (_serviceProvider.GetService<PayoutMethodHandlerDictionary>().TryGetValue(paymentMethod, out var handler))
if (handler is null)
{ {
result.Success = false; result.Success = false;
} }
@@ -63,7 +63,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
var invoiceBtcpayModel = JObject.Parse(await httpClient.GetStringAsync(invoiceUrl).ConfigureAwait(false)); var invoiceBtcpayModel = JObject.Parse(await httpClient.GetStringAsync(invoiceUrl).ConfigureAwait(false));
var destination = invoiceBtcpayModel.Value<string>("btcAddress"); var destination = invoiceBtcpayModel.Value<string>("btcAddress");
var claimDestination = await handler.ParseClaimDestination(paymentMethod,destination, CancellationToken.None); var claimDestination = await handler.ParseClaimDestination(destination, CancellationToken.None);
if (claimDestination.destination is null) if (claimDestination.destination is null)
{ {
@@ -72,7 +72,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
result.Success = true; result.Success = true;
result.PaymentMethod = paymentMethod; result.PayoutMethodId = paymentMethod;
return result; return result;
} }
catch (Exception e) catch (Exception e)

View File

@@ -1,18 +1,12 @@
using System; using System;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Custodians;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payouts;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism; namespace BTCPayServer.Plugins.Prism;
@@ -41,7 +35,7 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
var parts = args1.Split(":", StringSplitOptions.RemoveEmptyEntries); var parts = args1.Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "opensats"; var project = "opensats";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike); var paymentMethod = PayoutTypes.LN.GetPayoutMethodId("BTC");
if (parts.Length > 1) if (parts.Length > 1)
{ {
project = parts[1]; project = parts[1];
@@ -49,11 +43,11 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
if (parts.Length > 2) if (parts.Length > 2)
{ {
paymentMethod = PaymentMethodId.Parse(parts[2]); paymentMethod = PayoutMethodId.Parse(parts[2]);
} }
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod); _serviceProvider.GetService<PayoutMethodHandlerDictionary>().TryGetValue(paymentMethod, out var handler);
if (handler is null) if (handler is null)
{ {
return null; return null;
@@ -75,7 +69,7 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
var destination = invoiceBtcpayModel.Value<string>("btcAddress"); var destination = invoiceBtcpayModel.Value<string>("btcAddress");
var receiptLink = invoiceBtcpayModel.Value<string>("receiptLink"); var receiptLink = invoiceBtcpayModel.Value<string>("receiptLink");
var claimDestination = await handler.ParseClaimDestination(paymentMethod,destination, CancellationToken.None); var claimDestination = await handler.ParseClaimDestination(destination, CancellationToken.None);
if (claimDestination.destination is null) if (claimDestination.destination is null)
{ {
@@ -88,7 +82,7 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
}); });
claimRequest.Destination = claimDestination.destination; claimRequest.Destination = claimDestination.destination;
claimRequest.PaymentMethodId = paymentMethod; claimRequest.PayoutMethodId = paymentMethod;
return claimRequest; return claimRequest;

View File

@@ -11,7 +11,7 @@ public class PrismPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"} new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -20,5 +20,5 @@ public class PrismDestination
public string Destination { get; set; } public string Destination { get; set; }
public decimal? Reserve { get; set; } public decimal? Reserve { get; set; }
public long? SatThreshold { get; set; } public long? SatThreshold { get; set; }
public string? PaymentMethodId { get; set; } public string? PayoutMethodId { get; set; }
} }

View File

@@ -13,6 +13,7 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
@@ -47,13 +48,13 @@ namespace BTCPayServer.Plugins.Prism
{ {
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly ILogger<SatBreaker> _logger; private readonly ILogger<SatBreaker> _logger;
private readonly LightningAddressService _lightningAddressService;
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly LightningLikePayoutHandler _lightningLikePayoutHandler;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly LightningClientFactoryService _lightningClientFactoryService; private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions; private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly PayoutMethodHandlerDictionary _payoutMethodHandlerDictionary;
private readonly IPluginHookService _pluginHookService; private readonly IPluginHookService _pluginHookService;
private Dictionary<string, PrismSettings> _prismSettings; private Dictionary<string, PrismSettings> _prismSettings;
@@ -62,24 +63,24 @@ namespace BTCPayServer.Plugins.Prism
public SatBreaker(StoreRepository storeRepository, public SatBreaker(StoreRepository storeRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
ILogger<SatBreaker> logger, ILogger<SatBreaker> logger,
LightningAddressService lightningAddressService,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
LightningLikePayoutHandler lightningLikePayoutHandler,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
LightningClientFactoryService lightningClientFactoryService, LightningClientFactoryService lightningClientFactoryService,
IOptions<LightningNetworkOptions> lightningNetworkOptions, IOptions<LightningNetworkOptions> lightningNetworkOptions,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
PayoutMethodHandlerDictionary payoutMethodHandlerDictionary,
IPluginHookService pluginHookService) : base(eventAggregator, logger) IPluginHookService pluginHookService) : base(eventAggregator, logger)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_logger = logger; _logger = logger;
_lightningAddressService = lightningAddressService;
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
_lightningLikePayoutHandler = lightningLikePayoutHandler;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactoryService = lightningClientFactoryService; _lightningClientFactoryService = lightningClientFactoryService;
_lightningNetworkOptions = lightningNetworkOptions; _lightningNetworkOptions = lightningNetworkOptions;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_payoutMethodHandlerDictionary = payoutMethodHandlerDictionary;
_pluginHookService = pluginHookService; _pluginHookService = pluginHookService;
} }
@@ -148,17 +149,25 @@ namespace BTCPayServer.Plugins.Prism
foreach (var payout in storePayouts) foreach (var payout in storePayouts)
{ {
if (!pendingPayouts.TryGetValue(payout.Id, out var pendingPayout)) if (!pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
{ {
continue; continue;
} }
if(payout.GetPayoutMethodId() is not { } payoutMethodId)
continue;
if (!_payoutMethodHandlerDictionary.TryGetValue(payoutMethodId, out var handler))
{
continue;
}
long toCredit = 0; long toCredit = 0;
switch (payout.State) switch (payout.State)
{ {
case PayoutState.Completed: case PayoutState.Completed:
var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob; var proof = handler.ParseProof(payout) as PayoutLightningBlob;
long? feePaid = null; long? feePaid = null;
if (!string.IsNullOrEmpty(proof?.PaymentHash)) if (!string.IsNullOrEmpty(proof?.PaymentHash))
@@ -168,10 +177,10 @@ namespace BTCPayServer.Plugins.Prism
var store = await _storeRepository.FindStore(payout.StoreDataId); var store = await _storeRepository.FindStore(payout.StoreDataId);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"); var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var id = new PaymentMethodId("BTC", LightningPaymentType.Instance); var id = PaymentTypes.LN.GetPaymentMethodId("BTC");
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) var existing =
.OfType<LightningSupportedPaymentMethod>() store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id,
.FirstOrDefault(d => d.PaymentId == id); _paymentMethodHandlerDictionary);
if (existing?.GetExternalLightningUrl() is { } connectionString) if (existing?.GetExternalLightningUrl() is { } connectionString)
{ {
lnClient = _lightningClientFactoryService.Create(connectionString, lnClient = _lightningClientFactoryService.Create(connectionString,
@@ -313,12 +322,16 @@ namespace BTCPayServer.Plugins.Prism
private (Split, LightMoney)[] DetermineMatches(PrismSettings prismSettings, InvoiceEntity entity) private (Split, LightMoney)[] DetermineMatches(PrismSettings prismSettings, InvoiceEntity entity)
{ {
//first check the primary thing - ln address //first check the primary thing - ln address
var explicitPMI = new PaymentMethodId("BTC", LNURLPayPaymentType.Instance); var explicitPMI = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
var pm = entity.GetPaymentMethod(explicitPMI); var pm = entity.GetPaymentPrompt(explicitPMI);
var pmd = pm?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
List<(Split, LightMoney)> result = new(); var payments = entity.GetPayments(true).GroupBy(paymentEntity => paymentEntity.PaymentMethodId).ToArray();
List<(Split, LightMoney)> result = new();
if(_paymentMethodHandlerDictionary.TryGetValue(explicitPMI, out var handler) && pm is not null)
{
var pmd = handler.ParsePaymentPromptDetails(pm.Details) as LNURLPayPaymentMethodDetails;
var payments = entity.GetPayments(true).GroupBy(paymentEntity => paymentEntity.GetPaymentMethodId()).ToArray();
if (pmd?.ConsumedLightningAddress is not null) if (pmd?.ConsumedLightningAddress is not null)
{ {
var address = pmd.ConsumedLightningAddress.Split("@")[0]; var address = pmd.ConsumedLightningAddress.Split("@")[0];
@@ -337,6 +350,7 @@ namespace BTCPayServer.Plugins.Prism
} }
} }
} }
}
var catchAlls = prismSettings.Splits.Where(split => split.Source.StartsWith("*")).Select(split => var catchAlls = prismSettings.Splits.Where(split => split.Source.StartsWith("*")).Select(split =>
{ {
@@ -346,20 +360,25 @@ namespace BTCPayServer.Plugins.Prism
switch (split.Source) switch (split.Source)
{ {
case "*": case "*":
pmi = new PaymentMethodId("BTC", PaymentTypes.LightningLike); pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
break; break;
case "*All": case "*All":
break; break;
case var s when PaymentTypes.TryParse(s.Substring(1), out var pType): case var s when s.StartsWith("*") && s.Substring(1) ==PaymentTypes.CHAIN.ToString():
pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
pmi = new PaymentMethodId("BTC", pType); break;
case var s2 when s2.StartsWith("*") && s2.Substring(1) ==PaymentTypes.LN.ToString():
pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
break;
case var s3 when s3.StartsWith("*") && s3.Substring(1) ==PaymentTypes.LNURL.ToString():
pmi = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
break; break;
case var s when !PaymentMethodId.TryParse(s.Substring(1), out pmi): case var s when !PaymentMethodId.TryParse(s.Substring(1), out pmi):
valid = false; valid = false;
break; break;
} }
if (pmi is not null && pmi.CryptoCode != "BTC") if (pmi is not null && !pmi.ToString().StartsWith("BTC-"))
{ {
valid = false; valid = false;
} }
@@ -367,6 +386,7 @@ namespace BTCPayServer.Plugins.Prism
return (pmi, valid, split); return (pmi, valid, split);
}).Where(tuple => tuple.valid).ToDictionary(split => split.pmi, split => split.split); }).Where(tuple => tuple.valid).ToDictionary(split => split.pmi, split => split.split);
while(payments.Any() || catchAlls.Any()) while(payments.Any() || catchAlls.Any())
{ {
decimal paymentSum; decimal paymentSum;
@@ -511,9 +531,9 @@ namespace BTCPayServer.Plugins.Prism
continue; continue;
} }
var pmi = string.IsNullOrEmpty(destinationSettings?.PaymentMethodId) || var pmi = string.IsNullOrEmpty(destinationSettings?.PayoutMethodId) ||
!PaymentMethodId.TryParse(destinationSettings?.PaymentMethodId, out var pmi2) !PayoutMethodId.TryParse(destinationSettings?.PayoutMethodId, out var pmi2)
? new PaymentMethodId("BTC", LightningPaymentType.Instance) ? PayoutTypes.LN.GetPayoutMethodId("BTC")
: pmi2; : pmi2;
var source = "Prism"; var source = "Prism";
@@ -526,7 +546,7 @@ namespace BTCPayServer.Plugins.Prism
Destination = new PrismPlaceholderClaimDestination(destinationSettings?.Destination ?? destination), Destination = new PrismPlaceholderClaimDestination(destinationSettings?.Destination ?? destination),
PreApprove = true, PreApprove = true,
StoreId = storeId, StoreId = storeId,
PaymentMethodId = pmi, PayoutMethodId = pmi,
Value = Money.Satoshis(payoutAmount).ToDecimal(MoneyUnit.BTC), Value = Money.Satoshis(payoutAmount).ToDecimal(MoneyUnit.BTC),
Metadata = JObject.FromObject(new Metadata = JObject.FromObject(new
{ {

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>SideShift</Product> <Product>SideShift</Product>
<Description>Allows you to embed a SideShift conversion screen to allow customers to pay with altcoins.</Description> <Description>Allows you to embed a SideShift conversion screen to allow customers to pay with altcoins.</Description>
<Version>1.1.13</Version> <Version>1.1.14</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -1,84 +0,0 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data.Payouts.LightningLike;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.SideShift;
public class PrismClaimCreate : IPluginHookFilter
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly BTCPayNetworkProvider _networkProvider;
public string Hook => "prism-claim-create";
public PrismClaimCreate(IHttpClientFactory httpClientFactory, BTCPayNetworkProvider networkProvider)
{
_httpClientFactory = httpClientFactory;
_networkProvider = networkProvider;
}
public async Task<object> Execute(object args)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>("BTC");
if (args is not ClaimRequest claimRequest || network is null)
{
return args;
}
if (claimRequest.Destination?.ToString() is not { } args1 || !args1.StartsWith("sideshift:")) return args;
var request = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject<PrismSideshiftDestination>();
if (!request.Valid())
{
return null;
}
var client = _httpClientFactory.CreateClient("sideshift");
var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/variable", new
{
settleAddress = request.ShiftDestination,
affiliateId = "qg0OrfHJV",
settleMemo = request.ShiftMemo,
depositCoin = "BTC",
depositNetwork = request.SourceNetwork?? "lightning",
settleCoin = request.ShiftCoin,
settleNetwork = request.ShiftNetwork,
}
);
if (!shiftResponse.IsSuccessStatusCode)
{
return null;
}
var shift = await shiftResponse.Content.ReadAsAsync<SideShiftController.ShiftResponse>();
try
{
LNURL.LNURL.Parse(shift.depositAddress, out _);
claimRequest.Destination = new LNURLPayClaimDestinaton(shift.depositAddress);
claimRequest.Metadata = JObject.FromObject(new
{
Source = $"Prism->Sideshift",
SourceLink = $"https://sideshift.ai/orders/{shift.id}?openSupport=true",
});
return claimRequest;
}
catch (Exception e)
{
if (BOLT11PaymentRequest.TryParse(shift.depositAddress, out var bolt11, network.NBitcoinNetwork))
{
claimRequest.Destination = new BoltInvoiceClaimDestination(shift.depositAddress, bolt11);
claimRequest.Metadata = JObject.FromObject(new
{
Source = $"Prism->Sideshift",
SourceLink = $"https://sideshift.ai/orders/{shift.id}?openSupport=true",
});
return claimRequest;
}
}
return null;
}
}

View File

@@ -1,17 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.SideShift;
public class PrismDestinationValidate : IPluginHookFilter
{
public string Hook => "prism-destination-validate";
public async Task<object> Execute(object args)
{
if (args is not string args1 || !args1.StartsWith("sideshift:")) return args;
var json = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject<PrismSideshiftDestination>();
return json.Valid();
}
}

View File

@@ -1,15 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Plugins.SideShift;
public class PrismEditFilter : IPluginHookFilter
{
public string Hook => "prism-edit-buttons";
public Task<object> Execute(object args)
{
return Task.FromResult<object>(( args??"") + "<button type='button' class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#sideshiftModal\">Generate SideShift destination</button>");
}
}

View File

@@ -1,16 +0,0 @@
namespace BTCPayServer.Plugins.SideShift;
public class PrismSideshiftDestination
{
public string ShiftCoin { get; set; }
public string ShiftNetwork { get; set; }
public string ShiftDestination { get; set; }
public string ShiftMemo { get; set; }
public string SourceNetwork { get; set; }
public bool Valid()
{
return !string.IsNullOrEmpty(ShiftCoin) && !string.IsNullOrEmpty(ShiftNetwork) &&
!string.IsNullOrEmpty(ShiftDestination);
}
}

View File

@@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -26,7 +27,7 @@ namespace BTCPayServer.Plugins.SideShift
{ {
private readonly SideShiftService _sideShiftService; private readonly SideShiftService _sideShiftService;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IEnumerable<IPayoutHandler> _payoutHandlers; private readonly PayoutMethodHandlerDictionary _payoutMethodHandlerDictionary;
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
private readonly ApplicationDbContextFactory _dbContextFactory; private readonly ApplicationDbContextFactory _dbContextFactory;
@@ -34,13 +35,13 @@ namespace BTCPayServer.Plugins.SideShift
public SideShiftController( public SideShiftController(
SideShiftService sideShiftService, SideShiftService sideShiftService,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IEnumerable<IPayoutHandler> payoutHandlers, PayoutMethodHandlerDictionary payoutMethodHandlerDictionary,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings serializerSettings, ApplicationDbContextFactory dbContextFactory) BTCPayNetworkJsonSerializerSettings serializerSettings, ApplicationDbContextFactory dbContextFactory)
{ {
_sideShiftService = sideShiftService; _sideShiftService = sideShiftService;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_payoutHandlers = payoutHandlers; _payoutMethodHandlerDictionary = payoutMethodHandlerDictionary;
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
_serializerSettings = serializerSettings; _serializerSettings = serializerSettings;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
@@ -111,19 +112,23 @@ namespace BTCPayServer.Plugins.SideShift
ModelState.AddModelError(nameof(request.Amount), "Amount must be specified"); ModelState.AddModelError(nameof(request.Amount), "Amount must be specified");
} }
if (!PaymentMethodId.TryParse(request.PaymentMethod, out var pmi)) if (!PayoutMethodId.TryParse(request.PayoutMethodId, out var pmi))
{ {
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); ModelState.AddModelError(nameof(request.PayoutMethodId), "Invalid payout method");
} }
else else
{ {
handler = _payoutHandlers.FindPayoutHandler(pmi); if (!_payoutMethodHandlerDictionary.TryGetValue(pmi, out handler))
if (handler == null)
{ {
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); ModelState.AddModelError(nameof(request.PayoutMethodId), "Invalid payment method");
} }
} }
var isLN = pmi.ToString().EndsWith("-" +PayoutTypes.LN.Id);
if (isLN)
{
ModelState.AddModelError(nameof(request.PayoutMethodId), "SideShift does not support Lightning payouts");
}
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return this.CreateValidationError(ModelState); return this.CreateValidationError(ModelState);
@@ -166,13 +171,13 @@ namespace BTCPayServer.Plugins.SideShift
// shiftResponse.EnsureSuccessStatusCode(); // shiftResponse.EnsureSuccessStatusCode();
// var shift = await shiftResponse.Content.ReadAsAsync<ShiftResponse>(); // var shift = await shiftResponse.Content.ReadAsAsync<ShiftResponse>();
var cryptoCode = pmi.ToString().Split('-')[0];
var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/variable", new var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/variable", new
{ {
settleAddress = request.Destination, settleAddress = request.Destination,
affiliateId = "qg0OrfHJV", affiliateId = "qg0OrfHJV",
settleMemo = request.Memo, settleMemo = request.Memo,
depositCoin = pmi.CryptoCode, depositCoin = cryptoCode,
depositNetwork = pmi.PaymentType == LightningPaymentType.Instance ? "lightning" : null,
settleCoin = request.ShiftCurrency, settleCoin = request.ShiftCurrency,
settleNetwork = request.ShiftNetwork, settleNetwork = request.ShiftNetwork,
} }
@@ -188,20 +193,20 @@ namespace BTCPayServer.Plugins.SideShift
var destination = var destination =
await handler.ParseAndValidateClaimDestination(pmi, shift.depositAddress, ppBlob, await handler.ParseAndValidateClaimDestination(shift.depositAddress, ppBlob,
CancellationToken.None); CancellationToken.None);
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest() var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
{ {
PullPaymentId = pullPaymentId, PullPaymentId = pullPaymentId,
Destination = destination.destination, Destination = destination.destination,
PaymentMethodId = pmi, PayoutMethodId = pmi,
Value = request.Amount Value = request.Amount
}); });
if (claim.Result == ClaimRequest.ClaimResult.Ok) if (claim.Result == ClaimRequest.ClaimResult.Ok)
{ {
await using var ctx = _dbContextFactory.CreateContext(); await using var ctx = _dbContextFactory.CreateContext();
ppBlob.Description += $"<br/>The payout of {claim.PayoutData.Destination} will be forwarded to SideShift.ai for further conversion. Please go to <a href=\"https://sideshift.ai/orders/{shift.id}?openSupport=true\">the order page</a> for support."; ppBlob.Description += $"<br/>The payout of {destination.destination} will be forwarded to SideShift.ai for further conversion. Please go to <a href=\"https://sideshift.ai/orders/{shift.id}?openSupport=true\">the order page</a> for support.";
pp.SetBlob(ppBlob); pp.SetBlob(ppBlob);
ctx.Attach(pp).State = EntityState.Modified; ctx.Attach(pp).State = EntityState.Modified;
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
@@ -242,19 +247,21 @@ namespace BTCPayServer.Plugins.SideShift
private Client.Models.PayoutData ToModel(Data.PayoutData p) private Client.Models.PayoutData ToModel(Data.PayoutData p)
{ {
var blob = p.GetBlob(_serializerSettings); var blob = p.GetBlob(_serializerSettings);
var model = new Client.Models.PayoutData var model = new Client.Models.PayoutData()
{ {
Id = p.Id, Id = p.Id,
PullPaymentId = p.PullPaymentDataId, PullPaymentId = p.PullPaymentDataId,
Date = p.Date, Date = p.Date,
Amount = blob.Amount, OriginalCurrency = p.OriginalCurrency,
PaymentMethodAmount = blob.CryptoAmount, OriginalAmount = p.OriginalAmount,
PayoutCurrency = p.Currency,
PayoutAmount = p.Amount,
Revision = blob.Revision, Revision = blob.Revision,
State = p.State, State = p.State,
PayoutMethodId = p.PayoutMethodId,
PaymentProof = p.GetProofBlobJson(),
Destination = blob.Destination, Destination = blob.Destination,
PaymentMethod = p.PaymentMethodId, Metadata = blob.Metadata?? new JObject(),
CryptoCode = p.GetPaymentMethodId().CryptoCode,
PaymentProof = p.GetProofBlobJson()
}; };
return model; return model;
} }

View File

@@ -1,6 +1,5 @@
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Plugins.SideShift namespace BTCPayServer.Plugins.SideShift
@@ -8,41 +7,22 @@ namespace BTCPayServer.Plugins.SideShift
public class SideShiftPlugin : BaseBTCPayServerPlugin public class SideShiftPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ { new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddSingleton<SideShiftService>(); applicationBuilder.AddSingleton<SideShiftService>();
applicationBuilder.AddHostedService(provider => provider.GetService<SideShiftService>()); applicationBuilder.AddHostedService(provider => provider.GetService<SideShiftService>());
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/SideShiftNav",
"store-integrations-nav")); applicationBuilder.AddUIExtension("store-integrations-nav","SideShift/SideShiftNav");
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/PullPaymentViewInsert", applicationBuilder.AddUIExtension("pullpayment-foot","SideShift/PullPaymentViewInsert");
"pullpayment-foot")); applicationBuilder.AddUIExtension("store-integrations-list", "SideShift/StoreIntegrationSideShiftOption");
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/StoreIntegrationSideShiftOption",
"store-integrations-list"));
// Checkout v2 // Checkout v2
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutPaymentMethodExtension", applicationBuilder.AddUIExtension("checkout-payment-method", "SideShift/CheckoutPaymentMethodExtension");
"checkout-payment-method")); applicationBuilder.AddUIExtension("checkout-payment","SideShift/CheckoutPaymentExtension");
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutPaymentExtension",
"checkout-payment"));
// Checkout Classic
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutContentExtension",
"checkout-bitcoin-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutContentExtension",
"checkout-lightning-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutTabExtension",
"checkout-bitcoin-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutTabExtension",
"checkout-lightning-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutEnd",
"checkout-end"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/PrismEnhance",
"prism-edit"));
applicationBuilder.AddSingleton<IPluginHookFilter, PrismDestinationValidate>();
applicationBuilder.AddSingleton<IPluginHookFilter, PrismClaimCreate>();
applicationBuilder.AddSingleton<IPluginHookFilter, PrismEditFilter>();
base.Execute(applicationBuilder); base.Execute(applicationBuilder);
} }
} }

View File

@@ -1,26 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
<div id="sideshift" class="bp-view payment manual-flow" :class="{ active: currentTab == 'undefined' || currentTab == 'sideshift' }">
<div class="manual__step-two__instructions">
<span>
{{$t("ConversionTab_BodyTop", srvModel)}}
<br/><br/>
{{$t("ConversionTab_BodyDesc", srvModel)}}
</span>
</div>
<side-shift inline-template
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.btcDue * (1 + (@settings.AmountMarkupPercentage / 100)) "
:to-currency-address="srvModel.btcAddress">
<a v-on:click="openDialog($event)" href="#" class="action-button btn btn-secondary rounded-pill w-100 mt-4">{{$t("Pay with SideShift")}}</a>
</side-shift>
</div>
}
}

View File

@@ -1,14 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject BTCPayServer.Security.ContentSecurityPolicies csp
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
csp.Add("script-src", "https://sideshift.ai");
csp.Add("script-src", "*.sideshift.ai");
<script src="~/Resources/js/sideShiftComponent.js"></script>
<script src="https://sideshift.ai/static/js/main.js" defer></script>
}
}

View File

@@ -2,19 +2,18 @@
@using BTCPayServer.Payments @using BTCPayServer.Payments
@inject BTCPayServer.Security.ContentSecurityPolicies csp @inject BTCPayServer.Security.ContentSecurityPolicies csp
@inject SideShiftService SideShiftService @inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel @model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{ @{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId); var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
var preferredTargetPaymentMethodId = ""; PaymentMethodId preferredTargetPaymentMethodId = null;
if(!PaymentMethodId.TryParse(settings?.PreferredTargetPaymentMethodId, out var preferredPMI)) if(!PaymentMethodId.TryParse(settings?.PreferredTargetPaymentMethodId, out var preferredPMI))
{ {
preferredTargetPaymentMethodId = null; preferredTargetPaymentMethodId = null;
} }
else else
{ {
preferredTargetPaymentMethodId = Model.AvailableCryptos.FirstOrDefault(crypto => preferredTargetPaymentMethodId = Model.AvailablePaymentMethods.FirstOrDefault(crypto =>
crypto.PaymentMethodId == settings.PreferredTargetPaymentMethodId || crypto.PaymentMethodId == preferredPMI )?.PaymentMethodId;
(crypto.CryptoCode == preferredPMI.CryptoCode && crypto.PaymentMethodId.EndsWith(LNURLPayPaymentType.Instance.GetId()) || crypto.PaymentMethodId.EndsWith(LightningPaymentType.Instance.GetId())))?.PaymentMethodId;
} }
} }
@if (settings?.Enabled is true) @if (settings?.Enabled is true)
@@ -25,7 +24,8 @@
<template id="side-shift-checkout-template"> <template id="side-shift-checkout-template">
<div class="payment-box"> <div class="payment-box">
<p v-html="content"></p> <p v-html="content"></p>
<button type="button" v-on:click="openDialog" class="btn btn-primary rounded-pill w-100">{{$t("Pay with SideShift")}}</button> <p v-if="!settleMethodId" class="text-danger">Lightning is not supported via Sideshift. Select another payment method first, then come back.</p>
<button v-if="settleMethodId" type="button" v-on:click="openDialog" class="btn btn-primary rounded-pill w-100">{{$t("Pay with SideShift", {crryptoCode: settleMethodId})}}</button>
</div> </div>
</template> </template>
<script> <script>
@@ -48,7 +48,7 @@
},200) },200)
if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){ if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC"){ if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC-CHAIN"){
return; return;
} }
this.$parent.paymentMethodId = this.preferredToCurrency; this.$parent.paymentMethodId = this.preferredToCurrency;
@@ -58,16 +58,7 @@
}, },
computed: { computed: {
lightning () {
if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC"){
return null;
}
const index = this.model.invoiceBitcoinUrl.indexOf("lightning=");
if (index === -1){
return null;
}
return this.model.invoiceBitcoinUrl.slice(index + "lightning=".length);
},
content () { content () {
return this.$i18n.i18next.t("conversion_body", this.model).replace(/\n/ig, '<br>'); return this.$i18n.i18next.t("conversion_body", this.model).replace(/\n/ig, '<br>');
}, },
@@ -76,16 +67,16 @@
}, },
settleMethodId () { settleMethodId () {
const toCurrency = this.currency.toLowerCase(); const toCurrency = this.currency.toUpperCase();
if (toCurrency === "lbtc") { if (toCurrency === "lbtc") {
return 'liquid'; return 'liquid';
} else if (toCurrency === "usdt") { } else if (toCurrency === "usdt") {
return "usdtla"; return "usdtla";
} else if (toCurrency.endsWith('lightninglike') || toCurrency.endsWith('lnurlpay') || this.lightning) { } else if (toCurrency.endsWith('LN') || toCurrency.endsWith('LNURL')) {
return "ln"; return null;
} else { } else {
return toCurrency.replace('_btclike', '').replace('_monerolike', '').replace('_zcashlike', '').toLowerCase(); return toCurrency.replace('-CHAIN', '').replace('_CHAIN', '').toLowerCase();
} }
}, },
type () { type () {
@@ -96,16 +87,19 @@
amountDue () { amountDue () {
return this.model.isUnsetTopUp return this.model.isUnsetTopUp
? undefined ? undefined
: this.model.btcDue * (1 + (@settings.AmountMarkupPercentage / 100)); : this.model.due * (1 + (@settings.AmountMarkupPercentage / 100));
} }
}, },
methods: { methods: {
openDialog () { openDialog () {
if (!this.settleMethodId){
return;
}
window.__SIDESHIFT__ = { window.__SIDESHIFT__ = {
parentAffiliateId: "qg0OrfHJV", parentAffiliateId: "qg0OrfHJV",
defaultDepositMethodId: this.explicitId || undefined, defaultDepositMethodId: this.explicitId || undefined,
defaultSettleMethodId: this.settleMethodId, defaultSettleMethodId: this.settleMethodId,
settleAddress: this.lightning || this.model.btcAddress, settleAddress: this.model.address,
settleAmount: this.amountDue, settleAmount: this.amountDue,
type: this.type type: this.type
}; };

View File

@@ -1,6 +1,6 @@
@using BTCPayServer.Plugins.SideShift @using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService @inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel @model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{ @{
const string id = "SideShift"; const string id = "SideShift";
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId); var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);

View File

@@ -1,12 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
<div class="payment-tabs__tab py-0" id="sideshift-tab" v-on:click="switchTab('sideshift')" v-bind:class="{ 'active': currentTab == 'sideshift'}" v-if="!srvModel.paymentMethodId.endsWith('LNURLPAY')">
<span>{{$t("Altcoins (SideShift)")}}</span>
</div>
}
}

View File

@@ -1,212 +0,0 @@
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.SideShift
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject SideShiftService SideShiftService
@{
var coins = await SideShiftService.GetSettleCoins();
coins = coins.Where(tuple => new[] {SideShiftService.CoinType.VariableOnly, SideShiftService.CoinType.Both}.Contains(tuple.Type)).ToList();
if(coins.Any() is not true)
{
return;
}
}
<script>
const ssAvailableCoins = @Json.Serialize(coins.ToDictionary(tuple=> $"{tuple.CryptoCode}_{tuple.Network}",tuple =>
new {
coin = tuple.DisplayName,
code = tuple.CryptoCode,
memo = tuple.HasMemo,
network = tuple.Network
}));
document.addEventListener('DOMContentLoaded', (event) => {
if (new URLSearchParams(window.location.search).has("hidejunk")) {
localStorage.setItem('hidejunk', 'true');
}
if(localStorage.getItem("hidejunk")) {
[...document.querySelectorAll("#sscoin option")].forEach(option => {
const text = option.innerText.toLowerCase();
// Check if the option matches the criteria
const isMatch = (text.includes("bitcoin") || text.includes("tether") || text.includes("usd")) &&
!text.includes("bitcoincash");
// If it matches, show it; otherwise, hide it
//remove it
if (!isMatch)
option.remove();
});
}
// const sideshiftDestinationButton = document.createElement("button");
// sideshiftDestinationButton.type= "button";
// sideshiftDestinationButton.className = "btn btn-primary btn-sm";
// sideshiftDestinationButton.innerText = "Generate SideShift destination";
// document.getElementById("add-prism").insertAdjacentElement("afterend", sideshiftDestinationButton);
// const modal = new bootstrap.Modal('#sideshiftModal');
// sideshiftDestinationButton.addEventListener("click", ev => modal.show());
const selectedSideShiftCoin = document.getElementById("sscoin");
const specifiedSideShiftDestination = document.getElementById("ssdest");
const specifiedSideShiftDepositNetwork = document.getElementById("ssdepositNetwork");
const specifiedSideShiftMemo= document.getElementById("ssmemo");
const shiftButton = document.getElementById("ssshift");
let selectedCoin = null;
const destinationContainer = document.getElementById("ss-dest-info");
specifiedSideShiftDestination.addEventListener("input", ev1 => {
document.getElementById("ss-result").style.display = "none";
if (isValid()){
shiftButton.removeAttribute("disabled");
}
});
specifiedSideShiftMemo.addEventListener("input", ev1 => {
if (isValid()){
shiftButton.removeAttribute("disabled");
}else{
shiftButton.setAttribute("disabled", "disabled");
}
});
isValid = ()=>{
return selectedCoin && specifiedSideShiftDestination.value &&
(!selectedCoin.memo || specifiedSideShiftMemo.value);
};
handleSelectChanges = ()=>{
if (selectedSideShiftCoin.value){
selectedCoin = ssAvailableCoins[selectedSideShiftCoin.value];
destinationContainer.style.display = "block";
if (selectedCoin){
specifiedSideShiftMemo.parentElement.style.display = selectedCoin.memo ? "block" : "none";
specifiedSideShiftMemo.value = selectedCoin.memo ? specifiedSideShiftMemo.value : "";
}
}else{
destinationContainer.style.display = "none";
}
};
selectedSideShiftCoin.addEventListener("change", ev1 => {
handleSelectChanges();
});
shiftButton.addEventListener("click", ev1 => {
document.getElementById("ss-server-errors").innerHTML = "";
document.getElementById("ss-result-txt").value = "";
document.getElementById("ss-result-additional-info").value = "";
if (isValid()){
shiftButton.setAttribute("disabled", "disabled");
const type = "permanent";
if (type ==="permanent"){
fetch("https://sideshift.ai/api/v2/shifts/variable",{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
settleAddress: specifiedSideShiftDestination.value,
settleMemo: specifiedSideShiftMemo.value,
affiliateId: "qg0OrfHJV",
depositCoin : "BTC",
depositNetwork : specifiedSideShiftDepositNetwork.value,
settleCoin: selectedCoin.code,
settleNetwork: selectedCoin.network,
permanent: true
})})
.then(async response => {
if (!response.ok){
try {
document.getElementById("ss-server-errors").innerHTML = (await response.json())["error"]["message"];
}catch{
document.getElementById("ss-server-errors").innerHTML = JSON.stringify((await response.json()));
}
return;
}
const shift = await response.json();
document.getElementById("ss-result").style.display = "block";
document.getElementById("ss-result-txt").value = shift.depositAddress;
const link = `https://sideshift.ai/orders/${shift.id}`;
document.getElementById("ss-result-additional-info").innerHTML = "<b>IMPORTANT:</b> You must keep this link to be able to recover your funds in case of a problem. <a href='"+link+"' target='_blank'>"+link+"</a> ";
})
.catch(error => document.getElementById("ss-server-errors").innerHTML = error)
.finally(() => shiftButton.removeAttribute("disabled"));
}else{
document.getElementById("ss-result").style.display = "block";
document.getElementById("ss-result-txt").value = "sideshift:"+JSON.stringify({
shiftCoin:selectedCoin.code,
shiftNetwork: selectedCoin.network,
shiftDestination: specifiedSideShiftDestination.value,
shiftMemo: specifiedSideShiftMemo.value,
shiftDepositNetwork: specifiedSideShiftDepositNetwork.value
});
shiftButton.removeAttribute("disabled");
}
}
});
handleSelectChanges();
});
</script>
<div class="modal" tabindex="-1" id="sideshiftModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Generate SideShift destination</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="ss-server-errors" class="text-danger"></div>
<p>This will generate a piece of code based on Sideshift configuration that can work as a valid destination in prism. Prism will then generate a "shift" on Sideshift and send the funds through LN to it, and Sideshift will send you the conversion. </p>
<div class="form-group">
<label class="form-label">How do you want to send BTC to SideShift?</label>
<select id="ssdepositNetwork" class="form-select">
<option value="lightning">Lightning</option>
<option value="bitcoin">On-Chain</option>
</select>
</div><div class="form-group">
<label class="form-label">Which coin should Sideshift send you</label>
<select id="sscoin" class="form-select">
@foreach (var opt in coins)
{
<option value="@($"{opt.CryptoCode}_{opt.Network}")">@opt.ToString()</option>
}
</select>
</div>
<div id="ss-dest-info" style="display: none">
<div class="form-group">
<label class="form-label">Destination</label>
<input type="text" id="ssdest" class="form-control"/>
</div>
<div class="form-group">
<label class="form-label">Memo</label>
<input type="text" id="ssmemo" class="form-control"/>
</div>
<button type="button" class="btn btn-primary" id="ssshift" disabled="disabled">Generate code</button>
<div id="ss-result" class="form-group mt-4" style="display: none;">
<label class="form-label">Generated code</label>
<div class="input-group">
<input type="text" id="ss-result-txt" class="form-control" readonly="readonly"/>
<button type="button" class="btn btn-secondary" data-clipboard-target="#ss-result-txt">
<vc:icon symbol="copy"/>
</button>
</div>
<p id="ss-result-additional-info"></p>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
@using BTCPayServer.Plugins.SideShift @using BTCPayServer.Payouts
@using BTCPayServer.Plugins.SideShift
@model BTCPayServer.Models.ViewPullPaymentModel @model BTCPayServer.Models.ViewPullPaymentModel
@inject SideShiftService SideShiftService @inject SideShiftService SideShiftService
@{ @{
@@ -14,7 +15,8 @@
return; return;
} }
var potentialPaymentMethods = Model.PaymentMethods;//.Where(id => id.CryptoCode.Equals(Model.Currency, StringComparison.OrdinalIgnoreCase)).ToList(); var potentialPaymentMethods = Model.PayoutMethodIds.ToList();//.Where(id => id.CryptoCode.Equals(Model.Currency, StringComparison.OrdinalIgnoreCase)).ToList();
potentialPaymentMethods.Remove(PayoutTypes.LN.GetPayoutMethodId("BTC"));
if (Model.IsPending && potentialPaymentMethods.Any()) if (Model.IsPending && potentialPaymentMethods.Any())
{ {
<script> <script>
@@ -26,7 +28,7 @@
memo = tuple.HasMemo, memo = tuple.HasMemo,
network = tuple.Network network = tuple.Network
})); }));
const ssPaymentMethods = @Json.Serialize(potentialPaymentMethods.Select(id => new { id = id.ToString(), name= id.ToPrettyString()})); const ssPaymentMethods = @Json.Serialize(potentialPaymentMethods.Select(id => new { id = id.ToString(), name= id.ToString()}));
document.addEventListener("DOMContentLoaded", ev => { document.addEventListener("DOMContentLoaded", ev => {
const ssButton = document.createElement("button"); const ssButton = document.createElement("button");
ssButton.type= "button"; ssButton.type= "button";
@@ -142,7 +144,7 @@
<select id="sspmi" class="form-select"> <select id="sspmi" class="form-select">
@foreach (var opt in potentialPaymentMethods) @foreach (var opt in potentialPaymentMethods)
{ {
<option value="@opt.ToString()">@opt.ToPrettyString()</option> <option value="@opt.ToString()">@opt.ToString()</option>
} }
</select> </select>
</div> </div>

View File

@@ -4,13 +4,14 @@
@using BTCPayServer.Plugins.SideShift @using BTCPayServer.Plugins.SideShift
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.SideShift.SideShiftSettings @model BTCPayServer.Plugins.SideShift.SideShiftSettings
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject SideShiftService SideShiftService @inject SideShiftService SideShiftService
@{ @{
ViewData.SetActivePage("SideShift", "SideShift", "SideShift"); ViewData.SetActivePage("SideShift", "SideShift", "SideShift");
var store = Context.GetStoreData(); var store = Context.GetStoreData();
var allowedPaymentMethods = store.GetEnabledPaymentIds(BTCPayNetworkProvider) var allowedPaymentMethods = store.GetEnabledPaymentIds()
.Select(pmi => new SelectListItem(pmi.ToPrettyString(), pmi.ToString())) .Where(id => !id.ToString().EndsWith("LN") && !id.ToString().EndsWith("LNURL"))
.Select(pmi => new SelectListItem(pmi.ToString(), pmi.ToString()))
.Prepend(new SelectListItem("Any", "")); .Prepend(new SelectListItem("Any", ""));
var coins = await SideShiftService.GetDepositOptions(); var coins = await SideShiftService.GetDepositOptions();
var allowedCoins = coins.OrderBy(coin => coin.ToString()).Select(c => new SelectListItem(c.ToString(), $"{c.CryptoCode}_{c.Network}")); var allowedCoins = coins.OrderBy(coin => coin.ToString()).Select(c => new SelectListItem(c.ToString(), $"{c.CryptoCode}_{c.Network}"));

View File

@@ -10,7 +10,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Subscriptions</Product> <Product>Subscriptions</Product>
<Description>Offer and manage subscriptions through BTCPay Server</Description> <Description>Offer and manage subscriptions through BTCPay Server</Description>
<Version>1.0.1</Version> <Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -1,14 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Plugins.Subscriptions; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.PaymentRequests; using BTCPayServer.Services.PaymentRequests;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -23,13 +22,16 @@ namespace BTCPayServer.Plugins.Subscriptions;
public class SubscriptionController : Controller public class SubscriptionController : Controller
{ {
private readonly AppService _appService; private readonly AppService _appService;
private readonly UriResolver _uriResolver;
private readonly PaymentRequestRepository _paymentRequestRepository; private readonly PaymentRequestRepository _paymentRequestRepository;
private readonly SubscriptionService _subscriptionService; private readonly SubscriptionService _subscriptionService;
public SubscriptionController(AppService appService, public SubscriptionController(AppService appService,
UriResolver uriResolver,
PaymentRequestRepository paymentRequestRepository, SubscriptionService subscriptionService) PaymentRequestRepository paymentRequestRepository, SubscriptionService subscriptionService)
{ {
_appService = appService; _appService = appService;
_uriResolver = uriResolver;
_paymentRequestRepository = paymentRequestRepository; _paymentRequestRepository = paymentRequestRepository;
_subscriptionService = subscriptionService; _subscriptionService = subscriptionService;
} }
@@ -44,7 +46,7 @@ public class SubscriptionController : Controller
return NotFound(); return NotFound();
var ss = app.GetSettings<SubscriptionAppSettings>(); var ss = app.GetSettings<SubscriptionAppSettings>();
ss.SubscriptionName = app.Name; ss.SubscriptionName = app.Name;
ViewData["StoreBranding"] = new StoreBrandingViewModel(app.StoreData.GetStoreBlob()); ViewData["StoreBranding"] =await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob());
return View(ss); return View(ss);
} }
@@ -62,8 +64,7 @@ public class SubscriptionController : Controller
{ {
return NotFound(); return NotFound();
} }
ViewData["StoreBranding"] =await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob());
ViewData["StoreBranding"] = new StoreBrandingViewModel(app.StoreData.GetStoreBlob());
return View(ss); return View(ss);
} }

View File

@@ -1,11 +1,9 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services; using BTCPayServer.Abstractions.Services;
using BTCPayServer.HostedServices.Webhooks; using BTCPayServer.HostedServices.Webhooks;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -18,12 +16,11 @@ namespace BTCPayServer.Plugins.Subscriptions
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
[ [
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"} new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
]; ];
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddSingleton<ISwaggerProvider, SubscriptionsSwaggerProvider>(); applicationBuilder.AddSingleton<ISwaggerProvider, SubscriptionsSwaggerProvider>();
applicationBuilder.AddSingleton<SubscriptionService>(); applicationBuilder.AddSingleton<SubscriptionService>();
applicationBuilder.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<SubscriptionService>()); applicationBuilder.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<SubscriptionService>());
@@ -41,13 +38,11 @@ namespace BTCPayServer.Plugins.Subscriptions
public SubscriptionsSwaggerProvider(IWebHostEnvironment webHostEnvironment) public SubscriptionsSwaggerProvider(IWebHostEnvironment webHostEnvironment)
{ {
_fileProvider = webHostEnvironment.WebRootFileProvider; _fileProvider = webHostEnvironment.WebRootFileProvider;
} }
public async Task<JObject> Fetch() public async Task<JObject> Fetch()
{ {
var file = _fileProvider.GetFileInfo("Resources/swagger.subscriptions.json"); var file = _fileProvider.GetFileInfo("Resources/swagger.subscriptions.json");
using var reader = new StreamReader(file.CreateReadStream()); using var reader = new StreamReader(file.CreateReadStream());
return JObject.Parse(await reader.ReadToEndAsync()); return JObject.Parse(await reader.ReadToEndAsync());

View File

@@ -48,10 +48,10 @@ public class AppMigrate : IStartupTask
await using var ctx = _contextFactory.CreateContext(); await using var ctx = _contextFactory.CreateContext();
var invoices = await ctx.Invoices var invoices = await ctx.Invoices
.Include(data => data.InvoiceSearchData) .Include(data => data.InvoiceSearchData)
.Where(data => data.StoreDataId == setting.Key && data.OrderId == "tickettailor").ToListAsync(cancellationToken: cancellationToken); .Where(data => data.StoreDataId == setting.Key && data.InvoiceSearchData.Any(searchData => searchData.Value == "tickettailor")).ToListAsync(cancellationToken: cancellationToken);
foreach (var invoice in invoices) foreach (var invoice in invoices)
{ {
var entity = invoice.GetBlob(_btcPayNetworkProvider); var entity = invoice.GetBlob();
entity.Metadata.SetAdditionalData("appId", app.Id); entity.Metadata.SetAdditionalData("appId", app.Id);
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id)); entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
InvoiceRepository.AddToTextSearch(ctx, invoice, AppService.GetAppSearchTerm(app) ); InvoiceRepository.AddToTextSearch(ctx, invoice, AppService.GetAppSearchTerm(app) );

View File

@@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Product>TicketTailor</Product> <Product>TicketTailor</Product>
<Description>Allows you to integrate with TicketTailor.com to sell tickets for Bitcoin</Description> <Description>Allows you to integrate with TicketTailor.com to sell tickets for Bitcoin</Description>
<Version>2.0.3</Version> <Version>2.0.4</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -28,6 +29,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{ {
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly TicketTailorService _ticketTailorService; private readonly TicketTailorService _ticketTailorService;
private readonly UriResolver _uriResolver;
private readonly AppService _appService; private readonly AppService _appService;
private readonly ApplicationDbContextFactory _contextFactory; private readonly ApplicationDbContextFactory _contextFactory;
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
@@ -35,6 +37,7 @@ namespace BTCPayServer.Plugins.TicketTailor
public TicketTailorController(IHttpClientFactory httpClientFactory, public TicketTailorController(IHttpClientFactory httpClientFactory,
TicketTailorService ticketTailorService, TicketTailorService ticketTailorService,
UriResolver uriResolver,
AppService appService, AppService appService,
ApplicationDbContextFactory contextFactory, ApplicationDbContextFactory contextFactory,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
@@ -42,6 +45,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{ {
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_ticketTailorService = ticketTailorService; _ticketTailorService = ticketTailorService;
_uriResolver = uriResolver;
_appService = appService; _appService = appService;
_contextFactory = contextFactory; _contextFactory = contextFactory;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
@@ -87,7 +91,7 @@ namespace BTCPayServer.Plugins.TicketTailor
return View(new TicketTailorViewModel() return View(new TicketTailorViewModel()
{ {
Event = evt, Settings = config, Event = evt, Settings = config,
StoreBranding = new StoreBrandingViewModel(app.StoreData.GetStoreBlob()) StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob())
}); });
} }
} }
@@ -270,7 +274,6 @@ namespace BTCPayServer.Plugins.TicketTailor
AdditionalSearchTerms = new[] {"tickettailor", hold.Value.Item1.Id, evt.Id, AppService.GetAppSearchTerm(app)}, AdditionalSearchTerms = new[] {"tickettailor", hold.Value.Item1.Id, evt.Id, AppService.GetAppSearchTerm(app)},
Checkout = Checkout =
{ {
RequiresRefundEmail = true,
RedirectAutomatically = price > 0, RedirectAutomatically = price > 0,
RedirectURL = redirectUrl, RedirectURL = redirectUrl,
}, },
@@ -292,13 +295,13 @@ namespace BTCPayServer.Plugins.TicketTailor
}, app.StoreData, HttpContext.Request.GetAbsoluteRoot(),new List<string> { AppService.GetAppInternalTag(appId) }, CancellationToken.None); }, app.StoreData, HttpContext.Request.GetAbsoluteRoot(),new List<string> { AppService.GetAppInternalTag(appId) }, CancellationToken.None);
while (inv.Price == 0 && inv.Status == InvoiceStatusLegacy.New) while (inv.Price == 0 && inv.Status == InvoiceStatus.New)
{ {
if (inv.Status == InvoiceStatusLegacy.New) if (inv.Status == InvoiceStatus.New)
inv = await _invoiceRepository.GetInvoice(inv.Id); inv = await _invoiceRepository.GetInvoice(inv.Id);
} }
return inv.Status.ToModernStatus() == InvoiceStatus.Settled return inv.Status == InvoiceStatus.Settled
? RedirectToAction("Receipt", new {invoiceId = inv.Id}) ? RedirectToAction("Receipt", new {invoiceId = inv.Id})
: RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id}); : RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id});
} }
@@ -342,13 +345,13 @@ namespace BTCPayServer.Plugins.TicketTailor
var appId = AppService.GetAppInternalTags(inv).First(); var appId = AppService.GetAppInternalTags(inv).First();
var result = new TicketReceiptPage() {InvoiceId = invoiceId}; var result = new TicketReceiptPage() {InvoiceId = invoiceId};
result.Status = inv.Status.ToModernStatus(); result.Status = inv.Status;
if (result.Status == InvoiceStatus.Settled && if (result.Status == InvoiceStatus.Settled &&
inv.Metadata.AdditionalData.TryGetValue("ticketIds", out var ticketIds)) inv.Metadata.AdditionalData.TryGetValue("ticketIds", out var ticketIds))
{ {
await SetTicketTailorTicketResult(appId, result, ticketIds.Values<string>()); await SetTicketTailorTicketResult(appId, result, ticketIds.Values<string>());
} }
else if (inv.Status.ToModernStatus() == InvoiceStatus.Settled) else if (inv.Status == InvoiceStatus.Settled)
{ {
await _ticketTailorService.CheckAndIssueTicket(inv.Id); await _ticketTailorService.CheckAndIssueTicket(inv.Id);
} }

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"} new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -101,8 +101,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
!new[] !new[]
{ {
InvoiceStatus.Settled, InvoiceStatus.Expired, InvoiceStatus.Invalid InvoiceStatus.Settled, InvoiceStatus.Expired, InvoiceStatus.Invalid
}.Contains(invoiceEvent.Invoice.GetInvoiceState().Status }.Contains(invoiceEvent.Invoice.GetInvoiceState().Status):
.ToModernStatus()):
return; return;
case InvoiceEvent invoiceEvent: case InvoiceEvent invoiceEvent:
@@ -157,7 +156,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
return; return;
} }
if (new[] {InvoiceStatus.Invalid, InvoiceStatus.Expired}.Contains(invoice.Status.ToModernStatus())) if (new[] {InvoiceStatus.Invalid, InvoiceStatus.Expired}.Contains(invoice.Status))
{ {
if (invoice.Metadata.AdditionalData.TryGetValue("holdId", out var jHoldIdx) && if (invoice.Metadata.AdditionalData.TryGetValue("holdId", out var jHoldIdx) &&
@@ -178,7 +177,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
return; return;
} }
if (invoice.Status.ToModernStatus() != InvoiceStatus.Settled) if (invoice.Status != InvoiceStatus.Settled)
{ {
return; return;
} }

View File

@@ -13,7 +13,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Coinjoin</Product> <Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description> <Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.100</Version> <Version>1.0.101</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>

View File

@@ -11,7 +11,9 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.PayJoin; using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using LinqKit; using LinqKit;
@@ -37,12 +39,14 @@ using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client; using WalletWasabi.WabiSabi.Client;
using WalletWasabi.Wallets; using WalletWasabi.Wallets;
using LogLevel = WalletWasabi.Logging.LogLevel; using LogLevel = WalletWasabi.Logging.LogLevel;
using SecureRandom = WalletWasabi.Crypto.Randomness.SecureRandom;
namespace BTCPayServer.Plugins.Wabisabi; namespace BTCPayServer.Plugins.Wabisabi;
public class BTCPayWallet : IWallet, IDestinationProvider public class BTCPayWallet : IWallet, IDestinationProvider
{ {
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly WalletRepository _walletRepository; private readonly WalletRepository _walletRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler; private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler;
@@ -56,6 +60,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new(); public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
public BTCPayWallet( public BTCPayWallet(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
WalletRepository walletRepository, WalletRepository walletRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
BitcoinLikePayoutHandler bitcoinLikePayoutHandler, BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
@@ -75,6 +80,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
WalletId = new WalletWasabi.Wallets.WalletId(new Guid(SHA256.HashData(Encoding.UTF8.GetBytes(storeId)).Take(16) WalletId = new WalletWasabi.Wallets.WalletId(new Guid(SHA256.HashData(Encoding.UTF8.GetBytes(storeId)).Take(16)
.ToArray())); .ToArray()));
KeyChain = keyChain; KeyChain = keyChain;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_walletRepository = walletRepository; _walletRepository = walletRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler; _bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
@@ -495,7 +501,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
if (storeIdForutxo != StoreId) if (storeIdForutxo != StoreId)
{ {
var s = await _storeRepository.FindStore(storeIdForutxo); var s = await _storeRepository.FindStore(storeIdForutxo);
var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); var scheme = s.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
utxoDerivationScheme = scheme.AccountDerivation; utxoDerivationScheme = scheme.AccountDerivation;
} }
@@ -689,7 +695,7 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
try try
{ {
var mixStore = await _storeRepository.FindStore(WabisabiStoreSettings.MixToOtherWallet); var mixStore = await _storeRepository.FindStore(WabisabiStoreSettings.MixToOtherWallet);
var pm = mixStore.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); var pm = mixStore.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
if (pm?.AccountDerivation?.ScriptPubKeyType() == DerivationScheme.ScriptPubKeyType()) if (pm?.AccountDerivation?.ScriptPubKeyType() == DerivationScheme.ScriptPubKeyType())
@@ -718,20 +724,19 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
{ {
States = new [] {PayoutState.AwaitingPayment}, States = new [] {PayoutState.AwaitingPayment},
Stores = new []{StoreId}, Stores = new []{StoreId},
PaymentMethods = new []{"BTC"} PayoutMethods = new []{ PayoutTypes.CHAIN.GetPayoutMethodId("BTC").ToString()}
})).Select(async data => })).Select(async data =>
{ {
var claim = await _bitcoinLikePayoutHandler.ParseClaimDestination(new PaymentMethodId("BTC", BitcoinPaymentType.Instance), var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings);
data.Destination, CancellationToken.None); var claim = await _bitcoinLikePayoutHandler.ParseClaimDestination(payoutBlob.Destination, CancellationToken.None);
if (!string.IsNullOrEmpty(claim.error) || claim.destination is not IBitcoinLikeClaimDestination bitcoinLikeClaimDestination ) if (!string.IsNullOrEmpty(claim.error) || claim.destination is not IBitcoinLikeClaimDestination bitcoinLikeClaimDestination || data.Amount is null)
{ {
return null; return null;
} }
var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings); var value = new Money(data.Amount.Value, MoneyUnit.BTC);
var value = new Money(payoutBlob.CryptoAmount.Value, MoneyUnit.BTC);
return new PendingPayment() return new PendingPayment()
{ {
Identifier = data.Id, Identifier = data.Id,

View File

@@ -5,6 +5,7 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using NBitcoin; using NBitcoin;
@@ -17,16 +18,19 @@ public class WabisabiScriptResolver: WabiSabiConfig.CoordinatorScriptResolver
{ {
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly BTCPayNetworkProvider _networkProvider; private readonly BTCPayNetworkProvider _networkProvider;
private readonly BTCPayWalletProvider _walletProvider; private readonly BTCPayWalletProvider _walletProvider;
public WabisabiScriptResolver(IHttpClientFactory httpClientFactory, public WabisabiScriptResolver(IHttpClientFactory httpClientFactory,
StoreRepository storeRepository, StoreRepository storeRepository,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
BTCPayWalletProvider walletProvider) BTCPayWalletProvider walletProvider)
{ {
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_networkProvider = networkProvider; _networkProvider = networkProvider;
_walletProvider = walletProvider; _walletProvider = walletProvider;
} }
@@ -61,7 +65,7 @@ public class WabisabiScriptResolver: WabiSabiConfig.CoordinatorScriptResolver
var store = await _storeRepository.FindStore(value); var store = await _storeRepository.FindStore(value);
var cryptoCode = _networkProvider.GetAll().OfType<BTCPayNetwork>() var cryptoCode = _networkProvider.GetAll().OfType<BTCPayNetwork>()
.First(payNetwork => payNetwork.NBitcoinNetwork == network); .First(payNetwork => payNetwork.NBitcoinNetwork == network);
var dss = store.GetDerivationSchemeSettings(_networkProvider, cryptoCode.CryptoCode); var dss = store.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, cryptoCode.CryptoCode);
var w = _walletProvider.GetWallet(cryptoCode.CryptoCode); var w = _walletProvider.GetWallet(cryptoCode.CryptoCode);
var kpi = await w.ReserveAddressAsync(store.Id, dss.AccountDerivation, "wabisabi coordinator"); var kpi = await w.ReserveAddressAsync(store.Id, dss.AccountDerivation, "wabisabi coordinator");
return kpi.ScriptPubKey; return kpi.ScriptPubKey;

Some files were not shown because too many files have changed in this diff Show More