- 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.
256 lines
8.1 KiB
C#
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);
|
|
}
|
|
}
|