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);
}
}