// 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<= 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 }