synor/sdk/csharp/Synor.Sdk/Wallet/SynorWallet.cs
Gulshan Yadav 74b82d2bb2 Add Synor Storage and Wallet SDKs for Swift
- Implement SynorStorage class for decentralized storage operations including upload, download, pinning, and CAR file management.
- Create supporting types and models for storage operations such as UploadOptions, Pin, and StorageConfig.
- Implement SynorWallet class for wallet operations including wallet creation, address generation, transaction signing, and balance queries.
- Create supporting types and models for wallet operations such as Wallet, Address, and Transaction.
- Introduce error handling for both storage and wallet operations.
2026-01-27 01:56:45 +05:30

256 lines
8.1 KiB
C#

using System.Net.Http.Json;
using System.Text.Json;
namespace Synor.Sdk.Wallet;
/// <summary>
/// Synor Wallet SDK client for C#.
///
/// Provides key management, transaction signing, and balance queries
/// for the Synor blockchain.
/// </summary>
/// <example>
/// <code>
/// 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();
/// </code>
/// </example>
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
};
}
/// <summary>
/// Create a new wallet.
/// </summary>
public async Task<CreateWalletResult> CreateWalletAsync(
WalletType type = WalletType.Standard,
CancellationToken cancellationToken = default)
{
var request = new { type = type.ToString().ToLower(), network = _config.Network.ToString().ToLower() };
return await PostAsync<CreateWalletResult>("/wallets", request, cancellationToken);
}
/// <summary>
/// Import a wallet from mnemonic.
/// </summary>
public async Task<Wallet> ImportWalletAsync(
string mnemonic,
string? passphrase = null,
CancellationToken cancellationToken = default)
{
var request = new
{
mnemonic,
passphrase,
network = _config.Network.ToString().ToLower()
};
return await PostAsync<Wallet>("/wallets/import", request, cancellationToken);
}
/// <summary>
/// Get a wallet by ID.
/// </summary>
public async Task<Wallet> GetWalletAsync(
string walletId,
CancellationToken cancellationToken = default)
{
return await GetAsync<Wallet>($"/wallets/{walletId}", cancellationToken);
}
/// <summary>
/// Generate a new address for a wallet.
/// </summary>
public async Task<Address> GenerateAddressAsync(
string walletId,
bool isChange = false,
CancellationToken cancellationToken = default)
{
var request = new { is_change = isChange };
return await PostAsync<Address>($"/wallets/{walletId}/addresses", request, cancellationToken);
}
/// <summary>
/// Get a stealth address for privacy transactions.
/// </summary>
public async Task<StealthAddress> GetStealthAddressAsync(
string walletId,
CancellationToken cancellationToken = default)
{
return await GetAsync<StealthAddress>($"/wallets/{walletId}/stealth-address", cancellationToken);
}
/// <summary>
/// Sign a transaction.
/// </summary>
public async Task<SignedTransaction> SignTransactionAsync(
string walletId,
Transaction transaction,
CancellationToken cancellationToken = default)
{
var request = new { wallet_id = walletId, transaction };
return await PostAsync<SignedTransaction>("/transactions/sign", request, cancellationToken);
}
/// <summary>
/// Sign a message.
/// </summary>
public async Task<Signature> SignMessageAsync(
string walletId,
string message,
int addressIndex = 0,
CancellationToken cancellationToken = default)
{
var request = new { wallet_id = walletId, message, address_index = addressIndex };
return await PostAsync<Signature>("/messages/sign", request, cancellationToken);
}
/// <summary>
/// Verify a message signature.
/// </summary>
public async Task<bool> VerifyMessageAsync(
string message,
string signature,
string address,
CancellationToken cancellationToken = default)
{
var request = new { message, signature, address };
var response = await PostAsync<JsonElement>("/messages/verify", request, cancellationToken);
return response.GetProperty("valid").GetBoolean();
}
/// <summary>
/// Get balance for an address.
/// </summary>
public async Task<Balance> GetBalanceAsync(
string address,
CancellationToken cancellationToken = default)
{
return await GetAsync<Balance>($"/addresses/{address}/balance", cancellationToken);
}
/// <summary>
/// Get UTXOs for an address.
/// </summary>
public async Task<List<UTXO>> GetUTXOsAsync(
string address,
int minConfirmations = 1,
CancellationToken cancellationToken = default)
{
return await GetAsync<List<UTXO>>(
$"/addresses/{address}/utxos?min_confirmations={minConfirmations}",
cancellationToken);
}
/// <summary>
/// Estimate transaction fee.
/// </summary>
public async Task<long> EstimateFeeAsync(
Priority priority = Priority.Medium,
CancellationToken cancellationToken = default)
{
var response = await GetAsync<JsonElement>(
$"/fees/estimate?priority={priority.ToString().ToLower()}",
cancellationToken);
return response.GetProperty("fee_per_byte").GetInt64();
}
private async Task<T> GetAsync<T>(string path, CancellationToken cancellationToken)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.GetAsync(path, cancellationToken);
await EnsureSuccessAsync(response);
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken)
?? throw new WalletException("Invalid response");
});
}
private async Task<T> PostAsync<T>(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<T>(_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<T> ExecuteWithRetryAsync<T>(Func<Task<T>> 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);
}
}