diff --git a/.editorconfig b/.editorconfig
index be443782..c7642292 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -54,7 +54,6 @@ dotnet_diagnostic.IDE0044.severity = error
# IDE0051: Remove unused private member
dotnet_diagnostic.IDE0051.severity = error
-dotnet_diagnostic.IDE0051.severity = error
# IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = error
@@ -86,6 +85,7 @@ dotnet_diagnostic.RS2008.severity = error
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
+# TODO: Check if there's a way of enabling local using directives under namespace for simplification
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:error
@@ -109,69 +109,44 @@ csharp_style_inlined_variable_declaration = true:warning
csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning
csharp_style_prefer_extended_property_pattern = true:warning
+# TODO: Check if there are newer ones for dotnet9
# Whitespace options
dotnet_style_allow_multiple_blank_lines_experimental = false
-# Non-private static fields are PascalCase
-dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = error
-dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
-dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
-
-dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
-dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
-dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
-
-dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
-
-# By default, name enum values with PascalCase
-dotnet_naming_rule.enum_values_should_be_pascal_case.severity = error
-dotnet_naming_rule.enum_values_should_be_pascal_case.symbols = enum_values
-dotnet_naming_rule.enum_values_should_be_pascal_case.style = enum_values_style
-
-dotnet_naming_symbols.enum_values.applicable_accessibilities = public, protected, internal, protected_internal, private_protected, private
-dotnet_naming_symbols.enum_values.applicable_kinds = enum
-
-dotnet_naming_style.enum_values_style.capitalization = pascal_case
-
-# Constants are uppercase
-dotnet_naming_rule.constants_should_be_upper_case.severity = error
-dotnet_naming_rule.constants_should_be_upper_case.symbols = constants
-dotnet_naming_rule.constants_should_be_upper_case.style = constant_style
-
-dotnet_naming_symbols.constants.applicable_kinds = field, local
-dotnet_naming_symbols.constants.required_modifiers = const
+# --- Naming Conventions ---
-dotnet_naming_style.constant_style.capitalization = all_upper
-
-# Static readonly fields are uppercase
-dotnet_naming_rule.non_private_readonly_static_fields_should_be_pascal_case.severity = error
-dotnet_naming_rule.non_private_readonly_static_fields_should_be_pascal_case.symbols = non_private_readonly_static_fields
-dotnet_naming_rule.non_private_readonly_static_fields_should_be_pascal_case.style = non_private_readonly_static_style
+# Re-use existing naming rules
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+dotnet_naming_style.camel_case_style.capitalization = camel_case
-dotnet_naming_symbols.non_private_readonly_static_fields.applicable_kinds = field
-dotnet_naming_symbols.non_private_readonly_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
-dotnet_naming_symbols.non_private_readonly_static_fields.required_modifiers = readonly, static
+# Private const fields are PascalCase and start with
+dotnet_naming_rule.private_const_fields_should_be_camel_case.severity = error
+dotnet_naming_rule.private_const_fields_should_be_camel_case.symbols = private_const_fields
+dotnet_naming_rule.private_const_fields_should_be_camel_case.style = pascal_case_style
-dotnet_naming_style.non_private_readonly_static_style.capitalization = pascal_case
+dotnet_naming_symbols.private_const_fields.applicable_kinds = field
+dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_const_fields.required_modifiers = const
-# Static fields are camelCase and start with s_
-dotnet_naming_rule.static_fields_should_be_camel_case.severity = error
-dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
-dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
+# Private static fields are camelCase and start with s_
+dotnet_naming_rule.private_static_fields_should_be_camel_case.severity = error
+dotnet_naming_rule.private_static_fields_should_be_camel_case.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_camel_case.style = private_static_field_style
-dotnet_naming_symbols.static_fields.applicable_kinds = field
-dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
-dotnet_naming_style.static_field_style.capitalization = camel_case
-dotnet_naming_style.static_field_style.required_prefix = s_
+dotnet_naming_style.private_static_field_style.capitalization = camel_case
+dotnet_naming_style.private_static_field_style.required_prefix = s_
-# Instance fields are camelCase and start with _
+# Private fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = error
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
-dotnet_naming_symbols.instance_fields.applicable_kinds = field, property
+dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_symbols.instance_fields.applicable_accessibilities = private
dotnet_naming_style.instance_field_style.capitalization = camel_case
@@ -184,25 +159,19 @@ dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
-dotnet_naming_style.camel_case_style.capitalization = camel_case
-
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = error
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
-dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
+dotnet_naming_rule.local_functions_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
-dotnet_naming_style.local_function_style.capitalization = pascal_case
-
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = error
-dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
+dotnet_naming_rule.members_should_be_pascal_case.symbols = other_pascal_case_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
-dotnet_naming_symbols.all_members.applicable_kinds = *
-
-dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+dotnet_naming_symbols.other_pascal_case_members.applicable_kinds = *
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:warning
diff --git a/.github/workflows/combined-report.yml b/.github/workflows/combined-report.yml
index 6fc3dbe6..7c75919d 100644
--- a/.github/workflows/combined-report.yml
+++ b/.github/workflows/combined-report.yml
@@ -1,7 +1,7 @@
name: NLightning Combined Coverage Report (Push)
on:
workflow_run:
- workflows: ["NLightning .NET Build & Tests (Push)", "NLightning.Native .NET Build & Tests (Push)", "NLightning.Wasm .NET Build & Tests (Push)"]
+ workflows: [ "NLightning .NET Build & Tests (Push)" ]
types:
- completed
@@ -12,6 +12,56 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Wait for all workflows to complete
+ run: |
+ REQUIRED_WORKFLOWS=(
+ "NLightning .NET Build & Tests (Push)"
+ "NLightning.Native .NET Build & Tests (Push)"
+ "NLightning.Wasm .NET Build & Tests (Push)"
+ )
+
+ echo "Waiting for all workflows to complete for commit ${{ github.event.workflow_run.head_sha }}"
+
+ # Set timeout to 5 minutes (300 seconds)
+ TIMEOUT=300
+ ELAPSED=0
+ SLEEP_INTERVAL=30
+
+ while [ $ELAPSED -lt $TIMEOUT ]; do
+ all_complete=true
+
+ for workflow in "${REQUIRED_WORKFLOWS[@]}"; do
+ # Get the latest run for this workflow and commit
+ run_data=$(gh api repos/${{ github.repository }}/actions/runs \
+ --jq ".workflow_runs[] | select(.head_sha==\"${{ github.event.workflow_run.head_sha }}\" and .name==\"$workflow\") | {status: .status, conclusion: .conclusion}" \
+ | head -1)
+
+ run_status=$(echo "$run_data" | jq -r '.status')
+ run_conclusion=$(echo "$run_data" | jq -r '.conclusion')
+
+ echo "Workflow '$workflow': status=$run_status, conclusion=$run_conclusion"
+
+ if [ "$run_status" != "completed" ]; then
+ all_complete=false
+ break
+ fi
+ done
+
+ if [ "$all_complete" = true ]; then
+ echo "✅ All workflows completed!"
+ exit 0
+ else
+ echo "⏳ Still waiting for workflows to complete... (${ELAPSED}s/${TIMEOUT}s)"
+ sleep $SLEEP_INTERVAL
+ ELAPSED=$((ELAPSED + SLEEP_INTERVAL))
+ fi
+ done
+
+ echo "❌ Timeout reached after ${TIMEOUT} seconds. Not all workflows completed in time."
+ exit 1
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/pr.combined-report.yml b/.github/workflows/pr.combined-report.yml
index f9d23d26..7dc7b6b3 100644
--- a/.github/workflows/pr.combined-report.yml
+++ b/.github/workflows/pr.combined-report.yml
@@ -1,7 +1,7 @@
name: NLightning Combined Coverage Report (PR)
on:
workflow_run:
- workflows: ["NLightning .NET Build & Tests (PR)", "NLightning.Native .NET Build & Tests (PR)", "NLightning.Wasm .NET Build & Tests (PR)"]
+ workflows: [ "NLightning .NET Build & Tests (PR)" ]
types:
- completed
@@ -12,35 +12,85 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Wait for all workflows to complete
+ run: |
+ REQUIRED_WORKFLOWS=(
+ "NLightning .NET Build & Tests (PR)"
+ "NLightning.Native .NET Build & Tests (PR)"
+ "NLightning.Wasm .NET Build & Tests (PR)"
+ )
+
+ echo "Waiting for all workflows to complete for commit ${{ github.event.workflow_run.head_sha }}"
+
+ # Set timeout to 5 minutes (300 seconds)
+ TIMEOUT=300
+ ELAPSED=0
+ SLEEP_INTERVAL=30
+
+ while [ $ELAPSED -lt $TIMEOUT ]; do
+ all_complete=true
+
+ for workflow in "${REQUIRED_WORKFLOWS[@]}"; do
+ # Get the latest run for this workflow and commit
+ run_data=$(gh api repos/${{ github.repository }}/actions/runs \
+ --jq ".workflow_runs[] | select(.head_sha==\"${{ github.event.workflow_run.head_sha }}\" and .name==\"$workflow\") | {status: .status, conclusion: .conclusion}" \
+ | head -1)
+
+ run_status=$(echo "$run_data" | jq -r '.status')
+ run_conclusion=$(echo "$run_data" | jq -r '.conclusion')
+
+ echo "Workflow '$workflow': status=$run_status, conclusion=$run_conclusion"
+
+ if [ "$run_status" != "completed" ]; then
+ all_complete=false
+ break
+ fi
+ done
+
+ if [ "$all_complete" = true ]; then
+ echo "✅ All workflows completed!"
+ exit 0
+ else
+ echo "⏳ Still waiting for workflows to complete... (${ELAPSED}s/${TIMEOUT}s)"
+ sleep $SLEEP_INTERVAL
+ ELAPSED=$((ELAPSED + SLEEP_INTERVAL))
+ fi
+ done
+
+ echo "❌ Timeout reached after ${TIMEOUT} seconds. Not all workflows completed in time."
+ exit 1
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
-
+
- name: Download Standard Coverage
uses: dawidd6/action-download-artifact@v3
with:
workflow: pr.yml
name: coverage
path: coverage-reports/standard
-
+
- name: Download Native Coverage
uses: dawidd6/action-download-artifact@v3
with:
workflow: pr.native.yml
name: coverage.Native
path: coverage-reports/native
-
+
- name: Download WASM Coverage
uses: dawidd6/action-download-artifact@v3
with:
workflow: pr.wasm.yml
name: coverage.Wasm
path: coverage-reports/wasm
-
+
- name: List All Downloaded Files
run: find coverage-reports -type f | sort
-
+
- name: Combine All Coverage Reports
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4
with:
@@ -52,7 +102,7 @@ jobs:
tag: "${{ github.run_number }}_${{ github.run_id }}"
toolpath: "reportgeneratortool"
assemblyfilters: "+*;-NLightning.Infrastructure.Blazor;-NLightning.BlazorTestApp"
-
+
- name: Generate Combined Coverage Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
@@ -65,7 +115,7 @@ jobs:
indicators: true
output: both
thresholds: '50 75'
-
+
- name: Find PR Number
id: pr-number
uses: potiuk/get-workflow-origin@v1_5
@@ -110,7 +160,7 @@ jobs:
- name: List All Downloaded Files
run: find test-results -type f | sort
-
+
- name: Publish Combined Test Results
uses: EnricoMi/publish-unit-test-result-action@v2.16.1
if: always()
diff --git a/NLightning.sln b/NLightning.sln
index 521d0ade..4dae4121 100644
--- a/NLightning.sln
+++ b/NLightning.sln
@@ -5,21 +5,17 @@ VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{123D0631-533C-447F-B7DA-D1D37E5E64BF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Application.NLTG", "src\NLightning.Application.NLTG\NLightning.Application.NLTG.csproj", "{A103C727-E983-4510-81FB-301625DC1A7F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Common", "src\NLightning.Common\NLightning.Common.csproj", "{97623AAC-B371-4515-819E-81607E1150BB}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Node", "src\NLightning.Node\NLightning.Node.csproj", "{A103C727-E983-4510-81FB-301625DC1A7F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{AF4411D4-8EE9-423E-8213-1C9D35E47882}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Models", "src\NLightning.Models\NLightning.Models.csproj", "{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Models.Postgres", "src\NLightning.Models.Postgres\NLightning.Models.Postgres.csproj", "{DC168173-FDDA-4C70-9A7F-F14D909B01EE}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Persistence", "src\NLightning.Infrastructure.Persistence\NLightning.Infrastructure.Persistence.csproj", "{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Models.Sqlite", "src\NLightning.Models.Sqlite\NLightning.Models.Sqlite.csproj", "{04D4EEFE-A127-445B-8D41-252E85CE969C}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Persistence.Postgres", "src\NLightning.Infrastructure.Persistence.Postgres\NLightning.Infrastructure.Persistence.Postgres.csproj", "{DC168173-FDDA-4C70-9A7F-F14D909B01EE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Models.SqlServer", "src\NLightning.Models.SqlServer\NLightning.Models.SqlServer.csproj", "{7F9B8714-BFAC-4F98-9BAB-EA2FC1B29688}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Persistence.Sqlite", "src\NLightning.Infrastructure.Persistence.Sqlite\NLightning.Infrastructure.Persistence.Sqlite.csproj", "{04D4EEFE-A127-445B-8D41-252E85CE969C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Common.Tests", "test\NLightning.Common.Tests\NLightning.Common.Tests.csproj", "{B6169CE9-FEB2-46EC-994D-4082186EB705}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Persistence.SqlServer", "src\NLightning.Infrastructure.Persistence.SqlServer\NLightning.Infrastructure.Persistence.SqlServer.csproj", "{7F9B8714-BFAC-4F98-9BAB-EA2FC1B29688}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Bolt11", "src\NLightning.Bolt11\NLightning.Bolt11.csproj", "{B29DC607-C3E0-4519-B47A-AB8A000F2BED}"
EndProject
@@ -53,7 +49,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Bolt11.Tests", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Serialization.Tests", "test\NLightning.Infrastructure.Serialization.Tests\NLightning.Infrastructure.Serialization.Tests.csproj", "{4550DC12-8EE8-4C35-B438-873EE128DA1A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Application.NLTG.Tests", "test\NLightning.Application.NLTG.Tests\NLightning.Application.NLTG.Tests.csproj", "{BC559AD8-72B9-4ABF-A7FF-6305E141AB62}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Node.Tests", "test\NLightning.Node.Tests\NLightning.Node.Tests.csproj", "{BC559AD8-72B9-4ABF-A7FF-6305E141AB62}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLightning.Infrastructure.Repositories", "src\NLightning.Infrastructure.Repositories\NLightning.Infrastructure.Repositories.csproj", "{02639428-3F4E-43A9-9585-A5D90EDCA1FF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{2746A75F-A7F5-438E-B68F-6AB643F7FE54}"
ProjectSection(SolutionItems) = preProject
@@ -131,26 +129,6 @@ Global
{B29DC607-C3E0-4519-B47A-AB8A000F2BED}.Release.Wasm|Any CPU.Build.0 = Release.Wasm|Any CPU
{B29DC607-C3E0-4519-B47A-AB8A000F2BED}.Release.Native|Any CPU.ActiveCfg = Release.Native|Any CPU
{B29DC607-C3E0-4519-B47A-AB8A000F2BED}.Release.Native|Any CPU.Build.0 = Release.Native|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release|Any CPU.Build.0 = Release|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug.Native|Any CPU.ActiveCfg = Debug.Native|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug.Native|Any CPU.Build.0 = Debug.Native|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug.Wasm|Any CPU.ActiveCfg = Debug.Wasm|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Debug.Wasm|Any CPU.Build.0 = Debug.Wasm|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release.Wasm|Any CPU.ActiveCfg = Release.Wasm|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release.Wasm|Any CPU.Build.0 = Release.Wasm|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release.Native|Any CPU.ActiveCfg = Release.Native|Any CPU
- {97623AAC-B371-4515-819E-81607E1150BB}.Release.Native|Any CPU.Build.0 = Release.Native|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Release|Any CPU.Build.0 = Release|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Debug.Native|Any CPU.ActiveCfg = Debug.Native|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Debug.Native|Any CPU.Build.0 = Debug.Native|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Release.Native|Any CPU.ActiveCfg = Release.Native|Any CPU
- {B6169CE9-FEB2-46EC-994D-4082186EB705}.Release.Native|Any CPU.Build.0 = Release.Native|Any CPU
{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -347,6 +325,18 @@ Global
{BC559AD8-72B9-4ABF-A7FF-6305E141AB62}.Debug.Native|Any CPU.Build.0 = Debug|Any CPU
{BC559AD8-72B9-4ABF-A7FF-6305E141AB62}.Debug.Wasm|Any CPU.ActiveCfg = Debug|Any CPU
{BC559AD8-72B9-4ABF-A7FF-6305E141AB62}.Debug.Wasm|Any CPU.Build.0 = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release.Native|Any CPU.ActiveCfg = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release.Native|Any CPU.Build.0 = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release.Wasm|Any CPU.ActiveCfg = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Release.Wasm|Any CPU.Build.0 = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug.Native|Any CPU.ActiveCfg = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug.Native|Any CPU.Build.0 = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug.Wasm|Any CPU.ActiveCfg = Debug|Any CPU
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF}.Debug.Wasm|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -356,12 +346,10 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A103C727-E983-4510-81FB-301625DC1A7F} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
- {97623AAC-B371-4515-819E-81607E1150BB} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
{2EBE72E4-F20D-4668-B2A5-F7DCDECA0832} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
{DC168173-FDDA-4C70-9A7F-F14D909B01EE} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
{04D4EEFE-A127-445B-8D41-252E85CE969C} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
{7F9B8714-BFAC-4F98-9BAB-EA2FC1B29688} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
- {B6169CE9-FEB2-46EC-994D-4082186EB705} = {AF4411D4-8EE9-423E-8213-1C9D35E47882}
{B29DC607-C3E0-4519-B47A-AB8A000F2BED} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
{6F76E9AF-7E6D-42D8-918A-4C5B26BE07C1} = {AF4411D4-8EE9-423E-8213-1C9D35E47882}
{99E240CC-6381-40DF-B784-528D2EDBBDFC} = {6F76E9AF-7E6D-42D8-918A-4C5B26BE07C1}
@@ -384,5 +372,6 @@ Global
{1D9EF48B-9D37-4996-8C26-56AC6245EAFF} = {735305B0-B08D-4C48-A1DE-47E8DC2D8032}
{9B420B23-5DAE-4CDE-A3DB-69663111750D} = {735305B0-B08D-4C48-A1DE-47E8DC2D8032}
{18F1E97C-8546-4359-93B3-8D1F5B1CC4B4} = {735305B0-B08D-4C48-A1DE-47E8DC2D8032}
+ {02639428-3F4E-43A9-9585-A5D90EDCA1FF} = {123D0631-533C-447F-B7DA-D1D37E5E64BF}
EndGlobalSection
EndGlobal
diff --git a/src/NLightning.Application.NLTG/Constants/DaemonConstants.cs b/src/NLightning.Application.NLTG/Constants/DaemonConstants.cs
deleted file mode 100644
index 586292b8..00000000
--- a/src/NLightning.Application.NLTG/Constants/DaemonConstants.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace NLightning.Application.NLTG.Constants;
-
-public static class DaemonConstants
-{
- public const string DAEMON_FOLDER = "nltg";
- public const string KEY_FILE = "nltg.key.json";
- public const string PID_FILE = "nltg.pid";
-}
\ No newline at end of file
diff --git a/src/NLightning.Application.NLTG/Extensions/NltgServiceExtensions.cs b/src/NLightning.Application.NLTG/Extensions/NltgServiceExtensions.cs
deleted file mode 100644
index 5fd6cf45..00000000
--- a/src/NLightning.Application.NLTG/Extensions/NltgServiceExtensions.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-
-namespace NLightning.Application.NLTG.Extensions;
-
-using Common.Options;
-using Domain.Bitcoin.Services;
-using Domain.Factories;
-using Domain.Node.Options;
-using Domain.Protocol.Factories;
-using Domain.Protocol.Managers;
-using Factories;
-using Infrastructure.Node.Factories;
-using Infrastructure.Node.Interfaces;
-using Infrastructure.Node.Managers;
-using Infrastructure.Protocol.Factories;
-using Infrastructure.Transport.Factories;
-using Interfaces;
-using Managers;
-using Services;
-
-public static class NltgServiceExtensions
-{
- ///
- /// Registers all NLTG application services for dependency injection
- ///
- public static IHostBuilder ConfigureNltgServices(this IHostBuilder hostBuilder, SecureKeyManager secureKeyManager)
- {
- return hostBuilder.ConfigureServices((hostContext, services) =>
- {
- // Get configuration
- var configuration = hostContext.Configuration;
-
- // Register configuration as a service
- services.AddSingleton(configuration);
-
- // Register the main daemon service
- services.AddHostedService();
-
- // Add HttpClient for FeeService with configuration
- services.AddHttpClient(client =>
- {
- client.Timeout = TimeSpan.FromSeconds(30);
- client.DefaultRequestHeaders.Add("Accept", "application/json");
- });
-
- // Register application services
- // Singleton services (one instance throughout the application)
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton(secureKeyManager);
- services.AddSingleton();
- services.AddSingleton();
-
- // Scoped services (one instance per scope)
-
- // Transient services (new instance each time)
-
- // Register options with values from configuration
- services.AddOptions().BindConfiguration("FeeEstimation").ValidateOnStart();
- services.AddOptions()
- .BindConfiguration("Node")
- .PostConfigure(options =>
- {
- var configuredAddresses = configuration.GetSection("Node:ListenAddresses").Get();
- if (configuredAddresses is { Length: > 0 })
- {
- options.ListenAddresses = configuredAddresses.ToList();
- }
- })
- .ValidateOnStart();
- });
- }
-}
\ No newline at end of file
diff --git a/src/NLightning.Application.NLTG/Models/KeyFileData.cs b/src/NLightning.Application.NLTG/Models/KeyFileData.cs
deleted file mode 100644
index 699c6c90..00000000
--- a/src/NLightning.Application.NLTG/Models/KeyFileData.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace NLightning.Application.NLTG.Models;
-
-public class KeyFileData
-{
- [JsonPropertyName("network")]
- public string Network { get; set; } = string.Empty;
-
- [JsonPropertyName("descriptor")]
- public string Descriptor { get; set; } = string.Empty;
-
- [JsonPropertyName("lastUsedIndex")]
- public uint LastUsedIndex { get; set; }
-
- [JsonPropertyName("encryptedExtKey")]
- public string EncryptedExtKey { get; set; } = string.Empty;
-
- [JsonPropertyName("nonce")]
- public string Nonce { get; set; } = string.Empty;
-
- [JsonPropertyName("salt")]
- public string Salt { get; set; } = string.Empty;
-}
\ No newline at end of file
diff --git a/src/NLightning.Application/AssemblyInfo.cs b/src/NLightning.Application/AssemblyInfo.cs
new file mode 100644
index 00000000..3b6a4884
--- /dev/null
+++ b/src/NLightning.Application/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
+[assembly: InternalsVisibleTo("NLightning.Infrastructure")]
+[assembly: InternalsVisibleTo("NLightning.Infrastructure.Blazor")]
\ No newline at end of file
diff --git a/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionBuilder.cs b/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionBuilder.cs
new file mode 100644
index 00000000..92cfcd36
--- /dev/null
+++ b/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionBuilder.cs
@@ -0,0 +1,9 @@
+namespace NLightning.Application.Bitcoin.Interfaces;
+
+using Domain.Bitcoin.ValueObjects;
+using Domain.Transactions.Models;
+
+public interface ICommitmentTransactionBuilder
+{
+ SignedTransaction Build(CommitmentTransactionModel transaction);
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionService.cs b/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionService.cs
new file mode 100644
index 00000000..0f232b5a
--- /dev/null
+++ b/src/NLightning.Application/Bitcoin/Interfaces/ICommitmentTransactionService.cs
@@ -0,0 +1,16 @@
+namespace NLightning.Application.Bitcoin.Interfaces;
+
+using Domain.Bitcoin.Transactions;
+using Domain.Channels.Models;
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
+using Domain.Node.Options;
+
+public interface ICommitmentTransactionService
+{
+ Task CreateAndSignCommitmentTransactionAsync(ChannelModel channelModel,
+ IEnumerable htlcOutputsToInclude,
+ ulong localCommitmentNumber, bool isFunder,
+ NodeOptions nodeOptions,
+ CompactPubKey remoteCommitmentPoint);
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Bitcoin/Interfaces/IFundingTransactionFactory.cs b/src/NLightning.Application/Bitcoin/Interfaces/IFundingTransactionFactory.cs
new file mode 100644
index 00000000..6877a4a0
--- /dev/null
+++ b/src/NLightning.Application/Bitcoin/Interfaces/IFundingTransactionFactory.cs
@@ -0,0 +1,19 @@
+namespace NLightning.Application.Bitcoin.Interfaces;
+
+using Domain.Bitcoin.Outputs;
+using Domain.Bitcoin.Transactions;
+using Domain.Bitcoin.ValueObjects;
+using Domain.Crypto.ValueObjects;
+using Domain.Money;
+
+public interface IFundingTransactionFactory
+{
+ ITransaction CreateFundingTransaction(CompactPubKey localFundingCompactPubKey,
+ CompactPubKey remoteFundingCompactPubKey, LightningMoney fundingSatoshis,
+ BitcoinScript changeBitcoinScript, params IOutput[] outputs);
+
+ ITransaction CreateFundingTransaction(CompactPubKey localFundingCompactPubKey,
+ CompactPubKey remoteFundingCompactPubKey, LightningMoney fundingSatoshis,
+ BitcoinScript redeemBitcoinScript, BitcoinScript changeBitcoinScript,
+ params IOutput[] outputs);
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Bitcoin/Interfaces/IHtlcTransactionFactory.cs b/src/NLightning.Application/Bitcoin/Interfaces/IHtlcTransactionFactory.cs
new file mode 100644
index 00000000..f5e33405
--- /dev/null
+++ b/src/NLightning.Application/Bitcoin/Interfaces/IHtlcTransactionFactory.cs
@@ -0,0 +1,6 @@
+namespace NLightning.Application.Bitcoin.Interfaces;
+
+public interface IHtlcTransactionFactory
+{
+
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Bitcoin/Interfaces/ITransactionValidator.cs b/src/NLightning.Application/Bitcoin/Interfaces/ITransactionValidator.cs
new file mode 100644
index 00000000..cf62d302
--- /dev/null
+++ b/src/NLightning.Application/Bitcoin/Interfaces/ITransactionValidator.cs
@@ -0,0 +1,6 @@
+namespace NLightning.Application.Bitcoin.Interfaces;
+
+public interface ITransactionValidator
+{
+
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Channels/Handlers/ChannelReadyMessageHandler.cs b/src/NLightning.Application/Channels/Handlers/ChannelReadyMessageHandler.cs
new file mode 100644
index 00000000..e137368d
--- /dev/null
+++ b/src/NLightning.Application/Channels/Handlers/ChannelReadyMessageHandler.cs
@@ -0,0 +1,175 @@
+using System.Security.Cryptography;
+using Microsoft.Extensions.Logging;
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Application.Channels.Handlers;
+
+using Domain.Channels.Enums;
+using Domain.Channels.Interfaces;
+using Domain.Channels.Models;
+using Domain.Crypto.ValueObjects;
+using Domain.Enums;
+using Domain.Exceptions;
+using Domain.Node.Options;
+using Domain.Persistence.Interfaces;
+using Domain.Protocol.Messages;
+using Interfaces;
+
+public class ChannelReadyMessageHandler : IChannelMessageHandler
+{
+ private readonly IChannelMemoryRepository _channelMemoryRepository;
+ private readonly ILogger _logger;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public ChannelReadyMessageHandler(IChannelMemoryRepository channelMemoryRepository,
+ ILogger logger, IUnitOfWork unitOfWork)
+ {
+ _channelMemoryRepository = channelMemoryRepository;
+ _logger = logger;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task HandleAsync(ChannelReadyMessage message, ChannelState currentState,
+ FeatureOptions negotiatedFeatures, CompactPubKey peerPubKey)
+ {
+ _logger.LogTrace("Processing ChannelReadyMessage with ChannelId: {ChannelId} from Peer: {PeerPubKey}",
+ message.Payload.ChannelId, peerPubKey);
+
+ var payload = message.Payload;
+
+ if (currentState is not (ChannelState.V1FundingSigned
+ or ChannelState.ReadyForThem
+ or ChannelState.ReadyForUs
+ or ChannelState.Open))
+ throw new ChannelErrorException("Channel had the wrong state", payload.ChannelId,
+ "This channel is not ready to be opened");
+
+ // Check if there's a channel for this peer
+ if (!_channelMemoryRepository.TryGetChannel(payload.ChannelId, out var channel) || channel is null)
+ throw new ChannelErrorException("Channel not found", payload.ChannelId,
+ "This channel is not ready to be opened");
+
+ var mustUseScidAlias = channel.ChannelConfig.UseScidAlias > FeatureSupport.No;
+ if (mustUseScidAlias && message.ShortChannelIdTlv is null)
+ throw new ChannelWarningException("No ShortChannelIdTlv provided",
+ payload.ChannelId,
+ "This channel requires a ShortChannelIdTlv to be provided");
+
+ // Store their new per-commitment point
+ if (channel.RemoteKeySet.CurrentPerCommitmentIndex == 0)
+ channel.RemoteKeySet.UpdatePerCommitmentPoint(payload.SecondPerCommitmentPoint);
+
+ // Handle ScidAlias
+ if (currentState is ChannelState.Open or ChannelState.ReadyForThem)
+ {
+ if (mustUseScidAlias)
+ {
+ if (ShouldReplaceAlias())
+ {
+ var oldAlias = channel.RemoteAlias;
+ channel.RemoteAlias = message.ShortChannelIdTlv!.ShortChannelId;
+
+ _logger.LogDebug("Updated remote alias for channel {ChannelId} from {OldAlias} to {NewAlias}",
+ payload.ChannelId, oldAlias, channel.RemoteAlias);
+
+ await PersistChannelAsync(channel);
+ }
+ else
+ {
+ _logger.LogDebug(
+ "Keeping existing remote alias {ExistingAlias} for channel {ChannelId}", channel.RemoteAlias,
+ payload.ChannelId);
+ }
+ }
+ else
+ _logger.LogDebug("Received duplicate ChannelReady message for channel {ChannelId} in Open state",
+ payload.ChannelId);
+
+ return null; // No further action needed, we are already open
+ }
+
+ if (channel.IsInitiator) // Handle state transitions based on whether we are the initiator
+ {
+ // We already sent our ChannelReady, now they sent theirs
+ if (currentState == ChannelState.ReadyForUs)
+ {
+ // Valid transition: ReadyForUs -> Open
+ channel.UpdateState(ChannelState.Open);
+ await PersistChannelAsync(channel);
+
+ _logger.LogInformation("Channel {ChannelId} is now open (we are initiator)", payload.ChannelId);
+
+ // TODO: Notify application layer that channel is fully open
+ // TODO: Update routing tables
+
+ return null;
+ }
+
+ // Invalid state for initiator receiving ChannelReady
+ _logger.LogError(
+ "Received ChannelReady message for channel {ChannelId} in invalid state {CurrentState} (we are initiator). Expected: ReadyForUs",
+ payload.ChannelId, currentState);
+
+ throw new ChannelErrorException($"Unexpected ChannelReady message in state {currentState}",
+ payload.ChannelId,
+ "Protocol violation: unexpected ChannelReady message");
+ }
+
+ if (currentState == ChannelState.V1FundingSigned) // We are not the initiator
+ {
+ // First ChannelReady from initiator
+ // Valid transition: V1FundingSigned -> ReadyForThem
+ channel.UpdateState(ChannelState.ReadyForThem);
+ await PersistChannelAsync(channel);
+
+ _logger.LogInformation(
+ "Received ChannelReady from initiator for channel {ChannelId}, waiting for funding confirmation",
+ payload.ChannelId);
+
+ return null;
+ }
+
+ // Invalid state for non-initiator receiving ChannelReady
+ _logger.LogError(
+ "Received ChannelReady message for channel {ChannelId} in invalid state {CurrentState} (we are not initiator). Expected: V1FundingSigned or ReadyForThem",
+ payload.ChannelId, currentState);
+
+ throw new ChannelErrorException($"Unexpected ChannelReady message in state {currentState}",
+ payload.ChannelId,
+ "Protocol violation: unexpected ChannelReady message");
+ }
+
+ ///
+ /// Persists a channel to the database using a scoped Unit of Work
+ ///
+ private async Task PersistChannelAsync(ChannelModel channel)
+ {
+ try
+ {
+ // Check if the channel already exists
+ _ = await _unitOfWork.ChannelDbRepository.GetByIdAsync(channel.ChannelId)
+ ?? throw new ChannelWarningException("Channel not found in database", channel.ChannelId,
+ "Sorry, we had an internal error");
+ await _unitOfWork.ChannelDbRepository.UpdateAsync(channel);
+ await _unitOfWork.SaveChangesAsync();
+
+ _channelMemoryRepository.UpdateChannel(channel);
+
+ _logger.LogDebug("Successfully persisted channel {ChannelId} to database", channel.ChannelId);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to persist channel {ChannelId} to database", channel.ChannelId);
+ throw;
+ }
+ }
+
+ private static bool ShouldReplaceAlias()
+ {
+ return RandomNumberGenerator.GetInt32(0, 2) switch
+ {
+ 0 => true,
+ _ => false
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Channels/Handlers/FundingCreatedMessageHandler.cs b/src/NLightning.Application/Channels/Handlers/FundingCreatedMessageHandler.cs
new file mode 100644
index 00000000..a2e5b437
--- /dev/null
+++ b/src/NLightning.Application/Channels/Handlers/FundingCreatedMessageHandler.cs
@@ -0,0 +1,148 @@
+using Microsoft.Extensions.Logging;
+
+namespace NLightning.Application.Channels.Handlers;
+
+using Bitcoin.Interfaces;
+using Domain.Bitcoin.Interfaces;
+using Domain.Channels.Enums;
+using Domain.Channels.Interfaces;
+using Domain.Channels.Models;
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
+using Domain.Exceptions;
+using Domain.Node.Options;
+using Domain.Persistence.Interfaces;
+using Domain.Protocol.Interfaces;
+using Domain.Protocol.Messages;
+using Domain.Transactions.Enums;
+using Domain.Transactions.Interfaces;
+using Interfaces;
+
+public class FundingCreatedMessageHandler : IChannelMessageHandler
+{
+ private readonly IChannelIdFactory _channelIdFactory;
+ private readonly IChannelMemoryRepository _channelMemoryRepository;
+ private readonly ICommitmentTransactionBuilder _commitmentTransactionBuilder;
+ private readonly ICommitmentTransactionModelFactory _commitmentTransactionModelFactory;
+ private readonly ILightningSigner _lightningSigner;
+ private readonly ILogger _logger;
+ private readonly IMessageFactory _messageFactory;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public FundingCreatedMessageHandler(IChannelIdFactory channelIdFactory,
+ IChannelMemoryRepository channelMemoryRepository,
+ ICommitmentTransactionBuilder commitmentTransactionBuilder,
+ ICommitmentTransactionModelFactory commitmentTransactionModelFactory,
+ ILightningSigner lightningSigner, ILogger logger,
+ IMessageFactory messageFactory, IUnitOfWork unitOfWork)
+ {
+ _channelIdFactory = channelIdFactory;
+ _channelMemoryRepository = channelMemoryRepository;
+ _commitmentTransactionBuilder = commitmentTransactionBuilder;
+ _commitmentTransactionModelFactory = commitmentTransactionModelFactory;
+ _lightningSigner = lightningSigner;
+ _logger = logger;
+ _messageFactory = messageFactory;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task HandleAsync(FundingCreatedMessage message, ChannelState currentState,
+ FeatureOptions negotiatedFeatures, CompactPubKey peerPubKey)
+ {
+ _logger.LogTrace("Processing FundingCreatedMessage with ChannelId: {ChannelId} from Peer: {PeerPubKey}",
+ message.Payload.ChannelId, peerPubKey);
+
+ var payload = message.Payload;
+
+ if (currentState != ChannelState.None)
+ throw new ChannelErrorException("A channel with this id already exists", payload.ChannelId);
+
+ // Check if there's a temporary channel for this peer
+ if (!_channelMemoryRepository.TryGetTemporaryChannelState(peerPubKey, payload.ChannelId, out currentState))
+ throw new ChannelErrorException("This channel has never been negotiated", payload.ChannelId);
+
+ if (currentState != ChannelState.V1Opening)
+ throw new ChannelErrorException("Channel had the wrong state", payload.ChannelId,
+ "This channel is already being negotiated with peer");
+
+ // Get the channel and set missing props
+ if (!_channelMemoryRepository.TryGetTemporaryChannel(peerPubKey, payload.ChannelId, out var channel)
+ || channel is null)
+ throw new ChannelErrorException("Temporary channel not found", payload.ChannelId);
+
+ channel.FundingOutput.TransactionId = payload.FundingTxId;
+ channel.FundingOutput.Index = payload.FundingOutputIndex;
+
+ // Create a new channelId
+ var oldChannelId = channel.ChannelId;
+ channel.UpdateChannelId(_channelIdFactory.CreateV1(payload.FundingTxId, payload.FundingOutputIndex));
+
+ // Register the channel with the signer
+ var channelSigningInfo = new ChannelSigningInfo(
+ payload.FundingTxId,
+ payload.FundingOutputIndex,
+ channel.FundingOutput.Amount,
+ channel.LocalKeySet.FundingCompactPubKey,
+ channel.RemoteKeySet.FundingCompactPubKey,
+ channel.LocalKeySet.KeyIndex
+ );
+ _lightningSigner.RegisterChannel(channel.ChannelId, channelSigningInfo);
+
+ // Generate the base commitment transactions
+ var localCommitmentTransaction =
+ _commitmentTransactionModelFactory.CreateCommitmentTransactionModel(channel, CommitmentSide.Local);
+ var remoteCommitmentTransaction =
+ _commitmentTransactionModelFactory.CreateCommitmentTransactionModel(channel, CommitmentSide.Remote);
+
+ // Build the output and the transactions
+ var localUnsignedCommitmentTransaction = _commitmentTransactionBuilder.Build(localCommitmentTransaction);
+ var remoteUnsignedCommitmentTransaction = _commitmentTransactionBuilder.Build(remoteCommitmentTransaction);
+
+ // Validate remote signature for our local commitment transaction
+ _lightningSigner.ValidateSignature(channel.ChannelId, payload.Signature, localUnsignedCommitmentTransaction);
+
+ // Sign our remote commitment transaction
+ var ourSignature = _lightningSigner.SignTransaction(channel.ChannelId, remoteUnsignedCommitmentTransaction);
+
+ channel.UpdateState(ChannelState.V1FundingSigned);
+ // Save to the database
+ await PersistChannelAsync(channel);
+
+ // Create the funding signed message
+ var fundingSignedMessage =
+ _messageFactory.CreatedFundingSignedMessage(channel.ChannelId, ourSignature);
+
+ // Add the channel to the dictionary
+ _channelMemoryRepository.AddChannel(channel);
+
+ // Remove the temporary channel
+ _channelMemoryRepository.RemoveTemporaryChannel(peerPubKey, oldChannelId);
+
+ return fundingSignedMessage;
+ }
+
+ ///
+ /// Persists a channel to the database using a scoped Unit of Work
+ ///
+ private async Task PersistChannelAsync(ChannelModel channel)
+ {
+ try
+ {
+ // Check if the channel already exists
+ var existingChannel = await _unitOfWork.ChannelDbRepository.GetByIdAsync(channel.ChannelId);
+ if (existingChannel is not null)
+ throw new ChannelWarningException("Channel already exists", channel.ChannelId,
+ "This channel is already in our database");
+
+ await _unitOfWork.ChannelDbRepository.AddAsync(channel);
+ await _unitOfWork.SaveChangesAsync();
+
+ _logger.LogDebug("Successfully persisted channel {ChannelId} to database", channel.ChannelId);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to persist channel {ChannelId} to database", channel.ChannelId);
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Channels/Handlers/Interfaces/IChannelMessageHandler.cs b/src/NLightning.Application/Channels/Handlers/Interfaces/IChannelMessageHandler.cs
new file mode 100644
index 00000000..380dae80
--- /dev/null
+++ b/src/NLightning.Application/Channels/Handlers/Interfaces/IChannelMessageHandler.cs
@@ -0,0 +1,25 @@
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Application.Channels.Handlers.Interfaces;
+
+using Domain.Channels.Enums;
+using Domain.Crypto.ValueObjects;
+using Domain.Node.Options;
+
+///
+/// Base interface for all channel message handlers
+///
+/// The type of message this handler can process
+public interface IChannelMessageHandler where TMessage : IChannelMessage
+{
+ ///
+ /// Handles a channel message and returns a response message if needed
+ ///
+ /// The message to handle
+ /// The current state of the channel
+ /// Features negotiated with the peer
+ /// The public key of the peer
+ /// A response message if needed, or null if no response is needed
+ Task HandleAsync(TMessage message, ChannelState currentState, FeatureOptions negotiatedFeatures,
+ CompactPubKey peerPubKey);
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Channels/Handlers/OpenChannel1MessageHandler.cs b/src/NLightning.Application/Channels/Handlers/OpenChannel1MessageHandler.cs
new file mode 100644
index 00000000..70074c57
--- /dev/null
+++ b/src/NLightning.Application/Channels/Handlers/OpenChannel1MessageHandler.cs
@@ -0,0 +1,83 @@
+using Microsoft.Extensions.Logging;
+
+namespace NLightning.Application.Channels.Handlers;
+
+using Domain.Channels.Enums;
+using Domain.Channels.Interfaces;
+using Domain.Crypto.ValueObjects;
+using Domain.Exceptions;
+using Domain.Node.Options;
+using Domain.Protocol.Interfaces;
+using Domain.Protocol.Messages;
+using Domain.Protocol.Tlv;
+using Interfaces;
+
+public class OpenChannel1MessageHandler : IChannelMessageHandler
+{
+ private readonly IChannelFactory _channelFactory;
+ private readonly IChannelMemoryRepository _channelMemoryRepository;
+ private readonly ILogger _logger;
+ private readonly IMessageFactory _messageFactory;
+
+ public OpenChannel1MessageHandler(IChannelFactory channelFactory, IChannelMemoryRepository channelMemoryRepository,
+ ILogger logger, IMessageFactory messageFactory)
+ {
+ _channelFactory = channelFactory;
+ _channelMemoryRepository = channelMemoryRepository;
+ _logger = logger;
+ _messageFactory = messageFactory;
+ }
+
+ public async Task HandleAsync(OpenChannel1Message message, ChannelState currentState,
+ FeatureOptions negotiatedFeatures, CompactPubKey peerPubKey)
+ {
+ _logger.LogTrace("Processing OpenChannel1Message with ChannelId: {ChannelId} from Peer: {PeerPubKey}",
+ message.Payload.ChannelId, peerPubKey);
+
+ var payload = message.Payload;
+
+ if (currentState != ChannelState.None)
+ throw new ChannelErrorException("A channel with this id already exists", payload.ChannelId);
+
+ // Check if there's a temporary channel for this peer
+ if (_channelMemoryRepository.TryGetTemporaryChannelState(peerPubKey, payload.ChannelId, out currentState))
+ {
+ if (currentState != ChannelState.V1Opening)
+ {
+ throw new ChannelErrorException("Channel had the wrong state", payload.ChannelId,
+ "This channel is already being negotiated with peer");
+ }
+ }
+
+ // Create the channel
+ var channel = await _channelFactory.CreateChannelV1AsNonInitiatorAsync(message, negotiatedFeatures, peerPubKey);
+
+ _logger.LogTrace("Created Channel with fundingPubKey: {fundingPubKey}",
+ channel.LocalKeySet.FundingCompactPubKey);
+
+ // Add the channel to dictionaries
+ _channelMemoryRepository.AddTemporaryChannel(peerPubKey, channel);
+
+ // Create UpfrontShutdownScriptTlv if needed
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv = null;
+ if (channel.LocalUpfrontShutdownScript is not null)
+ upfrontShutdownScriptTlv = new UpfrontShutdownScriptTlv(channel.LocalUpfrontShutdownScript.Value);
+
+ // TODO: Create the ChannelTypeTlv
+
+ // Create the reply message
+ var acceptChannel1ReplyMessage = _messageFactory
+ .CreateAcceptChannel1Message(channel.ChannelConfig.ChannelReserveAmount!, null,
+ channel.LocalKeySet.DelayedPaymentCompactBasepoint,
+ channel.LocalKeySet.CurrentPerCommitmentCompactPoint,
+ channel.LocalKeySet.FundingCompactPubKey,
+ channel.LocalKeySet.HtlcCompactBasepoint,
+ channel.ChannelConfig.MaxAcceptedHtlcs,
+ channel.ChannelConfig.MaxHtlcAmountInFlight, channel.ChannelConfig.MinimumDepth,
+ channel.LocalKeySet.PaymentCompactBasepoint,
+ channel.LocalKeySet.RevocationCompactBasepoint, channel.ChannelId,
+ channel.ChannelConfig.ToSelfDelay, upfrontShutdownScriptTlv);
+
+ return acceptChannel1ReplyMessage;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Channels/Managers/ChannelManager.cs b/src/NLightning.Application/Channels/Managers/ChannelManager.cs
new file mode 100644
index 00000000..cab6aad4
--- /dev/null
+++ b/src/NLightning.Application/Channels/Managers/ChannelManager.cs
@@ -0,0 +1,188 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Application.Channels.Managers;
+
+using Domain.Bitcoin.Interfaces;
+using Domain.Channels.Constants;
+using Domain.Channels.Enums;
+using Domain.Channels.Interfaces;
+using Domain.Channels.Models;
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
+using Domain.Exceptions;
+using Domain.Node.Options;
+using Domain.Persistence.Interfaces;
+using Domain.Protocol.Constants;
+using Domain.Protocol.Messages;
+using Handlers.Interfaces;
+
+public class ChannelManager : IChannelManager
+{
+ private readonly IChannelMemoryRepository _channelMemoryRepository;
+ private readonly ILogger _logger;
+ private readonly ILightningSigner _lightningSigner;
+ private readonly IServiceProvider _serviceProvider;
+
+ public ChannelManager(IChannelMemoryRepository channelMemoryRepository, ILogger logger,
+ ILightningSigner lightningSigner, IServiceProvider serviceProvider)
+ {
+ _channelMemoryRepository = channelMemoryRepository;
+ _serviceProvider = serviceProvider;
+ _logger = logger;
+ _lightningSigner = lightningSigner;
+ }
+
+ public async Task InitializeAsync()
+ {
+ // Load existing channels from the database
+ using var scope = _serviceProvider.CreateScope();
+ using var unitOfWork = scope.ServiceProvider.GetRequiredService();
+
+ try
+ {
+ var existingChannels = await unitOfWork.ChannelDbRepository.GetReadyChannelsAsync();
+
+ var channelModels = existingChannels as ChannelModel[] ?? existingChannels.ToArray();
+ foreach (var channel in channelModels)
+ {
+ if (channel.FundingOutput.TransactionId is null)
+ {
+ _logger.LogError("Channel {ChannelId} has no funding transaction id, skipping", channel.ChannelId);
+ continue;
+ }
+
+ _channelMemoryRepository.AddChannel(channel);
+
+ // Register channel with signer
+ var channelSigningInfo = new ChannelSigningInfo(
+ channel.FundingOutput.TransactionId!.Value,
+ channel.FundingOutput.Index!.Value,
+ channel.FundingOutput.Amount,
+ channel.LocalKeySet.FundingCompactPubKey,
+ channel.RemoteKeySet.FundingCompactPubKey,
+ channel.LocalKeySet.KeyIndex
+ );
+ _lightningSigner.RegisterChannel(channel.ChannelId, channelSigningInfo);
+ }
+
+ _logger.LogInformation("Loaded {ChannelCount} channels from database", channelModels.Length);
+ }
+ catch (Exception e)
+ {
+ throw new CriticalException("Failed to initialize channels from database", e);
+ }
+ }
+
+ public async Task HandleChannelMessageAsync(IChannelMessage message,
+ FeatureOptions negotiatedFeatures,
+ CompactPubKey peerPubKey)
+ {
+ using var scope = _serviceProvider.CreateScope();
+
+ // Check if the channel exists on the state dictionary
+ _channelMemoryRepository.TryGetChannelState(message.Payload.ChannelId, out var currentState);
+
+ // In this case we can only handle messages that are opening a channel
+ switch (message.Type)
+ {
+ case MessageTypes.OpenChannel:
+ // Handle opening channel message
+ var openChannel1Message = message as OpenChannel1Message
+ ?? throw new ChannelErrorException("Error boxing message to OpenChannel1Message",
+ "Sorry, we had an internal error");
+ return await GetChannelMessageHandler(scope)
+ .HandleAsync(openChannel1Message, currentState, negotiatedFeatures, peerPubKey);
+
+ case MessageTypes.FundingCreated:
+ // Handle the funding-created message
+ var fundingCreatedMessage = message as FundingCreatedMessage
+ ?? throw new ChannelErrorException(
+ "Error boxing message to FundingCreatedMessage",
+ "Sorry, we had an internal error");
+ return await GetChannelMessageHandler(scope)
+ .HandleAsync(fundingCreatedMessage, currentState, negotiatedFeatures, peerPubKey);
+
+ case MessageTypes.ChannelReady:
+ // Handle channel ready message
+ var channelReadyMessage = message as ChannelReadyMessage
+ ?? throw new ChannelErrorException("Error boxing message to ChannelReadyMessage",
+ "Sorry, we had an internal error");
+ return await GetChannelMessageHandler(scope)
+ .HandleAsync(channelReadyMessage, currentState, negotiatedFeatures, peerPubKey);
+ default:
+ throw new ChannelErrorException("Unknown message type", "Sorry, we had an internal error");
+ }
+ }
+
+ public async Task ForgetOldChannelByBlockHeightAsync(uint blockHeight)
+ {
+ var heightLimit = blockHeight - ChannelConstants.MaxUnconfirmedChannelAge;
+ var staleChannels = _channelMemoryRepository.FindChannels(c => c.FundingCreatedAtBlockHeight <= heightLimit);
+
+ _logger.LogDebug(
+ "Forgetting stale channels created before block height {HeightLimit}, found {StaleChannelCount} channels",
+ heightLimit, staleChannels.Count);
+
+ foreach (var staleChannel in staleChannels)
+ {
+ _logger.LogInformation(
+ "Forgetting stale channel {ChannelId} with funding created at block height {BlockHeight}",
+ staleChannel.ChannelId, staleChannel.FundingCreatedAtBlockHeight);
+
+ // Set states
+ staleChannel.UpdateState(ChannelState.Stale);
+ _channelMemoryRepository.UpdateChannel(staleChannel);
+
+ // Persist on Db
+ try
+ {
+ await PersistChannelAsync(staleChannel);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Failed to persist stale channel {ChannelId} to database at height {currentHeight}",
+ staleChannel.ChannelId, blockHeight);
+ }
+ }
+ }
+
+ private IChannelMessageHandler GetChannelMessageHandler(IServiceScope scope)
+ where T : IChannelMessage
+ {
+ var handler = scope.ServiceProvider.GetRequiredService>() ??
+ throw new ChannelErrorException($"No handler found for message type {typeof(T).FullName}",
+ "Sorry, we had an internal error");
+ return handler;
+ }
+
+ ///
+ /// Persists a channel to the database using a scoped Unit of Work
+ ///
+ private async Task PersistChannelAsync(ChannelModel channel)
+ {
+ using var scope = _serviceProvider.CreateScope();
+ var unitOfWork = scope.ServiceProvider.GetRequiredService();
+
+ try
+ {
+ // Check if the channel already exists
+
+ _ = await unitOfWork.ChannelDbRepository.GetByIdAsync(channel.ChannelId)
+ ?? throw new ChannelWarningException("Channel not found", channel.ChannelId);
+ await unitOfWork.ChannelDbRepository.UpdateAsync(channel);
+ await unitOfWork.SaveChangesAsync();
+
+ // Remove from dictionaries
+ _channelMemoryRepository.RemoveChannel(channel.ChannelId);
+
+ _logger.LogDebug("Successfully persisted channel {ChannelId} to database", channel.ChannelId);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to persist channel {ChannelId} to database", channel.ChannelId);
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/DependencyInjection.cs b/src/NLightning.Application/DependencyInjection.cs
new file mode 100644
index 00000000..4335c4e7
--- /dev/null
+++ b/src/NLightning.Application/DependencyInjection.cs
@@ -0,0 +1,73 @@
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace NLightning.Application;
+
+using Channels.Handlers.Interfaces;
+using Channels.Managers;
+using Domain.Bitcoin.Interfaces;
+using Domain.Channels.Interfaces;
+using Domain.Protocol.Interfaces;
+using Node.Services;
+using Protocol.Factories;
+
+///
+/// Extension methods for setting up application services in an IServiceCollection.
+///
+public static class DependencyInjection
+{
+ ///
+ /// Adds application layer services to the specified IServiceCollection.
+ ///
+ /// The IServiceCollection to add services to.
+ /// The same service collection so that multiple calls can be chained.
+ public static IServiceCollection AddApplicationServices(this IServiceCollection services)
+ {
+ // Singleton services (one instance throughout the application)
+ services.AddSingleton(sp =>
+ {
+ var channelMemoryRepository = sp.GetRequiredService();
+ var lightningSigner = sp.GetRequiredService();
+ var loggerFactory = sp.GetRequiredService();
+ return new ChannelManager(channelMemoryRepository, loggerFactory.CreateLogger(),
+ lightningSigner, sp);
+ });
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Automatically register all channel message handlers
+ services.AddChannelMessageHandlers();
+
+ return services;
+ }
+
+ ///
+ /// Registers all classes that implement IChannelMessageHandler<T> from the current assembly
+ ///
+ private static void AddChannelMessageHandlers(this IServiceCollection services)
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+
+ // Find all types that implement IChannelMessageHandler<>
+ var handlerTypes = assembly
+ .GetTypes()
+ .Where(type => type is { IsClass: true, IsAbstract: false })
+ .Where(type => type.GetInterfaces()
+ .Any(i => i.IsGenericType
+ && i.GetGenericTypeDefinition() ==
+ typeof(IChannelMessageHandler<>)))
+ .ToArray();
+
+ foreach (var handlerType in handlerTypes)
+ {
+ // Get the interface this handler implements
+ var handlerInterface = handlerType
+ .GetInterfaces()
+ .First(i => i.IsGenericType
+ && i.GetGenericTypeDefinition() == typeof(IChannelMessageHandler<>));
+
+ services.AddScoped(handlerInterface, handlerType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Managers/LightningKeyManager.cs b/src/NLightning.Application/Managers/LightningKeyManager.cs
deleted file mode 100644
index 41404a20..00000000
--- a/src/NLightning.Application/Managers/LightningKeyManager.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-// using NBitcoin;
-// using NBitcoin.Crypto;
-//
-// namespace NLightning.Bolts.BOLT3.Managers;
-//
-// using Common.Managers;
-// using Services;
-//
-// public class LightningKeyManager
-// {
-// private readonly KeyDerivationService _keyDerivation;
-//
-// // Node's base key pairs - long-term identity keys
-// private readonly Key _paymentBaseSecret;
-// private readonly Key _htlcBaseSecret;
-// private readonly Key _delayedPaymentBaseSecret;
-// private readonly Key _revocationBaseSecret;
-//
-// // Corresponding public keys
-// public PubKey PaymentBasepoint => _paymentBaseSecret.PubKey;
-// public PubKey HtlcBasepoint => _htlcBaseSecret.PubKey;
-// public PubKey DelayedPaymentBasepoint => _delayedPaymentBaseSecret.PubKey;
-// public PubKey RevocationBasepoint => _revocationBaseSecret.PubKey;
-//
-// // Store the remote node's base public keys
-// public PubKey RemotePaymentBasepoint { get; set; }
-// public PubKey RemoteHtlcBasepoint { get; set; }
-// public PubKey RemoteDelayedBasepoint { get; set; }
-// public PubKey RemoteRevocationBasepoint { get; set; }
-//
-// // Per-commitment secret storage
-// private readonly KeyDerivationService.PerCommitmentSecretStorage _secretStorage =
-// new KeyDerivationService.PerCommitmentSecretStorage();
-//
-// // Initialize with node secrets or generate new ones
-// public LightningKeyManager(KeyDerivationService keyDerivation)
-// {
-// _keyDerivation = keyDerivation;
-//
-// // Generate node secrets or load from SecureKeyManager
-// byte[] nodeSeed = GenerateOrLoadNodeSeed();
-//
-// // Derive base secrets from seed
-// var hmac = new HMACSHA256(nodeSeed);
-// _paymentBaseSecret = new Key(hmac.ComputeHash(new byte[] { 0x01 }));
-// _htlcBaseSecret = new Key(hmac.ComputeHash(new byte[] { 0x02 }));
-// _delayedPaymentBaseSecret = new Key(hmac.ComputeHash(new byte[] { 0x03 }));
-// _revocationBaseSecret = new Key(hmac.ComputeHash(new byte[] { 0x04 }));
-//
-// // Initialize per-commitment seed
-// byte[] commitmentSeed = hmac.ComputeHash(new byte[] { 0x05 });
-// _keyDerivation.InitializeSecureSeed(commitmentSeed);
-// }
-//
-// private byte[] GenerateOrLoadNodeSeed()
-// {
-// try {
-// // Try to load from SecureKeyManager
-// return SecureKeyManager.GetPrivateKeyBytes();
-// }
-// catch (InvalidOperationException) {
-// // Generate new seed if not initialized
-// var seed = new byte[32];
-// using (var rng = RandomNumberGenerator.Create())
-// rng.GetBytes(seed);
-//
-// SecureKeyManager.Initialize(seed);
-// return seed;
-// }
-// }
-//
-// // Generate per-commitment secret for a specific commitment number
-// public byte[] GeneratePerCommitmentSecret(ulong commitmentNumber)
-// {
-// var seed = _keyDerivation.GetSecureSeed();
-// return KeyDerivationService.GeneratePerCommitmentSecret(seed, commitmentNumber);
-// }
-//
-// // Get keys for a specific commitment transaction
-// public CommitmentKeys DeriveCommitmentKeys(ulong commitmentNumber)
-// {
-// // Get per-commitment secret and point
-// byte[] perCommitmentSecret = GeneratePerCommitmentSecret(commitmentNumber);
-// PubKey perCommitmentPoint = _keyDerivation.GeneratePerCommitmentPoint(perCommitmentSecret);
-//
-// // Store the secret for later verification
-// _secretStorage.InsertSecret(perCommitmentSecret, commitmentNumber);
-//
-// // Derive all necessary keys for the commitment transaction
-// return new CommitmentKeys {
-// // Local keys
-// LocalPubkey = DerivePublicKey(PaymentBasepoint, perCommitmentPoint),
-// LocalHtlcPubkey = DerivePublicKey(HtlcBasepoint, perCommitmentPoint),
-// LocalDelayedPubkey = DerivePublicKey(DelayedPaymentBasepoint, perCommitmentPoint),
-//
-// // Remote keys
-// RemotePubkey = RemotePaymentBasepoint, // This is simply the remote's payment basepoint
-// RemoteHtlcPubkey = DerivePublicKey(RemoteHtlcBasepoint, perCommitmentPoint),
-// RemoteDelayedPubkey = DerivePublicKey(RemoteDelayedBasepoint, perCommitmentPoint),
-//
-// // Revocation key
-// RevocationPubkey = _keyDerivation.DeriveRevocationPubKey(
-// RevocationBasepoint, perCommitmentPoint),
-//
-// // Store commitment point for reference
-// PerCommitmentPoint = perCommitmentPoint
-// };
-// }
-//
-// // Helper method to use NBitcoin for key derivation
-// private PubKey DerivePublicKey(PubKey basepoint, PubKey perCommitmentPoint)
-// {
-// using var ms = new MemoryStream();
-// ms.Write(perCommitmentPoint.ToBytes(), 0, 33);
-// ms.Write(basepoint.ToBytes(), 0, 33);
-//
-// var hash = Hashes.SHA256(ms.ToArray());
-//
-// // In NBitcoin we need to use EC point addition: basepoint + hash*G
-// var ec = new ECKey(hash, true);
-// return basepoint.Derivation(ec);
-// }
-// }
\ No newline at end of file
diff --git a/src/NLightning.Application/NLightning.Application.csproj b/src/NLightning.Application/NLightning.Application.csproj
index d2f51a0e..b1fb59f1 100644
--- a/src/NLightning.Application/NLightning.Application.csproj
+++ b/src/NLightning.Application/NLightning.Application.csproj
@@ -57,7 +57,12 @@
-
+
+
+
+
+
+
diff --git a/src/NLightning.Application/Node/Interfaces/IPeerCommunicationService.cs b/src/NLightning.Application/Node/Interfaces/IPeerCommunicationService.cs
new file mode 100644
index 00000000..3856aac3
--- /dev/null
+++ b/src/NLightning.Application/Node/Interfaces/IPeerCommunicationService.cs
@@ -0,0 +1,56 @@
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Application.Node.Interfaces;
+
+using Domain.Crypto.ValueObjects;
+
+///
+/// Interface for communication with a single peer.
+///
+public interface IPeerCommunicationService
+{
+ ///
+ /// Gets a value indicating whether the connection is established.
+ ///
+ bool IsConnected { get; }
+
+ ///
+ /// Gets the peer's public key.
+ ///
+ CompactPubKey PeerCompactPubKey { get; }
+
+ ///
+ /// Event raised when a message is received from the peer.
+ ///
+ event EventHandler MessageReceived;
+
+ ///
+ /// Event raised when the peer is disconnected.
+ ///
+ event EventHandler? DisconnectEvent;
+
+ ///
+ /// Event raised when an exception occurs.
+ ///
+ event EventHandler? ExceptionRaised;
+
+ ///
+ /// Sends a message to the peer.
+ ///
+ /// The message to send.
+ /// The cancellation token.
+ /// A task that represents the asynchronous operation.
+ Task SendMessageAsync(IMessage message, CancellationToken cancellationToken = default);
+
+ ///
+ /// Initializes the communication with the peer.
+ ///
+ /// The network timeout.
+ /// A task that represents the asynchronous operation.
+ Task InitializeAsync(TimeSpan networkTimeout);
+
+ ///
+ /// Disconnects from the peer.
+ ///
+ void Disconnect();
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Node/Interfaces/IPeerService.cs b/src/NLightning.Application/Node/Interfaces/IPeerService.cs
new file mode 100644
index 00000000..0b328264
--- /dev/null
+++ b/src/NLightning.Application/Node/Interfaces/IPeerService.cs
@@ -0,0 +1,24 @@
+namespace NLightning.Application.Node.Interfaces;
+
+using Domain.Crypto.ValueObjects;
+
+///
+/// Interface for the peer application service.
+///
+public interface IPeerService
+{
+ ///
+ /// Gets the peer's public key.
+ ///
+ CompactPubKey PeerPubKey { get; }
+
+ ///
+ /// Event raised when the peer is disconnected.
+ ///
+ event EventHandler? DisconnectEvent;
+
+ ///
+ /// Disconnects from the peer.
+ ///
+ void Disconnect();
+}
\ No newline at end of file
diff --git a/src/NLightning.Application/Node/Interfaces/IPeerServiceFactory.cs b/src/NLightning.Application/Node/Interfaces/IPeerServiceFactory.cs
new file mode 100644
index 00000000..14036ce5
--- /dev/null
+++ b/src/NLightning.Application/Node/Interfaces/IPeerServiceFactory.cs
@@ -0,0 +1,26 @@
+using System.Net.Sockets;
+
+namespace NLightning.Application.Node.Interfaces;
+
+using Domain.Crypto.ValueObjects;
+
+///
+/// Interface for creating peer services.
+///
+public interface IPeerServiceFactory
+{
+ ///
+ /// Creates a peer that we're connecting to.
+ ///
+ /// Peer public key
+ /// TCP client
+ /// A task that represents the asynchronous operation. The task result contains the created peer.
+ Task CreateConnectedPeerAsync(CompactPubKey peerPubKey, TcpClient tcpClient);
+
+ ///
+ /// Creates a peer that is connecting to us.
+ ///
+ /// TCP client
+ /// A task that represents the asynchronous operation. The task result contains the created peer.
+ Task CreateConnectingPeerAsync(TcpClient tcpClient);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Factories/IPingPongServiceFactory.cs b/src/NLightning.Application/Node/Interfaces/IPingPongServiceFactory.cs
similarity index 78%
rename from src/NLightning.Domain/Protocol/Factories/IPingPongServiceFactory.cs
rename to src/NLightning.Application/Node/Interfaces/IPingPongServiceFactory.cs
index 4bb86b1a..e23c1209 100644
--- a/src/NLightning.Domain/Protocol/Factories/IPingPongServiceFactory.cs
+++ b/src/NLightning.Application/Node/Interfaces/IPingPongServiceFactory.cs
@@ -1,7 +1,6 @@
-namespace NLightning.Domain.Protocol.Factories;
-
-using Services;
+using NLightning.Domain.Protocol.Interfaces;
+namespace NLightning.Application.Node.Interfaces;
///
/// Interface for a ping pong service factory.
///
diff --git a/src/NLightning.Application/Node/Services/PeerApplicationService.cs b/src/NLightning.Application/Node/Services/PeerApplicationService.cs
new file mode 100644
index 00000000..a5eba278
--- /dev/null
+++ b/src/NLightning.Application/Node/Services/PeerApplicationService.cs
@@ -0,0 +1,174 @@
+using Microsoft.Extensions.Logging;
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Application.Node.Services;
+
+using Domain.Channels.Interfaces;
+using Domain.Crypto.ValueObjects;
+using Domain.Exceptions;
+using Domain.Node.Options;
+using Domain.Protocol.Constants;
+using Domain.Protocol.Messages;
+using Domain.Protocol.Tlv;
+using Interfaces;
+
+///
+/// Application service for peer communication that orchestrates domain logic.
+///
+public sealed class PeerApplicationService : IPeerService
+{
+ private readonly IChannelManager _channelManager;
+ private readonly IPeerCommunicationService _communicationService;
+ private readonly ILogger _logger;
+
+ private FeatureOptions _features;
+ private bool _isInitialized;
+
+ ///
+ /// Event raised when the peer is disconnected.
+ ///
+ public event EventHandler? DisconnectEvent;
+
+ ///
+ /// Gets the peer's public key.
+ ///
+ public CompactPubKey PeerPubKey => _communicationService.PeerCompactPubKey;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The channel manager
+ /// The peer communication service
+ /// The feature options
+ /// A logger
+ /// Network timeout
+ public PeerApplicationService(IChannelManager channelManager, IPeerCommunicationService communicationService,
+ FeatureOptions features, ILogger logger,
+ TimeSpan networkTimeout)
+ {
+ _channelManager = channelManager;
+ _communicationService = communicationService;
+ _features = features;
+ _logger = logger;
+
+ // Set up event handlers
+ _communicationService.MessageReceived += HandleMessage;
+ _communicationService.ExceptionRaised += HandleException;
+ _communicationService.DisconnectEvent += (_, _) => DisconnectEvent?.Invoke(this, EventArgs.Empty);
+
+ // Initialize communication
+ _communicationService.InitializeAsync(networkTimeout).Wait();
+ }
+
+ ///
+ /// Handles messages received from the peer.
+ ///
+ private void HandleMessage(object? sender, IMessage? message)
+ {
+ if (message is null)
+ {
+ return;
+ }
+
+ if (!_isInitialized)
+ {
+ _logger.LogTrace("Received message from peer {peer} but was not initialized", PeerPubKey);
+ HandleInitialization(message);
+ }
+ else if (message is IChannelMessage channelMessage)
+ {
+ // Handle channel-related messages
+ _ = HandleChannelMessageAsync(channelMessage);
+ }
+ }
+
+ ///
+ /// Handles exceptions raised by the communication service.
+ ///
+ private void HandleException(object? sender, Exception e)
+ {
+ _logger.LogError(e, "Exception occurred with peer {peer}", PeerPubKey);
+ }
+
+ ///
+ /// Handles the initialization process when receiving the first message.
+ ///
+ private void HandleInitialization(IMessage message)
+ {
+ // Check if the first message is an init message
+ if (message.Type != MessageTypes.Init || message is not InitMessage initMessage)
+ {
+ _logger.LogError("Failed to receive init message from peer {peer}", PeerPubKey);
+ Disconnect();
+ return;
+ }
+
+ // Check if Features are compatible
+ if (!_features.GetNodeFeatures().IsCompatible(initMessage.Payload.FeatureSet, out var negotiatedFeatures)
+ || negotiatedFeatures is null)
+ {
+ _logger.LogError("Peer {peer} is not compatible", PeerPubKey);
+ Disconnect();
+ return;
+ }
+
+ // Check if Chains are compatible
+ if (initMessage.Extension != null
+ && initMessage.Extension.TryGetTlv(TlvConstants.Networks, out var networksTlv))
+ {
+ // Check if ChainHash contained in networksTlv.ChainHashes exists in our ChainHashes
+ var networkChainHashes = ((NetworksTlv)networksTlv!).ChainHashes;
+ if (networkChainHashes is not null &&
+ networkChainHashes.Any(chainHash => !_features.ChainHashes.Contains(chainHash)))
+ {
+ _logger.LogError("Peer {peer} chain is not compatible", PeerPubKey);
+ Disconnect();
+ return;
+ }
+ }
+
+ _features = FeatureOptions.GetNodeOptions(negotiatedFeatures, initMessage.Extension);
+ _logger.LogTrace("Initialization from peer {peer} completed successfully", PeerPubKey);
+ _isInitialized = true;
+ }
+
+ ///
+ /// Handles channel messages.
+ ///
+ private async Task HandleChannelMessageAsync(IChannelMessage message)
+ {
+ try
+ {
+ _logger.LogTrace("Received channel message ({messageType}) from peer {peer}",
+ Enum.GetName(message.Type), PeerPubKey);
+
+ var replyMessage = await _channelManager.HandleChannelMessageAsync(message, _features, PeerPubKey);
+ if (replyMessage is not null)
+ await _communicationService.SendMessageAsync(replyMessage);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error handling channel message ({messageType}) from peer {peer}",
+ Enum.GetName(message.Type), PeerPubKey);
+
+ if (e is ChannelErrorException channelError)
+ {
+ _logger.LogError("Channel error: {message}",
+ !string.IsNullOrEmpty(channelError.PeerMessage)
+ ? channelError.PeerMessage
+ : channelError.Message);
+ }
+
+ Disconnect();
+ }
+ }
+
+ ///
+ /// Disconnects from the peer.
+ ///
+ public void Disconnect()
+ {
+ _logger.LogInformation("Disconnecting peer {peer}", PeerPubKey);
+ _communicationService.Disconnect();
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Infrastructure/Protocol/Services/PingPongService.cs b/src/NLightning.Application/Node/Services/PingPongService.cs
similarity index 82%
rename from src/NLightning.Infrastructure/Protocol/Services/PingPongService.cs
rename to src/NLightning.Application/Node/Services/PingPongService.cs
index 0b78b2b7..8469525b 100644
--- a/src/NLightning.Infrastructure/Protocol/Services/PingPongService.cs
+++ b/src/NLightning.Application/Node/Services/PingPongService.cs
@@ -1,13 +1,11 @@
using Microsoft.Extensions.Options;
-namespace NLightning.Infrastructure.Protocol.Services;
+namespace NLightning.Application.Node.Services;
using Domain.Exceptions;
-using Domain.Factories;
using Domain.Node.Options;
+using Domain.Protocol.Interfaces;
using Domain.Protocol.Messages;
-using Domain.Protocol.Messages.Interfaces;
-using Domain.Protocol.Services;
///
/// Service for managing the ping pong protocol.
@@ -34,7 +32,7 @@ public PingPongService(IMessageFactory messageFactory, IOptions nod
{
_messageFactory = messageFactory;
_nodeOptions = nodeOptions.Value;
- _pingMessage = (PingMessage)messageFactory.CreatePingMessage();
+ _pingMessage = messageFactory.CreatePingMessage();
}
///
@@ -50,14 +48,14 @@ public async Task StartPingAsync(CancellationToken cancellationToken)
PingMessageReadyEvent?.Invoke(this, _pingMessage);
using var pongTimeoutTokenSource = CancellationTokenSource
- .CreateLinkedTokenSource(cancellationToken,
- new CancellationTokenSource(_nodeOptions.NetworkTimeout).Token);
+ .CreateLinkedTokenSource(cancellationToken,
+ new CancellationTokenSource(_nodeOptions.NetworkTimeout).Token);
var task = await Task.WhenAny(_pongReceivedTaskSource.Task, Task.Delay(-1, pongTimeoutTokenSource.Token));
if (task.IsFaulted)
{
DisconnectEvent?
- .Invoke(this, new ConnectionException("Pong message not received within network timeout."));
+ .Invoke(this, new ConnectionException("Pong message not received within network timeout."));
return;
}
@@ -69,7 +67,7 @@ public async Task StartPingAsync(CancellationToken cancellationToken)
await Task.Delay(_random.Next(30_000, 300_000), cancellationToken);
_pongReceivedTaskSource = new TaskCompletionSource();
- _pingMessage = (PingMessage)_messageFactory.CreatePingMessage();
+ _pingMessage = _messageFactory.CreatePingMessage();
}
}
diff --git a/src/NLightning.Application/Factories/MessageFactory.cs b/src/NLightning.Application/Protocol/Factories/MessageFactory.cs
similarity index 61%
rename from src/NLightning.Application/Factories/MessageFactory.cs
rename to src/NLightning.Application/Protocol/Factories/MessageFactory.cs
index 75635d73..8fa5ad14 100644
--- a/src/NLightning.Application/Factories/MessageFactory.cs
+++ b/src/NLightning.Application/Protocol/Factories/MessageFactory.cs
@@ -1,18 +1,18 @@
using Microsoft.Extensions.Options;
-using NBitcoin;
-using NBitcoin.Crypto;
-namespace NLightning.Application.Factories;
+namespace NLightning.Application.Protocol.Factories;
-using Domain.Factories;
+using Domain.Bitcoin.ValueObjects;
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
using Domain.Money;
using Domain.Node.Options;
+using Domain.Protocol.Interfaces;
using Domain.Protocol.Messages;
-using Domain.Protocol.Messages.Interfaces;
using Domain.Protocol.Models;
using Domain.Protocol.Payloads;
using Domain.Protocol.Tlv;
-using Domain.ValueObjects;
+using Domain.Protocol.ValueObjects;
///
/// Factory for creating messages.
@@ -20,15 +20,16 @@ namespace NLightning.Application.Factories;
public class MessageFactory : IMessageFactory
{
private readonly NodeOptions _nodeOptions;
- private readonly Network _network;
+ private readonly BitcoinNetwork _bitcoinNetwork;
public MessageFactory(IOptions nodeOptions)
{
_nodeOptions = nodeOptions.Value;
- _network = _nodeOptions.Network;
+ _bitcoinNetwork = _nodeOptions.BitcoinNetwork;
}
#region Init Message
+
///
/// Create an Init message.
///
@@ -43,9 +44,11 @@ public InitMessage CreateInitMessage()
return new InitMessage(payload, _nodeOptions.Features.GetInitTlvs());
}
+
#endregion
#region Control Messages
+
///
/// Create a Warning message.
///
@@ -57,7 +60,7 @@ public InitMessage CreateInitMessage()
///
public WarningMessage CreateWarningMessage(string message, ChannelId? channelId)
{
- var payload = channelId is null ? new ErrorPayload(message) : new ErrorPayload(channelId.Value, message);
+ var payload = channelId is null ? new ErrorPayload(message) : new ErrorPayload(channelId, message);
return new WarningMessage(payload);
}
@@ -148,9 +151,11 @@ public PongMessage CreatePongMessage(IMessage pingMessage)
return new PongMessage(ping.Payload.NumPongBytes);
}
+
#endregion
#region Interactive Transaction Construction
+
///
/// Create a TxAddInput message.
///
@@ -163,8 +168,9 @@ public PongMessage CreatePongMessage(IMessage pingMessage)
///
///
///
- public TxAddInputMessage CreateTxAddInputMessage(ChannelId channelId, ulong serialId, byte[] prevTx, uint prevTxVout,
- uint sequence)
+ public TxAddInputMessage CreateTxAddInputMessage(ChannelId channelId, ulong serialId, byte[] prevTx,
+ uint prevTxVout,
+ uint sequence)
{
var payload = new TxAddInputPayload(channelId, serialId, prevTx, prevTxVout, sequence);
@@ -182,7 +188,8 @@ public TxAddInputMessage CreateTxAddInputMessage(ChannelId channelId, ulong seri
///
///
///
- public TxAddOutputMessage CreateTxAddOutputMessage(ChannelId channelId, ulong serialId, LightningMoney amount, Script script)
+ public TxAddOutputMessage CreateTxAddOutputMessage(ChannelId channelId, ulong serialId, LightningMoney amount,
+ BitcoinScript script)
{
var payload = new TxAddOutputPayload(amount, channelId, script, serialId);
@@ -266,7 +273,7 @@ public TxSignaturesMessage CreateTxSignaturesMessage(ChannelId channelId, byte[]
///
///
public TxInitRbfMessage CreateTxInitRbfMessage(ChannelId channelId, uint locktime, uint feerate,
- long fundingOutputContrubution, bool requireConfirmedInputs)
+ long fundingOutputContrubution, bool requireConfirmedInputs)
{
FundingOutputContributionTlv? fundingOutputContributionTlv = null;
RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null;
@@ -300,7 +307,7 @@ public TxInitRbfMessage CreateTxInitRbfMessage(ChannelId channelId, uint locktim
///
///
public TxAckRbfMessage CreateTxAckRbfMessage(ChannelId channelId, long fundingOutputContrubution,
- bool requireConfirmedInputs)
+ bool requireConfirmedInputs)
{
FundingOutputContributionTlv? fundingOutputContributionTlv = null;
RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null;
@@ -335,9 +342,11 @@ public TxAbortMessage CreateTxAbortMessage(ChannelId channelId, byte[] data)
return new TxAbortMessage(payload);
}
+
#endregion
#region Channel Messages
+
///
/// Create a ChannelReady message.
///
@@ -347,11 +356,11 @@ public TxAbortMessage CreateTxAbortMessage(ChannelId channelId, byte[] data)
/// The ChannelReady message.
///
///
- ///
+ ///
///
///
- public ChannelReadyMessage CreateChannelReadyMessage(ChannelId channelId, PubKey secondPerCommitmentPoint,
- ShortChannelId? shortChannelId = null)
+ public ChannelReadyMessage CreateChannelReadyMessage(ChannelId channelId, CompactPubKey secondPerCommitmentPoint,
+ ShortChannelId? shortChannelId = null)
{
var payload = new ChannelReadyPayload(channelId, secondPerCommitmentPoint);
@@ -367,9 +376,9 @@ public ChannelReadyMessage CreateChannelReadyMessage(ChannelId channelId, PubKey
/// The Shutdown message.
///
///
- ///
+ ///
///
- public ShutdownMessage CreateShutdownMessage(ChannelId channelId, Script scriptPubkey)
+ public ShutdownMessage CreateShutdownMessage(ChannelId channelId, BitcoinScript scriptPubkey)
{
var payload = new ShutdownPayload(channelId, scriptPubkey);
@@ -387,10 +396,11 @@ public ShutdownMessage CreateShutdownMessage(ChannelId channelId, Script scriptP
/// The ClosingSigned message.
///
///
- ///
+ ///
///
- public ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulong feeSatoshis, ECDSASignature signature,
- ulong minFeeSatoshis, ulong maxFeeSatoshis)
+ public ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulong feeSatoshis,
+ CompactSignature signature,
+ ulong minFeeSatoshis, ulong maxFeeSatoshis)
{
var payload = new ClosingSignedPayload(channelId, feeSatoshis, signature);
@@ -398,7 +408,59 @@ public ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulon
}
///
- /// Create a OpenChannel2 message.
+ /// Create an OpenChannel1 message.
+ ///
+ /// The temporary channel id.
+ /// The amount of satoshis we're adding to the channel.
+ /// The funding pubkey of the channel.
+ /// The amount of satoshis we're pushing to the other side.
+ /// The channel reserve amount.
+ /// The fee rate per kw.
+ /// The max accepted htlcs.
+ /// The revocation pubkey.
+ /// The payment pubkey.
+ /// The delayed payment pubkey.
+ /// The htlc pubkey.
+ /// The first per-commitment pubkey.
+ /// The flags for the channel.
+ /// The upfront shutdown script tlv.
+ /// The channel type tlv.
+ /// The OpenChannel1 message.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public OpenChannel1Message CreateOpenChannel1Message(ChannelId temporaryChannelId, LightningMoney fundingAmount,
+ CompactPubKey fundingPubKey, LightningMoney pushAmount,
+ LightningMoney channelReserveAmount,
+ LightningMoney feeRatePerKw, ushort maxAcceptedHtlcs,
+ CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ ChannelFlags channelFlags,
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv,
+ ChannelTypeTlv? channelTypeTlv)
+ {
+ var maxHtlcValueInFlight =
+ LightningMoney.Satoshis(_nodeOptions.AllowUpToPercentageOfChannelFundsInFlight * fundingAmount.Satoshi /
+ 100M);
+ var payload = new OpenChannel1Payload(_nodeOptions.BitcoinNetwork.ChainHash, channelFlags, temporaryChannelId,
+ channelReserveAmount, delayedPaymentBasepoint,
+ _nodeOptions.DustLimitAmount, feeRatePerKw, firstPerCommitmentPoint,
+ fundingAmount, fundingPubKey, htlcBasepoint,
+ _nodeOptions.HtlcMinimumAmount, maxAcceptedHtlcs, maxHtlcValueInFlight,
+ paymentBasepoint, pushAmount, revocationBasepoint,
+ _nodeOptions.ToSelfDelay);
+
+ return new OpenChannel1Message(payload, upfrontShutdownScriptTlv, channelTypeTlv);
+ }
+
+ ///
+ /// Create an OpenChannel2 message.
///
/// The temporary channel id.
/// The funding fee rate to open the channel.
@@ -409,8 +471,8 @@ public ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulon
/// The payment pubkey.
/// The delayed payment pubkey.
/// The htlc pubkey.
- /// The first per commitment pubkey.
- /// The second per commitment pubkey.
+ /// The first per-commitment pubkey.
+ /// The second per-commitment pubkey.
/// The flags for the channel.
/// The shutdown script to be used when closing the channel.
/// The type of the channel.
@@ -418,38 +480,93 @@ public ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulon
/// The OpenChannel2 message.
///
///
- ///
+ ///
///
- ///
+ ///
///
public OpenChannel2Message CreateOpenChannel2Message(ChannelId temporaryChannelId, uint fundingFeeRatePerKw,
- uint commitmentFeeRatePerKw, ulong fundingSatoshis, PubKey fundingPubKey,
- PubKey revocationBasepoint, PubKey paymentBasepoint,
- PubKey delayedPaymentBasepoint, PubKey htlcBasepoint,
- PubKey firstPerCommitmentPoint, PubKey secondPerCommitmentPoint,
- ChannelFlags channelFlags, Script? shutdownScriptPubkey = null,
- byte[]? channelType = null, bool requireConfirmedInputs = false)
- {
- var payload = new OpenChannel2Payload(_network.ChainHash, channelFlags, commitmentFeeRatePerKw,
+ uint commitmentFeeRatePerKw, ulong fundingSatoshis,
+ CompactPubKey fundingPubKey,
+ CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ CompactPubKey secondPerCommitmentPoint,
+ ChannelFlags channelFlags,
+ BitcoinScript? shutdownScriptPubkey = null,
+ byte[]? channelType = null,
+ bool requireConfirmedInputs = false)
+ {
+ var maxHtlcValueInFlight =
+ LightningMoney.Satoshis(_nodeOptions.AllowUpToPercentageOfChannelFundsInFlight * fundingSatoshis / 100M);
+
+ var payload = new OpenChannel2Payload(_bitcoinNetwork.ChainHash, channelFlags, commitmentFeeRatePerKw,
delayedPaymentBasepoint, _nodeOptions.DustLimitAmount,
firstPerCommitmentPoint, fundingSatoshis, fundingFeeRatePerKw,
fundingPubKey, htlcBasepoint, _nodeOptions.HtlcMinimumAmount,
_nodeOptions.Locktime, _nodeOptions.MaxAcceptedHtlcs,
- _nodeOptions.MaxHtlcValueInFlight, paymentBasepoint, revocationBasepoint,
+ maxHtlcValueInFlight, paymentBasepoint, revocationBasepoint,
secondPerCommitmentPoint, _nodeOptions.ToSelfDelay, temporaryChannelId);
return new OpenChannel2Message(payload,
shutdownScriptPubkey is null
? null
- : new UpfrontShutdownScriptTlv(shutdownScriptPubkey),
- channelType is null ?
- null
+ : new UpfrontShutdownScriptTlv(shutdownScriptPubkey.Value),
+ channelType is null
+ ? null
: new ChannelTypeTlv(channelType),
requireConfirmedInputs ? new RequireConfirmedInputsTlv() : null);
}
///
- /// Create a AcceptChannel2 message.
+ /// Creates an AcceptChannel1 message.
+ ///
+ /// The reserve amount for the channel.
+ /// Optional parameter specifying the channel type.
+ /// The basepoint for the delayed payment key.
+ /// The first per-commitment point for the channel.
+ /// Public key associated with the channel funding.
+ /// The basepoint for the HTLC key.
+ /// The maximum number of HTLCs to be accepted for this channel.
+ /// The maximum HTLC value that can be in flight.
+ /// The minimum confirmation depth required for the channel opening transaction.
+ /// The basepoint for the payment key.
+ /// The basepoint for the revocation key.
+ /// The temporary identifier for the channel negotiation.
+ /// The delay in blocks before self outputs can be claimed.
+ /// Optional parameter specifying the upfront shutdown script TLV.
+ /// The created AcceptChannel1 message.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public AcceptChannel1Message CreateAcceptChannel1Message(LightningMoney channelReserveAmount,
+ ChannelTypeTlv? channelTypeTlv,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ CompactPubKey fundingPubKey, CompactPubKey htlcBasepoint,
+ ushort maxAcceptedHtlcs,
+ LightningMoney maxHtlcValueInFlight, uint minimumDepth,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey revocationBasepoint,
+ ChannelId temporaryChannelId, ushort toSelfDelay,
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv)
+ {
+ var payload = new AcceptChannel1Payload(temporaryChannelId, channelReserveAmount, delayedPaymentBasepoint,
+ _nodeOptions.DustLimitAmount, firstPerCommitmentPoint, fundingPubKey,
+ htlcBasepoint, _nodeOptions.HtlcMinimumAmount, maxAcceptedHtlcs,
+ maxHtlcValueInFlight, minimumDepth, paymentBasepoint,
+ revocationBasepoint, toSelfDelay);
+
+ return new AcceptChannel1Message(payload, upfrontShutdownScriptTlv, channelTypeTlv);
+ }
+
+ ///
+ /// Create an AcceptChannel2 message.
///
/// The temporary channel id.
/// The amount of satoshis we're adding to the channel.
@@ -458,44 +575,89 @@ channelType is null ?
/// The payment pubkey.
/// The delayed payment pubkey.
/// The htlc pubkey.
- /// The first per commitment pubkey.
+ /// The first per-commitment pubkey.
/// The shutdown script to be used when closing the channel.
/// The type of the channel.
/// If we want confirmed inputs to open the channel.
/// The AcceptChannel2 message.
///
///
- ///
- ///
+ ///
+ ///
///
- public AcceptChannel2Message CreateAcceptChannel2Message(ChannelId temporaryChannelId, LightningMoney fundingSatoshis,
- PubKey fundingPubKey, PubKey revocationBasepoint,
- PubKey paymentBasepoint, PubKey delayedPaymentBasepoint,
- PubKey htlcBasepoint, PubKey firstPerCommitmentPoint,
- Script? shutdownScriptPubkey = null, byte[]? channelType = null,
- bool requireConfirmedInputs = false)
+ public AcceptChannel2Message CreateAcceptChannel2Message(ChannelId temporaryChannelId,
+ LightningMoney fundingSatoshis,
+ CompactPubKey fundingPubKey,
+ CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ LightningMoney maxHtlcValueInFlight,
+ BitcoinScript? shutdownScriptPubkey = null,
+ byte[]? channelType = null,
+ bool requireConfirmedInputs = false)
{
var payload = new AcceptChannel2Payload(delayedPaymentBasepoint, _nodeOptions.DustLimitAmount,
firstPerCommitmentPoint, fundingSatoshis, fundingPubKey,
htlcBasepoint, _nodeOptions.HtlcMinimumAmount,
- _nodeOptions.MaxAcceptedHtlcs, _nodeOptions.MaxHtlcValueInFlight,
+ _nodeOptions.MaxAcceptedHtlcs, maxHtlcValueInFlight,
_nodeOptions.MinimumDepth, paymentBasepoint, revocationBasepoint,
temporaryChannelId, _nodeOptions.ToSelfDelay);
return new AcceptChannel2Message(payload,
- shutdownScriptPubkey is null
- ? null
- : new UpfrontShutdownScriptTlv(shutdownScriptPubkey),
- channelType is null ?
- null
- : new ChannelTypeTlv(channelType),
- requireConfirmedInputs ? new RequireConfirmedInputsTlv() : null);
+ shutdownScriptPubkey is null
+ ? null
+ : new UpfrontShutdownScriptTlv(shutdownScriptPubkey.Value),
+ channelType is null
+ ? null
+ : new ChannelTypeTlv(channelType),
+ requireConfirmedInputs ? new RequireConfirmedInputsTlv() : null);
}
+
+ ///
+ /// Create a FundingCreated message.
+ ///
+ /// The temporary channel id.
+ /// The funding transaction id.
+ /// The funding output index.
+ /// The signature for the funding transaction.
+ /// The FundingCreated message.
+ ///
+ ///
+ ///
+ ///
+ public FundingCreatedMessage CreatedFundingCreatedMessage(ChannelId temporaryChannelId, TxId fundingTxId,
+ ushort fundingOutputIndex, CompactSignature signature)
+ {
+ var payload = new FundingCreatedPayload(temporaryChannelId, fundingTxId, fundingOutputIndex, signature);
+
+ return new FundingCreatedMessage(payload);
+ }
+
+ ///
+ /// Create a FundingSigned message.
+ ///
+ /// The channel id.
+ ///
+ /// The FundingSigned message.
+ ///
+ ///
+ ///
+ ///
+ public FundingSignedMessage CreatedFundingSignedMessage(ChannelId channelId, CompactSignature signature)
+ {
+ var payload = new FundingSignedPayload(channelId, signature);
+
+ return new FundingSignedMessage(payload);
+ }
+
#endregion
#region Commitment
+
///
- /// Create a UpdateAddHtlc message.
+ /// Create an UpdateAddHtlc message.
///
/// The channel id.
/// The htlc id.
@@ -508,8 +670,8 @@ channelType is null ?
///
///
public UpdateAddHtlcMessage CreateUpdateAddHtlcMessage(ChannelId channelId, ulong id, ulong amountMsat,
- ReadOnlyMemory paymentHash, uint cltvExpiry,
- ReadOnlyMemory? onionRoutingPacket = null)
+ ReadOnlyMemory paymentHash, uint cltvExpiry,
+ ReadOnlyMemory? onionRoutingPacket = null)
{
var payload = new UpdateAddHtlcPayload(amountMsat, channelId, cltvExpiry, id, paymentHash, onionRoutingPacket);
@@ -517,7 +679,7 @@ public UpdateAddHtlcMessage CreateUpdateAddHtlcMessage(ChannelId channelId, ulon
}
///
- /// Create a UpdateFulfillHtlc message.
+ /// Create an UpdateFulfillHtlc message.
///
/// The channel id.
/// The htlc id.
@@ -526,7 +688,8 @@ public UpdateAddHtlcMessage CreateUpdateAddHtlcMessage(ChannelId channelId, ulon
///
///
///
- public UpdateFulfillHtlcMessage CreateUpdateFulfillHtlcMessage(ChannelId channelId, ulong id, ReadOnlyMemory preimage)
+ public UpdateFulfillHtlcMessage CreateUpdateFulfillHtlcMessage(ChannelId channelId, ulong id,
+ ReadOnlyMemory preimage)
{
var payload = new UpdateFulfillHtlcPayload(channelId, id, preimage);
@@ -534,7 +697,7 @@ public UpdateFulfillHtlcMessage CreateUpdateFulfillHtlcMessage(ChannelId channel
}
///
- /// Create a UpdateFailHtlc message.
+ /// Create an UpdateFailHtlc message.
///
/// The channel id.
/// The htlc id.
@@ -559,10 +722,10 @@ public UpdateFailHtlcMessage CreateUpdateFailHtlcMessage(ChannelId channelId, ul
/// The CommitmentSigned message.
///
///
- ///
+ ///
///
- public CommitmentSignedMessage CreateCommitmentSignedMessage(ChannelId channelId, ECDSASignature signature,
- IEnumerable htlcSignatures)
+ public CommitmentSignedMessage CreateCommitmentSignedMessage(ChannelId channelId, CompactSignature signature,
+ IEnumerable htlcSignatures)
{
var payload = new CommitmentSignedPayload(channelId, htlcSignatures, signature);
@@ -578,10 +741,10 @@ public CommitmentSignedMessage CreateCommitmentSignedMessage(ChannelId channelId
/// The RevokeAndAck message.
///
///
- ///
+ ///
///
public RevokeAndAckMessage CreateRevokeAndAckMessage(ChannelId channelId, ReadOnlyMemory perCommitmentSecret,
- PubKey nextPerCommitmentPoint)
+ CompactPubKey nextPerCommitmentPoint)
{
var payload = new RevokeAndAckPayload(channelId, nextPerCommitmentPoint, perCommitmentSecret);
@@ -605,7 +768,7 @@ public UpdateFeeMessage CreateUpdateFeeMessage(ChannelId channelId, uint feerate
}
///
- /// Create a UpdateFailMalformedHtlc message.
+ /// Create an UpdateFailMalformedHtlc message.
///
/// The channel id.
/// The htlc id.
@@ -616,7 +779,8 @@ public UpdateFeeMessage CreateUpdateFeeMessage(ChannelId channelId, uint feerate
///
///
public UpdateFailMalformedHtlcMessage CreateUpdateFailMalformedHtlcMessage(ChannelId channelId, ulong id,
- ReadOnlyMemory sha256OfOnion, ushort failureCode)
+ ReadOnlyMemory sha256OfOnion,
+ ushort failureCode)
{
var payload = new UpdateFailMalformedHtlcPayload(channelId, failureCode, id, sha256OfOnion);
@@ -629,21 +793,22 @@ public UpdateFailMalformedHtlcMessage CreateUpdateFailMalformedHtlcMessage(Chann
/// The channel id.
/// The next commitment number.
/// The next revocation number.
- /// The peer last per commitment secret.
+ /// The peer's last per-commitment secret.
/// Our current per commitment point.
/// The ChannelReestablish message.
///
///
///
public ChannelReestablishMessage CreateChannelReestablishMessage(ChannelId channelId, ulong nextCommitmentNumber,
- ulong nextRevocationNumber,
- ReadOnlyMemory yourLastPerCommitmentSecret,
- PubKey myCurrentPerCommitmentPoint)
+ ulong nextRevocationNumber,
+ ReadOnlyMemory yourLastPerCommitmentSecret,
+ CompactPubKey myCurrentPerCommitmentPoint)
{
var payload = new ChannelReestablishPayload(channelId, myCurrentPerCommitmentPoint, nextCommitmentNumber,
nextRevocationNumber, yourLastPerCommitmentSecret);
return new ChannelReestablishMessage(payload);
}
+
#endregion
}
\ No newline at end of file
diff --git a/src/NLightning.Bolt11/Constants/TaggedFieldConstants.cs b/src/NLightning.Bolt11/Constants/TaggedFieldConstants.cs
index f7636805..cb156678 100644
--- a/src/NLightning.Bolt11/Constants/TaggedFieldConstants.cs
+++ b/src/NLightning.Bolt11/Constants/TaggedFieldConstants.cs
@@ -2,8 +2,8 @@ namespace NLightning.Bolt11.Constants;
public static class TaggedFieldConstants
{
- public const short HASH_LENGTH = 52;
- public const short PAYEE_PUBKEY_LENGTH = 53;
+ public const short HashLength = 52;
+ public const short PayeePubkeyLength = 53;
///
/// The length of a single routing information entry in bits
@@ -11,5 +11,5 @@ public static class TaggedFieldConstants
///
/// The routing information entry is 264 + 64 + 32 + 32 + 16 = 408 bits long
///
- public const int ROUTING_INFO_LENGTH = 408;
+ public const int RoutingInfoLength = 408;
}
\ No newline at end of file
diff --git a/src/NLightning.Bolt11/Enums/TaggedFieldTypes.cs b/src/NLightning.Bolt11/Enums/TaggedFieldTypes.cs
index 701b8732..ccbe1a4d 100644
--- a/src/NLightning.Bolt11/Enums/TaggedFieldTypes.cs
+++ b/src/NLightning.Bolt11/Enums/TaggedFieldTypes.cs
@@ -11,7 +11,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter p
///
- PAYMENT_HASH = 1,
+ PaymentHash = 1,
///
/// The Routing Information
@@ -19,7 +19,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter r
///
- ROUTING_INFO = 3,
+ RoutingInfo = 3,
///
/// The Features
@@ -27,7 +27,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter 9
///
- FEATURES = 5,
+ Features = 5,
///
/// The Expiry Time
@@ -35,7 +35,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter x
///
- EXPIRY_TIME = 6,
+ ExpiryTime = 6,
///
/// The FallBack Address
@@ -43,7 +43,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter f
///
- FALLBACK_ADDRESS = 9,
+ FallbackAddress = 9,
///
/// The Description
@@ -51,7 +51,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter d
///
- DESCRIPTION = 13,
+ Description = 13,
///
/// The Payment Secret
@@ -59,7 +59,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter s
///
- PAYMENT_SECRET = 16,
+ PaymentSecret = 16,
///
/// The Payee Public Key
@@ -67,7 +67,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter n
///
- PAYEE_PUB_KEY = 19,
+ PayeePubKey = 19,
///
/// The Description Hash
@@ -75,7 +75,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter h
///
- DESCRIPTION_HASH = 23,
+ DescriptionHash = 23,
///
/// The Min Final Cltv Expiry
@@ -83,7 +83,7 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter c
///
- MIN_FINAL_CLTV_EXPIRY = 24,
+ MinFinalCltvExpiry = 24,
///
/// The Additional Metadata
@@ -91,5 +91,5 @@ public enum TaggedFieldTypes : byte
///
/// represented by the letter m
///
- METADATA = 27
+ Metadata = 27
}
\ No newline at end of file
diff --git a/src/NLightning.Bolt11/Factories/TaggedFieldFactory.cs b/src/NLightning.Bolt11/Factories/TaggedFieldFactory.cs
index a95803aa..c3d3adf6 100644
--- a/src/NLightning.Bolt11/Factories/TaggedFieldFactory.cs
+++ b/src/NLightning.Bolt11/Factories/TaggedFieldFactory.cs
@@ -1,7 +1,7 @@
namespace NLightning.Bolt11.Factories;
-using Common.Utils;
-using Domain.ValueObjects;
+using Domain.Protocol.ValueObjects;
+using Domain.Utils;
using Enums;
using Interfaces;
using Models.TaggedFields;
@@ -17,24 +17,26 @@ internal static class TaggedFieldFactory
/// The type of tagged field to create.
/// The bit reader to read the tagged field from.
/// The length of the tagged field.
+ /// The network context for the tagged field, used for address parsing.
/// The tagged field.
/// Thrown when the tagged field type is unknown.
internal static ITaggedField CreateTaggedFieldFromBitReader(TaggedFieldTypes type, BitReader bitReader,
- short length, Network network)
+ short length, BitcoinNetwork bitcoinNetwork)
{
return type switch
{
- TaggedFieldTypes.PAYMENT_HASH => PaymentHashTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.ROUTING_INFO => RoutingInfoTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.FEATURES => FeaturesTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.EXPIRY_TIME => ExpiryTimeTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.FALLBACK_ADDRESS => FallbackAddressTaggedField.FromBitReader(bitReader, length, network),
- TaggedFieldTypes.DESCRIPTION => DescriptionTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.PAYMENT_SECRET => PaymentSecretTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.PAYEE_PUB_KEY => PayeePubKeyTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.DESCRIPTION_HASH => DescriptionHashTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.MIN_FINAL_CLTV_EXPIRY => MinFinalCltvExpiryTaggedField.FromBitReader(bitReader, length),
- TaggedFieldTypes.METADATA => MetadataTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.PaymentHash => PaymentHashTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.RoutingInfo => RoutingInfoTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.Features => FeaturesTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.ExpiryTime => ExpiryTimeTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.FallbackAddress => FallbackAddressTaggedField.FromBitReader(
+ bitReader, length, bitcoinNetwork),
+ TaggedFieldTypes.Description => DescriptionTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.PaymentSecret => PaymentSecretTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.PayeePubKey => PayeePubKeyTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.DescriptionHash => DescriptionHashTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.MinFinalCltvExpiry => MinFinalCltvExpiryTaggedField.FromBitReader(bitReader, length),
+ TaggedFieldTypes.Metadata => MetadataTaggedField.FromBitReader(bitReader, length),
// Add more cases as needed for other types
_ => throw new ArgumentException($"Unknown tagged field type: {type}", nameof(type))
};
diff --git a/src/NLightning.Bolt11/Interfaces/ITaggedField.cs b/src/NLightning.Bolt11/Interfaces/ITaggedField.cs
index 6da16e69..a22890d7 100644
--- a/src/NLightning.Bolt11/Interfaces/ITaggedField.cs
+++ b/src/NLightning.Bolt11/Interfaces/ITaggedField.cs
@@ -1,6 +1,6 @@
namespace NLightning.Bolt11.Interfaces;
-using Common.Utils;
+using Domain.Utils;
using Enums;
///
diff --git a/src/NLightning.Bolt11/Models/Invoice.cs b/src/NLightning.Bolt11/Models/Invoice.cs
index 39481c61..f46bac7c 100644
--- a/src/NLightning.Bolt11/Models/Invoice.cs
+++ b/src/NLightning.Bolt11/Models/Invoice.cs
@@ -4,7 +4,6 @@
namespace NLightning.Bolt11.Models;
-using Common.Utils;
using Domain.Constants;
using Domain.Crypto.Constants;
using Domain.Enums;
@@ -12,12 +11,13 @@ namespace NLightning.Bolt11.Models;
using Domain.Money;
using Domain.Node;
using Domain.Protocol.Constants;
-using Domain.Protocol.Managers;
-using Domain.ValueObjects;
+using Domain.Protocol.Interfaces;
+using Domain.Protocol.ValueObjects;
+using Domain.Utils;
using Enums;
using Exceptions;
+using Infrastructure.Bitcoin.Encoders;
using Infrastructure.Crypto.Hashes;
-using Infrastructure.Encoders;
using TaggedFields;
///
@@ -29,16 +29,16 @@ namespace NLightning.Bolt11.Models;
public partial class Invoice
{
#region Private Fields
- private static readonly Dictionary s_supportedNetworks = new()
+ private static readonly Dictionary s_supportedNetworks = new()
{
- { InvoiceConstants.PREFIX_MAINET, Network.MAINNET },
- { InvoiceConstants.PREFIX_TESTNET, Network.TESTNET },
- { InvoiceConstants.PREFIX_SIGNET, Network.SIGNET },
- { InvoiceConstants.PREFIX_REGTEST, Network.REGTEST },
- { InvoiceConstants.PREFIX_MAINET.ToUpperInvariant(), Network.MAINNET },
- { InvoiceConstants.PREFIX_TESTNET.ToUpperInvariant(), Network.TESTNET },
- { InvoiceConstants.PREFIX_SIGNET.ToUpperInvariant(), Network.SIGNET },
- { InvoiceConstants.PREFIX_REGTEST.ToUpperInvariant(), Network.REGTEST }
+ { InvoiceConstants.PrefixMainet, BitcoinNetwork.Mainnet },
+ { InvoiceConstants.PrefixTestnet, BitcoinNetwork.Testnet },
+ { InvoiceConstants.PrefixSignet, BitcoinNetwork.Signet },
+ { InvoiceConstants.PrefixRegtest, BitcoinNetwork.Regtest },
+ { InvoiceConstants.PrefixMainet.ToUpperInvariant(), BitcoinNetwork.Mainnet },
+ { InvoiceConstants.PrefixTestnet.ToUpperInvariant(), BitcoinNetwork.Testnet },
+ { InvoiceConstants.PrefixSignet.ToUpperInvariant(), BitcoinNetwork.Signet },
+ { InvoiceConstants.PrefixRegtest.ToUpperInvariant(), BitcoinNetwork.Regtest }
};
[GeneratedRegex(@"^[a-z]+((\d+)([munp])?)?$")]
@@ -46,7 +46,7 @@ public partial class Invoice
private readonly ISecureKeyManager? _secureKeyManager;
- private TaggedFieldList _taggedFields { get; } = [];
+ private TaggedFieldList TaggedFields { get; } = [];
private string? _invoiceString;
#endregion
@@ -55,7 +55,7 @@ public partial class Invoice
///
/// The network the invoice is created for
///
- public Network Network { get; }
+ public BitcoinNetwork BitcoinNetwork { get; }
///
/// The amount for the invoice
@@ -93,13 +93,13 @@ public uint256 PaymentHash
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.PAYMENT_HASH, out PaymentHashTaggedField? paymentHash)
+ return TaggedFields.TryGet(TaggedFieldTypes.PaymentHash, out PaymentHashTaggedField? paymentHash)
? paymentHash!.Value
: new uint256();
}
internal set
{
- _taggedFields.Add(new PaymentHashTaggedField(value));
+ TaggedFields.Add(new PaymentHashTaggedField(value));
}
}
@@ -115,7 +115,7 @@ public RoutingInfoCollection? RoutingInfos
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.ROUTING_INFO, out RoutingInfoTaggedField? routingInfo)
+ return TaggedFields.TryGet(TaggedFieldTypes.RoutingInfo, out RoutingInfoTaggedField? routingInfo)
? routingInfo!.Value
: null;
}
@@ -123,7 +123,7 @@ public RoutingInfoCollection? RoutingInfos
{
if (value != null)
{
- _taggedFields.Add(new RoutingInfoTaggedField(value));
+ TaggedFields.Add(new RoutingInfoTaggedField(value));
value.Changed += OnTaggedFieldsChanged;
}
}
@@ -140,7 +140,7 @@ public FeatureSet? Features
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.FEATURES, out FeaturesTaggedField? features)
+ return TaggedFields.TryGet(TaggedFieldTypes.Features, out FeaturesTaggedField? features)
? features!.Value
: null;
}
@@ -148,7 +148,7 @@ public FeatureSet? Features
{
if (value != null)
{
- _taggedFields.Add(new FeaturesTaggedField(value));
+ TaggedFields.Add(new FeaturesTaggedField(value));
value.Changed += OnTaggedFieldsChanged;
}
}
@@ -165,14 +165,14 @@ public DateTimeOffset ExpiryDate
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.EXPIRY_TIME, out ExpiryTimeTaggedField? expireIn)
+ return TaggedFields.TryGet(TaggedFieldTypes.ExpiryTime, out ExpiryTimeTaggedField? expireIn)
? DateTimeOffset.FromUnixTimeSeconds(Timestamp + expireIn!.Value)
- : DateTimeOffset.FromUnixTimeSeconds(Timestamp + InvoiceConstants.DEFAULT_EXPIRATION_SECONDS);
+ : DateTimeOffset.FromUnixTimeSeconds(Timestamp + InvoiceConstants.DefaultExpirationSeconds);
}
set
{
var expireIn = value.ToUnixTimeSeconds() - Timestamp;
- _taggedFields.Add(new ExpiryTimeTaggedField((int)expireIn));
+ TaggedFields.Add(new ExpiryTimeTaggedField((int)expireIn));
}
}
@@ -187,8 +187,8 @@ public List? FallbackAddresses
{
get
{
- return _taggedFields
- .TryGetAll(TaggedFieldTypes.FALLBACK_ADDRESS, out List fallbackAddress)
+ return TaggedFields
+ .TryGetAll(TaggedFieldTypes.FallbackAddress, out List fallbackAddress)
? fallbackAddress.Select(x => x.Value).ToList()
: null;
}
@@ -196,7 +196,7 @@ public List? FallbackAddresses
{
if (value != null)
{
- _taggedFields.AddRange(value.Select(x => new FallbackAddressTaggedField(x)));
+ TaggedFields.AddRange(value.Select(x => new FallbackAddressTaggedField(x)));
}
}
}
@@ -211,7 +211,7 @@ public string? Description
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.DESCRIPTION, out DescriptionTaggedField? description)
+ return TaggedFields.TryGet(TaggedFieldTypes.Description, out DescriptionTaggedField? description)
? description!.Value
: null;
}
@@ -219,7 +219,7 @@ internal set
{
if (value != null)
{
- _taggedFields.Add(new DescriptionTaggedField(value));
+ TaggedFields.Add(new DescriptionTaggedField(value));
}
}
}
@@ -235,13 +235,13 @@ public uint256 PaymentSecret
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.PAYMENT_SECRET, out PaymentSecretTaggedField? paymentSecret)
+ return TaggedFields.TryGet(TaggedFieldTypes.PaymentSecret, out PaymentSecretTaggedField? paymentSecret)
? paymentSecret!.Value
: new uint256();
}
internal set
{
- _taggedFields.Add(new PaymentSecretTaggedField(value));
+ TaggedFields.Add(new PaymentSecretTaggedField(value));
}
}
@@ -256,7 +256,7 @@ public PubKey? PayeePubKey
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.PAYEE_PUB_KEY, out PayeePubKeyTaggedField? payeePubKey)
+ return TaggedFields.TryGet(TaggedFieldTypes.PayeePubKey, out PayeePubKeyTaggedField? payeePubKey)
? payeePubKey!.Value
: null;
}
@@ -264,7 +264,7 @@ public PubKey? PayeePubKey
{
if (value != null)
{
- _taggedFields.Add(new PayeePubKeyTaggedField(value));
+ TaggedFields.Add(new PayeePubKeyTaggedField(value));
}
}
}
@@ -280,8 +280,8 @@ public uint256? DescriptionHash
{
get
{
- return _taggedFields
- .TryGet(TaggedFieldTypes.DESCRIPTION_HASH, out DescriptionHashTaggedField? descriptionHash)
+ return TaggedFields
+ .TryGet(TaggedFieldTypes.DescriptionHash, out DescriptionHashTaggedField? descriptionHash)
? descriptionHash!.Value
: null;
}
@@ -289,7 +289,7 @@ internal set
{
if (value != null)
{
- _taggedFields.Add(new DescriptionHashTaggedField(value));
+ TaggedFields.Add(new DescriptionHashTaggedField(value));
}
}
}
@@ -304,8 +304,8 @@ public ushort? MinFinalCltvExpiry
{
get
{
- return _taggedFields
- .TryGet(TaggedFieldTypes.MIN_FINAL_CLTV_EXPIRY, out MinFinalCltvExpiryTaggedField? minFinalCltvExpiry)
+ return TaggedFields
+ .TryGet(TaggedFieldTypes.MinFinalCltvExpiry, out MinFinalCltvExpiryTaggedField? minFinalCltvExpiry)
? minFinalCltvExpiry!.Value
: null;
}
@@ -313,7 +313,7 @@ public ushort? MinFinalCltvExpiry
{
if (value.HasValue)
{
- _taggedFields.Add(new MinFinalCltvExpiryTaggedField(value.Value));
+ TaggedFields.Add(new MinFinalCltvExpiryTaggedField(value.Value));
}
}
}
@@ -328,7 +328,7 @@ public byte[]? Metadata
{
get
{
- return _taggedFields.TryGet(TaggedFieldTypes.METADATA, out MetadataTaggedField? metadata)
+ return TaggedFields.TryGet(TaggedFieldTypes.Metadata, out MetadataTaggedField? metadata)
? metadata!.Value
: null;
}
@@ -336,7 +336,7 @@ public byte[]? Metadata
{
if (value != null)
{
- _taggedFields.Add(new MetadataTaggedField(value));
+ TaggedFields.Add(new MetadataTaggedField(value));
}
}
}
@@ -351,20 +351,20 @@ public byte[]? Metadata
/// The description of the invoice
/// The payment hash of the invoice
/// The payment secret of the invoice
- /// The network the invoice is created for
+ /// The network the invoice is created for
/// Secure key manager
///
/// The invoice is created with the given amount of millisatoshis, a description, the payment hash and the
/// payment secret.
///
- ///
+ ///
public Invoice(LightningMoney amount, string description, uint256 paymentHash, uint256 paymentSecret,
- Network network, ISecureKeyManager? secureKeyManager = null)
+ BitcoinNetwork bitcoinNetwork, ISecureKeyManager? secureKeyManager = null)
{
_secureKeyManager = secureKeyManager;
Amount = amount;
- Network = network;
+ BitcoinNetwork = bitcoinNetwork;
HumanReadablePart = BuildHumanReadablePart();
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
Signature = new CompactSignature(0, new byte[64]);
@@ -374,7 +374,7 @@ public Invoice(LightningMoney amount, string description, uint256 paymentHash, u
PaymentHash = paymentHash;
PaymentSecret = paymentSecret;
- _taggedFields.Changed += OnTaggedFieldsChanged;
+ TaggedFields.Changed += OnTaggedFieldsChanged;
}
///
@@ -384,20 +384,20 @@ public Invoice(LightningMoney amount, string description, uint256 paymentHash, u
/// The description hash of the invoice
/// The payment hash of the invoice
/// The payment secret of the invoice
- /// The network the invoice is created for
+ /// The network the invoice is created for
/// Secure key manager
///
/// The invoice is created with the given amount of millisatoshis, a description hash, the payment hash and the
/// payment secret.
///
- ///
+ ///
public Invoice(LightningMoney amount, uint256 descriptionHash, uint256 paymentHash, uint256 paymentSecret,
- Network network, ISecureKeyManager? secureKeyManager = null)
+ BitcoinNetwork bitcoinNetwork, ISecureKeyManager? secureKeyManager = null)
{
_secureKeyManager = secureKeyManager;
Amount = amount;
- Network = network;
+ BitcoinNetwork = bitcoinNetwork;
HumanReadablePart = BuildHumanReadablePart();
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
Signature = new CompactSignature(0, new byte[64]);
@@ -407,28 +407,28 @@ public Invoice(LightningMoney amount, uint256 descriptionHash, uint256 paymentHa
PaymentHash = paymentHash;
PaymentSecret = paymentSecret;
- _taggedFields.Changed += OnTaggedFieldsChanged;
+ TaggedFields.Changed += OnTaggedFieldsChanged;
}
///
/// This constructor is used by tests
///
- /// The network the invoice is created for
+ /// The network the invoice is created for
/// The amount of the invoice
/// The timestamp of the invoice
///
/// The invoice is created with the given network, amount of millisatoshis and timestamp.
///
- ///
- internal Invoice(Network network, LightningMoney? amount = null, long? timestamp = null)
+ ///
+ internal Invoice(BitcoinNetwork bitcoinNetwork, LightningMoney? amount = null, long? timestamp = null)
{
Amount = amount ?? LightningMoney.Zero;
- Network = network;
+ BitcoinNetwork = bitcoinNetwork;
HumanReadablePart = BuildHumanReadablePart();
Timestamp = timestamp ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();
Signature = new CompactSignature(0, new byte[64]);
- _taggedFields.Changed += OnTaggedFieldsChanged;
+ TaggedFields.Changed += OnTaggedFieldsChanged;
}
///
@@ -436,7 +436,7 @@ internal Invoice(Network network, LightningMoney? amount = null, long? timestamp
///
/// The invoice string
/// The human-readable part of the invoice
- /// The network the invoice is created for
+ /// The network the invoice is created for
/// The amount of the invoice
/// The timestamp of the invoice
/// The tagged fields of the invoice
@@ -445,20 +445,20 @@ internal Invoice(Network network, LightningMoney? amount = null, long? timestamp
/// The invoice is created with the given human-readable part, network, amount of millisatoshis,
/// timestamp and tagged fields.
///
- ///
- private Invoice(string invoiceString, string humanReadablePart, Network network, LightningMoney amount,
+ ///
+ private Invoice(string invoiceString, string humanReadablePart, BitcoinNetwork bitcoinNetwork, LightningMoney amount,
long timestamp, TaggedFieldList taggedFields, CompactSignature signature)
{
_invoiceString = invoiceString;
- Network = network;
+ BitcoinNetwork = bitcoinNetwork;
HumanReadablePart = humanReadablePart;
Amount = amount;
Timestamp = timestamp;
- _taggedFields = taggedFields;
+ TaggedFields = taggedFields;
Signature = signature;
- _taggedFields.Changed += OnTaggedFieldsChanged;
+ TaggedFields.Changed += OnTaggedFieldsChanged;
}
#endregion
@@ -471,16 +471,16 @@ private Invoice(string invoiceString, string humanReadablePart, Network network,
/// The description of the invoice
/// The payment hash of the invoice
/// The payment secret of the invoice
- /// The network the invoice is created for
+ /// The network the invoice is created for
///
/// The invoice is created with the given amount of satoshis, a description, the payment hash and the
/// payment secret.
///
/// The invoice
public static Invoice InSatoshis(ulong amountSats, string description, uint256 paymentHash, uint256 paymentSecret,
- Network network)
+ BitcoinNetwork bitcoinNetwork)
{
- return new Invoice(LightningMoney.Satoshis(amountSats), description, paymentHash, paymentSecret, network);
+ return new Invoice(LightningMoney.Satoshis(amountSats), description, paymentHash, paymentSecret, bitcoinNetwork);
}
///
@@ -490,16 +490,16 @@ public static Invoice InSatoshis(ulong amountSats, string description, uint256 p
/// The description hash of the invoice
/// The payment hash of the invoice
/// The payment secret of the invoice
- /// The network the invoice is created for
+ /// The network the invoice is created for
///
/// The invoice is created with the given amount of satoshis, a description hash, the payment hash and the
/// payment secret.
///
/// The invoice
public static Invoice InSatoshis(ulong amountSats, uint256 descriptionHash, uint256 paymentHash,
- uint256 paymentSecret, Network network)
+ uint256 paymentSecret, BitcoinNetwork bitcoinNetwork)
{
- return new Invoice(LightningMoney.Satoshis(amountSats), descriptionHash, paymentHash, paymentSecret, network);
+ return new Invoice(LightningMoney.Satoshis(amountSats), descriptionHash, paymentHash, paymentSecret, bitcoinNetwork);
}
///
@@ -509,7 +509,7 @@ public static Invoice InSatoshis(ulong amountSats, uint256 descriptionHash, uint
/// The expected network of the invoice
/// The invoice
/// If something goes wrong in the decoding process
- public static Invoice Decode(string? invoiceString, Network? expectedNetwork = null)
+ public static Invoice Decode(string? invoiceString, BitcoinNetwork? expectedNetwork = null)
{
InvoiceSerializationException.ThrowIfNullOrWhiteSpace(invoiceString);
@@ -538,7 +538,7 @@ public static Invoice Decode(string? invoiceString, Network? expectedNetwork = n
new CompactSignature(signature[^1], signature[..^1]));
// Get pubkey from tagged fields
- if (taggedFields.TryGet(TaggedFieldTypes.PAYEE_PUB_KEY, out PayeePubKeyTaggedField? pubkeyTaggedField))
+ if (taggedFields.TryGet(TaggedFieldTypes.PayeePubKey, out PayeePubKeyTaggedField? pubkeyTaggedField))
{
invoice.PayeePubKey = pubkeyTaggedField?.Value;
}
@@ -567,7 +567,7 @@ public string Encode(Key nodeKey)
try
{
// Calculate the size needed for the buffer
- var sizeInBits = 35 + (_taggedFields.CalculateSizeInBits() * 5) + (_taggedFields.Count * 15);
+ var sizeInBits = 35 + (TaggedFields.CalculateSizeInBits() * 5) + (TaggedFields.Count * 15);
// Initialize the BitWriter buffer
var bitWriter = new BitWriter(sizeInBits);
@@ -576,7 +576,7 @@ public string Encode(Key nodeKey)
bitWriter.WriteInt64AsBits(Timestamp, 35);
// Write the tagged fields
- _taggedFields.WriteToBitWriter(bitWriter);
+ TaggedFields.WriteToBitWriter(bitWriter);
// Sign the invoice
var compactSignature = SignInvoice(HumanReadablePart, bitWriter, nodeKey);
@@ -605,7 +605,8 @@ public string Encode()
if (_secureKeyManager is null)
throw new NullReferenceException("Secure key manager is not set, please use Encode(Key nodeKey) instead");
- return Encode(_secureKeyManager.GetNodeKey());
+ var nodeKey = _secureKeyManager.GetNodeKeyPair().PrivKey;
+ return Encode(new Key(nodeKey));
}
#region Overrides
@@ -635,8 +636,8 @@ public string ToString(Key nodeKey)
#region Private Methods
private string BuildHumanReadablePart()
{
- StringBuilder sb = new(InvoiceConstants.PREFIX);
- sb.Append(GetPrefix(Network));
+ StringBuilder sb = new(InvoiceConstants.Prefix);
+ sb.Append(GetPrefix(BitcoinNetwork));
if (!Amount.IsZero)
{
ConvertAmountToHumanReadable(Amount, sb);
@@ -644,15 +645,15 @@ private string BuildHumanReadablePart()
return sb.ToString();
}
- private static string GetPrefix(Network network)
+ private static string GetPrefix(BitcoinNetwork bitcoinNetwork)
{
- return network.Name switch
+ return bitcoinNetwork.Name switch
{
- NetworkConstants.MAINNET => InvoiceConstants.PREFIX_MAINET,
- NetworkConstants.TESTNET => InvoiceConstants.PREFIX_TESTNET,
- NetworkConstants.REGTEST => InvoiceConstants.PREFIX_REGTEST,
- NetworkConstants.SIGNET => InvoiceConstants.PREFIX_SIGNET,
- _ => throw new ArgumentException("Unsupported network type", nameof(network)),
+ NetworkConstants.Mainnet => InvoiceConstants.PrefixMainet,
+ NetworkConstants.Testnet => InvoiceConstants.PrefixTestnet,
+ NetworkConstants.Regtest => InvoiceConstants.PrefixRegtest,
+ NetworkConstants.Signet => InvoiceConstants.PrefixSignet,
+ _ => throw new ArgumentException("Unsupported network type", nameof(bitcoinNetwork)),
};
}
@@ -662,7 +663,7 @@ private static void ConvertAmountToHumanReadable(LightningMoney amount, StringBu
// Start with the smallest multiplier
var tempAmount = btcAmount * 1_000_000_000_000m; // Start with pico
- char? suffix = InvoiceConstants.MULTIPLIER_PICO;
+ char? suffix = InvoiceConstants.MultiplierPico;
// Try nano
if (amount.MilliSatoshi % 10 == 0)
@@ -671,7 +672,7 @@ private static void ConvertAmountToHumanReadable(LightningMoney amount, StringBu
if (nanoAmount == decimal.Truncate(nanoAmount))
{
tempAmount = nanoAmount;
- suffix = InvoiceConstants.MULTIPLIER_NANO;
+ suffix = InvoiceConstants.MultiplierNano;
}
}
@@ -682,7 +683,7 @@ private static void ConvertAmountToHumanReadable(LightningMoney amount, StringBu
if (microAmount == decimal.Truncate(microAmount))
{
tempAmount = microAmount;
- suffix = InvoiceConstants.MULTIPLIER_MICRO;
+ suffix = InvoiceConstants.MultiplierMicro;
}
}
@@ -693,7 +694,7 @@ private static void ConvertAmountToHumanReadable(LightningMoney amount, StringBu
if (milliAmount == decimal.Truncate(milliAmount))
{
tempAmount = milliAmount;
- suffix = InvoiceConstants.MULTIPLIER_MILLI;
+ suffix = InvoiceConstants.MultiplierMilli;
}
}
@@ -753,7 +754,7 @@ private void CheckSignature(byte[] data)
data.CopyTo(message, HumanReadablePart.Length);
// Get sha256 hash of the message
- var hash = new byte[CryptoConstants.SHA256_HASH_LEN];
+ var hash = new byte[CryptoConstants.Sha256HashLen];
using var sha256 = new Sha256();
sha256.AppendData(message);
sha256.GetHashAndReset(hash);
@@ -785,7 +786,7 @@ private static CompactSignature SignInvoice(string hrp, BitWriter bitWriter, Key
data.CopyTo(message, hrp.Length);
// Get sha256 hash of the message
- var hash = new byte[CryptoConstants.SHA256_HASH_LEN];
+ var hash = new byte[CryptoConstants.Sha256HashLen];
using var sha256 = new Sha256();
sha256.AppendData(message);
sha256.GetHashAndReset(hash);
@@ -795,7 +796,7 @@ private static CompactSignature SignInvoice(string hrp, BitWriter bitWriter, Key
return key.SignCompact(nBitcoinHash, false);
}
- private static Network GetNetwork(string? invoiceString)
+ private static BitcoinNetwork GetNetwork(string? invoiceString)
{
ArgumentException.ThrowIfNullOrWhiteSpace(invoiceString);
diff --git a/src/NLightning.Bolt11/Models/TaggedFieldList.cs b/src/NLightning.Bolt11/Models/TaggedFieldList.cs
index c419abc5..03a92b73 100644
--- a/src/NLightning.Bolt11/Models/TaggedFieldList.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFieldList.cs
@@ -2,8 +2,8 @@
namespace NLightning.Bolt11.Models;
-using Common.Utils;
-using Domain.ValueObjects;
+using Domain.Protocol.ValueObjects;
+using Domain.Utils;
using Enums;
using Factories;
using Interfaces;
@@ -24,23 +24,27 @@ internal class TaggedFieldList : List
internal new void Add(ITaggedField taggedField)
{
// Check for uniqueness
- if (this.Any(x => x.Type.Equals(taggedField.Type)) && taggedField.Type != TaggedFieldTypes.FALLBACK_ADDRESS)
+ if (this.Any(x => x.Type.Equals(taggedField.Type)) && taggedField.Type != TaggedFieldTypes.FallbackAddress)
{
- throw new ArgumentException($"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
+ throw new ArgumentException(
+ $"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
}
switch (taggedField.Type)
{
- case TaggedFieldTypes.DESCRIPTION when this.Any(x => x.Type.Equals(TaggedFieldTypes.DESCRIPTION_HASH)):
- throw new ArgumentException($"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
- case TaggedFieldTypes.DESCRIPTION_HASH when this.Any(x => x.Type.Equals(TaggedFieldTypes.DESCRIPTION)):
- throw new ArgumentException($"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
+ case TaggedFieldTypes.Description when this.Any(x => x.Type.Equals(TaggedFieldTypes.DescriptionHash)):
+ throw new ArgumentException(
+ $"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
+ case TaggedFieldTypes.DescriptionHash when this.Any(x => x.Type.Equals(TaggedFieldTypes.Description)):
+ throw new ArgumentException(
+ $"TaggedFieldDictionary already contains a tagged field of type {taggedField.Type}");
default:
base.Add(taggedField);
if (_shouldInvokeChangedEvent)
{
OnChanged();
}
+
break;
}
}
@@ -140,9 +144,9 @@ internal bool TryGetAll(TaggedFieldTypes taggedFieldType, out List taggedF
/// Get a new TaggedFieldList from a BitReader
///
/// The BitReader to read from
- /// The network type
+ /// The network type
/// A new TaggedFieldList
- internal static TaggedFieldList FromBitReader(BitReader bitReader, Network network)
+ internal static TaggedFieldList FromBitReader(BitReader bitReader, BitcoinNetwork bitcoinNetwork)
{
var taggedFields = new TaggedFieldList();
while (bitReader.HasMoreBits(15))
@@ -162,7 +166,8 @@ internal static TaggedFieldList FromBitReader(BitReader bitReader, Network netwo
{
try
{
- var taggedField = TaggedFieldFactory.CreateTaggedFieldFromBitReader(type, bitReader, length, network);
+ var taggedField =
+ TaggedFieldFactory.CreateTaggedFieldFromBitReader(type, bitReader, length, bitcoinNetwork);
if (taggedField.IsValid())
{
try
@@ -235,8 +240,8 @@ internal int CalculateSizeInBits()
{
var taggedFields = this.Where(x => x.Type.Equals(taggedFieldType)).ToList();
return taggedFields.Count == 0
- ? null
- : taggedFields.Cast().ToList();
+ ? null
+ : taggedFields.Cast().ToList();
}
private void OnChanged()
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/DescriptionHashTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/DescriptionHashTaggedField.cs
index a6b58b13..29a12f62 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/DescriptionHashTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/DescriptionHashTaggedField.cs
@@ -2,8 +2,8 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Constants;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -16,9 +16,9 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class DescriptionHashTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.DESCRIPTION_HASH;
+ public TaggedFieldTypes Type => TaggedFieldTypes.DescriptionHash;
internal uint256 Value { get; }
- public short Length => TaggedFieldConstants.HASH_LENGTH;
+ public short Length => TaggedFieldConstants.HashLength;
///
/// Initializes a new instance of the class.
@@ -57,13 +57,14 @@ public bool IsValid()
/// Thrown when the length is invalid
internal static DescriptionHashTaggedField FromBitReader(BitReader bitReader, short length)
{
- if (length != TaggedFieldConstants.HASH_LENGTH)
+ if (length != TaggedFieldConstants.HashLength)
{
- throw new ArgumentException($"Invalid length for DescriptionHashTaggedField. Expected {TaggedFieldConstants.HASH_LENGTH}, but got {length}");
+ throw new ArgumentException(
+ $"Invalid length for DescriptionHashTaggedField. Expected {TaggedFieldConstants.HashLength}, but got {length}");
}
// Read the data from the BitReader
- var data = new byte[(TaggedFieldConstants.HASH_LENGTH * 5 + 7) / 8];
+ var data = new byte[(TaggedFieldConstants.HashLength * 5 + 7) / 8];
bitReader.ReadBits(data, length * 5);
data = data[..^1];
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/DescriptionTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/DescriptionTaggedField.cs
index 89201465..0a9704d8 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/DescriptionTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/DescriptionTaggedField.cs
@@ -2,7 +2,7 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -17,7 +17,7 @@ internal sealed class DescriptionTaggedField : ITaggedField
{
private readonly byte[] _data;
- public TaggedFieldTypes Type => TaggedFieldTypes.DESCRIPTION;
+ public TaggedFieldTypes Type => TaggedFieldTypes.Description;
internal string Value { get; }
public short Length { get; }
@@ -84,7 +84,8 @@ internal static DescriptionTaggedField FromBitReader(BitReader bitReader, short
switch (length)
{
case < 0:
- throw new ArgumentException("Invalid length for DescriptionTaggedField. Length must be greater or equal to 0", nameof(length));
+ throw new ArgumentException(
+ "Invalid length for DescriptionTaggedField. Length must be greater or equal to 0", nameof(length));
case 0:
return new DescriptionTaggedField(string.Empty);
}
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/ExpiryTimeTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/ExpiryTimeTaggedField.cs
index f430594d..bcf9733d 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/ExpiryTimeTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/ExpiryTimeTaggedField.cs
@@ -2,7 +2,7 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -15,7 +15,7 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
public sealed class ExpiryTimeTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.EXPIRY_TIME;
+ public TaggedFieldTypes Type => TaggedFieldTypes.ExpiryTime;
internal int Value { get; }
public short Length { get; }
@@ -55,7 +55,8 @@ internal static ExpiryTimeTaggedField FromBitReader(BitReader bitReader, short l
{
if (length <= 0)
{
- throw new ArgumentException("Invalid length for ExpiryTimeTaggedField. Length must be greater than 0", nameof(length));
+ throw new ArgumentException("Invalid length for ExpiryTimeTaggedField. Length must be greater than 0",
+ nameof(length));
}
// Read the data from the BitReader
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
index 7c187b08..3ed6d459 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
@@ -2,7 +2,8 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
+using Domain.Protocol.ValueObjects;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -17,7 +18,7 @@ internal sealed class FallbackAddressTaggedField : ITaggedField
{
private readonly byte[] _data;
- public TaggedFieldTypes Type => TaggedFieldTypes.FALLBACK_ADDRESS;
+ public TaggedFieldTypes Type => TaggedFieldTypes.FallbackAddress;
internal BitcoinAddress Value { get; }
public short Length { get; }
@@ -82,10 +83,11 @@ public bool IsValid()
///
/// The BitReader to read from
/// The length of the field
- /// The network type
+ /// The network type
/// The FallbackAddressTaggedField
/// Thrown when the address is unknown or invalid
- internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, short length, Network network)
+ internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, short length,
+ BitcoinNetwork bitcoinNetwork)
{
// Get Address Type
var addressType = bitReader.ReadByteFromBits(5);
@@ -100,6 +102,8 @@ internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, sh
data = data[..^1];
}
+ var network = Network.GetNetwork(bitcoinNetwork) ??
+ throw new ArgumentException("Network is unknown or invalid.", nameof(bitcoinNetwork));
BitcoinAddress address = addressType switch
{
// Witness P2WPKH
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/FeaturesTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/FeaturesTaggedField.cs
index 6cae2a78..5093eb95 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/FeaturesTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/FeaturesTaggedField.cs
@@ -1,7 +1,7 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Domain.Node;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -14,7 +14,7 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class FeaturesTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.FEATURES;
+ public TaggedFieldTypes Type => TaggedFieldTypes.Features;
internal FeatureSet Value { get; }
public short Length { get; }
@@ -54,7 +54,8 @@ internal static FeaturesTaggedField FromBitReader(BitReader bitReader, short len
{
if (length <= 0)
{
- throw new ArgumentException("Invalid length for FeaturesTaggedField. Length must be greater than 0", nameof(length));
+ throw new ArgumentException("Invalid length for FeaturesTaggedField. Length must be greater than 0",
+ nameof(length));
}
var shouldPad = length * 5 / 8 == (length * 5 - 7) / 8;
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/MetadataTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/MetadataTaggedField.cs
index 52669f71..e2395cc7 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/MetadataTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/MetadataTaggedField.cs
@@ -1,6 +1,6 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -13,7 +13,7 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class MetadataTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.METADATA;
+ public TaggedFieldTypes Type => TaggedFieldTypes.Metadata;
internal byte[] Value { get; }
public short Length { get; }
@@ -51,7 +51,8 @@ internal static MetadataTaggedField FromBitReader(BitReader bitReader, short len
{
if (length <= 0)
{
- throw new ArgumentException("Invalid length for MetadataTaggedField. Length must be greater than 0", nameof(length));
+ throw new ArgumentException("Invalid length for MetadataTaggedField. Length must be greater than 0",
+ nameof(length));
}
// Read the data from the BitReader
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/MinFinalCltvExpiryTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/MinFinalCltvExpiryTaggedField.cs
index 2a7778bc..fc5f1b36 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/MinFinalCltvExpiryTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/MinFinalCltvExpiryTaggedField.cs
@@ -2,7 +2,7 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -15,7 +15,7 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class MinFinalCltvExpiryTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.MIN_FINAL_CLTV_EXPIRY;
+ public TaggedFieldTypes Type => TaggedFieldTypes.MinFinalCltvExpiry;
internal ushort Value { get; }
public short Length { get; }
@@ -55,7 +55,8 @@ internal static MinFinalCltvExpiryTaggedField FromBitReader(BitReader bitReader,
{
if (length <= 0)
{
- throw new ArgumentException("Invalid length for MinFinalCltvExpiryTaggedField. Length must be greater than 0", nameof(length));
+ throw new ArgumentException(
+ "Invalid length for MinFinalCltvExpiryTaggedField. Length must be greater than 0", nameof(length));
}
// Read the data from the BitReader
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/PayeePubKeyTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/PayeePubKeyTaggedField.cs
index bbd03b97..2eddb09f 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/PayeePubKeyTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/PayeePubKeyTaggedField.cs
@@ -2,8 +2,8 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Constants;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -16,9 +16,9 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class PayeePubKeyTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.PAYEE_PUB_KEY;
+ public TaggedFieldTypes Type => TaggedFieldTypes.PayeePubKey;
internal PubKey Value { get; }
- public short Length => TaggedFieldConstants.PAYEE_PUBKEY_LENGTH;
+ public short Length => TaggedFieldConstants.PayeePubkeyLength;
///
/// Initializes a new instance of the class.
@@ -51,9 +51,10 @@ public bool IsValid()
/// Thrown when the length is invalid
internal static PayeePubKeyTaggedField FromBitReader(BitReader bitReader, short length)
{
- if (length != TaggedFieldConstants.PAYEE_PUBKEY_LENGTH)
+ if (length != TaggedFieldConstants.PayeePubkeyLength)
{
- throw new ArgumentException($"Invalid length for DescriptionHashTaggedField. Expected {TaggedFieldConstants.PAYEE_PUBKEY_LENGTH}, but got {length}");
+ throw new ArgumentException(
+ $"Invalid length for DescriptionHashTaggedField. Expected {TaggedFieldConstants.PayeePubkeyLength}, but got {length}");
}
// Read the data from the BitReader
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/PaymentHashTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/PaymentHashTaggedField.cs
index 9beceacd..58971b74 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/PaymentHashTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/PaymentHashTaggedField.cs
@@ -2,8 +2,8 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Constants;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -16,9 +16,9 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class PaymentHashTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.PAYMENT_HASH;
+ public TaggedFieldTypes Type => TaggedFieldTypes.PaymentHash;
internal uint256 Value { get; }
- public short Length => TaggedFieldConstants.HASH_LENGTH;
+ public short Length => TaggedFieldConstants.HashLength;
///
/// Initializes a new instance of the class.
@@ -57,13 +57,14 @@ public bool IsValid()
/// Thrown when the length is invalid
internal static PaymentHashTaggedField FromBitReader(BitReader bitReader, short length)
{
- if (length != TaggedFieldConstants.HASH_LENGTH)
+ if (length != TaggedFieldConstants.HashLength)
{
- throw new ArgumentException($"Invalid length for PaymentHashTaggedField. Expected {TaggedFieldConstants.HASH_LENGTH}, but got {length}");
+ throw new ArgumentException(
+ $"Invalid length for PaymentHashTaggedField. Expected {TaggedFieldConstants.HashLength}, but got {length}");
}
// Read the data from the BitReader
- var data = new byte[(TaggedFieldConstants.HASH_LENGTH * 5 + 7) / 8];
+ var data = new byte[(TaggedFieldConstants.HashLength * 5 + 7) / 8];
bitReader.ReadBits(data, length * 5);
data = data[..^1];
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/PaymentSecretTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/PaymentSecretTaggedField.cs
index d3be5f5d..56c7141f 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/PaymentSecretTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/PaymentSecretTaggedField.cs
@@ -2,8 +2,8 @@
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Constants;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -16,9 +16,9 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class PaymentSecretTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.PAYMENT_SECRET;
+ public TaggedFieldTypes Type => TaggedFieldTypes.PaymentSecret;
internal uint256 Value { get; }
- public short Length => TaggedFieldConstants.HASH_LENGTH;
+ public short Length => TaggedFieldConstants.HashLength;
///
/// Initializes a new instance of the class.
@@ -57,13 +57,14 @@ public bool IsValid()
/// Thrown when the length is invalid
internal static PaymentSecretTaggedField FromBitReader(BitReader bitReader, short length)
{
- if (length != TaggedFieldConstants.HASH_LENGTH)
+ if (length != TaggedFieldConstants.HashLength)
{
- throw new ArgumentException($"Invalid length for PaymentSecretTaggedField. Expected {TaggedFieldConstants.HASH_LENGTH}, but got {length}");
+ throw new ArgumentException(
+ $"Invalid length for PaymentSecretTaggedField. Expected {TaggedFieldConstants.HashLength}, but got {length}");
}
// Read the data from the BitReader
- var data = new byte[(TaggedFieldConstants.HASH_LENGTH * 5 + 7) / 8];
+ var data = new byte[(TaggedFieldConstants.HashLength * 5 + 7) / 8];
bitReader.ReadBits(data, length * 5);
data = data[..^1];
diff --git a/src/NLightning.Bolt11/Models/TaggedFields/RoutingInfoTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/RoutingInfoTaggedField.cs
index 5d9450ca..5afd8ce8 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/RoutingInfoTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/RoutingInfoTaggedField.cs
@@ -1,11 +1,10 @@
-using NBitcoin;
-
namespace NLightning.Bolt11.Models.TaggedFields;
-using Common.Utils;
using Constants;
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
using Domain.Models;
-using Domain.ValueObjects;
+using Domain.Utils;
using Enums;
using Interfaces;
@@ -19,7 +18,7 @@ namespace NLightning.Bolt11.Models.TaggedFields;
///
internal sealed class RoutingInfoTaggedField : ITaggedField
{
- public TaggedFieldTypes Type => TaggedFieldTypes.ROUTING_INFO;
+ public TaggedFieldTypes Type => TaggedFieldTypes.RoutingInfo;
internal RoutingInfoCollection Value { get; }
public short Length { get; private set; }
@@ -30,7 +29,7 @@ internal sealed class RoutingInfoTaggedField : ITaggedField
internal RoutingInfoTaggedField(RoutingInfoCollection value)
{
Value = value;
- Length = (short)((value.Count * TaggedFieldConstants.ROUTING_INFO_LENGTH + value.Count * 2) / 5);
+ Length = (short)((value.Count * TaggedFieldConstants.RoutingInfoLength + value.Count * 2) / 5);
Value.Changed += OnRoutingInfoCollectionChanged;
}
@@ -41,7 +40,7 @@ public void WriteToBitWriter(BitWriter bitWriter)
// Write data
foreach (var routingInfo in Value)
{
- bitWriter.WriteBits(routingInfo.PubKey.ToBytes(), 264);
+ bitWriter.WriteBits(routingInfo.CompactPubKey, 264);
bitWriter.WriteBits(routingInfo.ShortChannelId, 64);
bitWriter.WriteInt32AsBits(routingInfo.FeeBaseMsat, 32);
bitWriter.WriteInt32AsBits(routingInfo.FeeProportionalMillionths, 32);
@@ -90,7 +89,9 @@ internal static RoutingInfoTaggedField FromBitReader(BitReader bitReader, short
var bitsReadAcc = 0;
var routingInfos = new RoutingInfoCollection();
- for (var i = 0; i < l && l - bitsReadAcc >= TaggedFieldConstants.ROUTING_INFO_LENGTH; i += TaggedFieldConstants.ROUTING_INFO_LENGTH)
+ for (var i = 0;
+ i < l && l - bitsReadAcc >= TaggedFieldConstants.RoutingInfoLength;
+ i += TaggedFieldConstants.RoutingInfoLength)
{
var pubkeyBytes = new byte[34];
bitsReadAcc += bitReader.ReadBits(pubkeyBytes, 264);
@@ -107,11 +108,11 @@ internal static RoutingInfoTaggedField FromBitReader(BitReader bitReader, short
var minFinalCltvExpiry = bitReader.ReadInt16FromBits(16);
bitsReadAcc += 16;
- routingInfos.Add(new RoutingInfo(new PubKey(pubkeyBytes[..^1]),
- new ShortChannelId(shortChannelBytes[..^1]),
- feeBaseMsat,
- feeProportionalMillionths,
- minFinalCltvExpiry));
+ routingInfos.Add(new RoutingInfo(new CompactPubKey(pubkeyBytes[..^1]),
+ new ShortChannelId(shortChannelBytes[..^1]),
+ feeBaseMsat,
+ feeProportionalMillionths,
+ minFinalCltvExpiry));
}
// Skip any extra bits since padding is expected
@@ -126,6 +127,6 @@ internal static RoutingInfoTaggedField FromBitReader(BitReader bitReader, short
private void OnRoutingInfoCollectionChanged(object? sender, EventArgs e)
{
- Length = (short)((Value.Count * TaggedFieldConstants.ROUTING_INFO_LENGTH + Value.Count * 2) / 5);
+ Length = (short)((Value.Count * TaggedFieldConstants.RoutingInfoLength + Value.Count * 2) / 5);
}
}
\ No newline at end of file
diff --git a/src/NLightning.Bolt11/NLightning.Bolt11.csproj b/src/NLightning.Bolt11/NLightning.Bolt11.csproj
index fc8adabe..a83e7a24 100644
--- a/src/NLightning.Bolt11/NLightning.Bolt11.csproj
+++ b/src/NLightning.Bolt11/NLightning.Bolt11.csproj
@@ -67,6 +67,7 @@
+
diff --git a/src/NLightning.Common/AssemblyInfo.cs b/src/NLightning.Common/AssemblyInfo.cs
deleted file mode 100644
index 62ff2128..00000000
--- a/src/NLightning.Common/AssemblyInfo.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
\ No newline at end of file
diff --git a/src/NLightning.Common/CHANGELOG.md b/src/NLightning.Common/CHANGELOG.md
deleted file mode 100644
index 7ae4235c..00000000
--- a/src/NLightning.Common/CHANGELOG.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-## v0.0.1
-
-Initial release
\ No newline at end of file
diff --git a/src/NLightning.Common/NLightning.Common.csproj b/src/NLightning.Common/NLightning.Common.csproj
deleted file mode 100644
index 17c95c94..00000000
--- a/src/NLightning.Common/NLightning.Common.csproj
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
- net8.0;net9.0
- default
- enable
- enable
- true
- 0.0.1
- Debug;Release;Debug.Native;Debug.Wasm;Release.Native;Release.Wasm
- AnyCPU
-
-
-
- Nickolas Goline
- Common Library for NLightning
- Copyright © Níckolas Goline 2024-2025
- https://github.com/ipms-io/nlightning
- git
- 0.0.1
- 0.0.1
- en
- logo.png
- lightning-network,bitcoin,crypto,infrastructure,dotnet,cryptography,cross-platform,libsodium,blazor,webassembly,aot
- https://nlightning.ipms.io/api/NLightning.Common.html
- logo.png
- LICENSE
- README.md
- NLightning.Common
- Initial release of NLightning.Common library.
-
-
-
- DEBUG;$(DefineConstants)
-
-
-
- true
-
-
-
-
- True
- \
-
-
- True
- \
-
-
- True
- \
-
-
-
-
-
-
-
-
diff --git a/src/NLightning.Common/README.md b/src/NLightning.Common/README.md
deleted file mode 100644
index eac7bcd9..00000000
--- a/src/NLightning.Common/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# NLightning.Common
-
-NLightning.Common is a foundational library in the NLightning ecosystem that provides shared utilities, extensions, and common functionality used across all NLightning components.
-
-## Features
-
-- Utility classes and extension methods for Lightning Network operations
-- Cross-cutting concerns and shared abstractions
-- Common data structures and algorithms
-- Helper methods for working with Bitcoin and Lightning Network protocols
-- Low-level optimized operations (supports unsafe code)
-
-## Installation
-
-Install the package from NuGet:
-
-```bash
-dotnet add package NLightning.Common
-```
-
-## Usage
-
-NLightning.Common provides essential utilities and shared functionality:
-
-```csharp
-// Example usage of common utilities
-// Documentation coming soon
-```
-
-## Dependencies
-
-- NLightning.Domain for core domain models
-
-## Related Projects
-
-- NLightning.Infrastructure
-- NLightning.Application
-- NLightning.Domain
-- NLightning.Bolt11
\ No newline at end of file
diff --git a/src/NLightning.Domain/AssemblyInfo.cs b/src/NLightning.Domain/AssemblyInfo.cs
index eec9b74b..efb4d338 100644
--- a/src/NLightning.Domain/AssemblyInfo.cs
+++ b/src/NLightning.Domain/AssemblyInfo.cs
@@ -2,12 +2,10 @@
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("NLightning.Application")]
-[assembly: InternalsVisibleTo("NLightning.Application.NLTG")]
[assembly: InternalsVisibleTo("NLightning.Infrastructure")]
[assembly: InternalsVisibleTo("NLightning.Infrastructure.Blazor")]
+[assembly: InternalsVisibleTo("NLightning.Infrastructure.Bitcoin")]
[assembly: InternalsVisibleTo("NLightning.Infrastructure.Serialization")]
[assembly: InternalsVisibleTo("NLightning.Infrastructure.Serialization.Tests")]
[assembly: InternalsVisibleTo("NLightning.Infrastructure.Tests")]
-[assembly: InternalsVisibleTo("NLightning.Tests.Utils")]
-[assembly: InternalsVisibleTo("NLightning.Tests.Utils.Blazor")]
-[assembly: InternalsVisibleTo("NLightning.Integration.Tests")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("NLightning.Tests.Utils")]
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/Constants/ScriptConstants.cs b/src/NLightning.Domain/Bitcoin/Constants/ScriptConstants.cs
index bcfb4403..787285ce 100644
--- a/src/NLightning.Domain/Bitcoin/Constants/ScriptConstants.cs
+++ b/src/NLightning.Domain/Bitcoin/Constants/ScriptConstants.cs
@@ -2,5 +2,5 @@ namespace NLightning.Domain.Bitcoin.Constants;
public static class ScriptConstants
{
- public const int MAX_SCRIPT_SIZE = 10_000;
+ public const int MaxScriptSize = 10_000;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/Services/IFeeService.cs b/src/NLightning.Domain/Bitcoin/Interfaces/IFeeService.cs
similarity index 95%
rename from src/NLightning.Domain/Bitcoin/Services/IFeeService.cs
rename to src/NLightning.Domain/Bitcoin/Interfaces/IFeeService.cs
index fb97d814..f5771c74 100644
--- a/src/NLightning.Domain/Bitcoin/Services/IFeeService.cs
+++ b/src/NLightning.Domain/Bitcoin/Interfaces/IFeeService.cs
@@ -1,4 +1,4 @@
-namespace NLightning.Domain.Bitcoin.Services;
+namespace NLightning.Domain.Bitcoin.Interfaces;
using Money;
diff --git a/src/NLightning.Domain/Bitcoin/Interfaces/ILightningSigner.cs b/src/NLightning.Domain/Bitcoin/Interfaces/ILightningSigner.cs
new file mode 100644
index 00000000..e71dc75c
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/Interfaces/ILightningSigner.cs
@@ -0,0 +1,67 @@
+namespace NLightning.Domain.Bitcoin.Interfaces;
+
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
+using ValueObjects;
+
+///
+/// Interface for transaction signing services that can be implemented either locally
+/// or delegated to external services like VLS (Validating Lightning Signer)
+///
+public interface ILightningSigner
+{
+ ///
+ /// Generate a new channel key set and return the channel key index
+ ///
+ uint CreateNewChannel(out ChannelBasepoints basepoints, out CompactPubKey firstPerCommitmentPoint);
+
+ ///
+ /// Generate or retrieve channel basepoints for a channel
+ ///
+ ChannelBasepoints GetChannelBasepoints(uint channelKeyIndex);
+
+ ///
+ /// Generate or retrieve channel basepoints for a channel
+ ///
+ ChannelBasepoints GetChannelBasepoints(ChannelId channelId);
+
+ ///
+ /// Get the node's public key
+ ///
+ CompactPubKey GetNodePublicKey();
+
+ ///
+ /// Generate a per-commitment point for a specific commitment number
+ ///
+ CompactPubKey GetPerCommitmentPoint(uint channelKeyIndex, ulong commitmentNumber);
+
+ ///
+ /// Generate a per-commitment point for a specific commitment number
+ ///
+ CompactPubKey GetPerCommitmentPoint(ChannelId channelId, ulong commitmentNumber);
+
+ ///
+ /// Store channel information needed for signing
+ ///
+ void RegisterChannel(ChannelId channelId, ChannelSigningInfo signingInfo);
+
+ ///
+ /// Release (reveal) a per-commitment secret for revocation
+ ///
+ Secret ReleasePerCommitmentSecret(uint channelKeyIndex, ulong commitmentNumber);
+
+ ///
+ /// Release (reveal) a per-commitment secret for revocation
+ ///
+ Secret ReleasePerCommitmentSecret(ChannelId channelId, ulong commitmentNumber);
+
+ ///
+ /// Sign a transaction using the channel's signing context
+ ///
+ CompactSignature SignTransaction(ChannelId channelId, SignedTransaction unsignedTransaction);
+
+ ///
+ /// Verify a signature against a transaction
+ ///
+ void ValidateSignature(ChannelId channelId, CompactSignature signature, SignedTransaction unsignedTransaction);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/Interfaces/ISignatureValidator.cs b/src/NLightning.Domain/Bitcoin/Interfaces/ISignatureValidator.cs
new file mode 100644
index 00000000..c8da7dd1
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/Interfaces/ISignatureValidator.cs
@@ -0,0 +1,11 @@
+namespace NLightning.Domain.Bitcoin.Interfaces;
+
+using Crypto.ValueObjects;
+
+public interface ISignatureValidator
+{
+ ///
+ /// Validates a signature against protocol rules.
+ ///
+ bool ValidateSignature(CompactSignature signature);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/Outputs/IOutput.cs b/src/NLightning.Domain/Bitcoin/Outputs/IOutput.cs
new file mode 100644
index 00000000..2e2e8f9d
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/Outputs/IOutput.cs
@@ -0,0 +1,36 @@
+namespace NLightning.Domain.Bitcoin.Outputs;
+
+using Money;
+using ValueObjects;
+
+public interface IOutput
+{
+ ///
+ /// Gets the amount of the output.
+ ///
+ LightningMoney Amount { get; }
+
+ ///
+ /// Gets the scriptPubKey of the output.
+ ///
+ public BitcoinScript BitcoinScriptPubKey { get; }
+
+ ///
+ /// Gets the redeemScript of the output, if applicable.
+ ///
+ public BitcoinScript RedeemBitcoinScript { get; }
+
+ ///
+ /// Gets or sets the transaction ID of the output.
+ ///
+ public TxId TransactionId { get; set; }
+
+ ///
+ /// Gets or sets the index of the output in the transaction.
+ ///
+ public uint Index { get; set; }
+
+ // TxOut ToTxOut();
+ // ScriptCoin ToCoin();
+ int CompareTo(IOutput? other);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/Transactions/ITransaction.cs b/src/NLightning.Domain/Bitcoin/Transactions/ITransaction.cs
new file mode 100644
index 00000000..c6de63e0
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/Transactions/ITransaction.cs
@@ -0,0 +1,9 @@
+namespace NLightning.Domain.Bitcoin.Transactions;
+
+using ValueObjects;
+
+public interface ITransaction
+{
+ TxId TxId { get; }
+ bool IsValid { get; }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinKeyPath.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinKeyPath.cs
new file mode 100644
index 00000000..d70a3dc4
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinKeyPath.cs
@@ -0,0 +1,22 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+public readonly record struct BitcoinKeyPath
+{
+ public byte[] Value { get; }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The key path value.
+ public BitcoinKeyPath(byte[] value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+ if (value is null || value.Length == 0)
+ throw new ArgumentException("Key path must not be null or empty.", nameof(value));
+
+ Value = value;
+ }
+
+ public static implicit operator BitcoinKeyPath(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](BitcoinKeyPath script) => script.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinLockTime.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinLockTime.cs
new file mode 100644
index 00000000..c4e3a739
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinLockTime.cs
@@ -0,0 +1,7 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+public readonly record struct BitcoinLockTime(uint ValueOrHeight)
+{
+ public static implicit operator BitcoinLockTime(uint value) => new(value);
+ public static implicit operator uint(BitcoinLockTime bitcoinLockTime) => bitcoinLockTime.ValueOrHeight;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinScript.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinScript.cs
new file mode 100644
index 00000000..d9b29201
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinScript.cs
@@ -0,0 +1,52 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+using Domain.Interfaces;
+using Domain.Utils.Extensions;
+
+public readonly struct BitcoinScript : IValueObject, IEquatable
+{
+ private readonly byte[] _value;
+
+ public static BitcoinScript Empty => new([]);
+
+ public int Length => _value.Length;
+
+ public BitcoinScript(byte[] value)
+ {
+ _value = value ?? throw new ArgumentNullException(nameof(value), "BitcoinScript cannot be null.");
+ }
+
+ public static implicit operator BitcoinScript(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](BitcoinScript script) => script._value;
+ public static implicit operator ReadOnlyMemory(BitcoinScript compactPubKey) => compactPubKey._value;
+
+ public override string ToString()
+ {
+ return Convert.ToHexString(_value).ToLowerInvariant();
+ }
+
+ public bool Equals(BitcoinScript other)
+ {
+ return _value.SequenceEqual(other._value);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is BitcoinScript other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _value.GetByteArrayHashCode();
+ }
+
+ public static bool operator !=(BitcoinScript left, BitcoinScript right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator ==(BitcoinScript left, BitcoinScript right)
+ {
+ return left.Equals(right);
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinSequence.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinSequence.cs
new file mode 100644
index 00000000..4c55f2f9
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/BitcoinSequence.cs
@@ -0,0 +1,8 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+public readonly record struct BitcoinSequence(uint Value)
+{
+ public static implicit operator BitcoinSequence(uint value) => new(value);
+ public static implicit operator BitcoinSequence(int value) => new((uint)value);
+ public static implicit operator uint(BitcoinSequence lockTime) => lockTime.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/ExtPrivKey.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/ExtPrivKey.cs
new file mode 100644
index 00000000..9bb2ea93
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/ExtPrivKey.cs
@@ -0,0 +1,29 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+using Crypto.Constants;
+using Crypto.ValueObjects;
+
+public readonly record struct ExtPrivKey
+{
+ ///
+ /// The private key value.
+ ///
+ public byte[] Value { get; }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The private key value.
+ public ExtPrivKey(byte[] value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+ if (value is null || value.Length != CryptoConstants.ExtPrivkeyLen)
+ throw new ArgumentException($"Private key must be {CryptoConstants.ExtPrivkeyLen} bytes long.",
+ nameof(value));
+
+ Value = value;
+ }
+
+ public static implicit operator ExtPrivKey(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](ExtPrivKey script) => script.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/SignedTransaction.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/SignedTransaction.cs
new file mode 100644
index 00000000..5c5ef709
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/SignedTransaction.cs
@@ -0,0 +1,25 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+using Crypto.ValueObjects;
+
+///
+/// Represents a fully signed Bitcoin transaction in a domain-agnostic way.
+///
+public record SignedTransaction
+{
+ public TxId TxId { get; set; }
+ public byte[] RawTxBytes { get; set; }
+
+ public ICollection? Signatures { get; set; }
+
+ public SignedTransaction(TxId txId, byte[] rawTxBytes, ICollection? signatures = null)
+ {
+ ArgumentNullException.ThrowIfNull(rawTxBytes);
+ if (rawTxBytes.Length == 0)
+ throw new ArgumentException("Raw transaction bytes cannot be empty.", nameof(rawTxBytes));
+
+ TxId = txId;
+ RawTxBytes = rawTxBytes;
+ Signatures = signatures;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Bitcoin/ValueObjects/TxId.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/TxId.cs
new file mode 100644
index 00000000..830701b5
--- /dev/null
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/TxId.cs
@@ -0,0 +1,59 @@
+namespace NLightning.Domain.Bitcoin.ValueObjects;
+
+using Crypto.Constants;
+using Utils.Extensions;
+
+public struct TxId : IEquatable
+{
+ public byte[] Hash { get; }
+
+ public bool IsZero => Hash.SequenceEqual(Zero.Hash);
+ public bool IsOne => Hash.SequenceEqual(One.Hash);
+
+ public TxId(byte[] hash)
+ {
+ if (hash.Length < CryptoConstants.Sha256HashLen)
+ throw new ArgumentException("TxId cannot be empty.", nameof(hash));
+
+ Hash = hash;
+ }
+
+ public static TxId Zero => new byte[CryptoConstants.Sha256HashLen];
+
+ public static TxId One => new byte[]
+ {
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ };
+
+ public static implicit operator TxId(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](TxId txId) => txId.Hash;
+ public static implicit operator ReadOnlyMemory(TxId compactPubKey) => compactPubKey.Hash;
+
+ public static bool operator !=(TxId left, TxId right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator ==(TxId left, TxId right)
+ {
+ return left.Equals(right);
+ }
+
+ public bool Equals(TxId other)
+ {
+ return Hash.SequenceEqual(other.Hash);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is TxId other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return Hash.GetByteArrayHashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/ValueObjects/Witness.cs b/src/NLightning.Domain/Bitcoin/ValueObjects/Witness.cs
similarity index 68%
rename from src/NLightning.Domain/ValueObjects/Witness.cs
rename to src/NLightning.Domain/Bitcoin/ValueObjects/Witness.cs
index 26959fdd..cb489e9e 100644
--- a/src/NLightning.Domain/ValueObjects/Witness.cs
+++ b/src/NLightning.Domain/Bitcoin/ValueObjects/Witness.cs
@@ -1,6 +1,7 @@
-namespace NLightning.Domain.ValueObjects;
+namespace NLightning.Domain.Bitcoin.ValueObjects;
-using Interfaces;
+using Domain.Interfaces;
+using Utils.Extensions;
public readonly struct Witness : IValueObject, IEquatable
{
@@ -13,7 +14,14 @@ public Witness(byte[] value)
_value = value;
}
- #region Equality
+ #region Implicit Operators
+
+ public static implicit operator byte[](Witness s) => s._value;
+ public static implicit operator Witness(byte[] value) => new(value);
+ public static implicit operator ReadOnlyMemory(Witness s) => s._value;
+
+ #endregion
+
public bool Equals(Witness other)
{
return _value.SequenceEqual(other._value);
@@ -26,23 +34,6 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
- return _value.GetHashCode();
+ return _value.GetByteArrayHashCode();
}
- #endregion
-
- #region Implicit Operators
- public static implicit operator byte[](Witness s) => s._value;
- public static implicit operator Witness(byte[] value) => new(value);
- public static implicit operator ReadOnlyMemory(Witness s) => s._value;
-
- public static bool operator ==(Witness left, Witness right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(Witness left, Witness right)
- {
- return !(left == right);
- }
- #endregion
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Channel.cs b/src/NLightning.Domain/Channels/Channel.cs
deleted file mode 100644
index 0a1df93d..00000000
--- a/src/NLightning.Domain/Channels/Channel.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace NLightning.Domain.Channels;
-
-using Enums;
-using ValueObjects;
-
-public class Channel
-{
- public ChannelId ChannelId { get; }
- public ShortChannelId? ShortChannelId { get; private set; }
- public ChannelState State { get; private set; }
- public bool IsInitiator { get; }
-
- public Channel(ChannelId channelId, bool isInitiator)
- {
- ChannelId = channelId;
- IsInitiator = isInitiator;
- State = ChannelState.Opening; // Initial state
- }
-
- public void AssignShortChannelId(ShortChannelId shortChannelId)
- {
- ShortChannelId = shortChannelId;
- // TODO: Persist the assignment to the database
- }
-
- public void UpdateState(ChannelState newState)
- {
- // TODO: Persist state change to the database
- State = newState;
-
- // TODO: Notify other components about the state change
- }
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Constants/ChannelConstants.cs b/src/NLightning.Domain/Channels/Constants/ChannelConstants.cs
new file mode 100644
index 00000000..ea5865c4
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Constants/ChannelConstants.cs
@@ -0,0 +1,15 @@
+namespace NLightning.Domain.Channels.Constants;
+
+using Money;
+
+public static class ChannelConstants
+{
+ public const int MaxAcceptedHtlcs = 483;
+ public const int ChannelIdLength = 32;
+ public const int MaxUnconfirmedChannelAge = 2016; // 2 weeks at 6 blocks per hour
+
+ public static readonly LightningMoney LargeChannelAmount = LightningMoney.Satoshis(16_777_216);
+ public static readonly LightningMoney MaxFeePerKw = LightningMoney.Satoshis(100_000);
+ public static readonly LightningMoney MinFeePerKw = LightningMoney.Satoshis(1_000);
+ public static readonly LightningMoney MinDustLimitAmount = LightningMoney.Satoshis(354);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Enums/ChannelState.cs b/src/NLightning.Domain/Channels/Enums/ChannelState.cs
new file mode 100644
index 00000000..0a9ac0fa
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Enums/ChannelState.cs
@@ -0,0 +1,16 @@
+namespace NLightning.Domain.Channels.Enums;
+
+public enum ChannelState : byte
+{
+ None = 0,
+ V1Opening = 1,
+ V1FundingCreated = 2,
+ V1FundingSigned = 3,
+ V2Opening = 10,
+ ReadyForThem = 20,
+ ReadyForUs = 21,
+ Open = 22,
+ Closing = 30,
+ Closed = 40,
+ Stale = 50
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Enums/ChannelVersion.cs b/src/NLightning.Domain/Channels/Enums/ChannelVersion.cs
new file mode 100644
index 00000000..387f6fc4
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Enums/ChannelVersion.cs
@@ -0,0 +1,7 @@
+namespace NLightning.Domain.Channels.Enums;
+
+public enum ChannelVersion : byte
+{
+ V1 = 1,
+ V2 = 2,
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Enums/HtlcDirection.cs b/src/NLightning.Domain/Channels/Enums/HtlcDirection.cs
new file mode 100644
index 00000000..057fb74e
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Enums/HtlcDirection.cs
@@ -0,0 +1,7 @@
+namespace NLightning.Domain.Channels.Enums;
+
+public enum HtlcDirection : byte
+{
+ Incoming = 0,
+ Outgoing = 1,
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Enums/HtlcState.cs b/src/NLightning.Domain/Channels/Enums/HtlcState.cs
new file mode 100644
index 00000000..18bb953f
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Enums/HtlcState.cs
@@ -0,0 +1,9 @@
+namespace NLightning.Domain.Channels.Enums;
+
+public enum HtlcState : byte
+{
+ Offered = 0,
+ Fulfilled = 1,
+ Failed = 2,
+ Expired = 3,
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Factories/ChannelFactory.cs b/src/NLightning.Domain/Channels/Factories/ChannelFactory.cs
new file mode 100644
index 00000000..0308602c
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Factories/ChannelFactory.cs
@@ -0,0 +1,290 @@
+namespace NLightning.Domain.Channels.Factories;
+
+using Bitcoin.Interfaces;
+using Bitcoin.ValueObjects;
+using Constants;
+using Crypto.Hashes;
+using Crypto.ValueObjects;
+using Domain.Enums;
+using Enums;
+using Exceptions;
+using Interfaces;
+using Models;
+using Money;
+using Node.Options;
+using Protocol.Messages;
+using Protocol.Payloads;
+using Protocol.Tlv;
+using Protocol.ValueObjects;
+using Transactions.Constants;
+using Transactions.Outputs;
+using ValueObjects;
+
+public class ChannelFactory : IChannelFactory
+{
+ private readonly IFeeService _feeService;
+ private readonly ILightningSigner _lightningSigner;
+ private readonly NodeOptions _nodeOptions;
+ private readonly ISha256 _sha256;
+
+ public ChannelFactory(IFeeService feeService, ILightningSigner lightningSigner, NodeOptions nodeOptions,
+ ISha256 sha256)
+ {
+ _feeService = feeService;
+ _lightningSigner = lightningSigner;
+ _nodeOptions = nodeOptions;
+ _sha256 = sha256;
+ }
+
+ public async Task CreateChannelV1AsNonInitiatorAsync(OpenChannel1Message message,
+ FeatureOptions negotiatedFeatures,
+ CompactPubKey remoteNodeId)
+ {
+ var payload = message.Payload;
+
+ // If dual fund is negotiated fail the channel
+ if (negotiatedFeatures.DualFund == FeatureSupport.Compulsory)
+ throw new ChannelErrorException("We can only accept dual fund channels");
+
+ // Check if the channel type was negotiated and the channel type is present
+ if (message.ChannelTypeTlv is not null && negotiatedFeatures.ChannelType == FeatureSupport.Compulsory)
+ throw new ChannelErrorException("Channel type was negotiated but not provided");
+
+ // Perform optional checks for the channel
+ PerformOptionalChecks(payload);
+
+ // Perform mandatory checks for the channel
+ var currentFee = await _feeService.GetFeeRatePerKwAsync();
+ PerformMandatoryChecks(message.ChannelTypeTlv, currentFee, negotiatedFeatures, payload, out var minimumDepth);
+
+ // Check for the upfront shutdown script
+ if (message.UpfrontShutdownScriptTlv is null
+ && (negotiatedFeatures.UpfrontShutdownScript > FeatureSupport.No || message.ChannelTypeTlv is not null))
+ throw new ChannelErrorException("Upfront shutdown script is required but not provided");
+
+ BitcoinScript? remoteUpfrontShutdownScript = null;
+ if (message.UpfrontShutdownScriptTlv is not null && message.UpfrontShutdownScriptTlv.Value.Length > 0)
+ remoteUpfrontShutdownScript = message.UpfrontShutdownScriptTlv.Value;
+
+ // Calculate the amounts
+ var toLocalAmount = payload.PushAmount;
+ var toRemoteAmount = payload.FundingAmount - payload.PushAmount;
+
+ // Generate local keys through the signer
+ var localKeyIndex = _lightningSigner.CreateNewChannel(out var localBasepoints, out var firstPerCommitmentPoint);
+
+ // Create the local key set
+ var localKeySet = new ChannelKeySetModel(localKeyIndex, localBasepoints.FundingPubKey,
+ localBasepoints.RevocationBasepoint, localBasepoints.PaymentBasepoint,
+ localBasepoints.DelayedPaymentBasepoint, localBasepoints.HtlcBasepoint,
+ firstPerCommitmentPoint);
+
+ // Create the remote key set from the message
+ var remoteKeySet = ChannelKeySetModel.CreateForRemote(message.Payload.FundingPubKey,
+ message.Payload.RevocationBasepoint,
+ message.Payload.PaymentBasepoint,
+ message.Payload.DelayedPaymentBasepoint,
+ message.Payload.HtlcBasepoint,
+ message.Payload.FirstPerCommitmentPoint);
+
+ BitcoinScript? localUpfrontShutdownScript = null;
+ // Generate our upfront shutdown script
+ if (_nodeOptions.Features.UpfrontShutdownScript > FeatureSupport.No)
+ {
+ // Generate our upfront shutdown script
+ // TODO: Generate a script from the local key set
+ // localUpfrontShutdownScript = ;
+ }
+
+ // Generate the channel configuration
+ var useScidAlias = FeatureSupport.No;
+ if (negotiatedFeatures.ScidAlias > FeatureSupport.No)
+ {
+ if (message.ChannelTypeTlv?.Features.IsFeatureSet(Feature.OptionScidAlias, true) ?? false)
+ useScidAlias = FeatureSupport.Compulsory;
+ else
+ useScidAlias = FeatureSupport.Optional;
+ }
+
+ var channelConfig = new ChannelConfig(payload.ChannelReserveAmount, payload.FeeRatePerKw,
+ payload.HtlcMinimumAmount, _nodeOptions.DustLimitAmount,
+ payload.MaxAcceptedHtlcs, payload.MaxHtlcValueInFlight, minimumDepth,
+ negotiatedFeatures.AnchorOutputs != FeatureSupport.No,
+ payload.DustLimitAmount, payload.ToSelfDelay, useScidAlias,
+ localUpfrontShutdownScript, remoteUpfrontShutdownScript);
+
+ // Generate the commitment numbers
+ var commitmentNumber = new CommitmentNumber(remoteKeySet.PaymentCompactBasepoint,
+ localKeySet.PaymentCompactBasepoint, _sha256);
+
+ try
+ {
+ var fundingOutput = new FundingOutputInfo(payload.FundingAmount, localKeySet.FundingCompactPubKey,
+ remoteKeySet.FundingCompactPubKey);
+
+ // Create the channel
+ return new ChannelModel(channelConfig, payload.ChannelId, commitmentNumber, fundingOutput, false, null,
+ null, toLocalAmount, localKeySet, 1, 0, toRemoteAmount, remoteKeySet, 1,
+ remoteNodeId, 0, ChannelState.V1Opening, ChannelVersion.V1);
+ }
+ catch (Exception e)
+ {
+ throw new ChannelErrorException("Error creating commitment transaction", e);
+ }
+ }
+
+ ///
+ /// Conducts optional validation checks on channel parameters to ensure compliance with acceptable ranges
+ /// and configurations beyond the mandatory requirements.
+ ///
+ ///
+ /// This method verifies that optional configuration parameters meet recommended safety and usability thresholds:
+ /// - Validates that the funding amount meets the minimum channel size threshold.
+ /// - Checks that the HTLC minimum amount is not excessively large relative to the node's configured minimum value.
+ /// - Validates that the maximum HTLC value in flight is enough relative to the channel funds.
+ /// - Ensures the channel reserve amount is not excessively high relative to the node's channel reserve configuration.
+ /// - Verifies that the maximum number of accepted HTLCs meets a minimum threshold.
+ /// - Confirms that the dust limit is not excessively large relative to the node's configured dust limit.
+ ///
+ /// The payload containing the channel's configuration parameters, including funding amount, HTLC limits, and related settings.
+ ///
+ /// Thrown when one of the optional checks fails, including missing channel type when required, insufficient funding,
+ /// excessively high or low HTLC value limits, or incompatible reserve and dust limits.
+ ///
+ private void PerformOptionalChecks(OpenChannel1Payload payload)
+ {
+ // Check if Funding Satoshis is too small
+ if (payload.FundingAmount < _nodeOptions.MinimumChannelSize)
+ throw new ChannelErrorException($"Funding amount is too small: {payload.FundingAmount}");
+
+ // Check if we consider htlc_minimum_msat too large. IE. 20% bigger than our htlc minimum amount
+ if (payload.HtlcMinimumAmount > _nodeOptions.HtlcMinimumAmount * 1.2M)
+ throw new ChannelErrorException($"Htlc minimum amount is too large: {payload.HtlcMinimumAmount}");
+
+ // Check if we consider max_htlc_value_in_flight_msat too small. IE. 20% smaller than our maximum htlc value
+ var maxHtlcValueInFlight =
+ LightningMoney.Satoshis(_nodeOptions.AllowUpToPercentageOfChannelFundsInFlight *
+ payload.FundingAmount.Satoshi / 100M);
+ if (payload.MaxHtlcValueInFlight < maxHtlcValueInFlight * 0.8M)
+ throw new ChannelErrorException($"Max htlc value in flight is too small: {payload.MaxHtlcValueInFlight}");
+
+ // Check if we consider channel_reserve_satoshis too large. IE. 20% bigger than our channel reserve
+ if (payload.ChannelReserveAmount > _nodeOptions.ChannelReserveAmount * 1.2M)
+ throw new ChannelErrorException($"Channel reserve amount is too large: {payload.ChannelReserveAmount}");
+
+ // Check if we consider max_accepted_htlcs too small. IE. 20% smaller than our max-accepted htlcs
+ if (payload.MaxAcceptedHtlcs < (ushort)(_nodeOptions.MaxAcceptedHtlcs * 0.8M))
+ throw new ChannelErrorException($"Max accepted htlcs is too small: {payload.MaxAcceptedHtlcs}");
+
+ // Check if we consider dust_limit_satoshis too large. IE. 75% bigger than our dust limit
+ if (payload.DustLimitAmount > _nodeOptions.DustLimitAmount * 1.75M)
+ throw new ChannelErrorException($"Dust limit amount is too large: {payload.DustLimitAmount}");
+ }
+
+ ///
+ /// Enforce mandatory checks when establishing a new Lightning Network channel.
+ ///
+ ///
+ /// The method validates channel parameters to ensure they comply with predefined safety and compatibility checks:
+ /// - ChainHash must be compatible with the node's network.
+ /// - Push amount must not exceed 1000 times the funding amount.
+ /// - To_self_delay must not be unreasonably large compared to the node's configured value.
+ /// - Max_accepted_htlcs must not exceed the allowed maximum.
+ /// - Fee rate per kw must fall within acceptable limits.
+ /// - Dust limit must be lower than or equal to the channel reserve amount and adhere to minimum thresholds.
+ /// - Funding amount must be sufficient to cover fees and the channel reserve.
+ /// - Large channels must only be supported if negotiated features include support for them.
+ /// - Additional validation may apply to channel types based on negotiated options.
+ ///
+ /// Optional TLV data specifying the channel type, which may impose additional constraints.
+ /// The current network fee rate per kiloweight, used for fee validation.
+ /// Negotiated feature options between the participating nodes, affecting channel setup constraints.
+ /// The payload containing the channel's configuration parameters and constraints.
+ /// The minimum number of confirmations required for the channel to be considered operational.
+ ///
+ /// Thrown when any of the mandatory checks fail, such as invalid chain hash, excessive push amount, unreasonably large delay,
+ /// invalid funding amount, unsupported large channel, or mismatched channel type.
+ ///
+ private void PerformMandatoryChecks(ChannelTypeTlv? channelTypeTlv, LightningMoney currentFeeRatePerKw,
+ FeatureOptions negotiatedFeatures, OpenChannel1Payload payload,
+ out uint minimumDepth)
+ {
+ // Check if ChainHash is compatible
+ if (payload.ChainHash != _nodeOptions.BitcoinNetwork.ChainHash)
+ throw new ChannelErrorException("ChainHash is not compatible");
+
+ // Check if the push amount is too large
+ if (payload.PushAmount > 1_000 * payload.FundingAmount)
+ throw new ChannelErrorException($"Push amount is too large: {payload.PushAmount}");
+
+ // Check if we consider to_self_delay unreasonably large. IE. 50% bigger than our to_self_delay
+ if (payload.ToSelfDelay > _nodeOptions.ToSelfDelay * 1.5M)
+ throw new ChannelErrorException($"To self delay is too large: {payload.ToSelfDelay}");
+
+ // Check max_accepted_htlcs is too large
+ if (payload.MaxAcceptedHtlcs > ChannelConstants.MaxAcceptedHtlcs)
+ throw new ChannelErrorException($"Max accepted htlcs is too small: {payload.MaxAcceptedHtlcs}");
+
+ // Check if we consider fee_rate_per_kw too large
+ if (payload.FeeRatePerKw > ChannelConstants.MaxFeePerKw)
+ throw new ChannelErrorException($"Fee rate per kw is too large: {payload.FeeRatePerKw}");
+
+ // Check if we consider fee_rate_per_kw too small. IE. 20% smaller than our fee rate
+ if (payload.FeeRatePerKw < ChannelConstants.MinFeePerKw || payload.FeeRatePerKw < currentFeeRatePerKw * 0.8M)
+ throw new ChannelErrorException(
+ $"Fee rate per kw is too small: {payload.FeeRatePerKw}, currentFee{currentFeeRatePerKw}");
+
+ // Check if the dust limit is greater than the channel reserve amount
+ if (payload.DustLimitAmount > payload.ChannelReserveAmount)
+ throw new ChannelErrorException(
+ $"Dust limit({payload.DustLimitAmount}) is greater than channel reserve({payload.ChannelReserveAmount})");
+
+ // Check if dust_limit_satoshis is too small
+ if (payload.DustLimitAmount < ChannelConstants.MinDustLimitAmount)
+ throw new ChannelErrorException($"Dust limit amount is too small: {payload.DustLimitAmount}");
+
+ // Check if there are enough funds to pay for fees
+ var expectedWeight = negotiatedFeatures.AnchorOutputs > FeatureSupport.No
+ ? TransactionConstants.InitialCommitmentTransactionWeightNoAnchor
+ : TransactionConstants.InitialCommitmentTransactionWeightWithAnchor;
+ var expectedFee = LightningMoney.Satoshis(expectedWeight * currentFeeRatePerKw.Satoshi / 1000);
+ if (payload.FundingAmount < expectedFee + payload.ChannelReserveAmount)
+ throw new ChannelErrorException($"Funding amount is too small to cover fees: {payload.FundingAmount}");
+
+ // Check if this is a large channel and if we support it
+ if (payload.FundingAmount >= ChannelConstants.LargeChannelAmount &&
+ negotiatedFeatures.LargeChannels == FeatureSupport.No)
+ throw new ChannelErrorException("We don't support large channels");
+
+ // Check ChannelType against negotiated options
+ minimumDepth = _nodeOptions.MinimumDepth;
+ if (channelTypeTlv is not null)
+ {
+ // Check if it set any non-negotiated features
+ if (channelTypeTlv.Features.IsFeatureSet(Feature.OptionStaticRemoteKey, true))
+ {
+ if (negotiatedFeatures.StaticRemoteKey == FeatureSupport.No)
+ throw new ChannelErrorException("Static remote key feature is not supported but requested by peer");
+
+ if (channelTypeTlv.Features.IsFeatureSet(Feature.OptionAnchorOutputs, true)
+ && negotiatedFeatures.AnchorOutputs == FeatureSupport.No)
+ throw new ChannelErrorException("Anchor outputs feature is not supported but requested by peer");
+
+ if (channelTypeTlv.Features.IsFeatureSet(Feature.OptionScidAlias, true))
+ {
+ if (payload.ChannelFlags.AnnounceChannel)
+ throw new ChannelErrorException("Invalid channel flags for OPTION_SCID_ALIAS");
+ }
+
+ // Check for ZeroConf feature
+ if (channelTypeTlv.Features.IsFeatureSet(Feature.OptionZeroconf, true))
+ {
+ if (_nodeOptions.Features.ZeroConf == FeatureSupport.No)
+ throw new ChannelErrorException("ZeroConf feature not supported but requested by peer");
+
+ minimumDepth = 0U;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelConfigDbRepository.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelConfigDbRepository.cs
new file mode 100644
index 00000000..9bceed8e
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelConfigDbRepository.cs
@@ -0,0 +1,36 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using ValueObjects;
+
+///
+/// Repository interface for managing channel configurations
+///
+public interface IChannelConfigDbRepository
+{
+ ///
+ /// Adds a new channel configuration
+ ///
+ /// Channel ID
+ /// Channel configuration
+ void Add(ChannelId channelId, ChannelConfig config);
+
+ ///
+ /// Updates an existing channel configuration
+ ///
+ /// Channel ID
+ /// Updated channel configuration
+ void Update(ChannelId channelId, ChannelConfig config);
+
+ ///
+ /// Deletes a channel configuration
+ ///
+ /// Channel ID
+ Task DeleteAsync(ChannelId channelId);
+
+ ///
+ /// Gets a channel configuration by channel ID
+ ///
+ /// Channel ID
+ /// Channel configuration or null if not found
+ Task GetByChannelIdAsync(ChannelId channelId);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelDbRepository.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelDbRepository.cs
new file mode 100644
index 00000000..799dcd45
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelDbRepository.cs
@@ -0,0 +1,14 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Models;
+using ValueObjects;
+
+public interface IChannelDbRepository
+{
+ Task AddAsync(ChannelModel channelModel);
+ Task UpdateAsync(ChannelModel channelModel);
+ Task DeleteAsync(ChannelId channelId);
+ Task GetByIdAsync(ChannelId channelId);
+ Task> GetAllAsync();
+ Task> GetReadyChannelsAsync();
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelFactory.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelFactory.cs
new file mode 100644
index 00000000..9a419752
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelFactory.cs
@@ -0,0 +1,13 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Crypto.ValueObjects;
+using Models;
+using Node.Options;
+using Protocol.Messages;
+
+public interface IChannelFactory
+{
+ Task CreateChannelV1AsNonInitiatorAsync(OpenChannel1Message message,
+ FeatureOptions negotiatedFeatures,
+ CompactPubKey remoteNodeId);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelKeySetDbRepository.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelKeySetDbRepository.cs
new file mode 100644
index 00000000..7ff11860
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelKeySetDbRepository.cs
@@ -0,0 +1,41 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Models;
+using ValueObjects;
+
+///
+/// Repository interface for managing channel key sets
+///
+public interface IChannelKeySetDbRepository
+{
+ ///
+ /// Adds a new channel key set
+ ///
+ /// Channel ID
+ /// True if this is the local key set, false for remote
+ /// The key set to add
+ void Add(ChannelId channelId, bool isLocal, ChannelKeySetModel keySetModel);
+
+ ///
+ /// Updates an existing channel key set
+ ///
+ /// Channel ID
+ /// True if this is the local key set, false for remote
+ /// The updated key set
+ void Update(ChannelId channelId, bool isLocal, ChannelKeySetModel keySetModel);
+
+ ///
+ /// Deletes a channel key set
+ ///
+ /// Channel ID
+ /// True if this is the local key set, false for remote
+ Task DeleteAsync(ChannelId channelId, bool isLocal);
+
+ ///
+ /// Gets a channel key set
+ ///
+ /// Channel ID
+ /// True if this is the local key set, false for remote
+ /// Channel key set or null if not found
+ Task GetByIdAsync(ChannelId channelId, bool isLocal);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelManager.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelManager.cs
new file mode 100644
index 00000000..9b52b020
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelManager.cs
@@ -0,0 +1,16 @@
+using NLightning.Domain.Protocol.Interfaces;
+
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Crypto.ValueObjects;
+using Node.Options;
+
+public interface IChannelManager
+{
+ Task InitializeAsync();
+
+ Task HandleChannelMessageAsync(IChannelMessage message, FeatureOptions negotiatedFeatures,
+ CompactPubKey peerPubKey);
+
+ Task ForgetOldChannelByBlockHeightAsync(uint blockHeight);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IChannelMemoryRepository.cs b/src/NLightning.Domain/Channels/Interfaces/IChannelMemoryRepository.cs
new file mode 100644
index 00000000..fb0378b0
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IChannelMemoryRepository.cs
@@ -0,0 +1,24 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Crypto.ValueObjects;
+using Enums;
+using Models;
+using ValueObjects;
+
+public interface IChannelMemoryRepository
+{
+ bool TryGetChannel(ChannelId channelId, out ChannelModel? channel);
+
+ List FindChannels(Func predicate);
+
+ bool TryGetChannelState(ChannelId channelId, out ChannelState channelState);
+ void AddChannel(ChannelModel channel);
+ void UpdateChannel(ChannelModel channel);
+ void RemoveChannel(ChannelId channelId);
+
+ bool TryGetTemporaryChannel(CompactPubKey compactPubKey, ChannelId channelId, out ChannelModel? channel);
+ bool TryGetTemporaryChannelState(CompactPubKey compactPubKey, ChannelId channelId, out ChannelState channelState);
+ void AddTemporaryChannel(CompactPubKey compactPubKey, ChannelModel channel);
+ void UpdateTemporaryChannel(CompactPubKey compactPubKey, ChannelModel channel);
+ void RemoveTemporaryChannel(CompactPubKey compactPubKey, ChannelId channelId);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Interfaces/IHtlcDbRepository.cs b/src/NLightning.Domain/Channels/Interfaces/IHtlcDbRepository.cs
new file mode 100644
index 00000000..9e6d1ebb
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Interfaces/IHtlcDbRepository.cs
@@ -0,0 +1,70 @@
+namespace NLightning.Domain.Channels.Interfaces;
+
+using Enums;
+using ValueObjects;
+
+///
+/// Repository interface for managing HTLC (Hashed Time-Locked Contract) entities
+///
+public interface IHtlcDbRepository
+{
+ ///
+ /// Adds a new HTLC to a channel
+ ///
+ /// Channel ID
+ /// HTLC to add
+ Task AddAsync(ChannelId channelId, Htlc htlc);
+
+ ///
+ /// Updates an existing HTLC
+ ///
+ /// Channel ID
+ /// Updated HTLC
+ Task UpdateAsync(ChannelId channelId, Htlc htlc);
+
+ ///
+ /// Deletes an HTLC
+ ///
+ /// Channel ID
+ /// HTLC ID
+ /// HTLC direction (incoming/outgoing)
+ Task DeleteAsync(ChannelId channelId, ulong htlcId, HtlcDirection direction);
+
+ ///
+ /// Deletes all HTLCs for a channel
+ ///
+ /// Channel ID
+ void DeleteAllForChannelId(ChannelId channelId);
+
+ ///
+ /// Gets a specific HTLC
+ ///
+ /// Channel ID
+ /// HTLC ID
+ /// HTLC direction (incoming/outgoing)
+ /// HTLC or null if not found
+ Task GetByIdAsync(ChannelId channelId, ulong htlcId, HtlcDirection direction);
+
+ ///
+ /// Gets all HTLCs for a channel
+ ///
+ /// Channel ID
+ /// Collection of HTLCs
+ Task> GetAllForChannelAsync(ChannelId channelId);
+
+ ///
+ /// Gets HTLCs for a channel with a specific state
+ ///
+ /// Channel ID
+ /// HTLC state
+ /// Collection of HTLCs matching the state
+ Task> GetByChannelIdAndStateAsync(ChannelId channelId, HtlcState state);
+
+ ///
+ /// Gets HTLCs for a channel with a specific direction
+ ///
+ /// Channel ID
+ /// HTLC direction (incoming/outgoing)
+ /// Collection of HTLCs matching the direction
+ Task> GetByChannelIdAndDirectionAsync(ChannelId channelId, HtlcDirection direction);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Models/ChannelKeySetModel.cs b/src/NLightning.Domain/Channels/Models/ChannelKeySetModel.cs
new file mode 100644
index 00000000..236b9966
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Models/ChannelKeySetModel.cs
@@ -0,0 +1,69 @@
+namespace NLightning.Domain.Channels.Models;
+
+using Crypto.Constants;
+using Crypto.ValueObjects;
+
+public class ChannelKeySetModel
+{
+ public uint KeyIndex { get; }
+ public CompactPubKey FundingCompactPubKey { get; }
+ public CompactPubKey RevocationCompactBasepoint { get; }
+ public CompactPubKey PaymentCompactBasepoint { get; }
+ public CompactPubKey DelayedPaymentCompactBasepoint { get; }
+
+ public CompactPubKey HtlcCompactBasepoint { get; }
+ public CompactPubKey CurrentPerCommitmentCompactPoint { get; private set; }
+ public ulong CurrentPerCommitmentIndex { get; private set; }
+
+ ///
+ /// For remote key sets: stores their last revealed per-commitment secret
+ /// This is needed to create penalty transactions if they broadcast old commitments
+ /// For local key sets: this should be null (we don't store our own secrets)
+ ///
+ public byte[]? LastRevealedPerCommitmentSecret { get; private set; }
+
+ public ChannelKeySetModel(uint keyIndex, CompactPubKey fundingCompactPubKey,
+ CompactPubKey revocationCompactBasepoint, CompactPubKey paymentCompactBasepoint,
+ CompactPubKey delayedPaymentCompactBasepoint, CompactPubKey htlcCompactBasepoint,
+ CompactPubKey currentPerCommitmentCompactPoint,
+ ulong currentPerCommitmentIndex = CryptoConstants.FirstPerCommitmentIndex,
+ byte[]? lastRevealedPerCommitmentSecret = null)
+ {
+ KeyIndex = keyIndex;
+ FundingCompactPubKey = fundingCompactPubKey;
+ RevocationCompactBasepoint = revocationCompactBasepoint;
+ PaymentCompactBasepoint = paymentCompactBasepoint;
+ DelayedPaymentCompactBasepoint = delayedPaymentCompactBasepoint;
+ HtlcCompactBasepoint = htlcCompactBasepoint;
+ CurrentPerCommitmentCompactPoint = currentPerCommitmentCompactPoint;
+ CurrentPerCommitmentIndex = currentPerCommitmentIndex;
+ LastRevealedPerCommitmentSecret = lastRevealedPerCommitmentSecret;
+ }
+
+ public void UpdatePerCommitmentPoint(CompactPubKey newPoint)
+ {
+ CurrentPerCommitmentCompactPoint = newPoint;
+ CurrentPerCommitmentIndex--;
+ }
+
+ ///
+ /// Store a revealed per-commitment secret from the counterparty
+ /// This is called when they send a revoke_and_ack message
+ ///
+ public void RevealPerCommitmentSecret(byte[] secret)
+ {
+ LastRevealedPerCommitmentSecret = secret;
+ }
+
+ ///
+ /// Create a ChannelKeySet for the remote party (we don't generate their keys)
+ ///
+ public static ChannelKeySetModel CreateForRemote(CompactPubKey fundingPubKey, CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint, CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint)
+ {
+ return new ChannelKeySetModel(0, fundingPubKey, revocationBasepoint, paymentBasepoint, delayedPaymentBasepoint,
+ htlcBasepoint, firstPerCommitmentPoint);
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/Models/ChannelModel.cs b/src/NLightning.Domain/Channels/Models/ChannelModel.cs
new file mode 100644
index 00000000..9ea46b7a
--- /dev/null
+++ b/src/NLightning.Domain/Channels/Models/ChannelModel.cs
@@ -0,0 +1,117 @@
+namespace NLightning.Domain.Channels.Models;
+
+using Bitcoin.ValueObjects;
+using Crypto.ValueObjects;
+using Enums;
+using Money;
+using Protocol.ValueObjects;
+using Transactions.Outputs;
+using ValueObjects;
+
+public class ChannelModel
+{
+ #region Base Properties
+
+ public ChannelConfig ChannelConfig { get; }
+ public ChannelId ChannelId { get; private set; }
+ public CommitmentNumber CommitmentNumber { get; }
+ public uint FundingCreatedAtBlockHeight { get; set; }
+ public FundingOutputInfo FundingOutput { get; }
+ public bool IsInitiator { get; }
+ public CompactPubKey RemoteNodeId { get; }
+ public ChannelState State { get; private set; }
+ public ChannelVersion Version { get; }
+
+ #endregion
+
+ #region Signatures
+
+ public CompactSignature? LastSentSignature { get; }
+ public CompactSignature? LastReceivedSignature { get; }
+
+ #endregion
+
+ #region Local Information
+
+ public LightningMoney LocalBalance { get; }
+ public ChannelKeySetModel LocalKeySet { get; }
+ public ulong LocalNextHtlcId { get; }
+ public ICollection? LocalOfferedHtlcs { get; }
+ public ICollection? LocalFullfiledHtlcs { get; }
+ public ICollection? LocalOldHtlcs { get; }
+ public ulong LocalRevocationNumber { get; }
+ public BitcoinScript? LocalUpfrontShutdownScript { get; }
+
+ #endregion
+
+ #region Remote Information
+
+ public ShortChannelId? RemoteAlias { get; set; }
+ public LightningMoney RemoteBalance { get; }
+ public ChannelKeySetModel RemoteKeySet { get; }
+ public ulong RemoteNextHtlcId { get; }
+ public ulong RemoteRevocationNumber { get; }
+ public ICollection? RemoteFulfilledHtlcs { get; }
+ public ICollection? RemoteOfferedHtlcs { get; }
+ public ICollection? RemoteOldHtlcs { get; }
+ public BitcoinScript? RemoteUpfrontShutdownScript { get; }
+
+ #endregion
+
+ public ChannelModel(ChannelConfig channelConfig, ChannelId channelId, CommitmentNumber commitmentNumber,
+ FundingOutputInfo fundingOutput, bool isInitiator, CompactSignature? lastSentSignature,
+ CompactSignature? lastReceivedSignature, LightningMoney localBalance,
+ ChannelKeySetModel localKeySet,
+ ulong localNextHtlcId, ulong localRevocationNumber, LightningMoney remoteBalance,
+ ChannelKeySetModel remoteKeySet, ulong remoteNextHtlcId, CompactPubKey remoteNodeId,
+ ulong remoteRevocationNumber, ChannelState state, ChannelVersion version,
+ ICollection? localOfferedHtlcs = null, ICollection? localFulffiledHtlcs = null,
+ ICollection? localOldHtlcs = null, ICollection? remoteOfferedHtlcs = null,
+ ICollection? remoteFullfiledHtlcs = null, ICollection? remoteOldHtlcs = null)
+ {
+ ChannelConfig = channelConfig;
+ ChannelId = channelId;
+ CommitmentNumber = commitmentNumber;
+ FundingOutput = fundingOutput;
+ IsInitiator = isInitiator;
+ LastSentSignature = lastSentSignature;
+ LastReceivedSignature = lastReceivedSignature;
+ LocalBalance = localBalance;
+ LocalKeySet = localKeySet;
+ LocalNextHtlcId = localNextHtlcId;
+ LocalRevocationNumber = localRevocationNumber;
+ RemoteBalance = remoteBalance;
+ RemoteKeySet = remoteKeySet;
+ RemoteNextHtlcId = remoteNextHtlcId;
+ RemoteRevocationNumber = remoteRevocationNumber;
+ State = state;
+ Version = version;
+ RemoteNodeId = remoteNodeId;
+ LocalOfferedHtlcs = localOfferedHtlcs ?? new List();
+ LocalFullfiledHtlcs = localFulffiledHtlcs ?? new List();
+ LocalOldHtlcs = localOldHtlcs ?? new List();
+ RemoteOfferedHtlcs = remoteOfferedHtlcs ?? new List();
+ RemoteFulfilledHtlcs = remoteFullfiledHtlcs ?? new List();
+ RemoteOldHtlcs = remoteOldHtlcs ?? new List();
+ }
+
+ public void UpdateState(ChannelState newState)
+ {
+ if (State == ChannelState.V2Opening && newState < ChannelState.V2Opening
+ || State >= ChannelState.V1Opening && newState == ChannelState.V2Opening)
+ throw new ArgumentOutOfRangeException(nameof(newState), "Invalid channel state for update.");
+
+ if (newState <= State)
+ throw new ArgumentOutOfRangeException(nameof(newState), "New state must be greater than current state.");
+
+ State = newState;
+ }
+
+ public void UpdateChannelId(ChannelId newChannelId)
+ {
+ if (newChannelId == ChannelId.Zero)
+ throw new ArgumentException("New channel ID cannot be empty.", nameof(newChannelId));
+
+ ChannelId = newChannelId;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/ChannelBasepoints.cs b/src/NLightning.Domain/Channels/ValueObjects/ChannelBasepoints.cs
new file mode 100644
index 00000000..17a60236
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/ChannelBasepoints.cs
@@ -0,0 +1,23 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using Crypto.ValueObjects;
+
+public record struct ChannelBasepoints
+{
+ public CompactPubKey FundingPubKey { get; init; }
+ public CompactPubKey RevocationBasepoint { get; init; }
+ public CompactPubKey PaymentBasepoint { get; init; }
+ public CompactPubKey DelayedPaymentBasepoint { get; init; }
+ public CompactPubKey HtlcBasepoint { get; init; }
+
+ public ChannelBasepoints(CompactPubKey fundingPubKey, CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint, CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint)
+ {
+ FundingPubKey = fundingPubKey;
+ RevocationBasepoint = revocationBasepoint;
+ PaymentBasepoint = paymentBasepoint;
+ DelayedPaymentBasepoint = delayedPaymentBasepoint;
+ HtlcBasepoint = htlcBasepoint;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/ChannelConfig.cs b/src/NLightning.Domain/Channels/ValueObjects/ChannelConfig.cs
new file mode 100644
index 00000000..cdf6e45d
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/ChannelConfig.cs
@@ -0,0 +1,44 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using Bitcoin.ValueObjects;
+using Domain.Enums;
+using Money;
+
+public readonly record struct ChannelConfig
+{
+ public LightningMoney? ChannelReserveAmount { get; }
+ public LightningMoney LocalDustLimitAmount { get; }
+ public LightningMoney FeeRateAmountPerKw { get; }
+ public LightningMoney HtlcMinimumAmount { get; }
+ public ushort MaxAcceptedHtlcs { get; }
+ public LightningMoney MaxHtlcAmountInFlight { get; }
+ public uint MinimumDepth { get; }
+ public bool OptionAnchorOutputs { get; }
+ public LightningMoney RemoteDustLimitAmount { get; }
+ public ushort ToSelfDelay { get; }
+ public FeatureSupport UseScidAlias { get; }
+ public BitcoinScript? LocalUpfrontShutdownScript { get; }
+ public BitcoinScript? RemoteShutdownScriptPubKey { get; }
+
+ public ChannelConfig(LightningMoney? channelReserveAmount, LightningMoney feeRateAmountPerKw,
+ LightningMoney htlcMinimumAmount, LightningMoney localDustLimitAmount,
+ ushort maxAcceptedHtlcs, LightningMoney maxHtlcAmountInFlight, uint minimumDepth,
+ bool optionAnchorOutputs, LightningMoney remoteDustLimitAmount, ushort toSelfDelay,
+ FeatureSupport useScidAlias, BitcoinScript? localUpfrontShutdownScript = null,
+ BitcoinScript? remoteShutdownScriptPubKey = null)
+ {
+ ChannelReserveAmount = channelReserveAmount;
+ FeeRateAmountPerKw = feeRateAmountPerKw;
+ HtlcMinimumAmount = htlcMinimumAmount;
+ LocalDustLimitAmount = localDustLimitAmount;
+ MaxAcceptedHtlcs = maxAcceptedHtlcs;
+ MaxHtlcAmountInFlight = maxHtlcAmountInFlight;
+ MinimumDepth = minimumDepth;
+ OptionAnchorOutputs = optionAnchorOutputs;
+ RemoteDustLimitAmount = remoteDustLimitAmount;
+ ToSelfDelay = toSelfDelay;
+ UseScidAlias = useScidAlias;
+ LocalUpfrontShutdownScript = localUpfrontShutdownScript;
+ RemoteShutdownScriptPubKey = remoteShutdownScriptPubKey;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/ValueObjects/ChannelFlags.cs b/src/NLightning.Domain/Channels/ValueObjects/ChannelFlags.cs
similarity index 54%
rename from src/NLightning.Domain/ValueObjects/ChannelFlags.cs
rename to src/NLightning.Domain/Channels/ValueObjects/ChannelFlags.cs
index d5fc4349..ba06cdb8 100644
--- a/src/NLightning.Domain/ValueObjects/ChannelFlags.cs
+++ b/src/NLightning.Domain/Channels/ValueObjects/ChannelFlags.cs
@@ -1,13 +1,13 @@
-namespace NLightning.Domain.ValueObjects;
+namespace NLightning.Domain.Channels.ValueObjects;
-using Enums;
-using Interfaces;
+using Domain.Enums;
+using Domain.Interfaces;
///
/// Only the least-significant bit of channel_flags is currently defined: announce_channel. This indicates whether
/// the initiator of the funding flow wishes to advertise this channel publicly to the network
///
-public readonly struct ChannelFlags : IValueObject, IEquatable
+public readonly record struct ChannelFlags : IValueObject, IEquatable
{
private readonly byte _value;
@@ -17,39 +17,18 @@ public ChannelFlags(byte value)
{
_value = value;
}
+
public ChannelFlags(ChannelFlag value)
{
_value = (byte)value;
}
- #region Equality
- public override bool Equals(object? obj)
- {
- if (obj is ChannelFlags other)
- {
- return _value == other._value;
- }
- return false;
- }
-
- public bool Equals(ChannelFlags other)
- {
- return _value == other._value;
- }
-
- public override int GetHashCode()
- {
- return _value.GetHashCode();
- }
-
- public static bool operator ==(ChannelFlags left, ChannelFlags right) => left.Equals(right);
- public static bool operator !=(ChannelFlags left, ChannelFlags right) => !(left == right);
- #endregion
-
#region Implicit Conversions
+
public static implicit operator byte(ChannelFlags c) => c._value;
public static implicit operator ChannelFlags(byte value) => new(value);
public static implicit operator byte[](ChannelFlags c) => [c._value];
public static implicit operator ChannelFlags(byte[] value) => new(value[0]);
+
#endregion
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/ChannelId.cs b/src/NLightning.Domain/Channels/ValueObjects/ChannelId.cs
new file mode 100644
index 00000000..75ea3e6e
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/ChannelId.cs
@@ -0,0 +1,81 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using Constants;
+using Domain.Interfaces;
+using Utils.Extensions;
+
+///
+/// Represents a channel id.
+///
+///
+/// The channel id is a unique identifier for a channel.
+///
+public readonly struct ChannelId : IEquatable, IValueObject
+{
+ private readonly byte[] _value;
+
+ public static ChannelId Zero => new byte[ChannelConstants.ChannelIdLength];
+
+ public ChannelId(ReadOnlySpan value)
+ {
+ if (value.Length != ChannelConstants.ChannelIdLength)
+ throw new ArgumentException($"ChannelId must be {ChannelConstants.ChannelIdLength} bytes", nameof(value));
+
+ _value = value.ToArray();
+ }
+
+ #region Overrides
+
+ public override string ToString()
+ {
+ return Convert.ToHexString(_value).ToLowerInvariant();
+ }
+
+ public bool Equals(ChannelId other)
+ {
+ // Handle null cases first
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (_value is null && other._value is null)
+ return true;
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (_value is null || other._value is null)
+ return false;
+
+ return _value.SequenceEqual(other._value);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is null)
+ return false;
+
+ return obj is ChannelId other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _value.GetByteArrayHashCode();
+ }
+
+ #endregion
+
+ #region Implicit Conversions
+
+ public static implicit operator byte[](ChannelId c) => c._value;
+ public static implicit operator ReadOnlyMemory(ChannelId c) => c._value;
+ public static implicit operator ChannelId(byte[] value) => new(value);
+ public static implicit operator ChannelId(Span value) => new(value);
+
+ public static bool operator !=(ChannelId left, ChannelId right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator ==(ChannelId left, ChannelId right)
+ {
+ return left.Equals(right);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/ChannelSigningInfo.cs b/src/NLightning.Domain/Channels/ValueObjects/ChannelSigningInfo.cs
new file mode 100644
index 00000000..f9560ac2
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/ChannelSigningInfo.cs
@@ -0,0 +1,29 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using Bitcoin.ValueObjects;
+using Crypto.ValueObjects;
+
+///
+/// Information needed by the signer for a specific channel
+///
+public record struct ChannelSigningInfo
+{
+ public TxId FundingTxId { get; init; }
+ public uint FundingOutputIndex { get; init; }
+ public ulong FundingSatoshis { get; init; }
+ public CompactPubKey LocalFundingPubKey { get; init; }
+ public CompactPubKey RemoteFundingPubKey { get; init; }
+ public uint ChannelKeyIndex { get; init; } // For deterministic key derivation
+
+ public ChannelSigningInfo(TxId fundingTxId, uint fundingOutputIndex, ulong fundingSatoshis,
+ CompactPubKey localFundingPubKey, CompactPubKey remoteFundingPubKey,
+ uint channelKeyIndex)
+ {
+ FundingTxId = fundingTxId;
+ FundingOutputIndex = fundingOutputIndex;
+ FundingSatoshis = fundingSatoshis;
+ LocalFundingPubKey = localFundingPubKey;
+ RemoteFundingPubKey = remoteFundingPubKey;
+ ChannelKeyIndex = channelKeyIndex;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/CommitmentKeys.cs b/src/NLightning.Domain/Channels/ValueObjects/CommitmentKeys.cs
new file mode 100644
index 00000000..b82f8ca9
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/CommitmentKeys.cs
@@ -0,0 +1,66 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using NLightning.Domain.Crypto.ValueObjects;
+
+public record struct CommitmentKeys
+{
+ ///
+ /// The localpubkey from the commitment owner's perspective.
+ /// Derived as: payment_basepoint + SHA256(per_commitment_point || payment_basepoint) * G
+ /// Used for to_remote outputs (the other party can spend immediately).
+ ///
+ public CompactPubKey LocalPubKey { get; init; }
+
+ ///
+ /// The local_delayedpubkey from the commitment owner's perspective.
+ /// Derived as: delayed_payment_basepoint + SHA256(per_commitment_point || delayed_payment_basepoint) * G
+ /// Used for to_local outputs (the commitment owner can spend after a delay).
+ ///
+ public CompactPubKey LocalDelayedPubKey { get; init; }
+
+ ///
+ /// The revocationpubkey that allows the other party to revoke this commitment.
+ /// Complex derivation involving both parties' keys to ensure neither can compute the private key alone.
+ ///
+ public CompactPubKey RevocationPubKey { get; init; }
+
+ ///
+ /// The local_htlcpubkey from the commitment owner's perspective.
+ /// Derived as: htlc_basepoint + SHA256(per_commitment_point || htlc_basepoint) * G
+ /// Used for HTLC outputs owned by the commitment owner.
+ ///
+ public CompactPubKey LocalHtlcPubKey { get; init; }
+
+ ///
+ /// The remote_htlcpubkey from the commitment owner's perspective.
+ /// Derived as: remote_htlc_basepoint + SHA256(per_commitment_point || remote_htlc_basepoint) * G
+ /// Used for HTLC outputs owned by the other party.
+ ///
+ public CompactPubKey RemoteHtlcPubKey { get; init; }
+
+ ///
+ /// The per-commitment point used to derive all the above keys.
+ /// Generated as: per_commitment_secret * G
+ ///
+ public CompactPubKey PerCommitmentPoint { get; init; }
+
+ ///
+ /// The per-commitment secret used to generate the per-commitment point.
+ /// Only available for our own commitments, not for remote commitments.
+ ///
+ public Secret? PerCommitmentSecret { get; init; }
+
+ public CommitmentKeys(CompactPubKey localPubKey, CompactPubKey localDelayedPubKey,
+ CompactPubKey revocationPubKey, CompactPubKey localHtlcPubKey,
+ CompactPubKey remoteHtlcPubKey, CompactPubKey perCommitmentPoint,
+ Secret? perCommitmentSecret)
+ {
+ LocalPubKey = localPubKey;
+ LocalDelayedPubKey = localDelayedPubKey;
+ RevocationPubKey = revocationPubKey;
+ LocalHtlcPubKey = localHtlcPubKey;
+ RemoteHtlcPubKey = remoteHtlcPubKey;
+ PerCommitmentPoint = perCommitmentPoint;
+ PerCommitmentSecret = perCommitmentSecret;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Channels/ValueObjects/Htlc.cs b/src/NLightning.Domain/Channels/ValueObjects/Htlc.cs
new file mode 100644
index 00000000..276ac408
--- /dev/null
+++ b/src/NLightning.Domain/Channels/ValueObjects/Htlc.cs
@@ -0,0 +1,36 @@
+namespace NLightning.Domain.Channels.ValueObjects;
+
+using Crypto.ValueObjects;
+using Enums;
+using Money;
+using Protocol.Messages;
+
+public readonly record struct Htlc
+{
+ public ulong Id { get; }
+ public LightningMoney Amount { get; }
+ public Hash PaymentHash { get; }
+ public Hash? PaymentPreimage { get; }
+ public uint CltvExpiry { get; }
+ public HtlcState State { get; }
+ public HtlcDirection Direction { get; }
+ public UpdateAddHtlcMessage AddMessage { get; }
+ public ulong ObscuredCommitmentNumber { get; }
+ public CompactSignature? Signature { get; }
+
+ public Htlc(LightningMoney amount, UpdateAddHtlcMessage addMessage, HtlcDirection direction, uint cltvExpiry,
+ ulong id, ulong obscuredCommitmentNumber, Hash paymentHash, HtlcState state,
+ Hash? paymentPreimage = null, CompactSignature? signature = null)
+ {
+ Id = id;
+ Amount = amount;
+ PaymentHash = paymentHash;
+ PaymentPreimage = paymentPreimage;
+ CltvExpiry = cltvExpiry;
+ State = state;
+ Direction = direction;
+ AddMessage = addMessage;
+ ObscuredCommitmentNumber = obscuredCommitmentNumber;
+ Signature = signature;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/ValueObjects/ShortChannelId.cs b/src/NLightning.Domain/Channels/ValueObjects/ShortChannelId.cs
similarity index 80%
rename from src/NLightning.Domain/ValueObjects/ShortChannelId.cs
rename to src/NLightning.Domain/Channels/ValueObjects/ShortChannelId.cs
index 3aefd434..6ee2fea6 100644
--- a/src/NLightning.Domain/ValueObjects/ShortChannelId.cs
+++ b/src/NLightning.Domain/Channels/ValueObjects/ShortChannelId.cs
@@ -1,6 +1,6 @@
-namespace NLightning.Domain.ValueObjects;
+namespace NLightning.Domain.Channels.ValueObjects;
-using Interfaces;
+using Domain.Interfaces;
///
/// Represents a short channel id.
@@ -8,11 +8,11 @@ namespace NLightning.Domain.ValueObjects;
///
/// The short channel id is a unique description of the funding transaction.
///
-public readonly struct ShortChannelId : IValueObject, IEquatable
+public readonly struct ShortChannelId : IEquatable, IValueObject
{
private readonly byte[] _value;
- public const int LENGTH = 8;
+ public const int Length = 8;
public readonly uint BlockHeight;
public readonly uint TransactionIndex;
@@ -24,7 +24,8 @@ public ShortChannelId(uint blockHeight, uint transactionIndex, ushort outputInde
TransactionIndex = transactionIndex;
OutputIndex = outputIndex;
- _value = [
+ _value =
+ [
(byte)(BlockHeight >> 16),
(byte)(BlockHeight >> 8),
(byte)BlockHeight,
@@ -38,9 +39,9 @@ public ShortChannelId(uint blockHeight, uint transactionIndex, ushort outputInde
public ShortChannelId(byte[] value)
{
- if (value.Length != LENGTH)
+ if (value.Length != Length)
{
- throw new ArgumentException($"ShortChannelId must be {LENGTH} bytes", nameof(value));
+ throw new ArgumentException($"ShortChannelId must be {Length} bytes", nameof(value));
}
_value = value;
@@ -52,10 +53,11 @@ public ShortChannelId(byte[] value)
public ShortChannelId(ulong channelId) : this(
(uint)((channelId >> 40) & 0xFFFFFF), // BLOCK_HEIGHT
- (uint)((channelId >> 16) & 0xFFFF), // TRANSACTION_INDEX
- (ushort)(channelId & 0xFF) // OUTPUT_INDEX
+ (uint)((channelId >> 16) & 0xFFFF), // TRANSACTION_INDEX
+ (ushort)(channelId & 0xFF) // OUTPUT_INDEX
)
- { }
+ {
+ }
public static ShortChannelId Parse(string shortChannelId)
{
@@ -73,35 +75,31 @@ public static ShortChannelId Parse(string shortChannelId)
}
#region Overrides
+
public override string ToString()
{
return $"{BlockHeight}x{TransactionIndex}x{OutputIndex}";
}
- public override bool Equals(object? obj)
+ public bool Equals(ShortChannelId other)
{
- if (obj is ShortChannelId other)
- {
- return Equals(other);
- }
-
- return false;
+ return _value.SequenceEqual(other._value);
}
- public bool Equals(ShortChannelId other)
+ public override bool Equals(object? obj)
{
- return BlockHeight == other.BlockHeight &&
- TransactionIndex == other.TransactionIndex &&
- OutputIndex == other.OutputIndex;
+ return obj is ShortChannelId other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(BlockHeight, TransactionIndex, OutputIndex);
}
+
#endregion
#region Implicit Operators
+
public static implicit operator byte[](ShortChannelId s) => s._value;
public static implicit operator ShortChannelId(byte[] value) => new(value);
public static implicit operator ReadOnlyMemory(ShortChannelId s) => s._value;
@@ -109,14 +107,15 @@ public override int GetHashCode()
public static implicit operator ShortChannelId(Span value) => new(value.ToArray());
public static implicit operator ShortChannelId(ulong value) => new(value);
- public static bool operator ==(ShortChannelId left, ShortChannelId right)
+ public static bool operator !=(ShortChannelId left, ShortChannelId right)
{
- return left.Equals(right);
+ return !left.Equals(right);
}
- public static bool operator !=(ShortChannelId left, ShortChannelId right)
+ public static bool operator ==(ShortChannelId left, ShortChannelId right)
{
- return !(left == right);
+ return left.Equals(right);
}
+
#endregion
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Constants/InvoiceConstants.cs b/src/NLightning.Domain/Constants/InvoiceConstants.cs
index fe0f920c..c42403e1 100644
--- a/src/NLightning.Domain/Constants/InvoiceConstants.cs
+++ b/src/NLightning.Domain/Constants/InvoiceConstants.cs
@@ -5,17 +5,17 @@ namespace NLightning.Domain.Constants;
[ExcludeFromCodeCoverage]
public static class InvoiceConstants
{
- public const string PREFIX = "ln";
- public const char SEPARATOR = '1';
- public const string PREFIX_MAINET = "bc";
- public const string PREFIX_TESTNET = "tb";
- public const string PREFIX_SIGNET = "tbs";
- public const string PREFIX_REGTEST = "bcrt";
- public const char MULTIPLIER_MILLI = 'm';
- public const char MULTIPLIER_MICRO = 'u';
- public const char MULTIPLIER_NANO = 'n';
- public const char MULTIPLIER_PICO = 'p';
- public const decimal BTC_IN_SATOSHIS = 100_000_000m;
- public const decimal BTC_IN_MILLISATOSHIS = 100_000_000_000m;
- public const int DEFAULT_EXPIRATION_SECONDS = 3600;
+ public const string Prefix = "ln";
+ public const char Separator = '1';
+ public const string PrefixMainet = "bc";
+ public const string PrefixTestnet = "tb";
+ public const string PrefixSignet = "tbs";
+ public const string PrefixRegtest = "bcrt";
+ public const char MultiplierMilli = 'm';
+ public const char MultiplierMicro = 'u';
+ public const char MultiplierNano = 'n';
+ public const char MultiplierPico = 'p';
+ public const decimal BtcInSatoshis = 100_000_000m;
+ public const decimal BtcInMillisatoshis = 100_000_000_000m;
+ public const int DefaultExpirationSeconds = 3600;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/Constants/CryptoConstants.cs b/src/NLightning.Domain/Crypto/Constants/CryptoConstants.cs
index 852d1412..d5fbdfff 100644
--- a/src/NLightning.Domain/Crypto/Constants/CryptoConstants.cs
+++ b/src/NLightning.Domain/Crypto/Constants/CryptoConstants.cs
@@ -5,20 +5,25 @@ namespace NLightning.Domain.Crypto.Constants;
[ExcludeFromCodeCoverage]
public static class CryptoConstants
{
- public const int CHACHA20_POLY1305_TAG_LEN = 16;
- public const int XCHACHA20_POLY1305_TAG_LEN = 16;
+ public const int Chacha20Poly1305TagLen = 16;
+ public const int Xchacha20Poly1305TagLen = 16;
- public const int CHACHA20_POLY1305_NONCE_LEN = 12;
- public const int XCHACHA20_POLY1305_NONCE_LEN = 24;
+ public const int Chacha20Poly1305NonceLen = 12;
+ public const int Xchacha20Poly1305NonceLen = 24;
- public const int SHA256_HASH_LEN = 32;
- public const int SHA256_BLOCK_LEN = 64;
- public const int LIBSODIUM_SHA256_STATE_LEN = 104;
+ public const int Sha256HashLen = 32;
+ public const int Sha256BlockLen = 64;
+ public const int LibsodiumSha256StateLen = 104;
- public const int LIBSODIUM_RIPEMD160_STATE_LEN = 80;
+ public const int Ripemd160HashLen = 20;
- public const int PRIVKEY_LEN = 32;
- public const int PUBKEY_LEN = 33;
+ public const int ExtPrivkeyLen = 74;
+ public const int PrivkeyLen = 32;
+ public const int CompactPubkeyLen = 33;
+ public const int SecretLen = 32;
- public const int MAX_SIGNATURE_SIZE = 64;
+ public const ulong FirstPerCommitmentIndex = 281474976710655;
+
+ public const int MaxSignatureSize = 64;
+ public const int MinSignatureSize = 63;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/Constants/HashConstants.cs b/src/NLightning.Domain/Crypto/Constants/HashConstants.cs
deleted file mode 100644
index 9ced488a..00000000
--- a/src/NLightning.Domain/Crypto/Constants/HashConstants.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace NLightning.Domain.Crypto.Constants;
-
-[ExcludeFromCodeCoverage]
-public class HashConstants
-{
- ///
- /// A constant specifying the size in bytes of the SHA256 hash function's output.
- ///
- public const int SHA256_HASH_LEN = 32;
-
- ///
- /// A constant specifying the size in bytes that the SHA256 hash function
- /// uses internally to divide its input for iterative processing.
- ///
- internal const int SHA256_BLOCK_LEN = 64;
-
- ///
- /// A constant specifying the size in bytes of the Ripemd160 hash function's output.
- ///
- public const int RIPEMD160_HASH_LEN = 20;
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/Hashes/ISha256.cs b/src/NLightning.Domain/Crypto/Hashes/ISha256.cs
new file mode 100644
index 00000000..a69a2c9a
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/Hashes/ISha256.cs
@@ -0,0 +1,7 @@
+namespace NLightning.Domain.Crypto.Hashes;
+
+public interface ISha256 : IDisposable
+{
+ void AppendData(ReadOnlySpan data);
+ void GetHashAndReset(Span hash);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/CompactPubKey.cs b/src/NLightning.Domain/Crypto/ValueObjects/CompactPubKey.cs
new file mode 100644
index 00000000..2aabd824
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/CompactPubKey.cs
@@ -0,0 +1,55 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+using Constants;
+using Utils.Extensions;
+
+public readonly struct CompactPubKey : IEquatable
+{
+ private readonly byte[] _compactBytes;
+
+ public CompactPubKey(byte[] compactBytes)
+ {
+ if (compactBytes.Length != CryptoConstants.CompactPubkeyLen)
+ throw new ArgumentException("PublicKey cannot be empty.", nameof(compactBytes));
+
+ if (compactBytes[0] != 0x02 && compactBytes[0] != 0x03)
+ throw new ArgumentException("Invalid CompactPubKey format. The first byte must be 0x02 or 0x03.",
+ nameof(compactBytes));
+
+ _compactBytes = compactBytes;
+ }
+
+ public static implicit operator CompactPubKey(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](CompactPubKey hash) => hash._compactBytes;
+
+ public static implicit operator ReadOnlySpan(CompactPubKey compactPubKey) => compactPubKey._compactBytes;
+
+ public static implicit operator ReadOnlyMemory(CompactPubKey compactPubKey) => compactPubKey._compactBytes;
+
+ public static bool operator !=(CompactPubKey left, CompactPubKey right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator ==(CompactPubKey left, CompactPubKey right)
+ {
+ return left.Equals(right);
+ }
+
+ public override string ToString() => Convert.ToHexString(_compactBytes).ToLowerInvariant();
+
+ public bool Equals(CompactPubKey other)
+ {
+ return _compactBytes.SequenceEqual(other._compactBytes);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is CompactPubKey other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _compactBytes.GetByteArrayHashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/CompactSignature.cs b/src/NLightning.Domain/Crypto/ValueObjects/CompactSignature.cs
new file mode 100644
index 00000000..2f2d1d6f
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/CompactSignature.cs
@@ -0,0 +1,24 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+using Constants;
+using Interfaces;
+
+public record CompactSignature : IValueObject
+{
+ public byte[] Value { get; }
+
+ public CompactSignature(byte[] value)
+ {
+ ArgumentNullException.ThrowIfNull(value, nameof(value));
+ if (value.Length is < CryptoConstants.MinSignatureSize or > CryptoConstants.MaxSignatureSize)
+ throw new ArgumentOutOfRangeException(nameof(value),
+ $"Signature must be less than or equal to {CryptoConstants.MaxSignatureSize} bytes");
+
+ Value = value;
+ }
+
+ public static implicit operator CompactSignature(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](CompactSignature hash) => hash.Value;
+
+ public static implicit operator ReadOnlyMemory(CompactSignature compactPubKey) => compactPubKey.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/CryptoKeyPair.cs b/src/NLightning.Domain/Crypto/ValueObjects/CryptoKeyPair.cs
new file mode 100644
index 00000000..c3f9ba45
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/CryptoKeyPair.cs
@@ -0,0 +1,28 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+///
+/// A secp256k1 private/public key pair.
+///
+public readonly record struct CryptoKeyPair
+{
+ ///
+ /// Gets the private key.
+ ///
+ public PrivKey PrivKey { get; }
+
+ ///
+ /// Gets the public key.
+ ///
+ public CompactPubKey CompactPubKey { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The private key.
+ ///
+ public CryptoKeyPair(PrivKey privKey, CompactPubKey compactPubKey)
+ {
+ PrivKey = privKey;
+ CompactPubKey = compactPubKey;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/Hash.cs b/src/NLightning.Domain/Crypto/ValueObjects/Hash.cs
new file mode 100644
index 00000000..537461af
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/Hash.cs
@@ -0,0 +1,23 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+using Constants;
+
+public readonly record struct Hash
+{
+ public byte[] Value { get; }
+ public static Hash Empty => new(new byte[CryptoConstants.Sha256HashLen]);
+
+ public Hash(byte[] value)
+ {
+ if (value.Length < CryptoConstants.Sha256HashLen)
+ throw new ArgumentOutOfRangeException(nameof(value), value.Length,
+ $"Hash must have {CryptoConstants.Sha256HashLen} bytes.");
+
+ Value = value;
+ }
+
+ public static implicit operator Hash(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](Hash hash) => hash.Value;
+
+ public static implicit operator ReadOnlyMemory(Hash hash) => hash.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/PrivKey.cs b/src/NLightning.Domain/Crypto/ValueObjects/PrivKey.cs
new file mode 100644
index 00000000..366f7e79
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/PrivKey.cs
@@ -0,0 +1,29 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+using Constants;
+
+public readonly record struct PrivKey
+{
+ ///
+ /// The private key value.
+ ///
+ public byte[] Value { get; }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The private key value.
+ public PrivKey(byte[] value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+ if (value is null || value.Length != CryptoConstants.PrivkeyLen)
+ throw new ArgumentException($"Private key must be {CryptoConstants.PrivkeyLen} bytes long.", nameof(value));
+
+ Value = value;
+ }
+
+ public static implicit operator PrivKey(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](PrivKey hash) => hash.Value;
+
+ public static implicit operator ReadOnlySpan(PrivKey hash) => hash.Value;
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Crypto/ValueObjects/Secret.cs b/src/NLightning.Domain/Crypto/ValueObjects/Secret.cs
new file mode 100644
index 00000000..41c5ce61
--- /dev/null
+++ b/src/NLightning.Domain/Crypto/ValueObjects/Secret.cs
@@ -0,0 +1,40 @@
+namespace NLightning.Domain.Crypto.ValueObjects;
+
+using Constants;
+using Utils.Extensions;
+
+public readonly struct Secret : IEquatable
+{
+ private readonly byte[] _value;
+ public static Secret Empty => new(new byte[CryptoConstants.SecretLen]);
+
+ public Secret(byte[] value)
+ {
+ if (value.Length < CryptoConstants.SecretLen)
+ throw new ArgumentOutOfRangeException(nameof(value), value.Length,
+ $"Hash must have {CryptoConstants.SecretLen} bytes.");
+
+ _value = value;
+ }
+
+ public static implicit operator Secret(byte[] bytes) => new(bytes);
+ public static implicit operator byte[](Secret hash) => hash._value;
+
+ public static implicit operator ReadOnlyMemory(Secret hash) => hash._value;
+ public static implicit operator ReadOnlySpan(Secret hash) => hash._value;
+
+ public bool Equals(Secret other)
+ {
+ return _value.SequenceEqual(other._value);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is Secret other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _value.GetByteArrayHashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Enums/ChannelState.cs b/src/NLightning.Domain/Enums/ChannelState.cs
deleted file mode 100644
index 19f5e04c..00000000
--- a/src/NLightning.Domain/Enums/ChannelState.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace NLightning.Domain.Enums;
-
-public enum ChannelState
-{
- Opening,
- Open,
- Closing,
- Closed
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Enums/LightningMoneyUnit.cs b/src/NLightning.Domain/Enums/LightningMoneyUnit.cs
index 1ecb0947..4edd58b9 100644
--- a/src/NLightning.Domain/Enums/LightningMoneyUnit.cs
+++ b/src/NLightning.Domain/Enums/LightningMoneyUnit.cs
@@ -3,8 +3,8 @@ namespace NLightning.Domain.Enums;
public enum LightningMoneyUnit : ulong
{
Btc = 100_000_000_000,
- MilliBtc = 100_000_000,
- Bit = 100_000,
+ // MilliBtc = 100_000_000,
+ // Bit = 100_000,
Satoshi = 1_000,
MilliSatoshi = 1,
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Exceptions/ChannelErrorException.cs b/src/NLightning.Domain/Exceptions/ChannelErrorException.cs
new file mode 100644
index 00000000..95bc8fd7
--- /dev/null
+++ b/src/NLightning.Domain/Exceptions/ChannelErrorException.cs
@@ -0,0 +1,43 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace NLightning.Domain.Exceptions;
+
+using Channels.ValueObjects;
+
+///
+/// Represents an exception that is thrown when a channel error occurs.
+///
+///
+/// We usually want to close the connection when this exception is thrown.
+///
+[ExcludeFromCodeCoverage]
+public class ChannelErrorException : ErrorException
+{
+ public ChannelId? ChannelId { get; }
+ public string? PeerMessage { get; }
+
+ public ChannelErrorException(string message, string? peerMessage = null) : base(message)
+ {
+ PeerMessage = peerMessage;
+ }
+
+ public ChannelErrorException(string message, Exception innerException, string? peerMessage = null)
+ : base(message, innerException)
+ {
+ PeerMessage = peerMessage;
+ }
+
+ public ChannelErrorException(string message, ChannelId? channelId, string? peerMessage = null) : base(message)
+ {
+ ChannelId = channelId;
+ PeerMessage = peerMessage;
+ }
+
+ public ChannelErrorException(string message, ChannelId? channelId, Exception innerException,
+ string? peerMessage = null)
+ : base(message, innerException)
+ {
+ ChannelId = channelId;
+ PeerMessage = peerMessage;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Exceptions/ChannelException.cs b/src/NLightning.Domain/Exceptions/ChannelException.cs
deleted file mode 100644
index 9275fba5..00000000
--- a/src/NLightning.Domain/Exceptions/ChannelException.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace NLightning.Domain.Exceptions;
-
-///
-/// Represents an exception that is thrown when a channel error occurs.
-///
-///
-/// We usually want to close the connection when this exception is thrown.
-///
-[ExcludeFromCodeCoverage]
-public class ChannelException : ErrorException
-{
- public ChannelException(string message) : base(message) { }
- public ChannelException(string message, Exception innerException) : base(message, innerException) { }
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Exceptions/ChannelWarningException.cs b/src/NLightning.Domain/Exceptions/ChannelWarningException.cs
new file mode 100644
index 00000000..b120993e
--- /dev/null
+++ b/src/NLightning.Domain/Exceptions/ChannelWarningException.cs
@@ -0,0 +1,42 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace NLightning.Domain.Exceptions;
+
+using Channels.ValueObjects;
+
+///
+/// Represents an exception thrown when a channel error occurs.
+///
+///
+/// We usually want to close the connection when this exception is thrown.
+///
+[ExcludeFromCodeCoverage]
+public class ChannelWarningException : WarningException
+{
+ public ChannelId? ChannelId { get; }
+ public string? PeerMessage { get; }
+
+ public ChannelWarningException(string message, string? peerMessage = null) : base(message)
+ {
+ PeerMessage = peerMessage;
+ }
+
+ public ChannelWarningException(string message, Exception innerException, string? peerMessage = null)
+ : base(message, innerException)
+ {
+ PeerMessage = peerMessage;
+ }
+
+ public ChannelWarningException(string message, ChannelId? channelId, string? peerMessage = null) : base(message)
+ {
+ ChannelId = channelId;
+ PeerMessage = peerMessage;
+ }
+ public ChannelWarningException(string message, ChannelId? channelId, Exception innerException,
+ string? peerMessage = null)
+ : base(message, innerException)
+ {
+ ChannelId = channelId;
+ PeerMessage = peerMessage;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Exceptions/ErrorException.cs b/src/NLightning.Domain/Exceptions/ErrorException.cs
index 558312d0..a270b81d 100644
--- a/src/NLightning.Domain/Exceptions/ErrorException.cs
+++ b/src/NLightning.Domain/Exceptions/ErrorException.cs
@@ -3,7 +3,7 @@
namespace NLightning.Domain.Exceptions;
///
-/// Represents an exception that is thrown when an error occurs.
+/// Represents an exception thrown when an error occurs.
///
///
/// This exception is the base class for all exceptions that are thrown when an error occurs.
diff --git a/src/NLightning.Domain/Exceptions/SignerException.cs b/src/NLightning.Domain/Exceptions/SignerException.cs
new file mode 100644
index 00000000..710fffcd
--- /dev/null
+++ b/src/NLightning.Domain/Exceptions/SignerException.cs
@@ -0,0 +1,26 @@
+namespace NLightning.Domain.Exceptions;
+
+using Channels.ValueObjects;
+
+public class SignerException : ChannelErrorException
+{
+ public SignerException(string message, string? peerMessage = null) : base(message, peerMessage)
+ {
+ }
+
+ public SignerException(string message, Exception innerException, string? peerMessage = null)
+ : base(message, innerException, peerMessage)
+ {
+ }
+
+ public SignerException(string message, ChannelId? channelId, string? peerMessage = null)
+ : base(message, channelId, peerMessage)
+ {
+ }
+
+ public SignerException(string message, ChannelId? channelId, Exception innerException,
+ string? peerMessage = null)
+ : base(message, channelId, innerException, peerMessage)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Factories/IMessageFactory.cs b/src/NLightning.Domain/Factories/IMessageFactory.cs
deleted file mode 100644
index 00e3ec39..00000000
--- a/src/NLightning.Domain/Factories/IMessageFactory.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using NBitcoin;
-using NBitcoin.Crypto;
-using NLightning.Domain.Protocol.Messages;
-
-namespace NLightning.Domain.Factories;
-
-using Money;
-using Protocol.Messages.Interfaces;
-using ValueObjects;
-
-public interface IMessageFactory
-{
- InitMessage CreateInitMessage();
- WarningMessage CreateWarningMessage(string message, ChannelId? channelId);
- WarningMessage CreateWarningMessage(byte[] data, ChannelId? channelId);
- StfuMessage CreateStfuMessage(ChannelId channelId, bool initiator);
- ErrorMessage CreateErrorMessage(string message, ChannelId? channelId);
- ErrorMessage CreateErrorMessage(byte[] data, ChannelId? channelId);
- PingMessage CreatePingMessage();
- PongMessage CreatePongMessage(IMessage pingMessage);
- TxAddInputMessage CreateTxAddInputMessage(ChannelId channelId, ulong serialId, byte[] prevTx, uint prevTxVout,
- uint sequence);
-
- TxAddOutputMessage CreateTxAddOutputMessage(ChannelId channelId, ulong serialId, LightningMoney amount, Script script);
- TxRemoveInputMessage CreateTxRemoveInputMessage(ChannelId channelId, ulong serialId);
- TxRemoveOutputMessage CreateTxRemoveOutputMessage(ChannelId channelId, ulong serialId);
- TxCompleteMessage CreateTxCompleteMessage(ChannelId channelId);
- TxSignaturesMessage CreateTxSignaturesMessage(ChannelId channelId, byte[] txId, List witnesses);
-
- TxInitRbfMessage CreateTxInitRbfMessage(ChannelId channelId, uint locktime, uint feerate, long fundingOutputContrubution,
- bool requireConfirmedInputs);
- TxAckRbfMessage CreateTxAckRbfMessage(ChannelId channelId, long fundingOutputContrubution, bool requireConfirmedInputs);
- TxAbortMessage CreateTxAbortMessage(ChannelId channelId, byte[] data);
- ChannelReadyMessage CreateChannelReadyMessage(ChannelId channelId, PubKey secondPerCommitmentPoint,
- ShortChannelId? shortChannelId = null);
- ShutdownMessage CreateShutdownMessage(ChannelId channelId, Script scriptPubkey);
- ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulong feeSatoshis, ECDSASignature signature,
- ulong minFeeSatoshis, ulong maxFeeSatoshis);
- OpenChannel2Message CreateOpenChannel2Message(ChannelId temporaryChannelId, uint fundingFeeRatePerKw,
- uint commitmentFeeRatePerKw, ulong fundingSatoshis, PubKey fundingPubKey,
- PubKey revocationBasepoint, PubKey paymentBasepoint,
- PubKey delayedPaymentBasepoint, PubKey htlcBasepoint,
- PubKey firstPerCommitmentPoint, PubKey secondPerCommitmentPoint,
- ChannelFlags channelFlags, Script? shutdownScriptPubkey = null,
- byte[]? channelType = null, bool requireConfirmedInputs = false);
- AcceptChannel2Message CreateAcceptChannel2Message(ChannelId temporaryChannelId, LightningMoney fundingSatoshis,
- PubKey fundingPubKey, PubKey revocationBasepoint, PubKey paymentBasepoint,
- PubKey delayedPaymentBasepoint, PubKey htlcBasepoint,
- PubKey firstPerCommitmentPoint, Script? shutdownScriptPubkey = null,
- byte[]? channelType = null, bool requireConfirmedInputs = false);
- UpdateAddHtlcMessage CreateUpdateAddHtlcMessage(ChannelId channelId, ulong id, ulong amountMsat,
- ReadOnlyMemory paymentHash, uint cltvExpiry,
- ReadOnlyMemory? onionRoutingPacket = null);
- UpdateFulfillHtlcMessage CreateUpdateFulfillHtlcMessage(ChannelId channelId, ulong id, ReadOnlyMemory preimage);
- UpdateFailHtlcMessage CreateUpdateFailHtlcMessage(ChannelId channelId, ulong id, ReadOnlyMemory reason);
- CommitmentSignedMessage CreateCommitmentSignedMessage(ChannelId channelId, ECDSASignature signature,
- IEnumerable htlcSignatures);
- RevokeAndAckMessage CreateRevokeAndAckMessage(ChannelId channelId, ReadOnlyMemory perCommitmentSecret,
- PubKey nextPerCommitmentPoint);
- UpdateFeeMessage CreateUpdateFeeMessage(ChannelId channelId, uint feeratePerKw);
- UpdateFailMalformedHtlcMessage CreateUpdateFailMalformedHtlcMessage(ChannelId channelId, ulong id, ReadOnlyMemory sha256OfOnion,
- ushort failureCode);
- ChannelReestablishMessage CreateChannelReestablishMessage(ChannelId channelId, ulong nextCommitmentNumber,
- ulong nextRevocationNumber,
- ReadOnlyMemory yourLastPerCommitmentSecret,
- PubKey myCurrentPerCommitmentPoint);
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Interfaces/IValueObject.cs b/src/NLightning.Domain/Interfaces/IValueObject.cs
new file mode 100644
index 00000000..c5ce6012
--- /dev/null
+++ b/src/NLightning.Domain/Interfaces/IValueObject.cs
@@ -0,0 +1,3 @@
+namespace NLightning.Domain.Interfaces;
+
+public interface IValueObject;
\ No newline at end of file
diff --git a/src/NLightning.Domain/Models/RoutingInfo.cs b/src/NLightning.Domain/Models/RoutingInfo.cs
index e57252c3..56374630 100644
--- a/src/NLightning.Domain/Models/RoutingInfo.cs
+++ b/src/NLightning.Domain/Models/RoutingInfo.cs
@@ -1,23 +1,27 @@
-using NBitcoin;
-
namespace NLightning.Domain.Models;
-using ValueObjects;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
///
/// Represents routing information for a payment
///
-/// The public key of the node
+/// The public key of the node
/// The short channel id of the channel
/// The base fee in millisatoshis
/// The proportional fee in millionths
/// The CLTV expiry delta
-public sealed class RoutingInfo(PubKey pubKey, ShortChannelId shortChannelId, int feeBaseMsat, int feeProportionalMillionths, short cltvExpiryDelta)
+public sealed class RoutingInfo(
+ CompactPubKey compactPubKey,
+ ShortChannelId shortChannelId,
+ int feeBaseMsat,
+ int feeProportionalMillionths,
+ short cltvExpiryDelta)
{
///
/// The public key of the node
///
- public PubKey PubKey { get; } = pubKey;
+ public CompactPubKey CompactPubKey { get; } = compactPubKey;
///
/// The short channel id of the channel
diff --git a/src/NLightning.Domain/Models/RoutingInfoCollection.cs b/src/NLightning.Domain/Models/RoutingInfoCollection.cs
index 632a7138..f18959ca 100644
--- a/src/NLightning.Domain/Models/RoutingInfoCollection.cs
+++ b/src/NLightning.Domain/Models/RoutingInfoCollection.cs
@@ -12,7 +12,7 @@ public sealed class RoutingInfoCollection : List
/// The maximum length of a tagged field is 1023 * 5 bits. The routing information is 408 bits long.
/// 1023 * 5 bits = 5115 bits / 408 bits = 12.5 => round down to 12
///
- private const int MAX_CAPACITY = 12;
+ private const int MaxCapacity = 12;
public event EventHandler? Changed;
@@ -26,9 +26,9 @@ public sealed class RoutingInfoCollection : List
///
public new void Add(RoutingInfo routingInfo)
{
- if (Count >= MAX_CAPACITY)
+ if (Count >= MaxCapacity)
{
- throw new InvalidOperationException($"The maximum capacity of {MAX_CAPACITY} has been reached");
+ throw new InvalidOperationException($"The maximum capacity of {MaxCapacity} has been reached");
}
base.Add(routingInfo);
@@ -39,9 +39,9 @@ public sealed class RoutingInfoCollection : List
public new void AddRange(IEnumerable routingInfos)
{
var iEnumerable = routingInfos.ToList();
- if (Count + iEnumerable.Count > MAX_CAPACITY)
+ if (Count + iEnumerable.Count > MaxCapacity)
{
- throw new InvalidOperationException($"The maximum capacity of {MAX_CAPACITY} has been reached");
+ throw new InvalidOperationException($"The maximum capacity of {MaxCapacity} has been reached");
}
base.AddRange(iEnumerable);
diff --git a/src/NLightning.Domain/Money/LightningMoney.cs b/src/NLightning.Domain/Money/LightningMoney.cs
index d3082b44..16251da9 100644
--- a/src/NLightning.Domain/Money/LightningMoney.cs
+++ b/src/NLightning.Domain/Money/LightningMoney.cs
@@ -1,21 +1,20 @@
using System.Globalization;
-using NBitcoin;
namespace NLightning.Domain.Money;
using Enums;
-public class LightningMoney : IMoney
+public class LightningMoney
{
// For decimal.TryParse. None of the NumberStyles' composed values is useful for bitcoin style
- private const NumberStyles BITCOIN_STYLE = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
- | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
+ private const NumberStyles BitcoinStyle = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
+ | NumberStyles.AllowDecimalPoint;
private ulong _milliSatoshi;
- public const ulong COIN = 100 * 1000 * 1000 * 1000UL;
- public const ulong CENT = COIN / 100;
- public const ulong NANO = CENT / 100;
+ public const ulong Coin = 100 * 1000 * 1000 * 1000UL;
+ public const ulong Cent = Coin / 100;
+ public const ulong Nano = Cent / 100;
public ulong MilliSatoshi
{
@@ -28,7 +27,8 @@ public ulong MilliSatoshi
public long Satoshi
{
- get => checked((long)(_milliSatoshi / 1000));
+ // Should round up to the nearest Satoshi
+ get => checked((long)Math.Round(_milliSatoshi / 1_000D, MidpointRounding.ToNegativeInfinity));
set
{
if (value < 0)
@@ -45,6 +45,7 @@ public long Satoshi
public bool IsZero => _milliSatoshi == 0;
#region Constructors
+
public LightningMoney(ulong milliSatoshi)
{
MilliSatoshi = milliSatoshi;
@@ -82,9 +83,11 @@ public LightningMoney(ulong amount, LightningMoneyUnit unit)
MilliSatoshi = milliSats;
}
}
+
#endregion
#region Parsers
+
///
/// Parse a bitcoin amount (Culture Invariant)
///
@@ -95,7 +98,7 @@ public static bool TryParse(string bitcoin, out LightningMoney? nRet)
{
nRet = null;
- if (!decimal.TryParse(bitcoin, BITCOIN_STYLE, CultureInfo.InvariantCulture, out var value))
+ if (!decimal.TryParse(bitcoin, BitcoinStyle, CultureInfo.InvariantCulture, out var value))
{
return false;
}
@@ -122,11 +125,14 @@ public static bool TryParse(string bitcoin, out LightningMoney? nRet)
{
return result;
}
+
throw new FormatException("Impossible to parse the string in a bitcoin amount");
}
+
#endregion
#region Conversions
+
///
/// Convert Money to decimal (same as ToDecimal)
///
@@ -139,6 +145,7 @@ public decimal ToUnit(LightningMoneyUnit unit)
// decimal operations are checked by default
return (decimal)MilliSatoshi / (ulong)unit;
}
+
///
/// Convert Money to decimal (same as ToUnit)
///
@@ -148,6 +155,7 @@ public decimal ToDecimal(LightningMoneyUnit unit)
{
return ToUnit(unit);
}
+
#endregion
///
@@ -171,12 +179,9 @@ public IEnumerable Split(int parts)
}
}
}
- IEnumerable IMoney.Split(int parts)
- {
- return Split(parts);
- }
#region Static Converters
+
public static LightningMoney FromUnit(decimal amount, LightningMoneyUnit unit)
{
return new LightningMoney(amount, unit);
@@ -186,21 +191,21 @@ public static LightningMoney Coins(decimal coins)
{
// overflow safe.
// decimal operations are checked by default
- return new LightningMoney(coins * COIN, LightningMoneyUnit.MilliSatoshi);
+ return new LightningMoney(coins * Coin, LightningMoneyUnit.MilliSatoshi);
}
public static LightningMoney Bits(decimal bits)
{
// overflow safe.
// decimal operations are checked by default
- return new LightningMoney(bits * CENT, LightningMoneyUnit.MilliSatoshi);
+ return new LightningMoney(bits * Cent, LightningMoneyUnit.MilliSatoshi);
}
public static LightningMoney Cents(decimal cents)
{
// overflow safe.
// decimal operations are checked by default
- return new LightningMoney(cents * CENT, LightningMoneyUnit.MilliSatoshi);
+ return new LightningMoney(cents * Cent, LightningMoneyUnit.MilliSatoshi);
}
public static LightningMoney Satoshis(decimal sats)
@@ -227,32 +232,25 @@ public static LightningMoney MilliSatoshis(long sats)
{
return new LightningMoney((ulong)sats);
}
+
#endregion
#region IEquatable Members
+
public bool Equals(LightningMoney? other)
{
return other is not null && _milliSatoshi.Equals(other._milliSatoshi);
}
- bool IEquatable.Equals(IMoney? other)
- {
- return Equals(other);
- }
public int CompareTo(LightningMoney? other)
{
return other is null ? 1 : _milliSatoshi.CompareTo(other._milliSatoshi);
}
- bool IMoney.IsCompatible(IMoney money)
- {
- ArgumentNullException.ThrowIfNull(money);
-
- return money is LightningMoney;
- }
#endregion
#region IComparable Members
+
public int CompareTo(object? obj)
{
return obj switch
@@ -262,17 +260,11 @@ public int CompareTo(object? obj)
_ => _milliSatoshi.CompareTo((ulong)obj)
};
}
- int IComparable.CompareTo(object? obj)
- {
- return CompareTo(obj);
- }
- int IComparable.CompareTo(IMoney? other)
- {
- return CompareTo(other);
- }
+
#endregion
#region Unary Operators
+
public static LightningMoney operator -(LightningMoney left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -283,10 +275,12 @@ int IComparable.CompareTo(IMoney? other)
return new LightningMoney(checked(left._milliSatoshi - right._milliSatoshi));
}
+
public static LightningMoney operator -(LightningMoney _)
{
throw new ArithmeticException("LightningMoney does not support unary negation");
}
+
public static LightningMoney operator +(LightningMoney left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -294,6 +288,7 @@ int IComparable.CompareTo(IMoney? other)
return new LightningMoney(checked(left._milliSatoshi + right._milliSatoshi));
}
+
public static LightningMoney operator *(ulong left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(right);
@@ -307,12 +302,14 @@ int IComparable.CompareTo(IMoney? other)
return MilliSatoshis(checked(left._milliSatoshi * right));
}
+
public static LightningMoney operator *(long left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(right);
return MilliSatoshis(checked((ulong)left * right._milliSatoshi));
}
+
public static LightningMoney operator *(LightningMoney left, long right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -320,6 +317,20 @@ int IComparable.CompareTo(IMoney? other)
return MilliSatoshis(checked((ulong)right * left._milliSatoshi));
}
+ public static LightningMoney operator *(decimal left, LightningMoney right)
+ {
+ ArgumentNullException.ThrowIfNull(right);
+
+ return MilliSatoshis((ulong)(left * right._milliSatoshi));
+ }
+
+ public static LightningMoney operator *(LightningMoney left, decimal right)
+ {
+ ArgumentNullException.ThrowIfNull(left);
+
+ return MilliSatoshis((ulong)(right * left._milliSatoshi));
+ }
+
public static LightningMoney operator /(LightningMoney left, ulong right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -334,6 +345,7 @@ int IComparable.CompareTo(IMoney? other)
return left._milliSatoshi < right._milliSatoshi;
}
+
public static bool operator >(LightningMoney left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -341,6 +353,7 @@ int IComparable.CompareTo(IMoney? other)
return left._milliSatoshi > right._milliSatoshi;
}
+
public static bool operator <=(LightningMoney left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -348,6 +361,7 @@ int IComparable.CompareTo(IMoney? other)
return left._milliSatoshi <= right._milliSatoshi;
}
+
public static bool operator >=(LightningMoney left, LightningMoney right)
{
ArgumentNullException.ThrowIfNull(left);
@@ -355,24 +369,25 @@ int IComparable.CompareTo(IMoney? other)
return left._milliSatoshi >= right._milliSatoshi;
}
+
#endregion
#region Implicit Operators
+
public static implicit operator LightningMoney(long value)
{
return new LightningMoney((ulong)value);
}
+
public static implicit operator LightningMoney(ulong value)
{
return new LightningMoney(value);
}
- public static implicit operator LightningMoney(NBitcoin.Money value)
- {
- return new LightningMoney((ulong)value.Satoshi * 1_000UL);
- }
+
public static implicit operator LightningMoney(string value)
{
- return Parse(value) ?? throw new ArgumentException("Cannot parse value into a valid LightningMoney", nameof(value));
+ return Parse(value) ??
+ throw new ArgumentException("Cannot parse value into a valid LightningMoney", nameof(value));
}
public static implicit operator long(LightningMoney value)
@@ -385,13 +400,10 @@ public static implicit operator ulong(LightningMoney value)
return value.MilliSatoshi;
}
- public static implicit operator NBitcoin.Money(LightningMoney value)
- {
- return new NBitcoin.Money(value.Satoshi);
- }
#endregion
#region Equality Operators
+
public override bool Equals(object? obj)
{
return obj is LightningMoney item && _milliSatoshi.Equals(item._milliSatoshi);
@@ -415,9 +427,11 @@ public override int GetHashCode()
{
return !(a == b);
}
+
#endregion
#region ToString
+
///
/// Returns a culture invariant string representation of Bitcoin amount
///
@@ -426,6 +440,7 @@ public override string ToString()
{
return ToString(false);
}
+
///
/// Returns a culture invariant string representation of Bitcoin amount
///
@@ -441,6 +456,7 @@ public string ToString(bool trimExcessZero = true)
return string.Format(CultureInfo.InvariantCulture, fmt, val);
}
+
#endregion
///
@@ -490,20 +506,22 @@ public static LightningMoney Max(LightningMoney a, LightningMoney b)
}
#region IMoney Members
- public IMoney Add(IMoney money)
+
+ public LightningMoney Add(LightningMoney money)
{
- return this + (LightningMoney)money;
+ return this + money;
}
- public IMoney Sub(IMoney money)
+ public LightningMoney Sub(LightningMoney money)
{
- return this - (LightningMoney)money;
+ return this - money;
}
- public IMoney Negate()
+ public LightningMoney Negate()
{
throw new ArithmeticException("LightningMoney does not support unary negation");
}
+
#endregion
private static void CheckLightningMoneyUnit(LightningMoneyUnit value, string paramName)
diff --git a/src/NLightning.Domain/NLightning.Domain.csproj b/src/NLightning.Domain/NLightning.Domain.csproj
index 9019e6ca..9b00262b 100644
--- a/src/NLightning.Domain/NLightning.Domain.csproj
+++ b/src/NLightning.Domain/NLightning.Domain.csproj
@@ -37,10 +37,6 @@
true
-
-
-
-
True
diff --git a/src/NLightning.Domain/Node/FeatureSet.cs b/src/NLightning.Domain/Node/FeatureSet.cs
index ec7edb44..9cb56933 100644
--- a/src/NLightning.Domain/Node/FeatureSet.cs
+++ b/src/NLightning.Domain/Node/FeatureSet.cs
@@ -1,11 +1,11 @@
using System.Collections;
using System.Runtime.Serialization;
using System.Text;
+using NLightning.Domain.Utils.Interfaces;
namespace NLightning.Domain.Node;
using Enums;
-using Serialization;
///
/// Represents the features supported by a node. BOLT-9
@@ -17,14 +17,14 @@ public class FeatureSet
///
private static readonly Dictionary s_featureDependencies = new()
{
- // This \/ Depends on this \/
- { Feature.GossipQueriesEx, [Feature.GossipQueries] },
- { Feature.PaymentSecret, [Feature.VarOnionOptin] },
- { Feature.BasicMpp, [Feature.PaymentSecret] },
- { Feature.OptionAnchorOutputs, [Feature.OptionStaticRemoteKey] },
+ // This \/ --- Depends on this \/
+ { Feature.GossipQueriesEx, [Feature.GossipQueries] },
+ { Feature.PaymentSecret, [Feature.VarOnionOptin] },
+ { Feature.BasicMpp, [Feature.PaymentSecret] },
+ { Feature.OptionAnchorOutputs, [Feature.OptionStaticRemoteKey] },
{ Feature.OptionAnchorsZeroFeeHtlcTx, [Feature.OptionStaticRemoteKey] },
- { Feature.OptionRouteBlinding, [Feature.VarOnionOptin] },
- { Feature.OptionZeroconf, [Feature.OptionScidAlias] },
+ { Feature.OptionRouteBlinding, [Feature.VarOnionOptin] },
+ { Feature.OptionZeroconf, [Feature.OptionScidAlias] },
};
internal BitArray FeatureFlags;
@@ -45,7 +45,7 @@ public FeatureSet()
public event EventHandler? Changed;
///
- /// Gets the position of the last index of one in the BitArray and add 1 because arrays starts at 0.
+ /// Gets the last index-of-one in the BitArray and add 1 because arrays starts at 0.
///
public int SizeInBits => GetLastIndexOfOne(FeatureFlags);
@@ -67,17 +67,13 @@ public void SetFeature(Feature feature, bool isCompulsory, bool isSet = true)
if (s_featureDependencies.TryGetValue(feature, out var dependencies))
{
foreach (var dependency in dependencies)
- {
SetFeature(dependency, isCompulsory, isSet);
- }
}
}
else // If we're unsetting the feature, and it has dependents, unset them first
{
foreach (var dependent in s_featureDependencies.Where(x => x.Value.Contains(feature)).Select(x => x.Key))
- {
SetFeature(dependent, isCompulsory, isSet);
- }
}
var bitPosition = (int)feature;
@@ -97,6 +93,7 @@ public void SetFeature(Feature feature, bool isCompulsory, bool isSet = true)
// Then set the feature itself
SetFeature(bitPosition, isSet);
}
+
///
/// Sets a feature.
///
@@ -105,15 +102,25 @@ public void SetFeature(Feature feature, bool isCompulsory, bool isSet = true)
public void SetFeature(int bitPosition, bool isSet)
{
if (bitPosition >= FeatureFlags.Length)
- {
FeatureFlags.Length = bitPosition + 1;
- }
FeatureFlags.Set(bitPosition, isSet);
OnChanged();
}
+ ///
+ /// Checks if a feature is set either as compulsory or optional.
+ ///
+ /// Feature to check.
+ /// true if the feature is set, false otherwise.
+ public bool IsFeatureSet(Feature feature)
+ {
+ var bitPosition = (int)feature;
+
+ return IsFeatureSet(bitPosition) || IsFeatureSet(bitPosition - 1);
+ }
+
///
/// Checks if a feature is set.
///
@@ -126,12 +133,11 @@ public bool IsFeatureSet(Feature feature, bool isCompulsory)
// If the feature is compulsory, adjust the bit position to be even
if (isCompulsory)
- {
bitPosition--;
- }
return IsFeatureSet(bitPosition);
}
+
///
/// Checks if a feature is set.
///
@@ -142,12 +148,11 @@ public bool IsFeatureSet(int bitPosition, bool isCompulsory)
{
// If the feature is compulsory, adjust the bit position to be even
if (isCompulsory)
- {
bitPosition--;
- }
return IsFeatureSet(bitPosition);
}
+
///
/// Checks if a feature is set.
///
@@ -155,74 +160,96 @@ public bool IsFeatureSet(int bitPosition, bool isCompulsory)
/// true if the feature is set, false otherwise.
private bool IsFeatureSet(int bitPosition)
{
- if (bitPosition >= FeatureFlags.Length)
- {
- return false;
- }
-
- return FeatureFlags.Get(bitPosition);
+ return bitPosition < FeatureFlags.Length && FeatureFlags.Get(bitPosition);
}
///
/// Checks if the option_anchor_outputs or option_anchors_zero_fee_htlc_tx feature is set.
///
- /// true if one of the features are set, false otherwise.
+ /// true if one of the features is set, false otherwise.
public bool IsOptionAnchorsSet()
{
- return IsFeatureSet(Feature.OptionAnchorOutputs, false) || IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, false);
+ return IsFeatureSet(Feature.OptionAnchorOutputs, false) ||
+ IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, false);
}
///
/// Check if this feature set is compatible with the other provided feature set.
///
/// The other feature set to check compatibility with.
+ /// The resulting negotiated feature set.
/// true if the feature sets are compatible, false otherwise.
///
/// The other feature set must support the var_onion_optin feature.
- /// The other feature set must have all dependencies set.
+ /// The other feature set must have all the dependencies set.
///
- public bool IsCompatible(FeatureSet other)
+ public bool IsCompatible(FeatureSet other, out FeatureSet? negotiatedFeatureSet)
{
// Check if the other node supports var_onion_optin
if (!other.IsFeatureSet(Feature.VarOnionOptin, false) && !other.IsFeatureSet(Feature.VarOnionOptin, true))
{
+ negotiatedFeatureSet = null;
return false;
}
- for (var i = 1; i < FeatureFlags.Length; i += 2)
+ // Check which one is bigger and iterate on it
+ var maxLength = Math.Max(FeatureFlags.Length, other.FeatureFlags.Length);
+
+ // Create a temporary feature set to store the negotiated features
+ negotiatedFeatureSet = new FeatureSet();
+ for (var i = 1; i < maxLength; i += 2)
{
var isLocalOptionalSet = IsFeatureSet(i, false);
var isLocalCompulsorySet = IsFeatureSet(i, true);
var isOtherOptionalSet = other.IsFeatureSet(i, false);
var isOtherCompulsorySet = other.IsFeatureSet(i, true);
- // If feature is unknown
+ // If the feature is unknown
if (!Enum.IsDefined(typeof(Feature), i))
{
// If the feature is unknown and even, close the connection
if (isOtherCompulsorySet)
{
+ negotiatedFeatureSet = null;
return false;
}
+
+ if (isOtherOptionalSet)
+ negotiatedFeatureSet.SetFeature(i, false);
}
else
{
// If the local feature is compulsory, the other feature should also be set (either optional or compulsory)
if (isLocalCompulsorySet && !(isOtherOptionalSet || isOtherCompulsorySet))
{
+ negotiatedFeatureSet = null;
return false;
}
// If the other feature is compulsory, the local feature should also be set (either optional or compulsory)
if (isOtherCompulsorySet && !(isLocalOptionalSet || isLocalCompulsorySet))
{
+ negotiatedFeatureSet = null;
return false;
}
+
+ if (isOtherCompulsorySet || isLocalCompulsorySet)
+ {
+ negotiatedFeatureSet.SetFeature(i, true);
+ }
+ else if (isLocalOptionalSet && isOtherOptionalSet)
+ {
+ negotiatedFeatureSet.SetFeature(i, false);
+ }
}
}
// Check if all the other node's dependencies are set
- return other.AreDependenciesSet();
+ if (other.AreDependenciesSet())
+ return true;
+
+ negotiatedFeatureSet = null;
+ return false;
}
///
@@ -233,14 +260,10 @@ public void WriteToBitWriter(IBitWriter bitWriter, int length, bool shouldPad)
// Check if _featureFlags is as long as the length
var extraLength = length - FeatureFlags.Length;
if (extraLength > 0)
- {
FeatureFlags.Length += extraLength;
- }
for (var i = 0; i < length && bitWriter.HasMoreBits(1); i++)
- {
bitWriter.WriteBit(FeatureFlags[length - i - (shouldPad ? 0 : 1)]);
- }
}
///
@@ -251,11 +274,7 @@ public void WriteToBitWriter(IBitWriter bitWriter, int length, bool shouldPad)
///
/// We don't care if the feature is compulsory or optional.
///
- public bool HasFeature(Feature feature)
- {
- // Check if feature is either set as compulsory or optional
- return IsFeatureSet(feature, false) || IsFeatureSet(feature, true);
- }
+ public bool HasFeature(Feature feature) => IsFeatureSet(feature, false) || IsFeatureSet(feature, true);
///
/// Deserializes the features from a byte array.
@@ -271,9 +290,7 @@ public static FeatureSet DeserializeFromBytes(byte[] data)
try
{
if (BitConverter.IsLittleEndian)
- {
Array.Reverse(data);
- }
var bitArray = new BitArray(data);
return new FeatureSet { FeatureFlags = bitArray };
@@ -299,9 +316,7 @@ public static FeatureSet DeserializeFromBitReader(IBitReader bitReader, int leng
// Create a new bit array
var bitArray = new BitArray(length + (shouldPad ? 1 : 0));
for (var i = 0; i < length; i++)
- {
bitArray.Set(length - i - (shouldPad ? 0 : 1), bitReader.ReadBit());
- }
return new FeatureSet { FeatureFlags = bitArray };
}
@@ -326,9 +341,7 @@ public static FeatureSet Combine(FeatureSet first, FeatureSet second)
var combinedFlags = new BitArray(combinedLength);
for (var i = 0; i < combinedLength; i++)
- {
combinedFlags.Set(i, first.IsFeatureSet(i) || second.IsFeatureSet(i));
- }
return new FeatureSet { FeatureFlags = combinedFlags };
}
@@ -339,9 +352,7 @@ public override string ToString()
for (var i = 0; i < FeatureFlags.Length; i++)
{
if (IsFeatureSet(i))
- {
sb.Append($"{(Feature)i}, ");
- }
}
return sb.ToString().TrimEnd(' ', ',');
@@ -360,19 +371,13 @@ private bool AreDependenciesSet()
foreach (var feature in Enum.GetValues())
{
if (!IsFeatureSet((int)feature, false) && !IsFeatureSet((int)feature, true))
- {
continue;
- }
if (!s_featureDependencies.TryGetValue(feature, out var dependencies))
- {
continue;
- }
if (dependencies.Any(dependency => !IsFeatureSet(dependency, false) && !IsFeatureSet(dependency, true)))
- {
return false;
- }
}
return true;
@@ -388,10 +393,9 @@ private static int GetLastIndexOfOne(BitArray bitArray)
for (var i = bitArray.Length - 1; i >= 0; i--)
{
if (bitArray[i])
- {
return i;
- }
}
- return -1; // Return -1 if no 1 is found
+
+ return -1; // Return -1 if no number 1 is found
}
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Node/Models/Peer.cs b/src/NLightning.Domain/Node/Models/Peer.cs
new file mode 100644
index 00000000..c0fa0eee
--- /dev/null
+++ b/src/NLightning.Domain/Node/Models/Peer.cs
@@ -0,0 +1,18 @@
+namespace NLightning.Domain.Node.Models;
+
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
+using ValueObjects;
+
+public class Peer
+{
+ public CompactPubKey CompactPubKey { get; set; }
+ public PeerNodeInfo NodeInfo { get; set; }
+ public List Channels { get; set; } = [];
+
+ public Peer(CompactPubKey compactPubKey, PeerNodeInfo nodeInfo)
+ {
+ CompactPubKey = compactPubKey;
+ NodeInfo = nodeInfo;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Node/Options/FeatureOptions.cs b/src/NLightning.Domain/Node/Options/FeatureOptions.cs
index 1d922b8a..55de2e95 100644
--- a/src/NLightning.Domain/Node/Options/FeatureOptions.cs
+++ b/src/NLightning.Domain/Node/Options/FeatureOptions.cs
@@ -2,11 +2,12 @@
namespace NLightning.Domain.Node.Options;
+using Domain.Crypto.Constants;
+using Domain.Protocol.ValueObjects;
using Enums;
using Protocol.Constants;
using Protocol.Models;
using Protocol.Tlv;
-using ValueObjects;
public class FeatureOptions
{
@@ -244,7 +245,7 @@ internal NetworksTlv GetInitTlvs()
// If there are no ChainHashes, use Mainnet as default
if (!ChainHashes.Any())
{
- ChainHashes = [ChainConstants.MAIN];
+ ChainHashes = [ChainConstants.Main];
}
return new NetworksTlv(ChainHashes);
@@ -268,106 +269,107 @@ public static FeatureOptions GetNodeOptions(FeatureSet featureSet, TlvStream? ex
var options = new FeatureOptions
{
DataLossProtect = featureSet.IsFeatureSet(Feature.OptionDataLossProtect, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionDataLossProtect, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionDataLossProtect, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
InitialRoutingSync = featureSet.IsFeatureSet(Feature.InitialRoutingSync, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.InitialRoutingSync, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.InitialRoutingSync, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
UpfrontShutdownScript = featureSet.IsFeatureSet(Feature.OptionUpfrontShutdownScript, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionUpfrontShutdownScript, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionUpfrontShutdownScript, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
GossipQueries = featureSet.IsFeatureSet(Feature.GossipQueries, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.GossipQueries, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.GossipQueries, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
ExpandedGossipQueries = featureSet.IsFeatureSet(Feature.GossipQueriesEx, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.GossipQueriesEx, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.GossipQueriesEx, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
StaticRemoteKey = featureSet.IsFeatureSet(Feature.OptionStaticRemoteKey, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionStaticRemoteKey, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionStaticRemoteKey, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
PaymentSecret = featureSet.IsFeatureSet(Feature.PaymentSecret, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.PaymentSecret, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.PaymentSecret, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
BasicMpp = featureSet.IsFeatureSet(Feature.BasicMpp, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.BasicMpp, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.BasicMpp, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
LargeChannels = featureSet.IsFeatureSet(Feature.OptionSupportLargeChannel, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionSupportLargeChannel, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionSupportLargeChannel, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
AnchorOutputs = featureSet.IsFeatureSet(Feature.OptionAnchorOutputs, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionAnchorOutputs, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionAnchorOutputs, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
ZeroFeeAnchorTx = featureSet.IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
RouteBlinding = featureSet.IsFeatureSet(Feature.OptionRouteBlinding, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionRouteBlinding, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionRouteBlinding, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
BeyondSegwitShutdown = featureSet.IsFeatureSet(Feature.OptionShutdownAnySegwit, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionShutdownAnySegwit, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionShutdownAnySegwit, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
DualFund = featureSet.IsFeatureSet(Feature.OptionDualFund, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionDualFund, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionDualFund, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
OnionMessages = featureSet.IsFeatureSet(Feature.OptionOnionMessages, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionOnionMessages, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionOnionMessages, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
ChannelType = featureSet.IsFeatureSet(Feature.OptionChannelType, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionChannelType, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionChannelType, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
ScidAlias = featureSet.IsFeatureSet(Feature.OptionScidAlias, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionScidAlias, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionScidAlias, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
PaymentMetadata = featureSet.IsFeatureSet(Feature.OptionPaymentMetadata, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionPaymentMetadata, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No,
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionPaymentMetadata, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No,
ZeroConf = featureSet.IsFeatureSet(Feature.OptionZeroconf, true)
- ? FeatureSupport.Compulsory
- : featureSet.IsFeatureSet(Feature.OptionZeroconf, false)
- ? FeatureSupport.Optional
- : FeatureSupport.No
+ ? FeatureSupport.Compulsory
+ : featureSet.IsFeatureSet(Feature.OptionZeroconf, false)
+ ? FeatureSupport.Optional
+ : FeatureSupport.No
};
if (extension?.TryGetTlv(new BigSize(1), out var chainHashes) ?? false)
{
- options.ChainHashes = Enumerable.Range(0, chainHashes!.Value.Length / ChainHash.LENGTH)
- .Select(i => new ChainHash(chainHashes.Value.Skip(i * 32).Take(32).ToArray()));
+ options.ChainHashes = Enumerable.Range(0, chainHashes!.Value.Length / CryptoConstants.Sha256HashLen)
+ .Select(i => new ChainHash(
+ chainHashes.Value.Skip(i * 32).Take(32).ToArray()));
}
// TODO: Add network when implementing BOLT7
diff --git a/src/NLightning.Domain/Node/Options/NodeOptions.cs b/src/NLightning.Domain/Node/Options/NodeOptions.cs
index e0b8440a..2edd6e8e 100644
--- a/src/NLightning.Domain/Node/Options/NodeOptions.cs
+++ b/src/NLightning.Domain/Node/Options/NodeOptions.cs
@@ -1,13 +1,17 @@
namespace NLightning.Domain.Node.Options;
using Money;
+using Protocol.Constants;
+using Protocol.ValueObjects;
public class NodeOptions
{
+ // private FeatureOptions _features;
+
///
/// The network to connect to. Can be "mainnet", "testnet", or "regtest"
///
- public string Network { get; set; } = "mainnet";
+ public BitcoinNetwork BitcoinNetwork { get; set; } = NetworkConstants.Mainnet;
///
/// True if NLTG should run in Daemon mode (background)
@@ -38,20 +42,20 @@ public class NodeOptions
///
public TimeSpan NetworkTimeout { get; set; } = TimeSpan.FromSeconds(15);
- public LightningMoney AnchorAmount { get; set; } = LightningMoney.Satoshis(330);
-
public bool MustTrimHtlcOutputs { get; set; }
- public LightningMoney DustLimitAmount { get; set; } = LightningMoney.Satoshis(546);
+ public LightningMoney DustLimitAmount { get; set; } = LightningMoney.Satoshis(354);
public ulong DefaultCltvExpiry { get; set; }
- public bool HasAnchorOutputs => !AnchorAmount.IsZero;
+ public bool HasAnchorOutputs { get; set; }
- public ushort MaxAcceptedHtlcs { get; set; }
+ public ushort MaxAcceptedHtlcs { get; set; } = 5;
public LightningMoney HtlcMinimumAmount { get; set; } = LightningMoney.Satoshis(1);
public uint Locktime { get; set; }
- public ushort ToSelfDelay { get; set; }
- public LightningMoney MaxHtlcValueInFlight { get; set; } = LightningMoney.Satoshis(1_000_000);
+ public ushort ToSelfDelay { get; set; } = 144;
+ public uint AllowUpToPercentageOfChannelFundsInFlight { get; set; } = 80;
public uint MinimumDepth { get; set; } = 3;
+ public LightningMoney MinimumChannelSize { get; set; } = LightningMoney.Satoshis(20_000);
+ public LightningMoney ChannelReserveAmount { get; set; } = LightningMoney.Satoshis(546);
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Node/ValueObjects/PeerNodeInfo.cs b/src/NLightning.Domain/Node/ValueObjects/PeerNodeInfo.cs
new file mode 100644
index 00000000..578fc135
--- /dev/null
+++ b/src/NLightning.Domain/Node/ValueObjects/PeerNodeInfo.cs
@@ -0,0 +1,5 @@
+namespace NLightning.Domain.Node.ValueObjects;
+
+using Interfaces;
+
+public readonly record struct PeerNodeInfo(string Address) : IValueObject;
\ No newline at end of file
diff --git a/src/NLightning.Domain/Persistence/Interfaces/IUnitOfWork.cs b/src/NLightning.Domain/Persistence/Interfaces/IUnitOfWork.cs
new file mode 100644
index 00000000..472aec18
--- /dev/null
+++ b/src/NLightning.Domain/Persistence/Interfaces/IUnitOfWork.cs
@@ -0,0 +1,14 @@
+namespace NLightning.Domain.Persistence.Interfaces;
+
+using Channels.Interfaces;
+
+public interface IUnitOfWork : IDisposable
+{
+ IChannelConfigDbRepository ChannelConfigDbRepository { get; }
+ IChannelDbRepository ChannelDbRepository { get; }
+ IChannelKeySetDbRepository ChannelKeySetDbRepository { get; }
+ IHtlcDbRepository HtlcDbRepository { get; }
+
+ void SaveChanges();
+ Task SaveChangesAsync();
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/ChainConstants.cs b/src/NLightning.Domain/Protocol/Constants/ChainConstants.cs
index 2eed0616..da8683a0 100644
--- a/src/NLightning.Domain/Protocol/Constants/ChainConstants.cs
+++ b/src/NLightning.Domain/Protocol/Constants/ChainConstants.cs
@@ -13,21 +13,21 @@ namespace NLightning.Domain.Protocol.Constants;
[ExcludeFromCodeCoverage]
public static class ChainConstants
{
- #pragma warning disable format
+#pragma warning disable format
///
/// The main chain.
///
- public static readonly ChainHash MAIN = new([
+ public static readonly ChainHash Main = new([
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00
]);
-
+
///
/// The testnet chain.
///
- public static readonly ChainHash TESTNET = new([
+ public static readonly ChainHash Testnet = new([
0x43, 0x49, 0x4f, 0x77, 0xd7, 0x8f, 0x26, 0x95,
0x71, 0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3,
0xae, 0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e,
@@ -37,11 +37,11 @@ public static class ChainConstants
///
/// The regtest chain.
///
- public static readonly ChainHash REGTEST = new([
+ public static readonly ChainHash Regtest = new([
0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59,
0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf,
0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f,
0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f
]);
- #pragma warning restore format
+#pragma warning restore format
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/InteractiveTransactionConstants.cs b/src/NLightning.Domain/Protocol/Constants/InteractiveTransactionConstants.cs
index a5529c55..4e1888e7 100644
--- a/src/NLightning.Domain/Protocol/Constants/InteractiveTransactionConstants.cs
+++ b/src/NLightning.Domain/Protocol/Constants/InteractiveTransactionConstants.cs
@@ -5,9 +5,9 @@ namespace NLightning.Domain.Protocol.Constants;
[ExcludeFromCodeCoverage]
public static class InteractiveTransactionConstants
{
- public const int MAX_INPUTS_ALLOWED = 252;
- public const uint MAX_SEQUENCE = 0xFFFFFFFD;
- public const int MAX_OUTPUTS_ALLOWED = 252;
- public const ulong MAX_MONEY = 2_100_000_000_000_000;
- public const ulong MAX_STANDARD_TX_WEIGHT = 400_000;
+ public const int MaxInputsAllowed = 252;
+ public const uint MaxSequence = 0xFFFFFFFD;
+ public const int MaxOutputsAllowed = 252;
+ public const ulong MaxMoney = 2_100_000_000_000_000;
+ public const ulong MaxStandardTxWeight = 400_000;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/MessageTypes.cs b/src/NLightning.Domain/Protocol/Constants/MessageTypes.cs
index 6009b9e7..f9afea4c 100644
--- a/src/NLightning.Domain/Protocol/Constants/MessageTypes.cs
+++ b/src/NLightning.Domain/Protocol/Constants/MessageTypes.cs
@@ -1,61 +1,68 @@
-using System.Diagnostics.CodeAnalysis;
-
namespace NLightning.Domain.Protocol.Constants;
///
/// Represents the message types used in the Lightning Network.
///
-[ExcludeFromCodeCoverage]
-public static class MessageTypes
+public enum MessageTypes : ushort
{
#region Setup & Control
- public const ushort WARNING = 1,
- STFU = 2,
- INIT = 16,
- ERROR = 17,
- PING = 18,
- PONG = 19;
+
+ Warning = 1,
+ Stfu = 2,
+ Init = 16,
+ Error = 17,
+ Ping = 18,
+ Pong = 19,
+
#endregion
#region Channel
- public const ushort OPEN_CHANNEL = 32, // NOT IMPLEMENTED
- ACCEPT_CHANNEL = 33, // NOT IMPLEMENTED
- FUNDING_CREATED = 34, // NOT IMPLEMENTED
- FUNDING_SIGNED = 35, // NOT IMPLEMENTED
- CHANNEL_READY = 36,
- SHUTDOWN = 38,
- CLOSING_SIGNED = 39,
- OPEN_CHANNEL_2 = 64,
- ACCEPT_CHANNEL_2 = 65;
+
+ OpenChannel = 32,
+ AcceptChannel = 33,
+ FundingCreated = 34,
+ FundingSigned = 35,
+ ChannelReady = 36,
+ Shutdown = 38,
+ ClosingSigned = 39,
+ OpenChannel2 = 64,
+ AcceptChannel2 = 65,
+
#endregion
#region Interactive Transaction Construction
- public const ushort TX_ADD_INPUT = 66,
- TX_ADD_OUTPUT = 67,
- TX_REMOVE_INPUT = 68,
- TX_REMOVE_OUTPUT = 69,
- TX_COMPLETE = 70,
- TX_SIGNATURES = 71,
- TX_INIT_RBF = 72,
- TX_ACK_RBF = 73,
- TX_ABORT = 74;
+
+ TxAddInput = 66,
+ TxAddOutput = 67,
+ TxRemoveInput = 68,
+ TxRemoveOutput = 69,
+ TxComplete = 70,
+ TxSignatures = 71,
+ TxInitRbf = 72,
+ TxAckRbf = 73,
+ TxAbort = 74,
+
#endregion
#region Commitment
- public const ushort UPDATE_ADD_HTLC = 128,
- UPDATE_FULFILL_HTLC = 130,
- UPDATE_FAIL_HTLC = 131,
- COMMITMENT_SIGNED = 132,
- REVOKE_AND_ACK = 133,
- UPDATE_FEE = 134,
- UPDATE_FAIL_MALFORMED_HTLC = 135,
- CHANNEL_REESTABLISH = 136;
+
+ UpdateAddHtlc = 128,
+ UpdateFulfillHtlc = 130,
+ UpdateFailHtlc = 131,
+ CommitmentSigned = 132,
+ RevokeAndAck = 133,
+ UpdateFee = 134,
+ UpdateFailMalformedHtlc = 135,
+ ChannelReestablish = 136,
+
#endregion
#region Routing
- public const ushort ANNOUNCEMENT_SIGNATURES = 259,
- CHANNEL_ANNOUNCEMENT = 256,
- NODE_ANNOUNCEMENT = 257,
- CHANNEL_UPDATE = 258;
+
+ AnnouncementSignatures = 259,
+ ChannelAnnouncement = 256,
+ NodeAnnouncement = 257,
+ ChannelUpdate = 258
+
#endregion
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/NetworkConstants.cs b/src/NLightning.Domain/Protocol/Constants/NetworkConstants.cs
index 544b4c20..85dc114b 100644
--- a/src/NLightning.Domain/Protocol/Constants/NetworkConstants.cs
+++ b/src/NLightning.Domain/Protocol/Constants/NetworkConstants.cs
@@ -11,8 +11,8 @@ namespace NLightning.Domain.Protocol.Constants;
[ExcludeFromCodeCoverage]
public static class NetworkConstants
{
- public const string MAINNET = "mainnet";
- public const string TESTNET = "testnet";
- public const string REGTEST = "regtest";
- public const string SIGNET = "signet";
+ public const string Mainnet = "mainnet";
+ public const string Testnet = "testnet";
+ public const string Regtest = "regtest";
+ public const string Signet = "signet";
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/TlvConstants.cs b/src/NLightning.Domain/Protocol/Constants/TlvConstants.cs
index 27b05f10..487f16b6 100644
--- a/src/NLightning.Domain/Protocol/Constants/TlvConstants.cs
+++ b/src/NLightning.Domain/Protocol/Constants/TlvConstants.cs
@@ -16,7 +16,7 @@ public static class TlvConstants
///
/// The funding output contribution TLV type is used in the TxInitRbfMessage to communicate the funding output contribution in satoshis.
///
- public static readonly BigSize FUNDING_OUTPUT_CONTRIBUTION = 0;
+ public static readonly BigSize FundingOutputContribution = 0;
///
/// Networks TLV type.
@@ -24,7 +24,7 @@ public static class TlvConstants
///
/// The networks TLV type is used in the InitMessage to communicate the networks that the node supports.
///
- public static readonly BigSize NETWORKS = 1;
+ public static readonly BigSize Networks = 1;
///
/// Remote address TLV type.
@@ -32,7 +32,7 @@ public static class TlvConstants
///
/// The remote address TLV type is used in the InitMessage to communicate the remote address of the node.
///
- public static readonly BigSize REMOTE_ADDRESS = 3;
+ public static readonly BigSize RemoteAddress = 3;
///
/// Upfront Shutdown Script TLV Type
@@ -41,7 +41,7 @@ public static class TlvConstants
/// The "Upfront Shutdown Script" is used in the OpenChannel2Message to set where the funds should be sent on a
/// mutual close
///
- public static readonly BigSize UPFRONT_SHUTDOWN_SCRIPT = 0;
+ public static readonly BigSize UpfrontShutdownScript = 0;
///
/// Channel Type TLV Type
@@ -49,7 +49,7 @@ public static class TlvConstants
///
/// The "Channel Type" is used in the OpenChannel2Message to set the channel type being oppened
///
- public static readonly BigSize CHANNEL_TYPE = 1;
+ public static readonly BigSize ChannelType = 1;
///
/// Require confirmed inputs TLV type.
@@ -58,7 +58,7 @@ public static class TlvConstants
/// The "Require Confirmed Inputs" TLV type is used in the TxInitRbfMessage and OpenChannel2Message to communicate
/// if the node requires confirmed inputs.
///
- public static readonly BigSize REQUIRE_CONFIRMED_INPUTS = 2;
+ public static readonly BigSize RequireConfirmedInputs = 2;
///
/// Fee Range TLV Type
@@ -66,7 +66,7 @@ public static class TlvConstants
///
/// The "Fee Range" is used in the ClosingSignedMessage to set the acceptable fee range
///
- public static readonly BigSize FEE_RANGE = 1;
+ public static readonly BigSize FeeRange = 1;
///
/// Short Channel ID TLV Type
@@ -74,7 +74,7 @@ public static class TlvConstants
///
/// The "Short Channel ID" is used in the ChannelReadyMessage
///
- public static readonly BigSize SHORT_CHANNEL_ID = 1;
+ public static readonly BigSize ShortChannelId = 1;
///
/// Blinded Path TLV Type
@@ -82,7 +82,7 @@ public static class TlvConstants
///
/// The "Blinded Path" is used in the UpdateAddHtlcMessage
///
- public static readonly BigSize BLINDED_PATH = 0;
+ public static readonly BigSize BlindedPath = 0;
///
/// Next Funding TLV Type
@@ -90,5 +90,5 @@ public static class TlvConstants
///
/// The "Next Funding" is used in the ChannelReestablishMessage
///
- public static readonly BigSize NEXT_FUNDING = 0;
+ public static readonly BigSize NextFunding = 0;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/TransactionConstants.cs b/src/NLightning.Domain/Protocol/Constants/TransactionConstants.cs
deleted file mode 100644
index 44c971ff..00000000
--- a/src/NLightning.Domain/Protocol/Constants/TransactionConstants.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace NLightning.Domain.Protocol.Constants;
-
-[ExcludeFromCodeCoverage]
-public static class TransactionConstants
-{
- public const uint COMMITMENT_TRANSACTION_VERSION = 2;
- public const uint HTLC_TRANSACTION_VERSION = 2;
- public const uint FUNDING_TRANSACTION_VERSION = 2;
-
- public const int COMMITMENT_TRANSACTION_INPUT_WEIGHT = WeightConstants.WITNESS_HEADER
- + WeightConstants.MULTISIG_WITNESS_WEIGHT
- + (4 * WeightConstants.P2WSH_INTPUT_WEIGHT);
-
- public const int TX_ID_LENGTH = 32;
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Constants/WeightConstants.cs b/src/NLightning.Domain/Protocol/Constants/WeightConstants.cs
deleted file mode 100644
index 508deb13..00000000
--- a/src/NLightning.Domain/Protocol/Constants/WeightConstants.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace NLightning.Domain.Protocol.Constants;
-
-[ExcludeFromCodeCoverage]
-public static class WeightConstants
-{
- // ReSharper disable InconsistentNaming
- // | Amount | Script Length | Script |
- public const int P2PKH_OUTPUT_WEIGHT = 34 * 4; // | 8 | 1 | 25 |
- public const int P2SH_OUTPUT_WEIGHT = 33 * 4; // | 8 | 1 | 23 |
- public const int P2WPKH_OUTPUT_WEIGHT = 31 * 4; // | 8 | 1 | 22 |
- public const int P2WSH_OUTPUT_WEIGHT = 43 * 4; // | 8 | 1 | 34 |
- public const int P2UNKOWN_S_OUTPUT_WEIGHT = 51 * 4; // | 8 | 1 | 42 |
-
- public const int P2PKH_INTPUT_WEIGHT = 148; // At Least
- public const int P2SH_INTPUT_WEIGHT = 148; // At Least
- public const int P2WPKH_INTPUT_WEIGHT = 41; // At Least
- public const int P2WSH_INTPUT_WEIGHT = P2WPKH_INTPUT_WEIGHT;
- public const int P2UNKOWN_S_INTPUT_WEIGHT = P2WPKH_INTPUT_WEIGHT;
- // ReSharper enable InconsistentNaming
-
- public const int WITNESS_HEADER = 2; // flag, marker
- public const int MULTISIG_WITNESS_WEIGHT = 222; // 1 byte for each signature
-
- public const int HTLC_OUTPUT_WEIGHT = P2WSH_OUTPUT_WEIGHT;
- public const int ANCHOR_OUTPUT_WEIGHT = P2WSH_OUTPUT_WEIGHT;
-
- public const int HTLC_TIMEOUT_WEIGHT_ANCHORS = 666;
- public const int HTLC_TIMEOUT_WEIGHT_NO_ANCHORS = 663;
- public const int HTLC_SUCCESS_WEIGHT_ANCHORS = 706;
- public const int HTLC_SUCCESS_WEIGHT_NO_ANCHORS = 703;
-
- public const int TRANSACTION_BASE_WEIGHT = 10 * 4; // version, input count, output count, locktime
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Enums/BasepointType.cs b/src/NLightning.Domain/Protocol/Enums/BasepointType.cs
new file mode 100644
index 00000000..3686af86
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Enums/BasepointType.cs
@@ -0,0 +1,10 @@
+namespace NLightning.Domain.Protocol.Enums;
+
+public enum BasepointType : byte
+{
+ Funding = 0,
+ Revocation = 1,
+ Payment = 2,
+ DelayedPayment = 3,
+ Htlc = 4
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Factories/ICommitmentTransactionFactory.cs b/src/NLightning.Domain/Protocol/Factories/ICommitmentTransactionFactory.cs
deleted file mode 100644
index 81fe49b7..00000000
--- a/src/NLightning.Domain/Protocol/Factories/ICommitmentTransactionFactory.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace NLightning.Domain.Protocol.Factories;
-
-public interface ICommitmentTransactionFactory
-{
-
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Factories/IFundingTransactionFactory.cs b/src/NLightning.Domain/Protocol/Factories/IFundingTransactionFactory.cs
deleted file mode 100644
index eb246c16..00000000
--- a/src/NLightning.Domain/Protocol/Factories/IFundingTransactionFactory.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace NLightning.Domain.Protocol.Factories;
-
-public interface IFundingTransactionFactory
-{
-
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Factories/IHtlcTransactionFactory.cs b/src/NLightning.Domain/Protocol/Factories/IHtlcTransactionFactory.cs
deleted file mode 100644
index 053c1299..00000000
--- a/src/NLightning.Domain/Protocol/Factories/IHtlcTransactionFactory.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace NLightning.Domain.Protocol.Factories;
-
-public interface IHtlcTransactionFactory
-{
-
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IChannelIdFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/IChannelIdFactory.cs
new file mode 100644
index 00000000..12d3c610
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IChannelIdFactory.cs
@@ -0,0 +1,11 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Bitcoin.ValueObjects;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
+
+public interface IChannelIdFactory
+{
+ ChannelId CreateV1(TxId fundingTxId, ushort fundingOutputIndex);
+ ChannelId CreateV2(CompactPubKey lesserRevocationBasepoint, CompactPubKey greaterRevocationBasepoint);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IChannelKeySetFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/IChannelKeySetFactory.cs
new file mode 100644
index 00000000..c35130a2
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IChannelKeySetFactory.cs
@@ -0,0 +1,11 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Channels.Models;
+using Payloads;
+
+public interface IChannelKeySetFactory
+{
+ ChannelKeySetModel CreateNew();
+ ChannelKeySetModel CreateFromIndex(uint index);
+ ChannelKeySetModel CreateFromRemoteInfo(OpenChannel1Payload payload);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IChannelMessage.cs b/src/NLightning.Domain/Protocol/Interfaces/IChannelMessage.cs
new file mode 100644
index 00000000..67cc5df2
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IChannelMessage.cs
@@ -0,0 +1,6 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+public interface IChannelMessage : IMessage
+{
+ new IChannelMessagePayload Payload { get; }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IChannelMessagePayload.cs b/src/NLightning.Domain/Protocol/Interfaces/IChannelMessagePayload.cs
new file mode 100644
index 00000000..bf9f4b72
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IChannelMessagePayload.cs
@@ -0,0 +1,8 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Channels.ValueObjects;
+
+public interface IChannelMessagePayload : IMessagePayload
+{
+ ChannelId ChannelId { get; }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/ICommitmentKeyDerivationService.cs b/src/NLightning.Domain/Protocol/Interfaces/ICommitmentKeyDerivationService.cs
new file mode 100644
index 00000000..556e31f7
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/ICommitmentKeyDerivationService.cs
@@ -0,0 +1,32 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
+
+public interface ICommitmentKeyDerivationService
+{
+ ///
+ /// Derives the local commitment keys based on the provided parameters, including local and remote basepoints and the commitment number.
+ ///
+ /// An index representing the local channel key for deriving commitment keys.
+ /// The set of cryptographic basepoints associated with the local channel.
+ /// The set of cryptographic basepoints associated with the remote channel.
+ /// A numeric identifier representing the specific commitment.
+ /// A instance containing the derived keys for the local commitment.
+ CommitmentKeys DeriveLocalCommitmentKeys(uint localChannelKeyIndex, ChannelBasepoints localBasepoints,
+ ChannelBasepoints remoteBasepoints, ulong commitmentNumber);
+
+ ///
+ /// Derives the remote commitment keys based on the provided parameters, including local and remote basepoints,
+ /// the remote per-commitment point, and the commitment number.
+ ///
+ /// An index representing the local channel key for deriving remote commitment keys.
+ /// The set of cryptographic basepoints associated with the local channel.
+ /// The set of cryptographic basepoints associated with the remote channel.
+ /// The per-commitment point provided by the remote party, used for key derivation.
+ /// A numeric identifier representing the specific commitment.
+ /// A instance containing the derived keys for the remote commitment.
+ CommitmentKeys DeriveRemoteCommitmentKeys(uint localChannelKeyIndex, ChannelBasepoints localBasepoints,
+ ChannelBasepoints remoteBasepoints,
+ CompactPubKey remotePerCommitmentPoint, ulong commitmentNumber);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IDustService.cs b/src/NLightning.Domain/Protocol/Interfaces/IDustService.cs
new file mode 100644
index 00000000..961781b0
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IDustService.cs
@@ -0,0 +1,11 @@
+namespace NLightning.Domain.Protocol.Services;
+
+public interface IDustService
+{
+ ulong CalculateP2PkhDustLimit();
+ ulong CalculateP2ShDustLimit();
+ ulong CalculateP2WpkhDustLimit();
+ ulong CalculateP2WshDustLimit();
+ ulong CalculateUnknownSegwitVersionDustLimit();
+
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IKeyDerivationService.cs b/src/NLightning.Domain/Protocol/Interfaces/IKeyDerivationService.cs
new file mode 100644
index 00000000..51e2da58
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IKeyDerivationService.cs
@@ -0,0 +1,15 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Crypto.ValueObjects;
+
+public interface IKeyDerivationService
+{
+ CompactPubKey DerivePublicKey(CompactPubKey compactBasepoint, CompactPubKey compactPerCommitmentPoint);
+ PrivKey DerivePrivateKey(PrivKey basepointSecretPriv, CompactPubKey compactPerCommitmentPoint);
+
+ CompactPubKey DeriveRevocationPubKey(CompactPubKey compactRevocationBasepoint,
+ CompactPubKey compactPerCommitmentPoint);
+
+ PrivKey DeriveRevocationPrivKey(PrivKey revocationBasepointSecretPriv, PrivKey perCommitmentSecretPriv);
+ Secret GeneratePerCommitmentSecret(Secret seed, ulong index);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/Interfaces/IMessage.cs b/src/NLightning.Domain/Protocol/Interfaces/IMessage.cs
similarity index 80%
rename from src/NLightning.Domain/Protocol/Messages/Interfaces/IMessage.cs
rename to src/NLightning.Domain/Protocol/Interfaces/IMessage.cs
index f19a159c..6ceb8dad 100644
--- a/src/NLightning.Domain/Protocol/Messages/Interfaces/IMessage.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/IMessage.cs
@@ -1,7 +1,7 @@
-namespace NLightning.Domain.Protocol.Messages.Interfaces;
+namespace NLightning.Domain.Protocol.Interfaces;
+using Constants;
using Models;
-using Payloads.Interfaces;
///
/// Interface for a message.
@@ -11,7 +11,7 @@ public interface IMessage
///
/// The type of the message. .
///
- ushort Type { get; }
+ MessageTypes Type { get; }
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Interfaces/IMessageFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/IMessageFactory.cs
new file mode 100644
index 00000000..3b37fba5
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/IMessageFactory.cs
@@ -0,0 +1,120 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Bitcoin.ValueObjects;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
+using Messages;
+using Money;
+using Tlv;
+
+public interface IMessageFactory
+{
+ InitMessage CreateInitMessage();
+ WarningMessage CreateWarningMessage(string message, ChannelId? channelId);
+ WarningMessage CreateWarningMessage(byte[] data, ChannelId? channelId);
+ StfuMessage CreateStfuMessage(ChannelId channelId, bool initiator);
+ ErrorMessage CreateErrorMessage(string message, ChannelId? channelId);
+ ErrorMessage CreateErrorMessage(byte[] data, ChannelId? channelId);
+ PingMessage CreatePingMessage();
+ PongMessage CreatePongMessage(IMessage pingMessage);
+
+ TxAddInputMessage CreateTxAddInputMessage(ChannelId channelId, ulong serialId, byte[] prevTx, uint prevTxVout,
+ uint sequence);
+
+ TxAddOutputMessage CreateTxAddOutputMessage(ChannelId channelId, ulong serialId, LightningMoney amount,
+ BitcoinScript script);
+
+ TxRemoveInputMessage CreateTxRemoveInputMessage(ChannelId channelId, ulong serialId);
+ TxRemoveOutputMessage CreateTxRemoveOutputMessage(ChannelId channelId, ulong serialId);
+ TxCompleteMessage CreateTxCompleteMessage(ChannelId channelId);
+ TxSignaturesMessage CreateTxSignaturesMessage(ChannelId channelId, byte[] txId, List witnesses);
+
+ TxInitRbfMessage CreateTxInitRbfMessage(ChannelId channelId, uint locktime, uint feerate,
+ long fundingOutputContrubution,
+ bool requireConfirmedInputs);
+
+ TxAckRbfMessage CreateTxAckRbfMessage(ChannelId channelId, long fundingOutputContrubution,
+ bool requireConfirmedInputs);
+
+ TxAbortMessage CreateTxAbortMessage(ChannelId channelId, byte[] data);
+
+ ChannelReadyMessage CreateChannelReadyMessage(ChannelId channelId, CompactPubKey secondPerCommitmentPoint,
+ ShortChannelId? shortChannelId = null);
+
+ ShutdownMessage CreateShutdownMessage(ChannelId channelId, BitcoinScript scriptPubkey);
+
+ ClosingSignedMessage CreateClosingSignedMessage(ChannelId channelId, ulong feeSatoshis, CompactSignature signature,
+ ulong minFeeSatoshis, ulong maxFeeSatoshis);
+
+ OpenChannel1Message CreateOpenChannel1Message(ChannelId temporaryChannelId, LightningMoney fundingAmount,
+ CompactPubKey fundingPubKey, LightningMoney pushAmount,
+ LightningMoney channelReserveAmount, LightningMoney feeRatePerKw,
+ ushort maxAcceptedHtlcs, CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint, CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint, CompactPubKey firstPerCommitmentPoint,
+ ChannelFlags channelFlags,
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv,
+ ChannelTypeTlv? channelTypeTlv);
+
+ OpenChannel2Message CreateOpenChannel2Message(ChannelId temporaryChannelId, uint fundingFeeRatePerKw,
+ uint commitmentFeeRatePerKw, ulong fundingSatoshis,
+ CompactPubKey fundingPubKey,
+ CompactPubKey revocationBasepoint, CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint, CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ CompactPubKey secondPerCommitmentPoint,
+ ChannelFlags channelFlags, BitcoinScript? shutdownScriptPubkey = null,
+ byte[]? channelType = null, bool requireConfirmedInputs = false);
+
+ AcceptChannel1Message CreateAcceptChannel1Message(LightningMoney channelReserveAmount,
+ ChannelTypeTlv? channelTypeTlv,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ CompactPubKey fundingPubKey, CompactPubKey htlcBasepoint,
+ ushort maxAcceptedHtlcs, LightningMoney maxHtlcValueInFlight,
+ uint minimumDepth, CompactPubKey paymentBasepoint,
+ CompactPubKey revocationBasepoint, ChannelId temporaryChannelId,
+ ushort toSelfDelay,
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv);
+
+ AcceptChannel2Message CreateAcceptChannel2Message(ChannelId temporaryChannelId, LightningMoney fundingSatoshis,
+ CompactPubKey fundingPubKey, CompactPubKey revocationBasepoint,
+ CompactPubKey paymentBasepoint,
+ CompactPubKey delayedPaymentBasepoint,
+ CompactPubKey htlcBasepoint,
+ CompactPubKey firstPerCommitmentPoint,
+ LightningMoney maxHtlcValueInFlight,
+ BitcoinScript? shutdownScriptPubkey = null,
+ byte[]? channelType = null, bool requireConfirmedInputs = false);
+
+ FundingCreatedMessage CreatedFundingCreatedMessage(ChannelId temporaryChannelId, TxId fundingTxId,
+ ushort fundingOutputIndex, CompactSignature signature);
+
+ FundingSignedMessage CreatedFundingSignedMessage(ChannelId channelId, CompactSignature signature);
+
+ UpdateAddHtlcMessage CreateUpdateAddHtlcMessage(ChannelId channelId, ulong id, ulong amountMsat,
+ ReadOnlyMemory paymentHash, uint cltvExpiry,
+ ReadOnlyMemory? onionRoutingPacket = null);
+
+ UpdateFulfillHtlcMessage CreateUpdateFulfillHtlcMessage(ChannelId channelId, ulong id,
+ ReadOnlyMemory preimage);
+
+ UpdateFailHtlcMessage CreateUpdateFailHtlcMessage(ChannelId channelId, ulong id, ReadOnlyMemory reason);
+
+ CommitmentSignedMessage CreateCommitmentSignedMessage(ChannelId channelId, CompactSignature signature,
+ IEnumerable htlcSignatures);
+
+ RevokeAndAckMessage CreateRevokeAndAckMessage(ChannelId channelId, ReadOnlyMemory perCommitmentSecret,
+ CompactPubKey nextPerCommitmentPoint);
+
+ UpdateFeeMessage CreateUpdateFeeMessage(ChannelId channelId, uint feeratePerKw);
+
+ UpdateFailMalformedHtlcMessage CreateUpdateFailMalformedHtlcMessage(ChannelId channelId, ulong id,
+ ReadOnlyMemory sha256OfOnion,
+ ushort failureCode);
+
+ ChannelReestablishMessage CreateChannelReestablishMessage(ChannelId channelId, ulong nextCommitmentNumber,
+ ulong nextRevocationNumber,
+ ReadOnlyMemory yourLastPerCommitmentSecret,
+ CompactPubKey myCurrentPerCommitmentPoint);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/Interfaces/IMessagePayload.cs b/src/NLightning.Domain/Protocol/Interfaces/IMessagePayload.cs
similarity index 73%
rename from src/NLightning.Domain/Protocol/Payloads/Interfaces/IMessagePayload.cs
rename to src/NLightning.Domain/Protocol/Interfaces/IMessagePayload.cs
index a93f9b85..d4cef414 100644
--- a/src/NLightning.Domain/Protocol/Payloads/Interfaces/IMessagePayload.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/IMessagePayload.cs
@@ -1,4 +1,4 @@
-namespace NLightning.Domain.Protocol.Payloads.Interfaces;
+namespace NLightning.Domain.Protocol.Interfaces;
///
/// Interface for a message payload representing core Lightning Network protocol data structures.
diff --git a/src/NLightning.Domain/Protocol/Services/IMessageService.cs b/src/NLightning.Domain/Protocol/Interfaces/IMessageService.cs
similarity index 93%
rename from src/NLightning.Domain/Protocol/Services/IMessageService.cs
rename to src/NLightning.Domain/Protocol/Interfaces/IMessageService.cs
index ac73dd6e..8ed2c451 100644
--- a/src/NLightning.Domain/Protocol/Services/IMessageService.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/IMessageService.cs
@@ -1,6 +1,4 @@
-namespace NLightning.Domain.Protocol.Services;
-
-using Messages.Interfaces;
+namespace NLightning.Domain.Protocol.Interfaces;
///
/// Interface for a message service.
diff --git a/src/NLightning.Domain/Protocol/Factories/IMessageServiceFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/IMessageServiceFactory.cs
similarity index 85%
rename from src/NLightning.Domain/Protocol/Factories/IMessageServiceFactory.cs
rename to src/NLightning.Domain/Protocol/Interfaces/IMessageServiceFactory.cs
index d40b3367..67185cf8 100644
--- a/src/NLightning.Domain/Protocol/Factories/IMessageServiceFactory.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/IMessageServiceFactory.cs
@@ -1,6 +1,5 @@
-namespace NLightning.Domain.Protocol.Factories;
+namespace NLightning.Domain.Protocol.Interfaces;
-using Services;
using Transport;
///
diff --git a/src/NLightning.Domain/Protocol/Services/IPingPongService.cs b/src/NLightning.Domain/Protocol/Interfaces/IPingPongService.cs
similarity index 92%
rename from src/NLightning.Domain/Protocol/Services/IPingPongService.cs
rename to src/NLightning.Domain/Protocol/Interfaces/IPingPongService.cs
index 53b55bf2..f80ec455 100644
--- a/src/NLightning.Domain/Protocol/Services/IPingPongService.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/IPingPongService.cs
@@ -1,6 +1,4 @@
-namespace NLightning.Domain.Protocol.Services;
-
-using Messages.Interfaces;
+namespace NLightning.Domain.Protocol.Interfaces;
///
/// Interface for a ping pong service.
diff --git a/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageService.cs b/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageService.cs
new file mode 100644
index 00000000..5001c9fe
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageService.cs
@@ -0,0 +1,54 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Crypto.ValueObjects;
+using Enums;
+
+public interface ISecretStorageService : IDisposable
+{
+ ///
+ /// Inserts a new secret and verifies it against existing secrets
+ ///
+ /// The secret to insert
+ /// The index of the secret
+ /// True if the secret was inserted successfully, false otherwise
+ bool InsertSecret(Secret secret, ulong index);
+
+ ///
+ /// Derives an old secret from a known higher-level secret
+ ///
+ /// The index of the secret
+ Secret DeriveOldSecret(ulong index);
+
+ ///
+ /// Stores the per-commitment seed securely
+ ///
+ /// The per-commitment secret to store
+ void StorePerCommitmentSeed(Secret secret);
+
+ ///
+ /// Retrieves the per-commitment seed
+ ///
+ /// The per-commitment seed as a Secret
+ Secret GetPerCommitmentSeed();
+
+ ///
+ /// Stores the private key for the specified basepoint type securely
+ ///
+ /// The type of basepoint associated with the private key
+ /// The private key to store securely
+ void StoreBasepointPrivateKey(BasepointType type, PrivKey privKey);
+
+ ///
+ /// Retrieves the private key associated with the specified basepoint type and key index
+ ///
+ /// The index of the key to retrieve
+ /// The basepoint type for which the private key is required
+ /// The private key associated with the specified basepoint type and key index
+ PrivKey GetBasepointPrivateKey(uint keyIndex, BasepointType type);
+
+ ///
+ /// Loads secrets from persistent storage using the specified index
+ ///
+ /// The index of the secrets to load
+ void LoadFromIndex(uint index);
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageServiceFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageServiceFactory.cs
new file mode 100644
index 00000000..e5a15d07
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/ISecretStorageServiceFactory.cs
@@ -0,0 +1,6 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+public interface ISecretStorageServiceFactory
+{
+ ISecretStorageService CreatePerCommitmentStorage();
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Interfaces/ISecureKeyManager.cs b/src/NLightning.Domain/Protocol/Interfaces/ISecureKeyManager.cs
new file mode 100644
index 00000000..6c756431
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Interfaces/ISecureKeyManager.cs
@@ -0,0 +1,14 @@
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Bitcoin.ValueObjects;
+using Crypto.ValueObjects;
+
+public interface ISecureKeyManager
+{
+ BitcoinKeyPath KeyPath { get; }
+
+ ExtPrivKey GetNextKey(out uint index);
+ ExtPrivKey GetKeyAtIndex(uint index);
+ CryptoKeyPair GetNodeKeyPair();
+ CompactPubKey GetNodePubKey();
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Tlv/Converters/ITlvConverter.cs b/src/NLightning.Domain/Protocol/Interfaces/ITlvConverter.cs
similarity index 87%
rename from src/NLightning.Domain/Protocol/Tlv/Converters/ITlvConverter.cs
rename to src/NLightning.Domain/Protocol/Interfaces/ITlvConverter.cs
index 8e8f92a7..cf94f5dc 100644
--- a/src/NLightning.Domain/Protocol/Tlv/Converters/ITlvConverter.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/ITlvConverter.cs
@@ -1,4 +1,7 @@
-namespace NLightning.Domain.Protocol.Tlv.Converters;
+namespace NLightning.Domain.Protocol.Interfaces;
+
+using Tlv;
+
///
/// Interface for serializers that handle specific message types
///
diff --git a/src/NLightning.Domain/Protocol/Factories/ITlvConverterFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/ITlvConverterFactory.cs
similarity index 63%
rename from src/NLightning.Domain/Protocol/Factories/ITlvConverterFactory.cs
rename to src/NLightning.Domain/Protocol/Interfaces/ITlvConverterFactory.cs
index 9db1ba07..d71490b2 100644
--- a/src/NLightning.Domain/Protocol/Factories/ITlvConverterFactory.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/ITlvConverterFactory.cs
@@ -1,7 +1,6 @@
-namespace NLightning.Domain.Protocol.Factories;
+namespace NLightning.Domain.Protocol.Interfaces;
using Tlv;
-using Tlv.Converters;
public interface ITlvConverterFactory
{
diff --git a/src/NLightning.Domain/Protocol/Factories/ITransportServiceFactory.cs b/src/NLightning.Domain/Protocol/Interfaces/ITransportServiceFactory.cs
similarity index 91%
rename from src/NLightning.Domain/Protocol/Factories/ITransportServiceFactory.cs
rename to src/NLightning.Domain/Protocol/Interfaces/ITransportServiceFactory.cs
index 05bee7e7..4fb2cfda 100644
--- a/src/NLightning.Domain/Protocol/Factories/ITransportServiceFactory.cs
+++ b/src/NLightning.Domain/Protocol/Interfaces/ITransportServiceFactory.cs
@@ -1,6 +1,6 @@
using System.Net.Sockets;
-namespace NLightning.Domain.Protocol.Factories;
+namespace NLightning.Domain.Protocol.Interfaces;
using Transport;
diff --git a/src/NLightning.Domain/Protocol/Managers/ISecureKeyManager.cs b/src/NLightning.Domain/Protocol/Managers/ISecureKeyManager.cs
deleted file mode 100644
index d6dd8064..00000000
--- a/src/NLightning.Domain/Protocol/Managers/ISecureKeyManager.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using NBitcoin;
-
-namespace NLightning.Domain.Protocol.Managers;
-
-public interface ISecureKeyManager
-{
- ExtKey GetNextKey(out uint index);
- Key GetNodeKey();
- PubKey GetNodePubKey();
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/AcceptChannel1Message.cs b/src/NLightning.Domain/Protocol/Messages/AcceptChannel1Message.cs
new file mode 100644
index 00000000..c2972901
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Messages/AcceptChannel1Message.cs
@@ -0,0 +1,46 @@
+namespace NLightning.Domain.Protocol.Messages;
+
+using Constants;
+using Models;
+using Payloads;
+using Tlv;
+
+///
+/// Represents an open_channel message.
+///
+///
+/// The accept_channel message is sent to the initiator in order to accept the channel opening.
+/// The message type is 33.
+///
+public sealed class AcceptChannel1Message : BaseChannelMessage
+{
+ ///
+ /// The payload of the message.
+ ///
+ public new AcceptChannel1Payload Payload { get => (AcceptChannel1Payload)base.Payload; }
+
+ ///
+ /// Optional UpfrontShutdownScriptTlv
+ ///
+ public UpfrontShutdownScriptTlv? UpfrontShutdownScriptTlv { get; }
+
+ ///
+ /// Optional ChannelTypeTlv
+ ///
+ public ChannelTypeTlv? ChannelTypeTlv { get; }
+
+ public AcceptChannel1Message(AcceptChannel1Payload payload,
+ UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv = null,
+ ChannelTypeTlv? channelTypeTlv = null)
+ : base(MessageTypes.AcceptChannel, payload)
+ {
+ UpfrontShutdownScriptTlv = upfrontShutdownScriptTlv;
+ ChannelTypeTlv = channelTypeTlv;
+
+ if (UpfrontShutdownScriptTlv is not null || ChannelTypeTlv is not null)
+ {
+ Extension = new TlvStream();
+ Extension.Add(UpfrontShutdownScriptTlv, ChannelTypeTlv);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/AcceptChannel2Message.cs b/src/NLightning.Domain/Protocol/Messages/AcceptChannel2Message.cs
index d36497b9..5036cde1 100644
--- a/src/NLightning.Domain/Protocol/Messages/AcceptChannel2Message.cs
+++ b/src/NLightning.Domain/Protocol/Messages/AcceptChannel2Message.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The accept_channel2 message is sent to the initiator to accept the channel opening.
/// The message type is 65.
///
-public sealed class AcceptChannel2Message : BaseMessage
+public sealed class AcceptChannel2Message : BaseChannelMessage
{
///
/// The payload of the message.
@@ -35,7 +35,7 @@ public sealed class AcceptChannel2Message : BaseMessage
public RequireConfirmedInputsTlv? RequireConfirmedInputsTlv { get; }
public AcceptChannel2Message(AcceptChannel2Payload payload, UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv = null, ChannelTypeTlv? channelTypeTlv = null, RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null)
- : base(MessageTypes.ACCEPT_CHANNEL_2, payload)
+ : base(MessageTypes.AcceptChannel2, payload)
{
UpfrontShutdownScriptTlv = upfrontShutdownScriptTlv;
ChannelTypeTlv = channelTypeTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/BaseChannelMessage.cs b/src/NLightning.Domain/Protocol/Messages/BaseChannelMessage.cs
new file mode 100644
index 00000000..656647d0
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Messages/BaseChannelMessage.cs
@@ -0,0 +1,23 @@
+namespace NLightning.Domain.Protocol.Messages;
+
+using Constants;
+using Interfaces;
+using Models;
+using Payloads;
+
+public abstract class BaseChannelMessage : BaseMessage, IChannelMessage
+{
+ ///
+ public new virtual IChannelMessagePayload Payload { get; protected set; }
+
+ public BaseChannelMessage(MessageTypes type, IChannelMessagePayload payload, TlvStream? extension = null)
+ : base(type, payload, extension)
+ {
+ Payload = payload;
+ }
+
+ internal BaseChannelMessage(MessageTypes type) : base(type)
+ {
+ Payload = new PlaceholderPayload();
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/BaseMessage.cs b/src/NLightning.Domain/Protocol/Messages/BaseMessage.cs
index d0701b44..191e31f5 100644
--- a/src/NLightning.Domain/Protocol/Messages/BaseMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/BaseMessage.cs
@@ -1,9 +1,9 @@
namespace NLightning.Domain.Protocol.Messages;
+using Constants;
using Interfaces;
using Models;
using Payloads;
-using Payloads.Interfaces;
///
/// Base class for a message.
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
public abstract class BaseMessage : IMessage
{
///
- public ushort Type { get; }
+ public MessageTypes Type { get; }
///
public virtual IMessagePayload Payload { get; protected init; }
@@ -19,13 +19,14 @@ public abstract class BaseMessage : IMessage
///
public TlvStream? Extension { get; protected init; }
- protected BaseMessage(ushort type, IMessagePayload payload, TlvStream? extension = null)
+ protected BaseMessage(MessageTypes type, IMessagePayload payload, TlvStream? extension = null)
{
Type = type;
Payload = payload;
Extension = extension;
}
- protected internal BaseMessage(ushort type)
+
+ protected internal BaseMessage(MessageTypes type)
{
Type = type;
Payload = new PlaceholderPayload();
diff --git a/src/NLightning.Domain/Protocol/Messages/ChannelReadyMessage.cs b/src/NLightning.Domain/Protocol/Messages/ChannelReadyMessage.cs
index eb6bb443..44a89fe2 100644
--- a/src/NLightning.Domain/Protocol/Messages/ChannelReadyMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/ChannelReadyMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The channel_ready message indicates that the funding transaction has sufficient confirms for channel use.
/// The message type is 36.
///
-public sealed class ChannelReadyMessage : BaseMessage
+public sealed class ChannelReadyMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -22,7 +22,7 @@ public sealed class ChannelReadyMessage : BaseMessage
public ShortChannelIdTlv? ShortChannelIdTlv { get; }
public ChannelReadyMessage(ChannelReadyPayload payload, ShortChannelIdTlv? shortChannelIdTlv = null)
- : base(MessageTypes.CHANNEL_READY, payload)
+ : base(MessageTypes.ChannelReady, payload)
{
ShortChannelIdTlv = shortChannelIdTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/ChannelReestablishMessage.cs b/src/NLightning.Domain/Protocol/Messages/ChannelReestablishMessage.cs
index b56e58d0..52a12519 100644
--- a/src/NLightning.Domain/Protocol/Messages/ChannelReestablishMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/ChannelReestablishMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The channel_reestablish message is sent when a connection is lost.
/// The message type is 136.
///
-public sealed class ChannelReestablishMessage : BaseMessage
+public sealed class ChannelReestablishMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -22,7 +22,7 @@ public sealed class ChannelReestablishMessage : BaseMessage
public NextFundingTlv? NextFundingTlv { get; }
public ChannelReestablishMessage(ChannelReestablishPayload payload, NextFundingTlv? nextFundingTlv = null)
- : base(MessageTypes.CHANNEL_REESTABLISH, payload)
+ : base(MessageTypes.ChannelReestablish, payload)
{
NextFundingTlv = nextFundingTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/ClosingSignedMessage.cs b/src/NLightning.Domain/Protocol/Messages/ClosingSignedMessage.cs
index 3db00a13..92d937ff 100644
--- a/src/NLightning.Domain/Protocol/Messages/ClosingSignedMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/ClosingSignedMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The closing_signed message is after shutdown is complete and there are no pending HTLCs.
/// The message type is 39.
///
-public sealed class ClosingSignedMessage : BaseMessage
+public sealed class ClosingSignedMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -21,7 +21,7 @@ public sealed class ClosingSignedMessage : BaseMessage
public FeeRangeTlv FeeRangeTlv { get; }
- public ClosingSignedMessage(ClosingSignedPayload payload, FeeRangeTlv feeRangeTlv) : base(MessageTypes.CLOSING_SIGNED, payload)
+ public ClosingSignedMessage(ClosingSignedPayload payload, FeeRangeTlv feeRangeTlv) : base(MessageTypes.ClosingSigned, payload)
{
FeeRangeTlv = feeRangeTlv;
Extension = new TlvStream();
diff --git a/src/NLightning.Domain/Protocol/Messages/CommitmentSignedMessage.cs b/src/NLightning.Domain/Protocol/Messages/CommitmentSignedMessage.cs
index 93770438..272fcb79 100644
--- a/src/NLightning.Domain/Protocol/Messages/CommitmentSignedMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/CommitmentSignedMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 132.
///
///
-public sealed class CommitmentSignedMessage(CommitmentSignedPayload payload) : BaseMessage(MessageTypes.COMMITMENT_SIGNED, payload)
+public sealed class CommitmentSignedMessage(CommitmentSignedPayload payload)
+ : BaseChannelMessage(MessageTypes.CommitmentSigned, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/ErrorMessage.cs b/src/NLightning.Domain/Protocol/Messages/ErrorMessage.cs
index 55789190..7b1c2e1a 100644
--- a/src/NLightning.Domain/Protocol/Messages/ErrorMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/ErrorMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 17.
///
/// The error payload.
-public sealed class ErrorMessage(ErrorPayload payload) : BaseMessage(MessageTypes.ERROR, payload)
+public sealed class ErrorMessage(ErrorPayload payload) : BaseMessage(MessageTypes.Error, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/FundingCreatedMessage.cs b/src/NLightning.Domain/Protocol/Messages/FundingCreatedMessage.cs
new file mode 100644
index 00000000..652ddcf6
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Messages/FundingCreatedMessage.cs
@@ -0,0 +1,23 @@
+namespace NLightning.Domain.Protocol.Messages;
+
+using Constants;
+using Payloads;
+
+///
+/// Represents a funding_created message.
+///
+///
+/// The funding_created message is sent by the funder to the fundee after the funding transaction has been created.
+/// The message type is 34.
+///
+public sealed class FundingCreatedMessage : BaseChannelMessage
+{
+ ///
+ /// The payload of the message.
+ ///
+ public new FundingCreatedPayload Payload { get => (FundingCreatedPayload)base.Payload; }
+
+ public FundingCreatedMessage(FundingCreatedPayload payload) : base(MessageTypes.FundingCreated, payload)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/FundingSignedMessage.cs b/src/NLightning.Domain/Protocol/Messages/FundingSignedMessage.cs
new file mode 100644
index 00000000..ba191cab
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Messages/FundingSignedMessage.cs
@@ -0,0 +1,23 @@
+namespace NLightning.Domain.Protocol.Messages;
+
+using Constants;
+using Payloads;
+
+///
+/// Represents a funding_signed message.
+///
+///
+/// The funding_signed message is sent by the funder to the fundee after the funding transaction has been created.
+/// The message type is 35.
+///
+public sealed class FundingSignedMessage : BaseChannelMessage
+{
+ ///
+ /// The payload of the message.
+ ///
+ public new FundingSignedPayload Payload { get => (FundingSignedPayload)base.Payload; }
+
+ public FundingSignedMessage(FundingSignedPayload payload) : base(MessageTypes.FundingSigned, payload)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/InitMessage.cs b/src/NLightning.Domain/Protocol/Messages/InitMessage.cs
index 9d05c3d1..5309598b 100644
--- a/src/NLightning.Domain/Protocol/Messages/InitMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/InitMessage.cs
@@ -21,7 +21,7 @@ public sealed class InitMessage : BaseMessage
public NetworksTlv? NetworksTlv { get; }
- public InitMessage(InitPayload payload, NetworksTlv? networksTlv = null) : base(MessageTypes.INIT, payload)
+ public InitMessage(InitPayload payload, NetworksTlv? networksTlv = null) : base(MessageTypes.Init, payload)
{
NetworksTlv = networksTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/OpenChannel1Message.cs b/src/NLightning.Domain/Protocol/Messages/OpenChannel1Message.cs
new file mode 100644
index 00000000..df3b84aa
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Messages/OpenChannel1Message.cs
@@ -0,0 +1,38 @@
+namespace NLightning.Domain.Protocol.Messages;
+
+using Constants;
+using Models;
+using Payloads;
+using Tlv;
+
+///
+/// Represents an open_channel2 message.
+///
+///
+/// The open_channel message is sent to another peer in order to start the channel negotiation.
+/// The message type is 32.
+///
+public sealed class OpenChannel1Message : BaseChannelMessage
+{
+ ///
+ /// The payload of the message.
+ ///
+ public new OpenChannel1Payload Payload { get => (OpenChannel1Payload)base.Payload; }
+
+ public UpfrontShutdownScriptTlv? UpfrontShutdownScriptTlv { get; }
+ public ChannelTypeTlv? ChannelTypeTlv { get; }
+
+ public OpenChannel1Message(OpenChannel1Payload payload, UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv = null,
+ ChannelTypeTlv? channelTypeTlv = null)
+ : base(MessageTypes.OpenChannel, payload)
+ {
+ UpfrontShutdownScriptTlv = upfrontShutdownScriptTlv;
+ ChannelTypeTlv = channelTypeTlv;
+
+ if (UpfrontShutdownScriptTlv is not null || ChannelTypeTlv is not null)
+ {
+ Extension = new TlvStream();
+ Extension.Add(UpfrontShutdownScriptTlv, ChannelTypeTlv);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Messages/OpenChannel2Message.cs b/src/NLightning.Domain/Protocol/Messages/OpenChannel2Message.cs
index a7906213..6c0148ed 100644
--- a/src/NLightning.Domain/Protocol/Messages/OpenChannel2Message.cs
+++ b/src/NLightning.Domain/Protocol/Messages/OpenChannel2Message.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The open_channel2 message is sent to another peer in order to start the channel negotiation.
/// The message type is 64.
///
-public sealed class OpenChannel2Message : BaseMessage
+public sealed class OpenChannel2Message : BaseChannelMessage
{
///
/// The payload of the message.
@@ -24,7 +24,7 @@ public sealed class OpenChannel2Message : BaseMessage
public RequireConfirmedInputsTlv? RequireConfirmedInputsTlv { get; }
public OpenChannel2Message(OpenChannel2Payload payload, UpfrontShutdownScriptTlv? upfrontShutdownScriptTlv = null, ChannelTypeTlv? channelTypeTlv = null, RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null)
- : base(MessageTypes.OPEN_CHANNEL_2, payload)
+ : base(MessageTypes.OpenChannel2, payload)
{
UpfrontShutdownScriptTlv = upfrontShutdownScriptTlv;
ChannelTypeTlv = channelTypeTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/PingMessage.cs b/src/NLightning.Domain/Protocol/Messages/PingMessage.cs
index a11a2b20..e5da87bd 100644
--- a/src/NLightning.Domain/Protocol/Messages/PingMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/PingMessage.cs
@@ -10,7 +10,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The ping message is used to check if the other party is still alive.
/// The message type is 18.
///
-public sealed class PingMessage() : BaseMessage(MessageTypes.PING, new PingPayload())
+public sealed class PingMessage() : BaseMessage(MessageTypes.Ping, new PingPayload())
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/PongMessage.cs b/src/NLightning.Domain/Protocol/Messages/PongMessage.cs
index addd4d96..5c103dc6 100644
--- a/src/NLightning.Domain/Protocol/Messages/PongMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/PongMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 19.
///
/// The number of bytes in the pong message.
-public sealed class PongMessage(ushort bytesLen) : BaseMessage(MessageTypes.PONG, new PongPayload(bytesLen))
+public sealed class PongMessage(ushort bytesLen) : BaseMessage(MessageTypes.Pong, new PongPayload(bytesLen))
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/RevokeAndAckMessage.cs b/src/NLightning.Domain/Protocol/Messages/RevokeAndAckMessage.cs
index a0bd6c8f..1c767240 100644
--- a/src/NLightning.Domain/Protocol/Messages/RevokeAndAckMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/RevokeAndAckMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 133.
///
///
-public sealed class RevokeAndAckMessage(RevokeAndAckPayload payload) : BaseMessage(MessageTypes.REVOKE_AND_ACK, payload)
+public sealed class RevokeAndAckMessage(RevokeAndAckPayload payload)
+ : BaseChannelMessage(MessageTypes.RevokeAndAck, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/ShutdownMessage.cs b/src/NLightning.Domain/Protocol/Messages/ShutdownMessage.cs
index 53158b8e..24af480a 100644
--- a/src/NLightning.Domain/Protocol/Messages/ShutdownMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/ShutdownMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 38.
///
///
-public sealed class ShutdownMessage(ShutdownPayload payload) : BaseMessage(MessageTypes.SHUTDOWN, payload)
+public sealed class ShutdownMessage(ShutdownPayload payload) : BaseChannelMessage(MessageTypes.Shutdown, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/StfuMessage.cs b/src/NLightning.Domain/Protocol/Messages/StfuMessage.cs
index f2d6d737..b9297889 100644
--- a/src/NLightning.Domain/Protocol/Messages/StfuMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/StfuMessage.cs
@@ -4,7 +4,7 @@ namespace NLightning.Domain.Protocol.Messages;
using Payloads;
///
-/// Represents an stfu message.
+/// Represents a stfu message.
///
///
/// The stfu message means SomeThing Fundamental is Underway, so we kindly ask the other node to STFU because we have
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 2.
///
///
-public sealed class StfuMessage(StfuPayload payload) : BaseMessage(MessageTypes.STFU, payload)
+public sealed class StfuMessage(StfuPayload payload) : BaseMessage(MessageTypes.Stfu, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxAbortMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxAbortMessage.cs
index df649d60..4b376ccd 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxAbortMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxAbortMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 74.
///
/// The tx_abort payload.
-public sealed class TxAbortMessage(TxAbortPayload payload) : BaseMessage(MessageTypes.TX_ABORT, payload)
+public sealed class TxAbortMessage(TxAbortPayload payload) : BaseChannelMessage(MessageTypes.TxAbort, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxAckRbfMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxAckRbfMessage.cs
index 77bfbb14..0ae53e03 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxAckRbfMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxAckRbfMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The tx_ack_rbf message acknowledges the replacement of the transaction.
/// The message type is 73.
///
-public sealed class TxAckRbfMessage : BaseMessage
+public sealed class TxAckRbfMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -24,7 +24,7 @@ public sealed class TxAckRbfMessage : BaseMessage
public TxAckRbfMessage(TxAckRbfPayload payload, FundingOutputContributionTlv? fundingOutputContributionTlv = null,
RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null)
- : base(MessageTypes.TX_ACK_RBF, payload)
+ : base(MessageTypes.TxAckRbf, payload)
{
FundingOutputContributionTlv = fundingOutputContributionTlv;
RequireConfirmedInputsTlv = requireConfirmedInputsTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/TxAddInputMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxAddInputMessage.cs
index acf06f83..9db05555 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxAddInputMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxAddInputMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 66.
///
/// The tx_add_input payload.
-public sealed class TxAddInputMessage(TxAddInputPayload payload) : BaseMessage(MessageTypes.TX_ADD_INPUT, payload)
+public sealed class TxAddInputMessage(TxAddInputPayload payload)
+ : BaseChannelMessage(MessageTypes.TxAddInput, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxAddOutputMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxAddOutputMessage.cs
index e7b0cfa2..9463ad9a 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxAddOutputMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxAddOutputMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 67.
///
/// The tx_add_output payload.
-public sealed class TxAddOutputMessage(TxAddOutputPayload payload) : BaseMessage(MessageTypes.TX_ADD_OUTPUT, payload)
+public sealed class TxAddOutputMessage(TxAddOutputPayload payload)
+ : BaseChannelMessage(MessageTypes.TxAddOutput, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxCompleteMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxCompleteMessage.cs
index a184647c..d641e768 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxCompleteMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxCompleteMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 70.
///
/// The tx_complete payload.
-public sealed class TxCompleteMessage(TxCompletePayload payload) : BaseMessage(MessageTypes.TX_COMPLETE, payload)
+public sealed class TxCompleteMessage(TxCompletePayload payload) : BaseChannelMessage(MessageTypes.TxComplete, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxInitRbfMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxInitRbfMessage.cs
index c81ef4ab..1365fff0 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxInitRbfMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxInitRbfMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The tx_init_rbf message initiates a replacement of the transaction after it's been completed.
/// The message type is 72.
///
-public sealed class TxInitRbfMessage : BaseMessage
+public sealed class TxInitRbfMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -23,7 +23,7 @@ public sealed class TxInitRbfMessage : BaseMessage
public RequireConfirmedInputsTlv? RequireConfirmedInputsTlv { get; }
public TxInitRbfMessage(TxInitRbfPayload payload, FundingOutputContributionTlv? fundingOutputContributionTlv = null, RequireConfirmedInputsTlv? requireConfirmedInputsTlv = null)
- : base(MessageTypes.TX_INIT_RBF, payload)
+ : base(MessageTypes.TxInitRbf, payload)
{
FundingOutputContributionTlv = fundingOutputContributionTlv;
RequireConfirmedInputsTlv = requireConfirmedInputsTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/TxRemoveInputMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxRemoveInputMessage.cs
index f1930f24..6fc64a72 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxRemoveInputMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxRemoveInputMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 68.
///
/// The tx_remove_input payload.
-public sealed class TxRemoveInputMessage(TxRemoveInputPayload payload) : BaseMessage(MessageTypes.TX_REMOVE_INPUT, payload)
+public sealed class TxRemoveInputMessage(TxRemoveInputPayload payload)
+ : BaseChannelMessage(MessageTypes.TxRemoveInput, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxRemoveOutputMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxRemoveOutputMessage.cs
index a6343927..df0a9fdd 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxRemoveOutputMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxRemoveOutputMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 69.
///
/// The tx_remove_output payload.
-public sealed class TxRemoveOutputMessage(TxRemoveOutputPayload payload) : BaseMessage(MessageTypes.TX_REMOVE_OUTPUT, payload)
+public sealed class TxRemoveOutputMessage(TxRemoveOutputPayload payload)
+ : BaseChannelMessage(MessageTypes.TxRemoveOutput, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/TxSignaturesMessage.cs b/src/NLightning.Domain/Protocol/Messages/TxSignaturesMessage.cs
index a3482247..6fdc64b4 100644
--- a/src/NLightning.Domain/Protocol/Messages/TxSignaturesMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/TxSignaturesMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 71.
///
/// The tx_signatures payload.
-public sealed class TxSignaturesMessage(TxSignaturesPayload payload) : BaseMessage(MessageTypes.TX_SIGNATURES, payload)
+public sealed class TxSignaturesMessage(TxSignaturesPayload payload)
+ : BaseChannelMessage(MessageTypes.TxSignatures, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/UpdateAddHtlcMessage.cs b/src/NLightning.Domain/Protocol/Messages/UpdateAddHtlcMessage.cs
index 1f5eab20..e5809ad5 100644
--- a/src/NLightning.Domain/Protocol/Messages/UpdateAddHtlcMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/UpdateAddHtlcMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The update_add_htlc message offers a new htlc to the peer.
/// The message type is 128.
///
-public sealed class UpdateAddHtlcMessage : BaseMessage
+public sealed class UpdateAddHtlcMessage : BaseChannelMessage
{
///
/// The payload of the message.
@@ -22,7 +22,7 @@ public sealed class UpdateAddHtlcMessage : BaseMessage
public BlindedPathTlv? BlindedPathTlv { get; }
public UpdateAddHtlcMessage(UpdateAddHtlcPayload payload, BlindedPathTlv? blindedPathTlv = null)
- : base(MessageTypes.UPDATE_ADD_HTLC, payload)
+ : base(MessageTypes.UpdateAddHtlc, payload)
{
BlindedPathTlv = blindedPathTlv;
diff --git a/src/NLightning.Domain/Protocol/Messages/UpdateFailHtlcMessage.cs b/src/NLightning.Domain/Protocol/Messages/UpdateFailHtlcMessage.cs
index c93f19b8..f2ec796f 100644
--- a/src/NLightning.Domain/Protocol/Messages/UpdateFailHtlcMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/UpdateFailHtlcMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 131.
///
///
-public sealed class UpdateFailHtlcMessage(UpdateFailHtlcPayload payload) : BaseMessage(MessageTypes.UPDATE_FAIL_HTLC, payload)
+public sealed class UpdateFailHtlcMessage(UpdateFailHtlcPayload payload)
+ : BaseChannelMessage(MessageTypes.UpdateFailHtlc, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/UpdateFailMalformedHtlcMessage.cs b/src/NLightning.Domain/Protocol/Messages/UpdateFailMalformedHtlcMessage.cs
index 42119cf7..87a43aef 100644
--- a/src/NLightning.Domain/Protocol/Messages/UpdateFailMalformedHtlcMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/UpdateFailMalformedHtlcMessage.cs
@@ -12,7 +12,7 @@ namespace NLightning.Domain.Protocol.Messages;
///
///
public sealed class UpdateFailMalformedHtlcMessage(UpdateFailMalformedHtlcPayload payload)
- : BaseMessage(MessageTypes.UPDATE_FAIL_MALFORMED_HTLC, payload)
+ : BaseChannelMessage(MessageTypes.UpdateFailMalformedHtlc, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/UpdateFeeMessage.cs b/src/NLightning.Domain/Protocol/Messages/UpdateFeeMessage.cs
index 228e4d20..1e33ce53 100644
--- a/src/NLightning.Domain/Protocol/Messages/UpdateFeeMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/UpdateFeeMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 134.
///
///
-public sealed class UpdateFeeMessage(UpdateFeePayload payload) : BaseMessage(MessageTypes.UPDATE_FEE, payload)
+public sealed class UpdateFeeMessage(UpdateFeePayload payload) : BaseChannelMessage(MessageTypes.UpdateFee, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/UpdateFulfillHtlcMessage.cs b/src/NLightning.Domain/Protocol/Messages/UpdateFulfillHtlcMessage.cs
index 7ccdd4d8..ca284cd0 100644
--- a/src/NLightning.Domain/Protocol/Messages/UpdateFulfillHtlcMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/UpdateFulfillHtlcMessage.cs
@@ -11,7 +11,8 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 130.
///
///
-public sealed class UpdateFulfillHtlcMessage(UpdateFulfillHtlcPayload payload) : BaseMessage(MessageTypes.UPDATE_FULFILL_HTLC, payload)
+public sealed class UpdateFulfillHtlcMessage(UpdateFulfillHtlcPayload payload)
+ : BaseChannelMessage(MessageTypes.UpdateFulfillHtlc, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Messages/WarningMessage.cs b/src/NLightning.Domain/Protocol/Messages/WarningMessage.cs
index a0c6923f..0e4c82ad 100644
--- a/src/NLightning.Domain/Protocol/Messages/WarningMessage.cs
+++ b/src/NLightning.Domain/Protocol/Messages/WarningMessage.cs
@@ -11,7 +11,7 @@ namespace NLightning.Domain.Protocol.Messages;
/// The message type is 1.
///
/// The warning payload.
-public sealed class WarningMessage(ErrorPayload payload) : BaseMessage(MessageTypes.WARNING, payload)
+public sealed class WarningMessage(ErrorPayload payload) : BaseMessage(MessageTypes.Warning, payload)
{
///
/// The payload of the message.
diff --git a/src/NLightning.Domain/Protocol/Models/IHtlc.cs b/src/NLightning.Domain/Protocol/Models/IHtlc.cs
deleted file mode 100644
index 6dab0ea2..00000000
--- a/src/NLightning.Domain/Protocol/Models/IHtlc.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using NBitcoin;
-
-namespace NLightning.Domain.Protocol.Models;
-
-using Money;
-
-public interface IHtlc
-{
- Script ScriptPubKey { get; }
- LightningMoney Amount { get; }
-}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/AcceptChannel1Payload.cs b/src/NLightning.Domain/Protocol/Payloads/AcceptChannel1Payload.cs
new file mode 100644
index 00000000..f6ee68bd
--- /dev/null
+++ b/src/NLightning.Domain/Protocol/Payloads/AcceptChannel1Payload.cs
@@ -0,0 +1,114 @@
+namespace NLightning.Domain.Protocol.Payloads;
+
+using Domain.Channels.ValueObjects;
+using Domain.Crypto.ValueObjects;
+using Interfaces;
+using Money;
+
+///
+/// Represents the payload for the accept_channel message.
+///
+///
+/// Initializes a new instance of the AcceptChannel1Payload class.
+///
+public class AcceptChannel1Payload : IChannelMessagePayload
+{
+ ///
+ /// The temporary_channel_id is used to identify this channel on a per-peer basis until the funding transaction
+ /// is established, at which point it is replaced by the channel_id, which is derived from the funding transaction.
+ ///
+ public ChannelId ChannelId { get; set; }
+
+ ///
+ /// dust_limit_satoshis is the threshold below which outputs should not be generated for this node's commitment or
+ /// HTLC transactions
+ ///
+ public LightningMoney DustLimitAmount { get; }
+
+ ///
+ /// max_htlc_value_in_flight_msat is a cap on total value of outstanding HTLCs offered by the remote node, which
+ /// allows the local node to limit its exposure to HTLCs
+ ///
+ public LightningMoney MaxHtlcValueInFlightAmount { get; }
+
+ ///
+ /// channel_reserve_satoshis is the amount the acceptor is reserving for the channel, which is not available for
+ /// spending
+ ///
+ public LightningMoney ChannelReserveAmount { get; set; }
+
+ ///
+ /// htlc_minimum_msat indicates the smallest value HTLC this node will accept.
+ ///
+ public LightningMoney HtlcMinimumAmount { get; }
+
+ ///
+ /// minimum_depth is the number of blocks we consider reasonable to avoid double-spending of the funding transaction.
+ /// In case channel_type includes option_zeroconf this MUST be 0
+ ///
+ public uint MinimumDepth { get; set; }
+
+ ///
+ /// to_self_delay is how long (in blocks) the other node will have to wait in case of breakdown before redeeming
+ /// its own funds.
+ ///
+ public ushort ToSelfDelay { get; }
+
+ ///
+ /// max_accepted_htlcs limits the number of outstanding HTLCs the remote node can offer.
+ ///
+ public ushort MaxAcceptedHtlcs { get; }
+
+ ///
+ /// funding_pubkey is the public key in the 2-of-2 multisig script of the funding transaction output.
+ ///
+ public CompactPubKey FundingPubKey { get; set; }
+
+ ///
+ /// revocation_basepoint is used to regenerate the scripts required for the penalty transaction
+ ///
+ public CompactPubKey RevocationBasepoint { get; set; }
+
+ ///
+ /// payment_basepoint is used to produce payment signatures for the protocol
+ ///
+ public CompactPubKey PaymentBasepoint { get; set; }
+
+ ///
+ /// delayed_payment_basepoint is used to regenerate the scripts required for the penalty transaction
+ ///
+ public CompactPubKey DelayedPaymentBasepoint { get; set; }
+
+ ///
+ /// htlc_basepoint is used to produce HTLC signatures for the protocol
+ ///
+ public CompactPubKey HtlcBasepoint { get; set; }
+
+ ///
+ /// first_per_commitment_point is the per-commitment point used for the first commitment transaction
+ ///
+ public CompactPubKey FirstPerCommitmentPoint { get; set; }
+
+ public AcceptChannel1Payload(ChannelId channelId, LightningMoney channelReserveAmount,
+ CompactPubKey delayedPaymentBasepoint, LightningMoney dustLimitAmount,
+ CompactPubKey firstPerCommitmentPoint, CompactPubKey fundingPubKey,
+ CompactPubKey htlcBasepoint, LightningMoney htlcMinimumAmount, ushort maxAcceptedHtlcs,
+ LightningMoney maxHtlcValueInFlight, uint minimumDepth, CompactPubKey paymentBasepoint,
+ CompactPubKey revocationBasepoint, ushort toSelfDelay)
+ {
+ ChannelId = channelId;
+ DustLimitAmount = dustLimitAmount;
+ MaxHtlcValueInFlightAmount = maxHtlcValueInFlight;
+ ChannelReserveAmount = channelReserveAmount;
+ HtlcMinimumAmount = htlcMinimumAmount;
+ MinimumDepth = minimumDepth;
+ ToSelfDelay = toSelfDelay;
+ MaxAcceptedHtlcs = maxAcceptedHtlcs;
+ FundingPubKey = fundingPubKey;
+ RevocationBasepoint = revocationBasepoint;
+ PaymentBasepoint = paymentBasepoint;
+ DelayedPaymentBasepoint = delayedPaymentBasepoint;
+ HtlcBasepoint = htlcBasepoint;
+ FirstPerCommitmentPoint = firstPerCommitmentPoint;
+ }
+}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/AcceptChannel2Payload.cs b/src/NLightning.Domain/Protocol/Payloads/AcceptChannel2Payload.cs
index e9cec112..473f52a3 100644
--- a/src/NLightning.Domain/Protocol/Payloads/AcceptChannel2Payload.cs
+++ b/src/NLightning.Domain/Protocol/Payloads/AcceptChannel2Payload.cs
@@ -1,10 +1,9 @@
-using NBitcoin;
-
namespace NLightning.Domain.Protocol.Payloads;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
using Interfaces;
using Money;
-using ValueObjects;
///
/// Represents the payload for the accept_channel2 message.
@@ -12,18 +11,27 @@ namespace NLightning.Domain.Protocol.Payloads;
///
/// Initializes a new instance of the AcceptChannel2Payload class.
///
-public class AcceptChannel2Payload(PubKey delayedPaymentBasepoint, LightningMoney dustLimitAmount,
- PubKey firstPerCommitmentPoint, LightningMoney fundingAmount, PubKey fundingPubKey,
- PubKey htlcBasepoint, LightningMoney htlcMinimumAmount, ushort maxAcceptedHtlcs,
- LightningMoney maxHtlcValueInFlight, uint minimumDepth, PubKey paymentBasepoint,
- PubKey revocationBasepoint, ChannelId temporaryChannelId, ushort toSelfDelay)
- : IMessagePayload
+public class AcceptChannel2Payload(
+ CompactPubKey delayedPaymentCompactBasepoint,
+ LightningMoney dustLimitAmount,
+ CompactPubKey firstPerCommitmentCompactPoint,
+ LightningMoney fundingAmount,
+ CompactPubKey fundingCompactPubKey,
+ CompactPubKey htlcCompactBasepoint,
+ LightningMoney htlcMinimumAmount,
+ ushort maxAcceptedHtlcs,
+ LightningMoney maxHtlcValueInFlight,
+ uint minimumDepth,
+ CompactPubKey paymentCompactBasepoint,
+ CompactPubKey revocationCompactBasepoint,
+ ChannelId channelId,
+ ushort toSelfDelay) : IChannelMessagePayload
{
///
/// The temporary_channel_id is used to identify this channel on a per-peer basis until the funding transaction
/// is established, at which point it is replaced by the channel_id, which is derived from the funding transaction.
///
- public ChannelId TemporaryChannelId { get; } = temporaryChannelId;
+ public ChannelId ChannelId { get; } = channelId;
///
/// funding_satoshis is the amount the acceptor is putting into the channel.
@@ -67,30 +75,30 @@ public class AcceptChannel2Payload(PubKey delayedPaymentBasepoint, LightningMone
///
/// funding_pubkey is the public key in the 2-of-2 multisig script of the funding transaction output.
///
- public PubKey FundingPubKey { get; } = fundingPubKey;
+ public CompactPubKey FundingCompactPubKey { get; } = fundingCompactPubKey;
///
/// revocation_basepoint is used to regenerate the scripts required for the penalty transaction
///
- public PubKey RevocationBasepoint { get; } = revocationBasepoint;
+ public CompactPubKey RevocationCompactBasepoint { get; } = revocationCompactBasepoint;
///
/// payment_basepoint is used to produce payment signatures for the protocol
///
- public PubKey PaymentBasepoint { get; } = paymentBasepoint;
+ public CompactPubKey PaymentCompactBasepoint { get; } = paymentCompactBasepoint;
///
/// delayed_payment_basepoint is used to regenerate the scripts required for the penalty transaction
///
- public PubKey DelayedPaymentBasepoint { get; } = delayedPaymentBasepoint;
+ public CompactPubKey DelayedPaymentCompactBasepoint { get; } = delayedPaymentCompactBasepoint;
///
/// htlc_basepoint is used to produce HTLC signatures for the protocol
///
- public PubKey HtlcBasepoint { get; } = htlcBasepoint;
+ public CompactPubKey HtlcCompactBasepoint { get; } = htlcCompactBasepoint;
///
/// first_per_commitment_point is the per-commitment point used for the first commitment transaction
///
- public PubKey FirstPerCommitmentPoint { get; } = firstPerCommitmentPoint;
+ public CompactPubKey FirstPerCommitmentCompactPoint { get; } = firstPerCommitmentCompactPoint;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/ChannelReadyPayload.cs b/src/NLightning.Domain/Protocol/Payloads/ChannelReadyPayload.cs
index 4c734dbc..328cd0ba 100644
--- a/src/NLightning.Domain/Protocol/Payloads/ChannelReadyPayload.cs
+++ b/src/NLightning.Domain/Protocol/Payloads/ChannelReadyPayload.cs
@@ -1,9 +1,8 @@
-using NBitcoin;
-
namespace NLightning.Domain.Protocol.Payloads;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
using Interfaces;
-using ValueObjects;
///
/// Represents the payload for the channel_ready message.
@@ -12,12 +11,12 @@ namespace NLightning.Domain.Protocol.Payloads;
/// Initializes a new instance of the ChannelReadyPayload class.
///
/// The channel ID.
-public class ChannelReadyPayload(ChannelId channelId, PubKey secondPerCommitmentPoint) : IMessagePayload
+public class ChannelReadyPayload(ChannelId channelId, CompactPubKey secondPerCommitmentPoint) : IChannelMessagePayload
{
///
/// Gets the channel ID.
///
public ChannelId ChannelId { get; } = channelId;
- public PubKey SecondPerCommitmentPoint { get; } = secondPerCommitmentPoint;
+ public CompactPubKey SecondPerCommitmentPoint { get; } = secondPerCommitmentPoint;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/ChannelReestablishPayload.cs b/src/NLightning.Domain/Protocol/Payloads/ChannelReestablishPayload.cs
index f4e962c5..4d57bc3e 100644
--- a/src/NLightning.Domain/Protocol/Payloads/ChannelReestablishPayload.cs
+++ b/src/NLightning.Domain/Protocol/Payloads/ChannelReestablishPayload.cs
@@ -1,9 +1,8 @@
-using NBitcoin;
-
namespace NLightning.Domain.Protocol.Payloads;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
using Interfaces;
-using ValueObjects;
///
/// Represents the payload for the channel_reestablish message.
@@ -12,9 +11,12 @@ namespace NLightning.Domain.Protocol.Payloads;
/// Initializes a new instance of the ChannelReestablishPayload class.
///
/// The channel ID.
-public class ChannelReestablishPayload(ChannelId channelId, PubKey myCurrentPerCommitmentPoint,
- ulong nextCommitmentNumber, ulong nextRevocationNumber,
- ReadOnlyMemory yourLastPerCommitmentSecret) : IMessagePayload
+public class ChannelReestablishPayload(
+ ChannelId channelId,
+ CompactPubKey myCurrentPerCommitmentPoint,
+ ulong nextCommitmentNumber,
+ ulong nextRevocationNumber,
+ ReadOnlyMemory yourLastPerCommitmentSecret) : IChannelMessagePayload
{
///
/// Gets the channel ID.
@@ -39,5 +41,5 @@ public class ChannelReestablishPayload(ChannelId channelId, PubKey myCurrentPerC
///
/// The current per commitment point
///
- public PubKey MyCurrentPerCommitmentPoint { get; } = myCurrentPerCommitmentPoint;
+ public CompactPubKey MyCurrentPerCommitmentPoint { get; } = myCurrentPerCommitmentPoint;
}
\ No newline at end of file
diff --git a/src/NLightning.Domain/Protocol/Payloads/ClosingSignedPayload.cs b/src/NLightning.Domain/Protocol/Payloads/ClosingSignedPayload.cs
index df43f4a8..1509dcd0 100644
--- a/src/NLightning.Domain/Protocol/Payloads/ClosingSignedPayload.cs
+++ b/src/NLightning.Domain/Protocol/Payloads/ClosingSignedPayload.cs
@@ -1,10 +1,9 @@
-using NBitcoin.Crypto;
-
namespace NLightning.Domain.Protocol.Payloads;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
using Interfaces;
using Money;
-using ValueObjects;
///
/// Represents the payload for the closing_signed message.
@@ -12,7 +11,7 @@ namespace NLightning.Domain.Protocol.Payloads;
///
/// Initializes a new instance of the ClosingSignedPayload class.
///
-public class ClosingSignedPayload : IMessagePayload
+public class ClosingSignedPayload : IChannelMessagePayload
{
///
/// The channel_id is used to identify this channel.
@@ -27,9 +26,9 @@ public class ClosingSignedPayload : IMessagePayload
///
/// The signature for the closing transaction
///
- public ECDSASignature Signature { get; }
+ public CompactSignature Signature { get; }
- public ClosingSignedPayload(ChannelId channelId, LightningMoney feeAmount, ECDSASignature signature)
+ public ClosingSignedPayload(ChannelId channelId, LightningMoney feeAmount, CompactSignature signature)
{
ChannelId = channelId;
FeeAmount = feeAmount;
diff --git a/src/NLightning.Domain/Protocol/Payloads/CommitmentSignedPayload.cs b/src/NLightning.Domain/Protocol/Payloads/CommitmentSignedPayload.cs
index 2852bdf3..9a7dc1cf 100644
--- a/src/NLightning.Domain/Protocol/Payloads/CommitmentSignedPayload.cs
+++ b/src/NLightning.Domain/Protocol/Payloads/CommitmentSignedPayload.cs
@@ -1,9 +1,8 @@
-using NBitcoin.Crypto;
-
namespace NLightning.Domain.Protocol.Payloads;
+using Channels.ValueObjects;
+using Crypto.ValueObjects;
using Interfaces;
-using ValueObjects;
///
/// Represents the payload for the commitment_signed message.
@@ -11,8 +10,10 @@ namespace NLightning.Domain.Protocol.Payloads;
///
/// Initializes a new instance of the CommitmentSignedPayload class.
///
-public class CommitmentSignedPayload(ChannelId channelId, IEnumerable htlcSignatures,
- ECDSASignature signature) : IMessagePayload
+public class CommitmentSignedPayload(
+ ChannelId channelId,
+ IEnumerable htlcSignatures,
+ CompactSignature signature) : IChannelMessagePayload
{
///
/// The channel_id this message refers to
@@ -22,7 +23,7 @@ public class CommitmentSignedPayload(ChannelId channelId, IEnumerable
/// The signature for the commitment transaction
///
- public ECDSASignature Signature { get; } = signature;
+ public CompactSignature Signature { get; } = signature;
///
/// Number of HTLCs outputs
@@ -38,5 +39,5 @@ public ushort NumHtlcs
///
/// List containing HTLCs signatures
///
- public IEnumerable HtlcSignatures { get; set; } = htlcSignatures;
+ public IEnumerable