Add Economics, Governance, and Mining SDKs for: - Java: Full SDK with CompletableFuture async operations - Kotlin: Coroutine-based SDK with suspend functions - Swift: Modern Swift SDK with async/await - Flutter/Dart: Complete Dart SDK with Future-based API - C: Header files and implementations with opaque handles - C++: Modern C++17 with std::future and PIMPL pattern - C#: Records, async/await Tasks, and IDisposable - Ruby: Struct-based types with Faraday HTTP client Also includes minor Dart lint fixes (const exceptions).
165 lines
6.8 KiB
C#
165 lines
6.8 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace Synor.Sdk.Economics;
|
|
|
|
/// <summary>
|
|
/// Synor Economics SDK client for C#.
|
|
/// Pricing, billing, staking, and discount management.
|
|
/// </summary>
|
|
public class SynorEconomics : IDisposable
|
|
{
|
|
private readonly EconomicsConfig _config;
|
|
private readonly HttpClient _httpClient;
|
|
private readonly JsonSerializerOptions _jsonOptions;
|
|
private bool _disposed;
|
|
|
|
public SynorEconomics(EconomicsConfig config)
|
|
{
|
|
_config = config;
|
|
_httpClient = new HttpClient { BaseAddress = new Uri(config.Endpoint), Timeout = config.Timeout };
|
|
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
|
|
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
|
|
_jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true };
|
|
}
|
|
|
|
// ==================== Pricing Operations ====================
|
|
|
|
public async Task<List<ServicePricing>> GetPricingAsync(ServiceType? service = null, CancellationToken ct = default)
|
|
{
|
|
var path = service.HasValue ? $"/pricing?service={service.Value.ToString().ToLower()}" : "/pricing";
|
|
var r = await GetAsync<PricingResponse>(path, ct);
|
|
return r.Pricing ?? new List<ServicePricing>();
|
|
}
|
|
|
|
public async Task<PriceResult> GetPriceAsync(ServiceType service, UsageMetrics usage, CancellationToken ct = default)
|
|
{
|
|
var body = new { service = service.ToString().ToLower(), usage };
|
|
return await PostAsync<PriceResult>("/pricing/calculate", body, ct);
|
|
}
|
|
|
|
public async Task<CostEstimate> EstimateCostAsync(List<UsagePlanItem> plan, CancellationToken ct = default)
|
|
=> await PostAsync<CostEstimate>("/pricing/estimate", new { plan }, ct);
|
|
|
|
// ==================== Billing Operations ====================
|
|
|
|
public async Task<Usage> GetUsageAsync(BillingPeriod? period = null, CancellationToken ct = default)
|
|
{
|
|
var path = period.HasValue ? $"/billing/usage?period={period.Value.ToString().ToLower()}" : "/billing/usage";
|
|
return await GetAsync<Usage>(path, ct);
|
|
}
|
|
|
|
public async Task<List<Invoice>> GetInvoicesAsync(CancellationToken ct = default)
|
|
{
|
|
var r = await GetAsync<InvoicesResponse>("/billing/invoices", ct);
|
|
return r.Invoices ?? new List<Invoice>();
|
|
}
|
|
|
|
public async Task<Invoice> GetInvoiceAsync(string invoiceId, CancellationToken ct = default)
|
|
=> await GetAsync<Invoice>($"/billing/invoices/{Uri.EscapeDataString(invoiceId)}", ct);
|
|
|
|
public async Task<AccountBalance> GetBalanceAsync(CancellationToken ct = default)
|
|
=> await GetAsync<AccountBalance>("/billing/balance", ct);
|
|
|
|
// ==================== Staking Operations ====================
|
|
|
|
public async Task<StakeReceipt> StakeAsync(string amount, int durationDays = 0, CancellationToken ct = default)
|
|
{
|
|
var body = new Dictionary<string, object> { ["amount"] = amount };
|
|
if (durationDays > 0) body["durationDays"] = durationDays;
|
|
return await PostAsync<StakeReceipt>("/staking/stake", body, ct);
|
|
}
|
|
|
|
public async Task<UnstakeReceipt> UnstakeAsync(string stakeId, CancellationToken ct = default)
|
|
=> await PostAsync<UnstakeReceipt>($"/staking/{Uri.EscapeDataString(stakeId)}/unstake", new { }, ct);
|
|
|
|
public async Task<List<Stake>> GetStakesAsync(CancellationToken ct = default)
|
|
{
|
|
var r = await GetAsync<StakesResponse>("/staking", ct);
|
|
return r.Stakes ?? new List<Stake>();
|
|
}
|
|
|
|
public async Task<Stake> GetStakeAsync(string stakeId, CancellationToken ct = default)
|
|
=> await GetAsync<Stake>($"/staking/{Uri.EscapeDataString(stakeId)}", ct);
|
|
|
|
public async Task<StakingRewards> GetStakingRewardsAsync(CancellationToken ct = default)
|
|
=> await GetAsync<StakingRewards>("/staking/rewards", ct);
|
|
|
|
public async Task<StakeReceipt> ClaimRewardsAsync(CancellationToken ct = default)
|
|
=> await PostAsync<StakeReceipt>("/staking/rewards/claim", new { }, ct);
|
|
|
|
// ==================== Discount Operations ====================
|
|
|
|
public async Task<DiscountResult> ApplyDiscountAsync(string code, CancellationToken ct = default)
|
|
=> await PostAsync<DiscountResult>("/discounts/apply", new { code }, ct);
|
|
|
|
public async Task RemoveDiscountAsync(string code, CancellationToken ct = default)
|
|
=> await DeleteAsync($"/discounts/{Uri.EscapeDataString(code)}", ct);
|
|
|
|
public async Task<List<Discount>> GetDiscountsAsync(CancellationToken ct = default)
|
|
{
|
|
var r = await GetAsync<DiscountsResponse>("/discounts", ct);
|
|
return r.Discounts ?? new List<Discount>();
|
|
}
|
|
|
|
// ==================== Lifecycle ====================
|
|
|
|
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
|
|
{
|
|
try { var r = await GetAsync<HealthResponse>("/health", ct); return r.Status == "healthy"; }
|
|
catch { return false; }
|
|
}
|
|
|
|
public bool IsClosed => _disposed;
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_disposed) { _httpClient.Dispose(); _disposed = true; }
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
// ==================== Private HTTP Methods ====================
|
|
|
|
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
|
|
=> await ExecuteAsync(async () => {
|
|
var r = await _httpClient.GetAsync(path, ct);
|
|
await EnsureSuccessAsync(r);
|
|
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
|
|
});
|
|
|
|
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
|
|
=> await ExecuteAsync(async () => {
|
|
var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
|
|
await EnsureSuccessAsync(r);
|
|
return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
|
|
});
|
|
|
|
private async Task DeleteAsync(string path, CancellationToken ct)
|
|
=> await ExecuteAsync(async () => {
|
|
var r = await _httpClient.DeleteAsync(path, ct);
|
|
await EnsureSuccessAsync(r);
|
|
return true;
|
|
});
|
|
|
|
private async Task<T> ExecuteAsync<T>(Func<Task<T>> op)
|
|
{
|
|
Exception? err = null;
|
|
for (int i = 0; i < _config.Retries; i++)
|
|
{
|
|
try { return await op(); }
|
|
catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); }
|
|
}
|
|
throw err!;
|
|
}
|
|
|
|
private async Task EnsureSuccessAsync(HttpResponseMessage r)
|
|
{
|
|
if (!r.IsSuccessStatusCode)
|
|
{
|
|
var c = await r.Content.ReadAsStringAsync();
|
|
var e = JsonSerializer.Deserialize<Dictionary<string, object>>(c, _jsonOptions);
|
|
throw new EconomicsException(e?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)r.StatusCode}",
|
|
e?.GetValueOrDefault("code")?.ToString(), (int)r.StatusCode);
|
|
}
|
|
}
|
|
}
|