Skip to content

Commit 49c0372

Browse files
feat: add Handshake dVPN — full WPF desktop client example
Complete reference implementation for building Windows dVPN apps with C# / .NET 8 and the Sentinel SDK. 5,980 lines across 7 files: - WPF theme system (colors, fonts, reusable styles) - Node browser with search, filter, protocol badges - Animated connection orb with state machine - Per-GB and per-hour pricing display - Real-time speed + IP monitoring - Built-in node tester with JSON/CSV export - Wallet management (create, import, send) - Handshake DNS integration - DiskCache persistence to LocalAppData - Full SDK integration (chain queries, sessions, tunnels) Also updates README to highlight .NET 8 and the example app.
1 parent fa40df8 commit 49c0372

16 files changed

Lines changed: 12403 additions & 2 deletions

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Sentinel dVPN SDK -- C#
1+
# Sentinel dVPN SDK C# / .NET 8
22

3-
Complete protocol library for building decentralized VPN applications on the [Sentinel network](https://sentinel.co). WireGuard and V2Ray tunnels, wallet management, session handling, and all chain message types for Sentinel v3. .NET 8, 814+ tests, zero external service dependencies.
3+
Complete protocol library for building decentralized VPN applications on the [Sentinel network](https://sentinel.co) with **C# and .NET 8**. WireGuard and V2Ray tunnels, wallet management, session handling, and all chain message types for Sentinel v3. 814+ tests, zero external service dependencies. Includes a full **WPF desktop client example** with documented UI patterns.
44

55
**Also available:** [JavaScript SDK](https://github.com/Sentinel-Autonomybuilder/sentinel-dvpn-sdk) | [AI Connect](https://github.com/Sentinel-Autonomybuilder/sentinel-ai-connect) (zero-config JS wrapper)
66

@@ -194,6 +194,26 @@ csharp-sdk/
194194
└── EDGE-CASES.md Gotchas and production lessons learned
195195
```
196196

197+
## Example: Full WPF Desktop Client
198+
199+
The [`examples/HandshakeDVPN/`](examples/HandshakeDVPN/) directory contains a complete, runnable dVPN desktop application built with WPF and .NET 8. Use it as a reference for building your own Windows desktop VPN client.
200+
201+
**What's included (5,980 lines across 7 files):**
202+
203+
| File | Lines | What You Learn |
204+
|------|-------|----------------|
205+
| `App.xaml` | 125 | Complete WPF theme — color tokens, font loading, reusable button/textbox styles with hover/disabled states |
206+
| `App.xaml.cs` | 62 | App startup, backend initialization, exception handling |
207+
| `MainWindow.xaml` | 583 | Full layout — sidebar node browser, connection orb, status bar, test dashboard, wallet overlay |
208+
| `MainWindow.xaml.cs` | 3,669 | Connection state machine, node rendering, search/filter, polling timers, speed display, animations |
209+
| `Services/IHnsVpnBackend.cs` | 403 | Service interface + all data models (nodes, sessions, status, pricing) |
210+
| `Services/NativeVpnClient.cs` | 1,076 | SDK integration — wallet, chain queries, connect/disconnect, session management, DNS config |
211+
| `Services/DiskCache.cs` | 62 | JSON file persistence to LocalAppData |
212+
213+
**Features:** node browser with search/filter, animated connection orb, per-GB and per-hour pricing, real-time speed display, built-in node tester with export, Handshake DNS integration, wallet management (create/import/send), session tracking.
214+
215+
See the [example README](examples/HandshakeDVPN/README.md) for the full architecture guide, UI structure, and code-behind patterns.
216+
197217
## Error Handling
198218

199219
Every SDK error extends `SentinelException` with a machine-readable `.Code`:

examples/HandshakeDVPN/App.xaml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<Application x:Class="HandshakeDVPN.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
StartupUri="MainWindow.xaml">
5+
<Application.Resources>
6+
<!-- ═══ Handshake Black & White Theme ═══ -->
7+
<SolidColorBrush x:Key="Bg0" Color="#FFFFFF"/>
8+
<SolidColorBrush x:Key="Bg1" Color="#FAFAFA"/>
9+
<SolidColorBrush x:Key="Bg2" Color="#F2F2F2"/>
10+
<SolidColorBrush x:Key="Bg3" Color="#E8E8E8"/>
11+
<SolidColorBrush x:Key="Bg4" Color="#D9D9D9"/>
12+
<SolidColorBrush x:Key="Bdr" Color="#E0E0E0"/>
13+
<SolidColorBrush x:Key="BdrH" Color="#C0C0C0"/>
14+
<SolidColorBrush x:Key="T1" Color="#000000"/>
15+
<SolidColorBrush x:Key="T2" Color="#555555"/>
16+
<SolidColorBrush x:Key="T3" Color="#999999"/>
17+
<SolidColorBrush x:Key="Acc" Color="#000000"/>
18+
<SolidColorBrush x:Key="AccDim" Color="#14000000"/>
19+
<SolidColorBrush x:Key="AccLight" Color="#F0F0F0"/>
20+
<SolidColorBrush x:Key="Blue" Color="#4F8FFF"/>
21+
<SolidColorBrush x:Key="BlueDim" Color="#1A4F8FFF"/>
22+
<SolidColorBrush x:Key="Green" Color="#22C55E"/>
23+
<SolidColorBrush x:Key="GreenDim" Color="#1422C55E"/>
24+
<SolidColorBrush x:Key="Red" Color="#DC2626"/>
25+
<SolidColorBrush x:Key="RedDim" Color="#1ADC2626"/>
26+
<SolidColorBrush x:Key="Amber" Color="#D97706"/>
27+
<SolidColorBrush x:Key="AmberDim" Color="#1AD97706"/>
28+
29+
<!-- ─── Fonts ─── -->
30+
<FontFamily x:Key="Sans">pack://application:,,,/Fonts/#Inter</FontFamily>
31+
<FontFamily x:Key="Mono">Cascadia Code, Consolas, monospace</FontFamily>
32+
<FontFamily x:Key="Emoji">Segoe UI Emoji</FontFamily>
33+
34+
<!-- ─── Primary Button (Black) ─── -->
35+
<Style x:Key="BtnAcc" TargetType="Button">
36+
<Setter Property="Background" Value="{StaticResource Acc}"/>
37+
<Setter Property="Foreground" Value="White"/>
38+
<Setter Property="FontFamily" Value="{StaticResource Sans}"/>
39+
<Setter Property="FontSize" Value="13"/>
40+
<Setter Property="FontWeight" Value="SemiBold"/>
41+
<Setter Property="Padding" Value="20,11"/>
42+
<Setter Property="Cursor" Value="Hand"/>
43+
<Setter Property="Template">
44+
<Setter.Value>
45+
<ControlTemplate TargetType="Button">
46+
<Border Background="{TemplateBinding Background}" CornerRadius="8" Padding="{TemplateBinding Padding}">
47+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
48+
</Border>
49+
</ControlTemplate>
50+
</Setter.Value>
51+
</Setter>
52+
<Style.Triggers>
53+
<Trigger Property="IsMouseOver" Value="True"><Setter Property="Background" Value="#333333"/></Trigger>
54+
<Trigger Property="IsEnabled" Value="False"><Setter Property="Background" Value="{StaticResource Bg4}"/><Setter Property="Foreground" Value="{StaticResource T3}"/></Trigger>
55+
</Style.Triggers>
56+
</Style>
57+
58+
<!-- ─── Ghost Button ─── -->
59+
<Style x:Key="BtnGhost" TargetType="Button">
60+
<Setter Property="Background" Value="Transparent"/>
61+
<Setter Property="Foreground" Value="{StaticResource T3}"/>
62+
<Setter Property="FontFamily" Value="{StaticResource Sans}"/>
63+
<Setter Property="FontSize" Value="11"/>
64+
<Setter Property="Padding" Value="8,4"/>
65+
<Setter Property="Cursor" Value="Hand"/>
66+
<Setter Property="Template">
67+
<Setter.Value>
68+
<ControlTemplate TargetType="Button">
69+
<Border Background="{TemplateBinding Background}" BorderBrush="{StaticResource Bdr}" BorderThickness="1" CornerRadius="6" Padding="{TemplateBinding Padding}">
70+
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
71+
</Border>
72+
</ControlTemplate>
73+
</Setter.Value>
74+
</Setter>
75+
<Style.Triggers>
76+
<Trigger Property="IsMouseOver" Value="True"><Setter Property="Foreground" Value="{StaticResource T1}"/></Trigger>
77+
</Style.Triggers>
78+
</Style>
79+
80+
<!-- ─── Light TextBox ─── -->
81+
<Style x:Key="TbLight" TargetType="TextBox">
82+
<Setter Property="Background" Value="{StaticResource Bg0}"/>
83+
<Setter Property="Foreground" Value="{StaticResource T1}"/>
84+
<Setter Property="BorderBrush" Value="{StaticResource Bdr}"/>
85+
<Setter Property="BorderThickness" Value="1"/>
86+
<Setter Property="Padding" Value="12,10"/>
87+
<Setter Property="FontFamily" Value="{StaticResource Mono}"/>
88+
<Setter Property="FontSize" Value="12"/>
89+
<Setter Property="CaretBrush" Value="{StaticResource T1}"/>
90+
<Setter Property="Template">
91+
<Setter.Value>
92+
<ControlTemplate TargetType="TextBox">
93+
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
94+
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="8" Padding="{TemplateBinding Padding}">
95+
<ScrollViewer x:Name="PART_ContentHost"/>
96+
</Border>
97+
</ControlTemplate>
98+
</Setter.Value>
99+
</Setter>
100+
</Style>
101+
102+
<!-- ─── Filter Button ─── -->
103+
<Style x:Key="FilterBtn" TargetType="Button">
104+
<Setter Property="Background" Value="Transparent"/>
105+
<Setter Property="Foreground" Value="{StaticResource T3}"/>
106+
<Setter Property="FontFamily" Value="{StaticResource Sans}"/>
107+
<Setter Property="FontSize" Value="11"/>
108+
<Setter Property="FontWeight" Value="Medium"/>
109+
<Setter Property="Padding" Value="0,6"/>
110+
<Setter Property="Cursor" Value="Hand"/>
111+
<Setter Property="Template">
112+
<Setter.Value>
113+
<ControlTemplate TargetType="Button">
114+
<Border Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="1" CornerRadius="6" Padding="{TemplateBinding Padding}">
115+
<ContentPresenter HorizontalAlignment="Center"/>
116+
</Border>
117+
</ControlTemplate>
118+
</Setter.Value>
119+
</Setter>
120+
<Style.Triggers>
121+
<Trigger Property="IsMouseOver" Value="True"><Setter Property="Foreground" Value="{StaticResource T1}"/></Trigger>
122+
</Style.Triggers>
123+
</Style>
124+
</Application.Resources>
125+
</Application>

examples/HandshakeDVPN/App.xaml.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.IO;
2+
using System.Windows;
3+
using System.Windows.Threading;
4+
using HandshakeDVPN.Services;
5+
6+
namespace HandshakeDVPN;
7+
8+
public partial class App : Application
9+
{
10+
public static IHnsVpnBackend Backend { get; private set; } = null!;
11+
12+
private static readonly string CrashLog = Path.Combine(
13+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
14+
"HandshakeDVPN", "crash.log");
15+
16+
protected override void OnStartup(StartupEventArgs e)
17+
{
18+
base.OnStartup(e);
19+
20+
// Catch all unhandled exceptions
21+
DispatcherUnhandledException += OnDispatcherUnhandledException;
22+
AppDomain.CurrentDomain.UnhandledException += OnDomainUnhandledException;
23+
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
24+
25+
Backend = new NativeVpnClient(null);
26+
}
27+
28+
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
29+
{
30+
LogCrash("Dispatcher", e.Exception);
31+
e.Handled = true; // prevent crash, keep app running
32+
}
33+
34+
private static void OnDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
35+
{
36+
if (e.ExceptionObject is Exception ex) LogCrash("Domain", ex);
37+
}
38+
39+
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
40+
{
41+
LogCrash("Task", e.Exception);
42+
e.SetObserved(); // prevent crash
43+
}
44+
45+
private static void LogCrash(string source, Exception ex)
46+
{
47+
try
48+
{
49+
var dir = Path.GetDirectoryName(CrashLog)!;
50+
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
51+
var msg = $"[{DateTime.Now:HH:mm:ss}] [{source}] {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}\n\n";
52+
File.AppendAllText(CrashLog, msg);
53+
}
54+
catch { }
55+
}
56+
57+
protected override void OnExit(ExitEventArgs e)
58+
{
59+
Backend?.Dispose();
60+
base.OnExit(e);
61+
}
62+
}

0 commit comments

Comments
 (0)