Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.XUnit" Version="3.0.19" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="6.0.0" />
<PackageVersion Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.15" />
<PackageVersion Include="Zafiro" Version="47.0.2" />
<PackageVersion Include="Zafiro.Avalonia" Version="50.0.1" />
Expand Down
1 change: 1 addition & 0 deletions src/avalonia/AngorApp/Composition/CompositionRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static IShellViewModel CreateMainViewModel(Control topLevelView, string p
.AddUIServices(topLevelView, profileContext, applicationStorage);

services.AddSecurityContext();
services.AddCloudBackup();
RegisterWalletServices(services, logger, network);
FundingContextServices.Register(services, logger);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Angor.Sdk.WalletExport;
using Angor.Sdk.WalletExport.Blossom;
using Microsoft.Extensions.DependencyInjection;

namespace AngorApp.Composition.Registrations.Services;

/// <summary>
/// Wires the encrypted-seed cloud-backup feature (NIP-44 outer + AES-GCM inner, Argon2id KDF, Blossom blobs).
/// </summary>
public static class CloudBackup
{
public static IServiceCollection AddCloudBackup(this IServiceCollection services)
{
services.AddSingleton<IBackupBlossomClient, BackupBlossomClient>();
services.AddSingleton<ICloudBackupService, CloudBackupService>();
services.AddSingleton<IBackupRecoveryService, BackupRecoveryService>();
services.AddSingleton<IWalletCloudBackupService, WalletCloudBackupService>();
return services;
}
}
6 changes: 6 additions & 0 deletions src/design/App/Composition/CompositionRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ public static IServiceProvider BuildServiceProvider(string profileName = "Defaul
services.AddSingleton<BlossomUploadService>();
services.AddSingleton<ILogExportService, LogExportService>();

// Cloud seed-backup feature (NIP-44 outer + AES-GCM inner over Blossom blobs)
services.AddSingleton<Angor.Sdk.WalletExport.Blossom.IBackupBlossomClient, Angor.Sdk.WalletExport.Blossom.BackupBlossomClient>();
services.AddSingleton<Angor.Sdk.WalletExport.ICloudBackupService, Angor.Sdk.WalletExport.CloudBackupService>();
services.AddSingleton<Angor.Sdk.WalletExport.IBackupRecoveryService, Angor.Sdk.WalletExport.BackupRecoveryService>();
services.AddSingleton<Angor.Sdk.WalletExport.IWalletCloudBackupService, Angor.Sdk.WalletExport.WalletCloudBackupService>();

// ── Shared singletons (replaces SharedViewModels static class) ──
services.AddSingleton<SignatureStore>();
services.AddSingleton<PrototypeSettings>();
Expand Down
130 changes: 130 additions & 0 deletions src/design/App/UI/Sections/Settings/SettingsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,136 @@
</StackPanel>
</Border>

<!-- ═══ 6b. Cloud Backup ═══ -->
<Border Background="{DynamicResource CardSurface}"
BorderBrush="{DynamicResource Stroke}"
BorderThickness="1"
CornerRadius="16"
ClipToBounds="True"
BoxShadow="{DynamicResource ItemShadow}">
<StackPanel>
<Border Background="{DynamicResource SurfaceHigh}"
CornerRadius="15,15,0,0"
BorderBrush="{DynamicResource Stroke}"
BorderThickness="0,0,0,1"
Padding="24,20">
<StackPanel Orientation="Horizontal" Spacing="12">
<i:Icon Value="fa-solid fa-cloud-arrow-up"
FontSize="20"
Foreground="{DynamicResource PrimaryGreenBrush}" />
<TextBlock Text="Cloud Backup"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource TextStrong}"
VerticalAlignment="Center" />
</StackPanel>
</Border>
<Border Padding="24">
<StackPanel Spacing="16">
<TextBlock Text="Encrypt your seed with a Recovery Passphrase and store it on Nostr + Blossom servers. Recover on any device with just the passphrase."
FontSize="14"
Foreground="{DynamicResource TextMuted}"
TextWrapping="Wrap" />

<!-- Disabled state -->
<StackPanel IsVisible="{Binding !IsCloudBackupEnabled}" Spacing="12">
<TextBlock Text="Cloud backup is not set up for this wallet."
FontSize="14"
Foreground="{DynamicResource TextMuted}" />
<Button Name="BtnEnableCloudBackup"
Click="OnEnableCloudBackupClick"
HorizontalAlignment="Left"
Padding="20,14"
Background="{DynamicResource GreenGradient}"
Foreground="White"
BorderThickness="0"
CornerRadius="8"
FontWeight="SemiBold"
FontSize="14"
Cursor="Hand"
IsEnabled="{Binding !IsCloudBackupBusy}">
<StackPanel Orientation="Horizontal" Spacing="8">
<i:Icon Value="fa-solid fa-shield-halved" FontSize="16" Foreground="White" />
<TextBlock Text="Set up Cloud Backup" VerticalAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>

<!-- Enabled state -->
<StackPanel IsVisible="{Binding IsCloudBackupEnabled}" Spacing="12">
<Border Background="{DynamicResource CreateWalletSeedBg}"
BorderBrush="{DynamicResource CreateWalletSeedBorder}"
BorderThickness="1"
CornerRadius="8"
Padding="16">
<StackPanel Spacing="6">
<TextBlock Text="Backup active" FontWeight="SemiBold" FontSize="14" Foreground="{DynamicResource TextStrong}" />
<TextBlock Text="{Binding CloudBackupBlobSha256, StringFormat='Blob hash: {0}'}"
FontFamily="Cascadia Mono, Consolas, monospace"
FontSize="11"
Foreground="{DynamicResource TextMuted}"
TextWrapping="Wrap" />
<TextBlock FontSize="12" Foreground="{DynamicResource TextMuted}">
<Run Text="Reachable on" />
<Run Text="{Binding CloudBackupServersHealthy}" />
<Run Text="/" />
<Run Text="{Binding CloudBackupServersTotal}" />
<Run Text="Blossom servers" />
</TextBlock>
</StackPanel>
</Border>

<StackPanel Orientation="Horizontal" Spacing="8">
<Button Click="OnVerifyCloudBackupClick"
Padding="16,10"
Background="{DynamicResource SurfaceHigh}"
Foreground="{DynamicResource TextStrong}"
BorderBrush="{DynamicResource Stroke}"
BorderThickness="1"
CornerRadius="8"
FontSize="13"
Cursor="Hand"
IsEnabled="{Binding !IsCloudBackupBusy}">
<TextBlock Text="Verify Health" />
</Button>
<Button Click="OnRefreshCloudBackupClick"
Padding="16,10"
Background="{DynamicResource SurfaceHigh}"
Foreground="{DynamicResource TextStrong}"
BorderBrush="{DynamicResource Stroke}"
BorderThickness="1"
CornerRadius="8"
FontSize="13"
Cursor="Hand"
IsEnabled="{Binding !IsCloudBackupBusy}">
<TextBlock Text="Refresh Backup" />
</Button>
<Button Click="OnDisableCloudBackupClick"
Padding="16,10"
Background="Transparent"
Foreground="{DynamicResource TextMuted}"
BorderBrush="{DynamicResource Stroke}"
BorderThickness="1"
CornerRadius="8"
FontSize="13"
Cursor="Hand"
IsEnabled="{Binding !IsCloudBackupBusy}">
<TextBlock Text="Disable" />
</Button>
</StackPanel>
</StackPanel>

<!-- Status line -->
<TextBlock IsVisible="{Binding CloudBackupStatusMessage, Converter={x:Static ObjectConverters.IsNotNull}}"
Text="{Binding CloudBackupStatusMessage}"
FontSize="12"
Foreground="{DynamicResource TextMuted}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</StackPanel>
</Border>

<!-- ═══ 7. Debug Mode (testnet only) ═══ -->
<!-- Visible only on testnet networks. Enables debug features like project wizard
data prepopulation and relaxed validation for easier testing. -->
Expand Down
27 changes: 27 additions & 0 deletions src/design/App/UI/Sections/Settings/SettingsView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,31 @@ private void OnModalBackdropPressed(object? sender, PointerPressedEventArgs e) =

private void OnModalContentPressed(object? sender, PointerPressedEventArgs e) =>
e.Handled = true; // Prevent backdrop close when clicking modal content

// ── Cloud Backup (passphrase-entry modal is a follow-up; for now Enable opens a TopLevel dialog) ──
private async void OnEnableCloudBackupClick(object? sender, RoutedEventArgs e)
{
if (Vm == null) return;
// Minimal first-pass: open the enable-backup modal (XAML for the modal still to be added).
// The ViewModel.ConfirmEnableBackupAsync covers the validation + crypto + relay/Blossom calls.
Vm.OpenEnableBackupModal();
}

private async void OnVerifyCloudBackupClick(object? sender, RoutedEventArgs e)
{
try { if (Vm != null) await Vm.VerifyBackupHealthAsync(); }
catch (Exception ex) { _logger.LogWarning(ex, "OnVerifyCloudBackupClick failed"); }
}

private async void OnRefreshCloudBackupClick(object? sender, RoutedEventArgs e)
{
if (Vm == null) return;
Vm.OpenRefreshBackupModal();
}

private async void OnDisableCloudBackupClick(object? sender, RoutedEventArgs e)
{
try { if (Vm != null) await Vm.DisableBackupAsync(); }
catch (Exception ex) { _logger.LogWarning(ex, "OnDisableCloudBackupClick failed"); }
}
}
Loading
Loading