synor/sdk/go/economics/economics.go
Gulshan Yadav 58e57db661 feat: add Economics, Governance, and Mining SDKs for core languages
Phase 4 implementation - adds three new service SDKs:

Economics SDK:
- Pricing calculations for all service types
- Billing and invoice management
- Staking with APY rewards and vesting
- Discount code system

Governance SDK:
- Proposal lifecycle (create, vote, execute, cancel)
- Voting power with delegation support
- DAO creation (token, multisig, hybrid types)
- Vesting schedules with cliff periods

Mining SDK:
- Stratum pool connections
- Block template retrieval and work submission
- Hashrate and earnings statistics
- GPU device management and configuration
- Worker management and algorithm switching

Languages: JavaScript, Python, Go, Rust
Total: 12 SDK packages
2026-01-27 02:39:27 +05:30

723 lines
20 KiB
Go

// Package economics provides the Synor Economics SDK for Go.
// Pricing, billing, staking, and discount management.
package economics
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
)
const (
DefaultEndpoint = "https://economics.synor.io/v1"
DefaultTimeout = 30 * time.Second
DefaultRetries = 3
)
// ServiceType represents a service type for pricing.
type ServiceType string
const (
ServiceCompute ServiceType = "compute"
ServiceStorage ServiceType = "storage"
ServiceDatabase ServiceType = "database"
ServiceHosting ServiceType = "hosting"
ServiceBridge ServiceType = "bridge"
ServiceRPC ServiceType = "rpc"
)
// BillingPeriod represents a billing period.
type BillingPeriod string
const (
PeriodHourly BillingPeriod = "hourly"
PeriodDaily BillingPeriod = "daily"
PeriodWeekly BillingPeriod = "weekly"
PeriodMonthly BillingPeriod = "monthly"
)
// StakeStatus represents stake status.
type StakeStatus string
const (
StakeActive StakeStatus = "active"
StakeUnstaking StakeStatus = "unstaking"
StakeWithdrawn StakeStatus = "withdrawn"
StakeSlashed StakeStatus = "slashed"
)
// DiscountType represents discount type.
type DiscountType string
const (
DiscountPercentage DiscountType = "percentage"
DiscountFixed DiscountType = "fixed"
DiscountVolume DiscountType = "volume"
DiscountReferral DiscountType = "referral"
)
// InvoiceStatus represents invoice status.
type InvoiceStatus string
const (
InvoicePending InvoiceStatus = "pending"
InvoicePaid InvoiceStatus = "paid"
InvoiceOverdue InvoiceStatus = "overdue"
InvoiceCancelled InvoiceStatus = "cancelled"
)
// Config for the Economics client.
type Config struct {
APIKey string
Endpoint string
Timeout time.Duration
Retries int
Debug bool
}
// UsageMetrics for pricing calculations.
type UsageMetrics struct {
ComputeHours *float64 `json:"compute_hours,omitempty"`
StorageBytes *int64 `json:"storage_bytes,omitempty"`
DatabaseOps *int64 `json:"database_ops,omitempty"`
HostingRequests *int64 `json:"hosting_requests,omitempty"`
BridgeTransfers *int64 `json:"bridge_transfers,omitempty"`
RPCCalls *int64 `json:"rpc_calls,omitempty"`
}
// ServiceUsage in a plan.
type ServiceUsage struct {
Service ServiceType `json:"service"`
Metrics UsageMetrics `json:"metrics"`
Tier string `json:"tier,omitempty"`
}
// UsagePlan for cost estimation.
type UsagePlan struct {
Services []ServiceUsage `json:"services"`
Period BillingPeriod `json:"period"`
StartDate *int64 `json:"start_date,omitempty"`
EndDate *int64 `json:"end_date,omitempty"`
}
// AppliedDiscount represents a discount applied to a price.
type AppliedDiscount struct {
Code string `json:"code"`
Type DiscountType `json:"type"`
Value string `json:"value"`
Savings string `json:"savings"`
Description string `json:"description,omitempty"`
}
// TaxAmount represents tax.
type TaxAmount struct {
Name string `json:"name"`
Rate float64 `json:"rate"`
Amount string `json:"amount"`
}
// Price breakdown.
type Price struct {
Service ServiceType `json:"service"`
BasePrice string `json:"base_price"`
Quantity string `json:"quantity"`
Unit string `json:"unit"`
Subtotal string `json:"subtotal"`
Discounts []AppliedDiscount `json:"discounts"`
Total string `json:"total"`
Currency string `json:"currency"`
}
// CostEstimate for a usage plan.
type CostEstimate struct {
Services []Price `json:"services"`
Subtotal string `json:"subtotal"`
Discounts []AppliedDiscount `json:"discounts"`
Taxes []TaxAmount `json:"taxes"`
Total string `json:"total"`
Currency string `json:"currency"`
ValidUntil int64 `json:"valid_until"`
}
// UsageDetail for a single operation.
type UsageDetail struct {
Timestamp int64 `json:"timestamp"`
Operation string `json:"operation"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
Cost string `json:"cost"`
}
// ServiceUsageRecord for billing.
type ServiceUsageRecord struct {
Service ServiceType `json:"service"`
Metrics UsageMetrics `json:"metrics"`
Cost string `json:"cost"`
Details []UsageDetail `json:"details"`
}
// Usage record.
type Usage struct {
Period BillingPeriod `json:"period"`
StartDate int64 `json:"start_date"`
EndDate int64 `json:"end_date"`
Services []ServiceUsageRecord `json:"services"`
TotalCost string `json:"total_cost"`
Currency string `json:"currency"`
}
// InvoiceItem in an invoice.
type InvoiceItem struct {
Service ServiceType `json:"service"`
Description string `json:"description"`
Quantity string `json:"quantity"`
UnitPrice string `json:"unit_price"`
Total string `json:"total"`
}
// Invoice record.
type Invoice struct {
ID string `json:"id"`
Status InvoiceStatus `json:"status"`
Period BillingPeriod `json:"period"`
StartDate int64 `json:"start_date"`
EndDate int64 `json:"end_date"`
Items []InvoiceItem `json:"items"`
Subtotal string `json:"subtotal"`
Discounts []AppliedDiscount `json:"discounts"`
Taxes []TaxAmount `json:"taxes"`
Total string `json:"total"`
Currency string `json:"currency"`
DueDate int64 `json:"due_date"`
PaidAt *int64 `json:"paid_at,omitempty"`
PaymentMethod string `json:"payment_method,omitempty"`
}
// AccountBalance for billing.
type AccountBalance struct {
Available string `json:"available"`
Pending string `json:"pending"`
Reserved string `json:"reserved"`
Total string `json:"total"`
Currency string `json:"currency"`
CreditLimit string `json:"credit_limit,omitempty"`
LastUpdated int64 `json:"last_updated"`
}
// StakeReceipt from staking.
type StakeReceipt struct {
ID string `json:"id"`
Amount string `json:"amount"`
LockDuration int `json:"lock_duration"`
StartDate int64 `json:"start_date"`
EndDate int64 `json:"end_date"`
APY string `json:"apy"`
EstimatedRewards string `json:"estimated_rewards"`
TxHash string `json:"tx_hash"`
}
// UnstakeReceipt from unstaking.
type UnstakeReceipt struct {
ID string `json:"id"`
StakeID string `json:"stake_id"`
Amount string `json:"amount"`
Rewards string `json:"rewards"`
Total string `json:"total"`
UnbondingPeriod int `json:"unbonding_period"`
AvailableAt int64 `json:"available_at"`
TxHash string `json:"tx_hash"`
}
// StakeInfo for active stakes.
type StakeInfo struct {
ID string `json:"id"`
Amount string `json:"amount"`
Status StakeStatus `json:"status"`
LockDuration int `json:"lock_duration"`
StartDate int64 `json:"start_date"`
EndDate int64 `json:"end_date"`
APY string `json:"apy"`
EarnedRewards string `json:"earned_rewards"`
PendingRewards string `json:"pending_rewards"`
Validator string `json:"validator,omitempty"`
}
// StakeRewardDetail for individual stake rewards.
type StakeRewardDetail struct {
StakeID string `json:"stake_id"`
Earned string `json:"earned"`
Pending string `json:"pending"`
APY string `json:"apy"`
}
// StakingRewards summary.
type StakingRewards struct {
TotalEarned string `json:"total_earned"`
Pending string `json:"pending"`
Claimed string `json:"claimed"`
LastClaimDate *int64 `json:"last_claim_date,omitempty"`
NextClaimAvailable *int64 `json:"next_claim_available,omitempty"`
Stakes []StakeRewardDetail `json:"stakes"`
}
// Discount available or active.
type Discount struct {
Code string `json:"code"`
Type DiscountType `json:"type"`
Value string `json:"value"`
Description string `json:"description"`
ValidFrom int64 `json:"valid_from"`
ValidUntil int64 `json:"valid_until"`
MinPurchase string `json:"min_purchase,omitempty"`
MaxDiscount string `json:"max_discount,omitempty"`
ApplicableServices []ServiceType `json:"applicable_services"`
UsageLimit *int `json:"usage_limit,omitempty"`
UsedCount int `json:"used_count"`
}
// StakeOptions for staking.
type StakeOptions struct {
Validator string `json:"validator,omitempty"`
AutoCompound *bool `json:"auto_compound,omitempty"`
LockDuration *int `json:"lock_duration,omitempty"`
}
// StakingAPY response.
type StakingAPY struct {
APY string `json:"apy"`
MinLockDuration int `json:"min_lock_duration"`
MaxLockDuration int `json:"max_lock_duration"`
}
// Client for the Synor Economics SDK.
type Client struct {
config Config
client *http.Client
mu sync.RWMutex
closed bool
}
// NewClient creates a new Economics client.
func NewClient(config Config) *Client {
if config.Endpoint == "" {
config.Endpoint = DefaultEndpoint
}
if config.Timeout == 0 {
config.Timeout = DefaultTimeout
}
if config.Retries == 0 {
config.Retries = DefaultRetries
}
return &Client{
config: config,
client: &http.Client{Timeout: config.Timeout},
}
}
// ==================== Pricing Operations ====================
// GetPrice gets price for a service based on usage.
func (c *Client) GetPrice(ctx context.Context, service ServiceType, usage UsageMetrics) (*Price, error) {
var resp struct {
Price Price `json:"price"`
}
err := c.post(ctx, "/pricing/calculate", map[string]interface{}{
"service": service,
"usage": usage,
}, &resp)
if err != nil {
return nil, err
}
return &resp.Price, nil
}
// EstimateCost estimates cost for a usage plan.
func (c *Client) EstimateCost(ctx context.Context, plan UsagePlan) (*CostEstimate, error) {
var resp CostEstimate
err := c.post(ctx, "/pricing/estimate", plan, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetPricingTiers gets pricing tiers for a service.
func (c *Client) GetPricingTiers(ctx context.Context, service ServiceType) ([]Price, error) {
var resp struct {
Tiers []Price `json:"tiers"`
}
err := c.get(ctx, fmt.Sprintf("/pricing/%s/tiers", service), &resp)
if err != nil {
return nil, err
}
return resp.Tiers, nil
}
// ==================== Usage & Billing Operations ====================
// GetUsage gets usage for a billing period.
func (c *Client) GetUsage(ctx context.Context, period *BillingPeriod) (*Usage, error) {
path := "/usage"
if period != nil {
path = fmt.Sprintf("/usage?period=%s", *period)
}
var resp Usage
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetUsageHistory gets usage history.
func (c *Client) GetUsageHistory(ctx context.Context, limit, offset *int) ([]Usage, error) {
params := url.Values{}
if limit != nil {
params.Set("limit", fmt.Sprintf("%d", *limit))
}
if offset != nil {
params.Set("offset", fmt.Sprintf("%d", *offset))
}
path := "/usage/history"
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Usage []Usage `json:"usage"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Usage, nil
}
// GetInvoices gets all invoices.
func (c *Client) GetInvoices(ctx context.Context) ([]Invoice, error) {
var resp struct {
Invoices []Invoice `json:"invoices"`
}
err := c.get(ctx, "/invoices", &resp)
if err != nil {
return nil, err
}
return resp.Invoices, nil
}
// GetInvoice gets a specific invoice.
func (c *Client) GetInvoice(ctx context.Context, invoiceID string) (*Invoice, error) {
var resp Invoice
err := c.get(ctx, fmt.Sprintf("/invoices/%s", url.PathEscape(invoiceID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// PayInvoice pays an invoice.
func (c *Client) PayInvoice(ctx context.Context, invoiceID, paymentMethod string) (*Invoice, error) {
var resp Invoice
body := make(map[string]interface{})
if paymentMethod != "" {
body["payment_method"] = paymentMethod
}
err := c.post(ctx, fmt.Sprintf("/invoices/%s/pay", url.PathEscape(invoiceID)), body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetBalance gets account balance.
func (c *Client) GetBalance(ctx context.Context) (*AccountBalance, error) {
var resp AccountBalance
err := c.get(ctx, "/balance", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// AddFunds adds funds to account.
func (c *Client) AddFunds(ctx context.Context, amount, paymentMethod string) (*AccountBalance, error) {
var resp AccountBalance
err := c.post(ctx, "/balance/deposit", map[string]interface{}{
"amount": amount,
"payment_method": paymentMethod,
}, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== Staking Operations ====================
// Stake stakes tokens.
func (c *Client) Stake(ctx context.Context, amount string, options *StakeOptions) (*StakeReceipt, error) {
body := map[string]interface{}{"amount": amount}
if options != nil {
if options.Validator != "" {
body["validator"] = options.Validator
}
if options.AutoCompound != nil {
body["auto_compound"] = *options.AutoCompound
}
if options.LockDuration != nil {
body["lock_duration"] = *options.LockDuration
}
}
var resp StakeReceipt
err := c.post(ctx, "/staking/stake", body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// Unstake unstakes tokens.
func (c *Client) Unstake(ctx context.Context, stakeID string) (*UnstakeReceipt, error) {
var resp UnstakeReceipt
err := c.post(ctx, fmt.Sprintf("/staking/stakes/%s/unstake", url.PathEscape(stakeID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetStakingRewards gets staking rewards.
func (c *Client) GetStakingRewards(ctx context.Context) (*StakingRewards, error) {
var resp StakingRewards
err := c.get(ctx, "/staking/rewards", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ClaimRewards claims staking rewards.
func (c *Client) ClaimRewards(ctx context.Context, stakeID string) (*StakingRewards, error) {
path := "/staking/rewards/claim"
if stakeID != "" {
path = fmt.Sprintf("/staking/stakes/%s/claim", url.PathEscape(stakeID))
}
var resp StakingRewards
err := c.post(ctx, path, nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ListStakes lists active stakes.
func (c *Client) ListStakes(ctx context.Context) ([]StakeInfo, error) {
var resp struct {
Stakes []StakeInfo `json:"stakes"`
}
err := c.get(ctx, "/staking/stakes", &resp)
if err != nil {
return nil, err
}
return resp.Stakes, nil
}
// GetStake gets stake details.
func (c *Client) GetStake(ctx context.Context, stakeID string) (*StakeInfo, error) {
var resp StakeInfo
err := c.get(ctx, fmt.Sprintf("/staking/stakes/%s", url.PathEscape(stakeID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetStakingAPY gets current APY for staking.
func (c *Client) GetStakingAPY(ctx context.Context) (*StakingAPY, error) {
var resp StakingAPY
err := c.get(ctx, "/staking/apy", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== Discount Operations ====================
// ApplyDiscount applies a discount code.
func (c *Client) ApplyDiscount(ctx context.Context, code string) (*Discount, error) {
var resp Discount
err := c.post(ctx, "/discounts/apply", map[string]interface{}{"code": code}, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetAvailableDiscounts gets available discounts.
func (c *Client) GetAvailableDiscounts(ctx context.Context) ([]Discount, error) {
var resp struct {
Discounts []Discount `json:"discounts"`
}
err := c.get(ctx, "/discounts", &resp)
if err != nil {
return nil, err
}
return resp.Discounts, nil
}
// GetActiveDiscounts gets active discounts on account.
func (c *Client) GetActiveDiscounts(ctx context.Context) ([]Discount, error) {
var resp struct {
Discounts []Discount `json:"discounts"`
}
err := c.get(ctx, "/discounts/active", &resp)
if err != nil {
return nil, err
}
return resp.Discounts, nil
}
// RemoveDiscount removes a discount.
func (c *Client) RemoveDiscount(ctx context.Context, code string) error {
return c.delete(ctx, fmt.Sprintf("/discounts/%s", url.PathEscape(code)))
}
// ==================== Lifecycle ====================
// HealthCheck performs a health check.
func (c *Client) HealthCheck(ctx context.Context) bool {
var resp struct {
Status string `json:"status"`
}
err := c.get(ctx, "/health", &resp)
return err == nil && resp.Status == "healthy"
}
// IsClosed returns true if the client is closed.
func (c *Client) IsClosed() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.closed
}
// Close closes the client.
func (c *Client) Close() {
c.mu.Lock()
defer c.mu.Unlock()
c.closed = true
}
// ==================== Private Methods ====================
func (c *Client) get(ctx context.Context, path string, result interface{}) error {
return c.execute(ctx, "GET", path, nil, result)
}
func (c *Client) post(ctx context.Context, path string, body, result interface{}) error {
return c.execute(ctx, "POST", path, body, result)
}
func (c *Client) delete(ctx context.Context, path string) error {
return c.execute(ctx, "DELETE", path, nil, nil)
}
func (c *Client) execute(ctx context.Context, method, path string, body, result interface{}) error {
c.mu.RLock()
if c.closed {
c.mu.RUnlock()
return &EconomicsError{Message: "Client has been closed"}
}
c.mu.RUnlock()
var lastErr error
for attempt := 0; attempt < c.config.Retries; attempt++ {
err := c.doRequest(ctx, method, path, body, result)
if err == nil {
return nil
}
lastErr = err
if c.config.Debug {
fmt.Printf("Attempt %d failed: %v\n", attempt+1, err)
}
if attempt < c.config.Retries-1 {
time.Sleep(time.Duration(1<<attempt) * time.Second)
}
}
return lastErr
}
func (c *Client) doRequest(ctx context.Context, method, path string, body, result interface{}) error {
url := c.config.Endpoint + path
var bodyReader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return err
}
bodyReader = bytes.NewReader(data)
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-SDK-Version", "go/0.1.0")
resp, err := c.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
var errResp struct {
Message string `json:"message"`
Code string `json:"code"`
}
json.Unmarshal(respBody, &errResp)
msg := errResp.Message
if msg == "" {
msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
}
return &EconomicsError{
Message: msg,
Code: errResp.Code,
StatusCode: resp.StatusCode,
}
}
if result != nil && len(respBody) > 0 {
return json.Unmarshal(respBody, result)
}
return nil
}
// EconomicsError represents an error from the Economics API.
type EconomicsError struct {
Message string
Code string
StatusCode int
}
func (e *EconomicsError) Error() string {
if e.Code != "" {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}
return e.Message
}