diff --git a/src/Data/Models/Channel.cs b/src/Data/Models/Channel.cs
index 263d0f96..30c78dbb 100644
--- a/src/Data/Models/Channel.cs
+++ b/src/Data/Models/Channel.cs
@@ -47,6 +47,12 @@ public enum ChannelStatus
public ChannelStatus Status { get; set; }
+ ///
+ /// Timestamp at which the channel transitioned to .
+ /// Null while the channel is open or for channels closed before this column existed.
+ ///
+ public DateTimeOffset? ClosedAt { get; set; }
+
///
/// Indicates if this channel was created by NodeGuard
///
diff --git a/src/Data/Repositories/ChannelRepository.cs b/src/Data/Repositories/ChannelRepository.cs
index d31b8824..2a8a7716 100644
--- a/src/Data/Repositories/ChannelRepository.cs
+++ b/src/Data/Repositories/ChannelRepository.cs
@@ -186,6 +186,8 @@ public async Task> GetAll()
{
using var applicationDbContext = _dbContextFactory.CreateDbContext();
+ type.SetUpdateDatetime();
+
//Automapper to avoid creation of entities
type = _mapper.Map(type);
@@ -340,6 +342,7 @@ public async Task> GetAllManagedByUserNodes(string loggedUserId)
}
channel.Status = Channel.ChannelStatus.Closed;
+ channel.ClosedAt = DateTimeOffset.UtcNow;
var markAsClosed = _repository.Update(channel, applicationDbContext);
diff --git a/src/Jobs/ChannelMonitorJob.cs b/src/Jobs/ChannelMonitorJob.cs
index a27aea90..11680344 100644
--- a/src/Jobs/ChannelMonitorJob.cs
+++ b/src/Jobs/ChannelMonitorJob.cs
@@ -206,6 +206,7 @@ public async Task MarkClosedChannelsAsClosed(Node source, List? channel
if (channel == null)
{
openChannel.Status = ChannelStatus.Closed;
+ openChannel.ClosedAt = DateTimeOffset.UtcNow;
await dbContext.SaveChangesAsync();
}
}
diff --git a/src/Jobs/NodeChannelSubscribeJob.cs b/src/Jobs/NodeChannelSubscribeJob.cs
index 146a7981..bcae7221 100644
--- a/src/Jobs/NodeChannelSubscribeJob.cs
+++ b/src/Jobs/NodeChannelSubscribeJob.cs
@@ -166,6 +166,7 @@ public async Task NodeUpdateManagement(ChannelEventUpdate channelEventUpdate, No
else
{
channelToClose.Status = Channel.ChannelStatus.Closed;
+ channelToClose.ClosedAt = DateTimeOffset.UtcNow;
var updateChannel = _channelRepository.Update(channelToClose);
if (!updateChannel.Item1)
{
diff --git a/src/Migrations/20260430155542_AddChannelClosedAt.Designer.cs b/src/Migrations/20260430155542_AddChannelClosedAt.Designer.cs
new file mode 100644
index 00000000..e156058f
--- /dev/null
+++ b/src/Migrations/20260430155542_AddChannelClosedAt.Designer.cs
@@ -0,0 +1,1621 @@
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodeGuard.Data;
+using NodeGuard.Helpers;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace NodeGuard.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260430155542_AddChannelClosedAt")]
+ partial class AddChannelClosedAt
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("ApplicationUserNode", b =>
+ {
+ b.Property("NodesId")
+ .HasColumnType("integer");
+
+ b.Property("UsersId")
+ .HasColumnType("text");
+
+ b.HasKey("NodesId", "UsersId");
+
+ b.HasIndex("UsersId");
+
+ b.ToTable("ApplicationUserNode");
+ });
+
+ modelBuilder.Entity("ChannelOperationRequestFMUTXO", b =>
+ {
+ b.Property("ChannelOperationRequestsId")
+ .HasColumnType("integer");
+
+ b.Property("UtxosId")
+ .HasColumnType("integer");
+
+ b.HasKey("ChannelOperationRequestsId", "UtxosId");
+
+ b.HasIndex("UtxosId");
+
+ b.ToTable("ChannelOperationRequestFMUTXO");
+ });
+
+ modelBuilder.Entity("FMUTXOWalletWithdrawalRequest", b =>
+ {
+ b.Property("UTXOsId")
+ .HasColumnType("integer");
+
+ b.Property("WalletWithdrawalRequestsId")
+ .HasColumnType("integer");
+
+ b.HasKey("UTXOsId", "WalletWithdrawalRequestsId");
+
+ b.HasIndex("WalletWithdrawalRequestsId");
+
+ b.ToTable("FMUTXOWalletWithdrawalRequest");
+ });
+
+ modelBuilder.Entity("KeyWallet", b =>
+ {
+ b.Property("KeysId")
+ .HasColumnType("integer");
+
+ b.Property("WalletsId")
+ .HasColumnType("integer");
+
+ b.HasKey("KeysId", "WalletsId");
+
+ b.HasIndex("WalletsId");
+
+ b.ToTable("KeyWallet");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Discriminator")
+ .IsRequired()
+ .HasMaxLength(21)
+ .HasColumnType("character varying(21)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+
+ b.HasDiscriminator().HasValue("IdentityUser");
+
+ b.UseTphMappingStrategy();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("ProviderKey")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("LoginProvider")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Name")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.APIToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatorId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ExpirationDate")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("IsBlocked")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TokenHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatorId");
+
+ b.ToTable("ApiTokens");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.AuditLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ActionType")
+ .HasColumnType("integer");
+
+ b.Property("Details")
+ .HasColumnType("text");
+
+ b.Property("EventType")
+ .HasColumnType("integer");
+
+ b.Property("IpAddress")
+ .HasMaxLength(45)
+ .HasColumnType("character varying(45)");
+
+ b.Property("ObjectAffected")
+ .HasColumnType("integer");
+
+ b.Property("ObjectId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)");
+
+ b.Property("Timestamp")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)");
+
+ b.Property("Username")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.ToTable("AuditLogs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Channel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BtcCloseAddress")
+ .HasColumnType("text");
+
+ b.Property("ChanId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("ClosedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedByNodeGuard")
+ .HasColumnType("boolean");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DestinationNodeId")
+ .HasColumnType("integer");
+
+ b.Property("FundingTx")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FundingTxOutputIndex")
+ .HasColumnType("bigint");
+
+ b.Property("IsAutomatedLiquidityEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("IsPrivate")
+ .HasColumnType("boolean");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("SourceNodeId")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DestinationNodeId");
+
+ b.HasIndex("SourceNodeId");
+
+ b.ToTable("Channels");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AmountCryptoUnit")
+ .HasColumnType("integer");
+
+ b.Property("Changeless")
+ .HasColumnType("boolean");
+
+ b.Property("ChannelId")
+ .HasColumnType("integer");
+
+ b.Property("ClosingReason")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DestNodeId")
+ .HasColumnType("integer");
+
+ b.Property("FeeRate")
+ .HasColumnType("numeric");
+
+ b.Property("IsChannelPrivate")
+ .HasColumnType("boolean");
+
+ b.Property("MempoolRecommendedFeesType")
+ .HasColumnType("integer");
+
+ b.Property("RequestType")
+ .HasColumnType("integer");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("SourceNodeId")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property>("StatusLogs")
+ .HasColumnType("jsonb");
+
+ b.Property("TxId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("WalletId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelId");
+
+ b.HasIndex("DestNodeId");
+
+ b.HasIndex("SourceNodeId");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("WalletId");
+
+ b.ToTable("ChannelOperationRequests");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequestPSBT", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChannelOperationRequestId")
+ .HasColumnType("integer");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsFinalisedPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsInternalWalletPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsTemplatePSBT")
+ .HasColumnType("boolean");
+
+ b.Property("PSBT")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserSignerId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelOperationRequestId");
+
+ b.HasIndex("UserSignerId");
+
+ b.ToTable("ChannelOperationRequestPSBTs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.FMUTXO", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OutputIndex")
+ .HasColumnType("bigint");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("TxId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("FMUTXOs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ForwardingHtlcEvent", b =>
+ {
+ b.Property("ManagedNodePubKey")
+ .HasColumnType("text");
+
+ b.Property("IncomingChannelId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("OutgoingChannelId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("IncomingHtlcId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("OutgoingHtlcId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EventCase")
+ .HasColumnType("integer");
+
+ b.Property("EventTimestamp")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EventType")
+ .HasColumnType("integer");
+
+ b.Property("FailureDetail")
+ .HasColumnType("integer");
+
+ b.Property("FailureString")
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)");
+
+ b.Property("FeeMsat")
+ .HasColumnType("bigint");
+
+ b.Property("GrossFeeMsat")
+ .HasColumnType("bigint");
+
+ b.Property("InboundFeeMsat")
+ .HasColumnType("bigint");
+
+ b.Property("InboundFeePpm")
+ .HasColumnType("bigint");
+
+ b.Property("IncomingAmountMsat")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("IncomingPeerAlias")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("IncomingTimelock")
+ .HasColumnType("bigint");
+
+ b.Property("ManagedNodeName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Outcome")
+ .HasColumnType("integer");
+
+ b.Property("OutgoingAmountMsat")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("OutgoingPeerAlias")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("OutgoingTimelock")
+ .HasColumnType("bigint");
+
+ b.Property("RoutingFeePpm")
+ .HasColumnType("bigint");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WireFailureCode")
+ .HasColumnType("integer");
+
+ b.HasKey("ManagedNodePubKey", "IncomingChannelId", "OutgoingChannelId", "IncomingHtlcId", "OutgoingHtlcId");
+
+ b.HasIndex("CreationDatetime");
+
+ b.HasIndex("EventTimestamp");
+
+ b.ToTable("ForwardingHtlcEvents");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.InternalWallet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DerivationPath")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("MasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("MnemonicString")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("XPUB")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("InternalWallets");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Key", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletId")
+ .HasColumnType("integer");
+
+ b.Property("IsArchived")
+ .HasColumnType("boolean");
+
+ b.Property("IsBIP39ImportedKey")
+ .HasColumnType("boolean");
+
+ b.Property("IsCompromised")
+ .HasColumnType("boolean");
+
+ b.Property("MasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Path")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("XPUB")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InternalWalletId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Keys");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.LiquidityRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChannelId")
+ .HasColumnType("integer");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsReverseSwapWalletRule")
+ .HasColumnType("boolean");
+
+ b.Property("MinimumLocalBalance")
+ .HasColumnType("numeric");
+
+ b.Property("MinimumRemoteBalance")
+ .HasColumnType("numeric");
+
+ b.Property("NodeId")
+ .HasColumnType("integer");
+
+ b.Property("RebalanceTarget")
+ .HasColumnType("numeric");
+
+ b.Property("ReverseSwapAddress")
+ .HasColumnType("text");
+
+ b.Property("ReverseSwapWalletId")
+ .HasColumnType("integer");
+
+ b.Property("SwapWalletId")
+ .HasColumnType("integer");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelId")
+ .IsUnique();
+
+ b.HasIndex("NodeId");
+
+ b.HasIndex("ReverseSwapWalletId");
+
+ b.HasIndex("SwapWalletId");
+
+ b.ToTable("LiquidityRules");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Node", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AutoLiquidityManagementEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("AutosweepEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("ChannelAdminMacaroon")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Endpoint")
+ .HasColumnType("text");
+
+ b.Property("FortySwapEndpoint")
+ .HasColumnType("text");
+
+ b.Property("FortySwapWeight")
+ .HasColumnType("integer");
+
+ b.Property("FundsDestinationWalletId")
+ .HasColumnType("integer");
+
+ b.Property("IsNodeDisabled")
+ .HasColumnType("boolean");
+
+ b.Property("LoopSwapWeight")
+ .HasColumnType("integer");
+
+ b.Property("LoopdCert")
+ .HasColumnType("text");
+
+ b.Property("LoopdEndpoint")
+ .HasColumnType("text");
+
+ b.Property("LoopdMacaroon")
+ .HasColumnType("text");
+
+ b.Property("MaxSwapRoutingFeeRatio")
+ .HasColumnType("numeric");
+
+ b.Property("MaxSwapsInFlight")
+ .HasColumnType("integer");
+
+ b.Property("MinimumBalanceThresholdSats")
+ .HasColumnType("bigint");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PubKey")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("SwapBudgetRefreshInterval")
+ .HasColumnType("interval");
+
+ b.Property("SwapBudgetSats")
+ .HasColumnType("bigint");
+
+ b.Property("SwapBudgetStartDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SwapMaxAmountSats")
+ .HasColumnType("bigint");
+
+ b.Property("SwapMinAmountSats")
+ .HasColumnType("bigint");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FundsDestinationWalletId");
+
+ b.HasIndex("PubKey")
+ .IsUnique();
+
+ b.ToTable("Nodes");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.SwapOut", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DestinationWalletId")
+ .HasColumnType("integer");
+
+ b.Property("ErrorDetails")
+ .HasColumnType("text");
+
+ b.Property("IsManual")
+ .HasColumnType("boolean");
+
+ b.Property("LightningFeeSats")
+ .HasColumnType("bigint");
+
+ b.Property("NodeId")
+ .HasColumnType("integer");
+
+ b.Property("OnChainFeeSats")
+ .HasColumnType("bigint");
+
+ b.Property("Provider")
+ .HasColumnType("integer");
+
+ b.Property("ProviderId")
+ .HasColumnType("text");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("ServiceFeeSats")
+ .HasColumnType("bigint");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("TxId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserRequestorId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DestinationWalletId");
+
+ b.HasIndex("NodeId");
+
+ b.HasIndex("UserRequestorId");
+
+ b.ToTable("SwapOuts");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.UTXOTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Outpoint")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Key", "Outpoint")
+ .IsUnique();
+
+ b.ToTable("UTXOTags");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Wallet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BIP39Seedphrase")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("ImportedOutputDescriptor")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletId")
+ .HasColumnType("integer");
+
+ b.Property("InternalWalletMasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletSubDerivationPath")
+ .HasColumnType("text");
+
+ b.Property("IsArchived")
+ .HasColumnType("boolean");
+
+ b.Property("IsBIP39Imported")
+ .HasColumnType("boolean");
+
+ b.Property