using System.Net.Http.Json; using System.Text.Json; namespace Synor.Sdk.Wallet; /// /// Synor Wallet SDK client for C#. /// /// Provides key management, transaction signing, and balance queries /// for the Synor blockchain. /// /// /// /// var wallet = new SynorWallet(new WalletConfig { ApiKey = "your-api-key" }); /// /// // Create a new wallet /// var result = await wallet.CreateWalletAsync(); /// Console.WriteLine($"Wallet ID: {result.Wallet.Id}"); /// Console.WriteLine($"Mnemonic: {result.Mnemonic}"); /// /// // Get balance /// var balance = await wallet.GetBalanceAsync(result.Wallet.Addresses[0].AddressString); /// Console.WriteLine($"Balance: {balance.Total}"); /// /// wallet.Dispose(); /// /// public class SynorWallet : IDisposable { private readonly WalletConfig _config; private readonly HttpClient _httpClient; private readonly JsonSerializerOptions _jsonOptions; private bool _disposed; public SynorWallet(WalletConfig config) { _config = config; _httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout }; _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}"); _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, PropertyNameCaseInsensitive = true }; } /// /// Create a new wallet. /// public async Task CreateWalletAsync( WalletType type = WalletType.Standard, CancellationToken cancellationToken = default) { var request = new { type = type.ToString().ToLower(), network = _config.Network.ToString().ToLower() }; return await PostAsync("/wallets", request, cancellationToken); } /// /// Import a wallet from mnemonic. /// public async Task ImportWalletAsync( string mnemonic, string? passphrase = null, CancellationToken cancellationToken = default) { var request = new { mnemonic, passphrase, network = _config.Network.ToString().ToLower() }; return await PostAsync("/wallets/import", request, cancellationToken); } /// /// Get a wallet by ID. /// public async Task GetWalletAsync( string walletId, CancellationToken cancellationToken = default) { return await GetAsync($"/wallets/{walletId}", cancellationToken); } /// /// Generate a new address for a wallet. /// public async Task
GenerateAddressAsync( string walletId, bool isChange = false, CancellationToken cancellationToken = default) { var request = new { is_change = isChange }; return await PostAsync
($"/wallets/{walletId}/addresses", request, cancellationToken); } /// /// Get a stealth address for privacy transactions. /// public async Task GetStealthAddressAsync( string walletId, CancellationToken cancellationToken = default) { return await GetAsync($"/wallets/{walletId}/stealth-address", cancellationToken); } /// /// Sign a transaction. /// public async Task SignTransactionAsync( string walletId, Transaction transaction, CancellationToken cancellationToken = default) { var request = new { wallet_id = walletId, transaction }; return await PostAsync("/transactions/sign", request, cancellationToken); } /// /// Sign a message. /// public async Task SignMessageAsync( string walletId, string message, int addressIndex = 0, CancellationToken cancellationToken = default) { var request = new { wallet_id = walletId, message, address_index = addressIndex }; return await PostAsync("/messages/sign", request, cancellationToken); } /// /// Verify a message signature. /// public async Task VerifyMessageAsync( string message, string signature, string address, CancellationToken cancellationToken = default) { var request = new { message, signature, address }; var response = await PostAsync("/messages/verify", request, cancellationToken); return response.GetProperty("valid").GetBoolean(); } /// /// Get balance for an address. /// public async Task GetBalanceAsync( string address, CancellationToken cancellationToken = default) { return await GetAsync($"/addresses/{address}/balance", cancellationToken); } /// /// Get UTXOs for an address. /// public async Task> GetUTXOsAsync( string address, int minConfirmations = 1, CancellationToken cancellationToken = default) { return await GetAsync>( $"/addresses/{address}/utxos?min_confirmations={minConfirmations}", cancellationToken); } /// /// Estimate transaction fee. /// public async Task EstimateFeeAsync( Priority priority = Priority.Medium, CancellationToken cancellationToken = default) { var response = await GetAsync( $"/fees/estimate?priority={priority.ToString().ToLower()}", cancellationToken); return response.GetProperty("fee_per_byte").GetInt64(); } private async Task GetAsync(string path, CancellationToken cancellationToken) { return await ExecuteWithRetryAsync(async () => { var response = await _httpClient.GetAsync(path, cancellationToken); await EnsureSuccessAsync(response); return await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken) ?? throw new WalletException("Invalid response"); }); } private async Task PostAsync(string path, object body, CancellationToken cancellationToken) { return await ExecuteWithRetryAsync(async () => { var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, cancellationToken); await EnsureSuccessAsync(response); return await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken) ?? throw new WalletException("Invalid response"); }); } private async Task EnsureSuccessAsync(HttpResponseMessage response) { if (!response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); throw new WalletException( $"HTTP error: {content}", statusCode: (int)response.StatusCode); } } private async Task ExecuteWithRetryAsync(Func> operation) { Exception? lastException = null; for (int attempt = 0; attempt < _config.Retries; attempt++) { try { return await operation(); } catch (Exception ex) { lastException = ex; if (_config.Debug) { Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}"); } if (attempt < _config.Retries - 1) { await Task.Delay(TimeSpan.FromSeconds(attempt + 1)); } } } throw lastException ?? new WalletException("Unknown error after retries"); } public void Dispose() { if (!_disposed) { _httpClient.Dispose(); _disposed = true; } GC.SuppressFinalize(this); } }