diff --git a/Spoke.UnitTestsClean/Spoke.UnitTestsClean.csproj b/Spoke.UnitTestsClean/Spoke.UnitTestsClean.csproj index 1d66adf..b5c42b0 100644 --- a/Spoke.UnitTestsClean/Spoke.UnitTestsClean.csproj +++ b/Spoke.UnitTestsClean/Spoke.UnitTestsClean.csproj @@ -7,10 +7,8 @@ - + - - \ No newline at end of file diff --git a/Spoke.UnitTestsClean/UnitTests/ExportViewingWalletTests.cs b/Spoke.UnitTestsClean/UnitTests/ExportViewingWalletTests.cs new file mode 100644 index 0000000..2a1f777 --- /dev/null +++ b/Spoke.UnitTestsClean/UnitTests/ExportViewingWalletTests.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using IXICore; +using IXICore.Meta; +using Spoke.Meta; +using Xunit; + +namespace Spoke.UnitTestsClean.UnitTests +{ + public class ExportViewingWalletTests : IDisposable + { + private readonly string _tempDir; + + public ExportViewingWalletTests() + { + _tempDir = Path.Combine(Path.GetTempPath(), "SpokeExportTest", Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempDir); + // Ensure Spoke uses our temp folder for wallet storage + Config.spokeUserFolder = _tempDir; + } + + public void Dispose() + { + try + { + if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); + } + catch { } + } + + [Fact] + public void ExportViewingWallet_CreatesViewFile() + { + string walletPath = Path.Combine(_tempDir, Node.walletFile); + + // Create a real WalletStorage and add to IxianHandler + WalletStorage ws = new WalletStorage(walletPath); + bool gen = ws.generateWallet("test-password-123"); + Assert.True(gen, "Failed to generate wallet for test"); + + bool added = IXICore.Meta.IxianHandler.addWallet(ws); + Assert.True(added, "Failed to add wallet to IxianHandler"); + + string? exported = Node.ExportViewingWallet(); + Assert.False(string.IsNullOrEmpty(exported)); + Assert.True(File.Exists(exported)); + var bytes = File.ReadAllBytes(exported); + Assert.True(bytes.Length > 0, "Exported view wallet should contain bytes"); + } + } +} diff --git a/Spoke/Spoke/Meta/Node.cs b/Spoke/Spoke/Meta/Node.cs index c34c068..1761b67 100644 --- a/Spoke/Spoke/Meta/Node.cs +++ b/Spoke/Spoke/Meta/Node.cs @@ -3,6 +3,7 @@ using IXICore.Network; using IXICore.RegNames; using Spoke.Network; +using System.IO; namespace Spoke.Meta; @@ -91,6 +92,48 @@ public void init() Logging.info("Spoke Node initialization complete"); } + /// + /// Export a view-only (viewing) wallet file for the currently loaded wallet + /// If destPath is null, writes to `Config.spokeUserFolder` as `wallet.view.ixi` and returns the written path. + /// Returns null on failure. + /// + public static string? ExportViewingWallet(string? destPath = null) + { + try + { + var ws = IxianHandler.getWalletStorage(); + if (ws == null) + { + Logging.error("No wallet storage available to export viewing wallet."); + return null; + } + + byte[] viewingBytes = ws.getRawViewingWallet(); + if (viewingBytes == null) + { + Logging.error("Failed to get viewing wallet bytes."); + return null; + } + + string outPath = destPath ?? Path.Combine(Config.spokeUserFolder, Path.GetFileNameWithoutExtension(walletFile) + ".view.ixi"); + string? directory = Path.GetDirectoryName(outPath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + File.WriteAllBytes(outPath, viewingBytes); + + Logging.info("Exported viewing wallet to {0}", outPath); + return outPath; + } + catch (Exception ex) + { + Logging.error("Exception exporting viewing wallet: {0}", ex.ToString()); + return null; + } + } + /// /// Checks for existing wallet file /// diff --git a/Spoke/Spoke/Pages/SettingsPage.xaml b/Spoke/Spoke/Pages/SettingsPage.xaml index 2027bd4..c05121b 100644 --- a/Spoke/Spoke/Pages/SettingsPage.xaml +++ b/Spoke/Spoke/Pages/SettingsPage.xaml @@ -67,6 +67,9 @@