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
This commit is contained in:
Gulshan Yadav 2026-01-27 02:39:27 +05:30
parent a874faef13
commit 58e57db661
24 changed files with 8998 additions and 0 deletions

View file

@ -0,0 +1,723 @@
// 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
}

View file

@ -0,0 +1,722 @@
// Package governance provides the Synor Governance SDK for Go.
// Proposals, voting, DAOs, and vesting.
package governance
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
)
const (
DefaultEndpoint = "https://governance.synor.io/v1"
DefaultTimeout = 30 * time.Second
DefaultRetries = 3
)
// ProposalStatus represents proposal status.
type ProposalStatus string
const (
ProposalDraft ProposalStatus = "draft"
ProposalActive ProposalStatus = "active"
ProposalPassed ProposalStatus = "passed"
ProposalRejected ProposalStatus = "rejected"
ProposalExecuted ProposalStatus = "executed"
ProposalCancelled ProposalStatus = "cancelled"
ProposalExpired ProposalStatus = "expired"
)
// VoteChoice represents vote choice.
type VoteChoice string
const (
VoteFor VoteChoice = "for"
VoteAgainst VoteChoice = "against"
VoteAbstain VoteChoice = "abstain"
)
// ProposalType represents proposal type.
type ProposalType string
const (
ProposalParameterChange ProposalType = "parameter_change"
ProposalTreasurySpend ProposalType = "treasury_spend"
ProposalUpgrade ProposalType = "upgrade"
ProposalText ProposalType = "text"
ProposalCustom ProposalType = "custom"
)
// DaoType represents DAO type.
type DaoType string
const (
DaoToken DaoType = "token"
DaoMultisig DaoType = "multisig"
DaoHybrid DaoType = "hybrid"
)
// VestingStatus represents vesting status.
type VestingStatus string
const (
VestingActive VestingStatus = "active"
VestingPaused VestingStatus = "paused"
VestingCompleted VestingStatus = "completed"
VestingCancelled VestingStatus = "cancelled"
)
// Config for the Governance client.
type Config struct {
APIKey string
Endpoint string
Timeout time.Duration
Retries int
Debug bool
}
// ProposalAction represents a proposal action.
type ProposalAction struct {
Target string `json:"target"`
Value string `json:"value"`
Calldata string `json:"calldata"`
Description string `json:"description,omitempty"`
}
// ProposalDraft for creating proposals.
type ProposalDraft struct {
Title string `json:"title"`
Description string `json:"description"`
Type ProposalType `json:"type"`
Actions []ProposalAction `json:"actions,omitempty"`
StartTime *int64 `json:"start_time,omitempty"`
EndTime *int64 `json:"end_time,omitempty"`
Quorum string `json:"quorum,omitempty"`
Threshold string `json:"threshold,omitempty"`
}
// Proposal represents a governance proposal.
type Proposal struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Type ProposalType `json:"type"`
Status ProposalStatus `json:"status"`
Proposer string `json:"proposer"`
Actions []ProposalAction `json:"actions"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Quorum string `json:"quorum"`
Threshold string `json:"threshold"`
ForVotes string `json:"for_votes"`
AgainstVotes string `json:"against_votes"`
AbstainVotes string `json:"abstain_votes"`
TotalVotes string `json:"total_votes"`
CreatedAt int64 `json:"created_at"`
ExecutedAt *int64 `json:"executed_at,omitempty"`
ExecutionTxHash string `json:"execution_tx_hash,omitempty"`
}
// ProposalFilter for filtering proposals.
type ProposalFilter struct {
Status *ProposalStatus
Type *ProposalType
Proposer string
Limit *int
Offset *int
}
// Vote represents a vote.
type Vote struct {
Choice VoteChoice `json:"choice"`
Reason string `json:"reason,omitempty"`
}
// VoteReceipt represents a vote receipt.
type VoteReceipt struct {
ProposalID string `json:"proposal_id"`
Voter string `json:"voter"`
Choice VoteChoice `json:"choice"`
Weight string `json:"weight"`
Reason string `json:"reason,omitempty"`
Timestamp int64 `json:"timestamp"`
TxHash string `json:"tx_hash"`
}
// DelegationInfo represents delegation information.
type DelegationInfo struct {
Address string `json:"address"`
Amount string `json:"amount"`
Timestamp int64 `json:"timestamp"`
}
// DelegationReceipt represents a delegation receipt.
type DelegationReceipt struct {
Delegator string `json:"delegator"`
Delegatee string `json:"delegatee"`
Amount string `json:"amount"`
Timestamp int64 `json:"timestamp"`
TxHash string `json:"tx_hash"`
}
// VotingPower represents voting power.
type VotingPower struct {
Address string `json:"address"`
OwnPower string `json:"own_power"`
DelegatedPower string `json:"delegated_power"`
TotalPower string `json:"total_power"`
DelegatedFrom []DelegationInfo `json:"delegated_from"`
DelegatedTo *DelegationInfo `json:"delegated_to,omitempty"`
}
// DaoConfig for creating DAOs.
type DaoConfig struct {
Name string `json:"name"`
Type DaoType `json:"type"`
TokenAddress string `json:"token_address,omitempty"`
Signers []string `json:"signers,omitempty"`
Threshold *int `json:"threshold,omitempty"`
VotingPeriod int `json:"voting_period"`
Quorum string `json:"quorum"`
ProposalThreshold string `json:"proposal_threshold"`
TimelockDelay *int `json:"timelock_delay,omitempty"`
}
// Dao represents a DAO.
type Dao struct {
ID string `json:"id"`
Name string `json:"name"`
Type DaoType `json:"type"`
TokenAddress string `json:"token_address,omitempty"`
Signers []string `json:"signers,omitempty"`
Threshold *int `json:"threshold,omitempty"`
VotingPeriod int `json:"voting_period"`
Quorum string `json:"quorum"`
ProposalThreshold string `json:"proposal_threshold"`
TimelockDelay *int `json:"timelock_delay,omitempty"`
Treasury string `json:"treasury"`
TotalProposals int `json:"total_proposals"`
ActiveProposals int `json:"active_proposals"`
TotalMembers int `json:"total_members"`
CreatedAt int64 `json:"created_at"`
}
// VestingSchedule for creating vesting contracts.
type VestingSchedule struct {
Beneficiary string `json:"beneficiary"`
TotalAmount string `json:"total_amount"`
StartTime int64 `json:"start_time"`
CliffDuration int `json:"cliff_duration"`
VestingDuration int `json:"vesting_duration"`
Revocable bool `json:"revocable"`
}
// VestingContract represents a vesting contract.
type VestingContract struct {
ID string `json:"id"`
Beneficiary string `json:"beneficiary"`
TotalAmount string `json:"total_amount"`
ReleasedAmount string `json:"released_amount"`
VestedAmount string `json:"vested_amount"`
StartTime int64 `json:"start_time"`
CliffTime int64 `json:"cliff_time"`
EndTime int64 `json:"end_time"`
Revocable bool `json:"revocable"`
Status VestingStatus `json:"status"`
CreatedAt int64 `json:"created_at"`
TxHash string `json:"tx_hash"`
}
// ClaimResult represents a claim result.
type ClaimResult struct {
VestingID string `json:"vesting_id"`
Amount string `json:"amount"`
Recipient string `json:"recipient"`
Timestamp int64 `json:"timestamp"`
TxHash string `json:"tx_hash"`
}
// GovernanceTransaction represents a transaction result.
type GovernanceTransaction struct {
TxHash string `json:"tx_hash"`
Timestamp int64 `json:"timestamp"`
BlockNumber int64 `json:"block_number"`
Status string `json:"status"`
}
// DaoMember represents a DAO member.
type DaoMember struct {
Address string `json:"address"`
Power string `json:"power"`
}
// ClaimableAmount represents claimable amount.
type ClaimableAmount struct {
Claimable string `json:"claimable"`
Vested string `json:"vested"`
}
// Client for the Synor Governance SDK.
type Client struct {
config Config
client *http.Client
mu sync.RWMutex
closed bool
}
// NewClient creates a new Governance 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},
}
}
// ==================== Proposal Operations ====================
// CreateProposal creates a new proposal.
func (c *Client) CreateProposal(ctx context.Context, draft ProposalDraft) (*Proposal, error) {
var resp Proposal
err := c.post(ctx, "/proposals", draft, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetProposal gets a proposal by ID.
func (c *Client) GetProposal(ctx context.Context, proposalID string) (*Proposal, error) {
var resp Proposal
err := c.get(ctx, fmt.Sprintf("/proposals/%s", url.PathEscape(proposalID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ListProposals lists proposals with optional filtering.
func (c *Client) ListProposals(ctx context.Context, filter *ProposalFilter) ([]Proposal, error) {
params := url.Values{}
if filter != nil {
if filter.Status != nil {
params.Set("status", string(*filter.Status))
}
if filter.Type != nil {
params.Set("type", string(*filter.Type))
}
if filter.Proposer != "" {
params.Set("proposer", filter.Proposer)
}
if filter.Limit != nil {
params.Set("limit", fmt.Sprintf("%d", *filter.Limit))
}
if filter.Offset != nil {
params.Set("offset", fmt.Sprintf("%d", *filter.Offset))
}
}
path := "/proposals"
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Proposals []Proposal `json:"proposals"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Proposals, nil
}
// CancelProposal cancels a proposal.
func (c *Client) CancelProposal(ctx context.Context, proposalID string) (*Proposal, error) {
var resp Proposal
err := c.post(ctx, fmt.Sprintf("/proposals/%s/cancel", url.PathEscape(proposalID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ExecuteProposal executes a passed proposal.
func (c *Client) ExecuteProposal(ctx context.Context, proposalID string) (*GovernanceTransaction, error) {
var resp GovernanceTransaction
err := c.post(ctx, fmt.Sprintf("/proposals/%s/execute", url.PathEscape(proposalID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== Voting Operations ====================
// Vote votes on a proposal.
func (c *Client) Vote(ctx context.Context, proposalID string, vote Vote, weight string) (*VoteReceipt, error) {
body := map[string]interface{}{
"choice": vote.Choice,
}
if vote.Reason != "" {
body["reason"] = vote.Reason
}
if weight != "" {
body["weight"] = weight
}
var resp VoteReceipt
err := c.post(ctx, fmt.Sprintf("/proposals/%s/vote", url.PathEscape(proposalID)), body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetVotes gets votes for a proposal.
func (c *Client) GetVotes(ctx context.Context, proposalID string, limit, offset *int) ([]VoteReceipt, 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 := fmt.Sprintf("/proposals/%s/votes", url.PathEscape(proposalID))
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Votes []VoteReceipt `json:"votes"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Votes, nil
}
// Delegate delegates voting power.
func (c *Client) Delegate(ctx context.Context, to string, amount string) (*DelegationReceipt, error) {
body := map[string]interface{}{"to": to}
if amount != "" {
body["amount"] = amount
}
var resp DelegationReceipt
err := c.post(ctx, "/voting/delegate", body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// Undelegate undelegates voting power.
func (c *Client) Undelegate(ctx context.Context, from string) (*DelegationReceipt, error) {
body := make(map[string]interface{})
if from != "" {
body["from"] = from
}
var resp DelegationReceipt
err := c.post(ctx, "/voting/undelegate", body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetVotingPower gets voting power for an address.
func (c *Client) GetVotingPower(ctx context.Context, address string) (*VotingPower, error) {
var resp VotingPower
err := c.get(ctx, fmt.Sprintf("/voting/power/%s", url.PathEscape(address)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetMyVotingPower gets voting power for the authenticated user.
func (c *Client) GetMyVotingPower(ctx context.Context) (*VotingPower, error) {
var resp VotingPower
err := c.get(ctx, "/voting/power", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== DAO Operations ====================
// CreateDao creates a new DAO.
func (c *Client) CreateDao(ctx context.Context, config DaoConfig) (*Dao, error) {
var resp Dao
err := c.post(ctx, "/daos", config, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetDao gets a DAO by ID.
func (c *Client) GetDao(ctx context.Context, daoID string) (*Dao, error) {
var resp Dao
err := c.get(ctx, fmt.Sprintf("/daos/%s", url.PathEscape(daoID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ListDaos lists DAOs.
func (c *Client) ListDaos(ctx context.Context, limit, offset *int) ([]Dao, 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 := "/daos"
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Daos []Dao `json:"daos"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Daos, nil
}
// GetDaoMembers gets DAO members.
func (c *Client) GetDaoMembers(ctx context.Context, daoID string, limit, offset *int) ([]DaoMember, 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 := fmt.Sprintf("/daos/%s/members", url.PathEscape(daoID))
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Members []DaoMember `json:"members"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Members, nil
}
// ==================== Vesting Operations ====================
// CreateVestingSchedule creates a vesting schedule.
func (c *Client) CreateVestingSchedule(ctx context.Context, schedule VestingSchedule) (*VestingContract, error) {
var resp VestingContract
err := c.post(ctx, "/vesting", schedule, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetVestingContract gets a vesting contract.
func (c *Client) GetVestingContract(ctx context.Context, contractID string) (*VestingContract, error) {
var resp VestingContract
err := c.get(ctx, fmt.Sprintf("/vesting/%s", url.PathEscape(contractID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ListVestingContracts lists vesting contracts.
func (c *Client) ListVestingContracts(ctx context.Context, beneficiary string) ([]VestingContract, error) {
path := "/vesting"
if beneficiary != "" {
path = fmt.Sprintf("%s?beneficiary=%s", path, url.QueryEscape(beneficiary))
}
var resp struct {
Contracts []VestingContract `json:"contracts"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Contracts, nil
}
// ClaimVested claims vested tokens.
func (c *Client) ClaimVested(ctx context.Context, contractID string) (*ClaimResult, error) {
var resp ClaimResult
err := c.post(ctx, fmt.Sprintf("/vesting/%s/claim", url.PathEscape(contractID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// RevokeVesting revokes a vesting contract.
func (c *Client) RevokeVesting(ctx context.Context, contractID string) (*VestingContract, error) {
var resp VestingContract
err := c.post(ctx, fmt.Sprintf("/vesting/%s/revoke", url.PathEscape(contractID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetClaimableAmount gets claimable amount for a vesting contract.
func (c *Client) GetClaimableAmount(ctx context.Context, contractID string) (*ClaimableAmount, error) {
var resp ClaimableAmount
err := c.get(ctx, fmt.Sprintf("/vesting/%s/claimable", url.PathEscape(contractID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== 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) execute(ctx context.Context, method, path string, body, result interface{}) error {
c.mu.RLock()
if c.closed {
c.mu.RUnlock()
return &GovernanceError{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 &GovernanceError{
Message: msg,
Code: errResp.Code,
StatusCode: resp.StatusCode,
}
}
if result != nil && len(respBody) > 0 {
return json.Unmarshal(respBody, result)
}
return nil
}
// GovernanceError represents an error from the Governance API.
type GovernanceError struct {
Message string
Code string
StatusCode int
}
func (e *GovernanceError) Error() string {
if e.Code != "" {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}
return e.Message
}

786
sdk/go/mining/mining.go Normal file
View file

@ -0,0 +1,786 @@
// Package mining provides the Synor Mining SDK for Go.
// Pool connections, block templates, hashrate stats, and GPU management.
package mining
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
)
const (
DefaultEndpoint = "https://mining.synor.io/v1"
DefaultTimeout = 30 * time.Second
DefaultRetries = 3
)
// DeviceType represents device type.
type DeviceType string
const (
DeviceCPU DeviceType = "cpu"
DeviceGPUNvidia DeviceType = "gpu_nvidia"
DeviceGPUAMD DeviceType = "gpu_amd"
DeviceASIC DeviceType = "asic"
)
// DeviceStatus represents device status.
type DeviceStatus string
const (
DeviceIdle DeviceStatus = "idle"
DeviceMining DeviceStatus = "mining"
DeviceError DeviceStatus = "error"
DeviceOffline DeviceStatus = "offline"
)
// ConnectionStatus represents connection status.
type ConnectionStatus string
const (
StatusDisconnected ConnectionStatus = "disconnected"
StatusConnecting ConnectionStatus = "connecting"
StatusConnected ConnectionStatus = "connected"
StatusReconnecting ConnectionStatus = "reconnecting"
)
// TimePeriod for stats.
type TimePeriod string
const (
PeriodHour TimePeriod = "hour"
PeriodDay TimePeriod = "day"
PeriodWeek TimePeriod = "week"
PeriodMonth TimePeriod = "month"
PeriodAll TimePeriod = "all"
)
// SubmitResultStatus represents submit result status.
type SubmitResultStatus string
const (
SubmitAccepted SubmitResultStatus = "accepted"
SubmitRejected SubmitResultStatus = "rejected"
SubmitStale SubmitResultStatus = "stale"
)
// Config for the Mining client.
type Config struct {
APIKey string
Endpoint string
Timeout time.Duration
Retries int
Debug bool
}
// PoolConfig for connecting to a pool.
type PoolConfig struct {
URL string `json:"url"`
User string `json:"user"`
Password string `json:"password,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Difficulty *float64 `json:"difficulty,omitempty"`
}
// StratumConnection represents a pool connection.
type StratumConnection struct {
ID string `json:"id"`
Pool string `json:"pool"`
Status ConnectionStatus `json:"status"`
Algorithm string `json:"algorithm"`
Difficulty float64 `json:"difficulty"`
ConnectedAt int64 `json:"connected_at"`
LastShareAt *int64 `json:"last_share_at,omitempty"`
AcceptedShares int64 `json:"accepted_shares"`
RejectedShares int64 `json:"rejected_shares"`
StaleShares int64 `json:"stale_shares"`
}
// TemplateTransaction in a block template.
type TemplateTransaction struct {
TxID string `json:"txid"`
Data string `json:"data"`
Fee string `json:"fee"`
Weight int `json:"weight"`
}
// BlockTemplate for mining.
type BlockTemplate struct {
ID string `json:"id"`
PreviousBlockHash string `json:"previous_block_hash"`
MerkleRoot string `json:"merkle_root"`
Timestamp int64 `json:"timestamp"`
Bits string `json:"bits"`
Height int64 `json:"height"`
CoinbaseValue string `json:"coinbase_value"`
Transactions []TemplateTransaction `json:"transactions"`
Target string `json:"target"`
Algorithm string `json:"algorithm"`
ExtraNonce string `json:"extra_nonce"`
}
// MinedWork to submit.
type MinedWork struct {
TemplateID string `json:"template_id"`
Nonce string `json:"nonce"`
ExtraNonce string `json:"extra_nonce"`
Timestamp int64 `json:"timestamp"`
Hash string `json:"hash"`
}
// ShareInfo in submit result.
type ShareInfo struct {
Hash string `json:"hash"`
Difficulty float64 `json:"difficulty"`
Timestamp int64 `json:"timestamp"`
Accepted bool `json:"accepted"`
}
// SubmitResult from submitting work.
type SubmitResult struct {
Status SubmitResultStatus `json:"status"`
Reason string `json:"reason,omitempty"`
Share ShareInfo `json:"share"`
BlockFound bool `json:"block_found"`
BlockHash string `json:"block_hash,omitempty"`
Reward string `json:"reward,omitempty"`
}
// Hashrate statistics.
type Hashrate struct {
Current float64 `json:"current"`
Average1h float64 `json:"average_1h"`
Average24h float64 `json:"average_24h"`
Peak float64 `json:"peak"`
Unit string `json:"unit"`
}
// ShareStats for mining.
type ShareStats struct {
Accepted int64 `json:"accepted"`
Rejected int64 `json:"rejected"`
Stale int64 `json:"stale"`
Total int64 `json:"total"`
AcceptRate float64 `json:"accept_rate"`
}
// DeviceTemperature info.
type DeviceTemperature struct {
Current float64 `json:"current"`
Max float64 `json:"max"`
Throttling bool `json:"throttling"`
}
// EarningsSnapshot for quick view.
type EarningsSnapshot struct {
Today string `json:"today"`
Yesterday string `json:"yesterday"`
ThisWeek string `json:"this_week"`
ThisMonth string `json:"this_month"`
Total string `json:"total"`
Currency string `json:"currency"`
}
// MiningStats comprehensive stats.
type MiningStats struct {
Hashrate Hashrate `json:"hashrate"`
Shares ShareStats `json:"shares"`
Uptime int64 `json:"uptime"`
Efficiency float64 `json:"efficiency"`
PowerConsumption *float64 `json:"power_consumption,omitempty"`
Temperature *DeviceTemperature `json:"temperature,omitempty"`
Earnings EarningsSnapshot `json:"earnings"`
}
// EarningsBreakdown by date.
type EarningsBreakdown struct {
Date int64 `json:"date"`
Amount string `json:"amount"`
Blocks int `json:"blocks"`
Shares int64 `json:"shares"`
Hashrate float64 `json:"hashrate"`
}
// Earnings detailed info.
type Earnings struct {
Period TimePeriod `json:"period"`
StartDate int64 `json:"start_date"`
EndDate int64 `json:"end_date"`
Amount string `json:"amount"`
Blocks int `json:"blocks"`
Shares int64 `json:"shares"`
AverageHashrate float64 `json:"average_hashrate"`
Currency string `json:"currency"`
Breakdown []EarningsBreakdown `json:"breakdown"`
}
// MiningDevice represents a mining device.
type MiningDevice struct {
ID string `json:"id"`
Name string `json:"name"`
Type DeviceType `json:"type"`
Status DeviceStatus `json:"status"`
Hashrate float64 `json:"hashrate"`
Temperature float64 `json:"temperature"`
FanSpeed float64 `json:"fan_speed"`
PowerDraw float64 `json:"power_draw"`
MemoryUsed int64 `json:"memory_used"`
MemoryTotal int64 `json:"memory_total"`
Driver string `json:"driver,omitempty"`
Firmware string `json:"firmware,omitempty"`
}
// DeviceConfig for device settings.
type DeviceConfig struct {
Enabled bool `json:"enabled"`
Intensity *int `json:"intensity,omitempty"`
PowerLimit *int `json:"power_limit,omitempty"`
CoreClockOffset *int `json:"core_clock_offset,omitempty"`
MemoryClockOffset *int `json:"memory_clock_offset,omitempty"`
FanSpeed *int `json:"fan_speed,omitempty"`
}
// WorkerInfo for worker details.
type WorkerInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Status ConnectionStatus `json:"status"`
Hashrate Hashrate `json:"hashrate"`
Shares ShareStats `json:"shares"`
Devices []MiningDevice `json:"devices"`
LastSeen int64 `json:"last_seen"`
Uptime int64 `json:"uptime"`
}
// PoolStats for pool info.
type PoolStats struct {
URL string `json:"url"`
Workers int `json:"workers"`
Hashrate float64 `json:"hashrate"`
Difficulty float64 `json:"difficulty"`
LastBlock int64 `json:"last_block"`
BlocksFound24h int `json:"blocks_found_24h"`
Luck float64 `json:"luck"`
}
// MiningAlgorithm info.
type MiningAlgorithm struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
HashUnit string `json:"hash_unit"`
Profitability string `json:"profitability"`
Difficulty float64 `json:"difficulty"`
BlockReward string `json:"block_reward"`
BlockTime int `json:"block_time"`
}
// WorkResult from getwork.
type WorkResult struct {
Work string `json:"work"`
Target string `json:"target"`
Algorithm string `json:"algorithm"`
}
// StartMiningResult from start mining.
type StartMiningResult struct {
Started bool `json:"started"`
Devices []string `json:"devices"`
}
// Client for the Synor Mining SDK.
type Client struct {
config Config
client *http.Client
activeConnection *StratumConnection
mu sync.RWMutex
closed bool
}
// NewClient creates a new Mining 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},
}
}
// ==================== Pool Connection ====================
// Connect connects to a mining pool.
func (c *Client) Connect(ctx context.Context, pool PoolConfig) (*StratumConnection, error) {
var resp StratumConnection
err := c.post(ctx, "/pool/connect", pool, &resp)
if err != nil {
return nil, err
}
c.mu.Lock()
c.activeConnection = &resp
c.mu.Unlock()
return &resp, nil
}
// Disconnect disconnects from the current pool.
func (c *Client) Disconnect(ctx context.Context) error {
c.mu.Lock()
conn := c.activeConnection
c.mu.Unlock()
if conn != nil {
err := c.post(ctx, fmt.Sprintf("/pool/disconnect/%s", conn.ID), nil, nil)
if err != nil {
return err
}
c.mu.Lock()
c.activeConnection = nil
c.mu.Unlock()
}
return nil
}
// GetConnectionStatus gets current connection status.
func (c *Client) GetConnectionStatus(ctx context.Context) (*StratumConnection, error) {
c.mu.RLock()
conn := c.activeConnection
c.mu.RUnlock()
if conn == nil {
return nil, nil
}
var resp StratumConnection
err := c.get(ctx, fmt.Sprintf("/pool/status/%s", conn.ID), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// Reconnect reconnects to the pool.
func (c *Client) Reconnect(ctx context.Context) (*StratumConnection, error) {
c.mu.RLock()
conn := c.activeConnection
c.mu.RUnlock()
if conn == nil {
return nil, &MiningError{Message: "No active connection to reconnect"}
}
var resp StratumConnection
err := c.post(ctx, fmt.Sprintf("/pool/reconnect/%s", conn.ID), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== Mining Operations ====================
// GetBlockTemplate gets the current block template.
func (c *Client) GetBlockTemplate(ctx context.Context) (*BlockTemplate, error) {
var resp BlockTemplate
err := c.get(ctx, "/mining/template", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// SubmitWork submits mined work.
func (c *Client) SubmitWork(ctx context.Context, work MinedWork) (*SubmitResult, error) {
var resp SubmitResult
err := c.post(ctx, "/mining/submit", work, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetWork gets work from pool.
func (c *Client) GetWork(ctx context.Context) (*WorkResult, error) {
var resp WorkResult
err := c.get(ctx, "/mining/getwork", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// StartMining starts mining on all enabled devices.
func (c *Client) StartMining(ctx context.Context, algorithm string) (*StartMiningResult, error) {
body := make(map[string]interface{})
if algorithm != "" {
body["algorithm"] = algorithm
}
var resp StartMiningResult
err := c.post(ctx, "/mining/start", body, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// StopMining stops mining on all devices.
func (c *Client) StopMining(ctx context.Context) (bool, error) {
var resp struct {
Stopped bool `json:"stopped"`
}
err := c.post(ctx, "/mining/stop", nil, &resp)
if err != nil {
return false, err
}
return resp.Stopped, nil
}
// ==================== Stats ====================
// GetHashrate gets current hashrate.
func (c *Client) GetHashrate(ctx context.Context) (*Hashrate, error) {
var resp Hashrate
err := c.get(ctx, "/stats/hashrate", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetStats gets mining stats.
func (c *Client) GetStats(ctx context.Context) (*MiningStats, error) {
var resp MiningStats
err := c.get(ctx, "/stats", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetEarnings gets earnings for a time period.
func (c *Client) GetEarnings(ctx context.Context, period *TimePeriod) (*Earnings, error) {
path := "/stats/earnings"
if period != nil {
path = fmt.Sprintf("%s?period=%s", path, *period)
}
var resp Earnings
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// GetEarningsHistory gets earnings history.
func (c *Client) GetEarningsHistory(ctx context.Context, limit, offset *int) ([]Earnings, 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 := "/stats/earnings/history"
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params.Encode())
}
var resp struct {
Earnings []Earnings `json:"earnings"`
}
err := c.get(ctx, path, &resp)
if err != nil {
return nil, err
}
return resp.Earnings, nil
}
// GetPoolStats gets pool stats.
func (c *Client) GetPoolStats(ctx context.Context) (*PoolStats, error) {
var resp PoolStats
err := c.get(ctx, "/pool/stats", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== GPU Management ====================
// ListDevices lists all mining devices.
func (c *Client) ListDevices(ctx context.Context) ([]MiningDevice, error) {
var resp struct {
Devices []MiningDevice `json:"devices"`
}
err := c.get(ctx, "/devices", &resp)
if err != nil {
return nil, err
}
return resp.Devices, nil
}
// GetDevice gets device details.
func (c *Client) GetDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
var resp MiningDevice
err := c.get(ctx, fmt.Sprintf("/devices/%s", url.PathEscape(deviceID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// SetDeviceConfig sets device configuration.
func (c *Client) SetDeviceConfig(ctx context.Context, deviceID string, config DeviceConfig) (*MiningDevice, error) {
var resp MiningDevice
err := c.post(ctx, fmt.Sprintf("/devices/%s/config", url.PathEscape(deviceID)), config, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// EnableDevice enables a device for mining.
func (c *Client) EnableDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
var resp MiningDevice
err := c.post(ctx, fmt.Sprintf("/devices/%s/enable", url.PathEscape(deviceID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// DisableDevice disables a device.
func (c *Client) DisableDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
var resp MiningDevice
err := c.post(ctx, fmt.Sprintf("/devices/%s/disable", url.PathEscape(deviceID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ResetDevice resets device to default settings.
func (c *Client) ResetDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
var resp MiningDevice
err := c.post(ctx, fmt.Sprintf("/devices/%s/reset", url.PathEscape(deviceID)), nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// ==================== Workers ====================
// ListWorkers lists all workers.
func (c *Client) ListWorkers(ctx context.Context) ([]WorkerInfo, error) {
var resp struct {
Workers []WorkerInfo `json:"workers"`
}
err := c.get(ctx, "/workers", &resp)
if err != nil {
return nil, err
}
return resp.Workers, nil
}
// GetWorker gets worker details.
func (c *Client) GetWorker(ctx context.Context, workerID string) (*WorkerInfo, error) {
var resp WorkerInfo
err := c.get(ctx, fmt.Sprintf("/workers/%s", url.PathEscape(workerID)), &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// CreateWorker creates a new worker.
func (c *Client) CreateWorker(ctx context.Context, name string) (*WorkerInfo, error) {
var resp WorkerInfo
err := c.post(ctx, "/workers", map[string]string{"name": name}, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// DeleteWorker deletes a worker.
func (c *Client) DeleteWorker(ctx context.Context, workerID string) error {
return c.delete(ctx, fmt.Sprintf("/workers/%s", url.PathEscape(workerID)))
}
// ==================== Algorithms ====================
// GetSupportedAlgorithms gets supported mining algorithms.
func (c *Client) GetSupportedAlgorithms(ctx context.Context) ([]MiningAlgorithm, error) {
var resp struct {
Algorithms []MiningAlgorithm `json:"algorithms"`
}
err := c.get(ctx, "/algorithms", &resp)
if err != nil {
return nil, err
}
return resp.Algorithms, nil
}
// GetCurrentAlgorithm gets current algorithm.
func (c *Client) GetCurrentAlgorithm(ctx context.Context) (*MiningAlgorithm, error) {
var resp MiningAlgorithm
err := c.get(ctx, "/algorithms/current", &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
// SwitchAlgorithm switches to a different algorithm.
func (c *Client) SwitchAlgorithm(ctx context.Context, algorithm string) (bool, error) {
var resp struct {
Switched bool `json:"switched"`
}
err := c.post(ctx, "/algorithms/switch", map[string]string{"algorithm": algorithm}, &resp)
if err != nil {
return false, err
}
return resp.Switched, nil
}
// ==================== 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(ctx context.Context) error {
c.Disconnect(ctx)
c.mu.Lock()
defer c.mu.Unlock()
c.closed = true
return nil
}
// ==================== 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 &MiningError{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 &MiningError{
Message: msg,
Code: errResp.Code,
StatusCode: resp.StatusCode,
}
}
if result != nil && len(respBody) > 0 {
return json.Unmarshal(respBody, result)
}
return nil
}
// MiningError represents an error from the Mining API.
type MiningError struct {
Message string
Code string
StatusCode int
}
func (e *MiningError) Error() string {
if e.Code != "" {
return fmt.Sprintf("%s (%s)", e.Message, e.Code)
}
return e.Message
}

View file

@ -0,0 +1,361 @@
/**
* Synor Economics SDK Client
* Pricing, billing, staking, and discount management.
*/
import {
EconomicsConfig,
ServiceType,
UsageMetrics,
Price,
UsagePlan,
CostEstimate,
BillingPeriod,
Usage,
Invoice,
AccountBalance,
StakeReceipt,
UnstakeReceipt,
StakingRewards,
StakeInfo,
StakeOptions,
Discount,
PriceResponse,
InvoicesResponse,
StakesResponse,
DiscountsResponse,
} from './types';
const DEFAULT_ENDPOINT = 'https://economics.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;
export class SynorEconomics {
private config: Required<EconomicsConfig>;
private closed = false;
constructor(config: EconomicsConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
timeout: config.timeout ?? DEFAULT_TIMEOUT,
retries: config.retries ?? DEFAULT_RETRIES,
debug: config.debug ?? false,
};
}
// ==================== Pricing Operations ====================
/**
* Get price for a service based on usage.
*/
async getPrice(service: ServiceType, usage: UsageMetrics): Promise<Price> {
const response = await this.post<PriceResponse>('/pricing/calculate', {
service,
usage,
});
return response.price;
}
/**
* Estimate cost for a usage plan.
*/
async estimateCost(plan: UsagePlan): Promise<CostEstimate> {
return await this.post<CostEstimate>('/pricing/estimate', plan);
}
/**
* Get pricing tiers for a service.
*/
async getPricingTiers(service: ServiceType): Promise<Price[]> {
const response = await this.get<{ tiers: Price[] }>(`/pricing/${service}/tiers`);
return response.tiers ?? [];
}
// ==================== Usage & Billing Operations ====================
/**
* Get usage for a billing period.
*/
async getUsage(period?: BillingPeriod): Promise<Usage> {
const params = period ? `?period=${period}` : '';
return await this.get<Usage>(`/usage${params}`);
}
/**
* Get usage history.
*/
async getUsageHistory(limit?: number, offset?: number): Promise<Usage[]> {
const params = new URLSearchParams();
if (limit) params.set('limit', limit.toString());
if (offset) params.set('offset', offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<{ usage: Usage[] }>(`/usage/history${query}`);
return response.usage ?? [];
}
/**
* Get invoices.
*/
async getInvoices(): Promise<Invoice[]> {
const response = await this.get<InvoicesResponse>('/invoices');
return response.invoices ?? [];
}
/**
* Get a specific invoice.
*/
async getInvoice(invoiceId: string): Promise<Invoice> {
return await this.get<Invoice>(`/invoices/${encodeURIComponent(invoiceId)}`);
}
/**
* Pay an invoice.
*/
async payInvoice(invoiceId: string, paymentMethod?: string): Promise<Invoice> {
return await this.post<Invoice>(`/invoices/${encodeURIComponent(invoiceId)}/pay`, {
paymentMethod,
});
}
/**
* Get account balance.
*/
async getBalance(): Promise<AccountBalance> {
return await this.get<AccountBalance>('/balance');
}
/**
* Add funds to account.
*/
async addFunds(amount: string, paymentMethod: string): Promise<AccountBalance> {
return await this.post<AccountBalance>('/balance/deposit', {
amount,
paymentMethod,
});
}
// ==================== Staking Operations ====================
/**
* Stake tokens.
*/
async stake(amount: string, options?: StakeOptions): Promise<StakeReceipt> {
const body: Record<string, unknown> = { amount };
if (options?.validator) body.validator = options.validator;
if (options?.autoCompound !== undefined) body.autoCompound = options.autoCompound;
if (options?.lockDuration) body.lockDuration = options.lockDuration;
return await this.post<StakeReceipt>('/staking/stake', body);
}
/**
* Unstake tokens.
*/
async unstake(stakeId: string): Promise<UnstakeReceipt> {
return await this.post<UnstakeReceipt>(`/staking/stakes/${encodeURIComponent(stakeId)}/unstake`, {});
}
/**
* Get staking rewards.
*/
async getStakingRewards(): Promise<StakingRewards> {
return await this.get<StakingRewards>('/staking/rewards');
}
/**
* Claim staking rewards.
*/
async claimRewards(stakeId?: string): Promise<StakingRewards> {
const path = stakeId
? `/staking/stakes/${encodeURIComponent(stakeId)}/claim`
: '/staking/rewards/claim';
return await this.post<StakingRewards>(path, {});
}
/**
* List active stakes.
*/
async listStakes(): Promise<StakeInfo[]> {
const response = await this.get<StakesResponse>('/staking/stakes');
return response.stakes ?? [];
}
/**
* Get stake details.
*/
async getStake(stakeId: string): Promise<StakeInfo> {
return await this.get<StakeInfo>(`/staking/stakes/${encodeURIComponent(stakeId)}`);
}
/**
* Get current APY for staking.
*/
async getStakingApy(): Promise<{ apy: string; minLockDuration: number; maxLockDuration: number }> {
return await this.get('/staking/apy');
}
// ==================== Discount Operations ====================
/**
* Apply a discount code.
*/
async applyDiscount(code: string): Promise<Discount> {
return await this.post<Discount>('/discounts/apply', { code });
}
/**
* Get available discounts.
*/
async getAvailableDiscounts(): Promise<Discount[]> {
const response = await this.get<DiscountsResponse>('/discounts');
return response.discounts ?? [];
}
/**
* Get active discounts on account.
*/
async getActiveDiscounts(): Promise<Discount[]> {
const response = await this.get<DiscountsResponse>('/discounts/active');
return response.discounts ?? [];
}
/**
* Remove a discount.
*/
async removeDiscount(code: string): Promise<void> {
await this.delete(`/discounts/${encodeURIComponent(code)}`);
}
// ==================== Lifecycle ====================
/**
* Health check.
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.get<{ status: string }>('/health');
return response.status === 'healthy';
} catch {
return false;
}
}
/**
* Check if client is closed.
*/
get isClosed(): boolean {
return this.closed;
}
/**
* Close the client.
*/
close(): void {
this.closed = true;
}
// ==================== Private HTTP Methods ====================
private async get<T>(path: string): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'GET',
headers: this.headers(),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
return await response.json();
});
}
private async post<T>(path: string, body: unknown): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(body),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
return await response.json();
});
}
private async delete(path: string): Promise<void> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'DELETE',
headers: this.headers(),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
});
}
private headers(): Record<string, string> {
return {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-SDK-Version': 'js/0.1.0',
};
}
private async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.closed) {
throw new EconomicsError('Client has been closed');
}
let lastError: Error | undefined;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (this.config.debug) {
console.log(`Attempt ${attempt + 1} failed:`, error);
}
if (attempt < this.config.retries - 1) {
await this.sleep(1000 * Math.pow(2, attempt));
}
}
}
throw lastError!;
}
private async ensureSuccess(response: Response): Promise<void> {
if (!response.ok) {
const body = await response.text();
let message = `HTTP ${response.status}`;
let code: string | undefined;
try {
const error = JSON.parse(body);
message = error.message ?? message;
code = error.code;
} catch {
// ignore parse errors
}
throw new EconomicsError(message, code, response.status);
}
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* Economics SDK Error
*/
export class EconomicsError extends Error {
constructor(
message: string,
public readonly code?: string,
public readonly statusCode?: number
) {
super(message);
this.name = 'EconomicsError';
}
}
// Re-export types
export * from './types';

View file

@ -0,0 +1,7 @@
/**
* Synor Economics SDK
* Pricing, billing, staking, and discount management.
*/
export { SynorEconomics, EconomicsError } from './client';
export * from './types';

View file

@ -0,0 +1,258 @@
/**
* Synor Economics SDK Types
* Pricing, billing, staking, and discount management.
*/
// Service types for pricing
export type ServiceType =
| 'compute'
| 'storage'
| 'database'
| 'hosting'
| 'bridge'
| 'rpc';
// Billing period
export type BillingPeriod = 'hourly' | 'daily' | 'weekly' | 'monthly';
// Stake status
export type StakeStatus = 'active' | 'unstaking' | 'withdrawn' | 'slashed';
// Discount type
export type DiscountType = 'percentage' | 'fixed' | 'volume' | 'referral';
// Invoice status
export type InvoiceStatus = 'pending' | 'paid' | 'overdue' | 'cancelled';
// Usage metrics for pricing
export interface UsageMetrics {
computeHours?: number;
storageBytes?: number;
databaseOps?: number;
hostingRequests?: number;
bridgeTransfers?: number;
rpcCalls?: number;
}
// Usage plan for cost estimation
export interface UsagePlan {
services: ServiceUsage[];
period: BillingPeriod;
startDate?: number;
endDate?: number;
}
// Service usage in plan
export interface ServiceUsage {
service: ServiceType;
metrics: UsageMetrics;
tier?: string;
}
// Price breakdown
export interface Price {
service: ServiceType;
basePrice: string;
quantity: string;
unit: string;
subtotal: string;
discounts: AppliedDiscount[];
total: string;
currency: string;
}
// Cost estimate
export interface CostEstimate {
services: Price[];
subtotal: string;
discounts: AppliedDiscount[];
taxes: TaxAmount[];
total: string;
currency: string;
validUntil: number;
}
// Tax amount
export interface TaxAmount {
name: string;
rate: number;
amount: string;
}
// Applied discount
export interface AppliedDiscount {
code: string;
type: DiscountType;
value: string;
savings: string;
description?: string;
}
// Usage record
export interface Usage {
period: BillingPeriod;
startDate: number;
endDate: number;
services: ServiceUsageRecord[];
totalCost: string;
currency: string;
}
// Service usage record
export interface ServiceUsageRecord {
service: ServiceType;
metrics: UsageMetrics;
cost: string;
details: UsageDetail[];
}
// Usage detail
export interface UsageDetail {
timestamp: number;
operation: string;
quantity: number;
unit: string;
cost: string;
}
// Invoice
export interface Invoice {
id: string;
status: InvoiceStatus;
period: BillingPeriod;
startDate: number;
endDate: number;
items: InvoiceItem[];
subtotal: string;
discounts: AppliedDiscount[];
taxes: TaxAmount[];
total: string;
currency: string;
dueDate: number;
paidAt?: number;
paymentMethod?: string;
}
// Invoice item
export interface InvoiceItem {
service: ServiceType;
description: string;
quantity: string;
unitPrice: string;
total: string;
}
// Account balance
export interface AccountBalance {
available: string;
pending: string;
reserved: string;
total: string;
currency: string;
creditLimit?: string;
lastUpdated: number;
}
// Stake receipt
export interface StakeReceipt {
id: string;
amount: string;
lockDuration: number;
startDate: number;
endDate: number;
apy: string;
estimatedRewards: string;
txHash: string;
}
// Unstake receipt
export interface UnstakeReceipt {
id: string;
stakeId: string;
amount: string;
rewards: string;
total: string;
unbondingPeriod: number;
availableAt: number;
txHash: string;
}
// Stake info
export interface StakeInfo {
id: string;
amount: string;
status: StakeStatus;
lockDuration: number;
startDate: number;
endDate: number;
apy: string;
earnedRewards: string;
pendingRewards: string;
validator?: string;
}
// Staking rewards
export interface StakingRewards {
totalEarned: string;
pending: string;
claimed: string;
lastClaimDate?: number;
nextClaimAvailable?: number;
stakes: StakeRewardDetail[];
}
// Stake reward detail
export interface StakeRewardDetail {
stakeId: string;
earned: string;
pending: string;
apy: string;
}
// Discount
export interface Discount {
code: string;
type: DiscountType;
value: string;
description: string;
validFrom: number;
validUntil: number;
minPurchase?: string;
maxDiscount?: string;
applicableServices: ServiceType[];
usageLimit?: number;
usedCount: number;
}
// Staking options
export interface StakeOptions {
validator?: string;
autoCompound?: boolean;
lockDuration?: number;
}
// Economics config
export interface EconomicsConfig {
apiKey: string;
endpoint?: string;
timeout?: number;
retries?: number;
debug?: boolean;
}
// Internal response types
export interface PriceResponse {
price: Price;
}
export interface InvoicesResponse {
invoices: Invoice[];
}
export interface StakesResponse {
stakes: StakeInfo[];
}
export interface DiscountsResponse {
discounts: Discount[];
}

View file

@ -0,0 +1,377 @@
/**
* Synor Governance SDK Client
* Proposals, voting, DAOs, and vesting.
*/
import {
GovernanceConfig,
ProposalDraft,
Proposal,
ProposalFilter,
Vote,
VoteReceipt,
DelegationReceipt,
VotingPower,
DaoConfig,
Dao,
VestingSchedule,
VestingContract,
ClaimResult,
GovernanceTransaction,
ProposalsResponse,
DaosResponse,
VestingContractsResponse,
} from './types';
const DEFAULT_ENDPOINT = 'https://governance.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;
export class SynorGovernance {
private config: Required<GovernanceConfig>;
private closed = false;
constructor(config: GovernanceConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
timeout: config.timeout ?? DEFAULT_TIMEOUT,
retries: config.retries ?? DEFAULT_RETRIES,
debug: config.debug ?? false,
};
}
// ==================== Proposal Operations ====================
/**
* Create a new proposal.
*/
async createProposal(draft: ProposalDraft): Promise<Proposal> {
return await this.post<Proposal>('/proposals', draft);
}
/**
* Get a proposal by ID.
*/
async getProposal(proposalId: string): Promise<Proposal> {
return await this.get<Proposal>(`/proposals/${encodeURIComponent(proposalId)}`);
}
/**
* List proposals with optional filtering.
*/
async listProposals(filter?: ProposalFilter): Promise<Proposal[]> {
const params = new URLSearchParams();
if (filter?.status) params.set('status', filter.status);
if (filter?.type) params.set('type', filter.type);
if (filter?.proposer) params.set('proposer', filter.proposer);
if (filter?.limit) params.set('limit', filter.limit.toString());
if (filter?.offset) params.set('offset', filter.offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<ProposalsResponse>(`/proposals${query}`);
return response.proposals ?? [];
}
/**
* Cancel a proposal.
*/
async cancelProposal(proposalId: string): Promise<Proposal> {
return await this.post<Proposal>(`/proposals/${encodeURIComponent(proposalId)}/cancel`, {});
}
/**
* Execute a passed proposal.
*/
async executeProposal(proposalId: string): Promise<GovernanceTransaction> {
return await this.post<GovernanceTransaction>(
`/proposals/${encodeURIComponent(proposalId)}/execute`,
{}
);
}
// ==================== Voting Operations ====================
/**
* Vote on a proposal.
*/
async vote(proposalId: string, vote: Vote, weight?: string): Promise<VoteReceipt> {
const body: Record<string, unknown> = { ...vote };
if (weight) body.weight = weight;
return await this.post<VoteReceipt>(
`/proposals/${encodeURIComponent(proposalId)}/vote`,
body
);
}
/**
* Get votes for a proposal.
*/
async getVotes(
proposalId: string,
limit?: number,
offset?: number
): Promise<VoteReceipt[]> {
const params = new URLSearchParams();
if (limit) params.set('limit', limit.toString());
if (offset) params.set('offset', offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<{ votes: VoteReceipt[] }>(
`/proposals/${encodeURIComponent(proposalId)}/votes${query}`
);
return response.votes ?? [];
}
/**
* Delegate voting power to another address.
*/
async delegate(to: string, amount?: string): Promise<DelegationReceipt> {
const body: Record<string, unknown> = { to };
if (amount) body.amount = amount;
return await this.post<DelegationReceipt>('/voting/delegate', body);
}
/**
* Undelegate voting power.
*/
async undelegate(from?: string): Promise<DelegationReceipt> {
const body: Record<string, unknown> = {};
if (from) body.from = from;
return await this.post<DelegationReceipt>('/voting/undelegate', body);
}
/**
* Get voting power for an address.
*/
async getVotingPower(address: string): Promise<VotingPower> {
return await this.get<VotingPower>(`/voting/power/${encodeURIComponent(address)}`);
}
/**
* Get current voting power for the authenticated user.
*/
async getMyVotingPower(): Promise<VotingPower> {
return await this.get<VotingPower>('/voting/power');
}
// ==================== DAO Operations ====================
/**
* Create a new DAO.
*/
async createDao(config: DaoConfig): Promise<Dao> {
return await this.post<Dao>('/daos', config);
}
/**
* Get a DAO by ID.
*/
async getDao(daoId: string): Promise<Dao> {
return await this.get<Dao>(`/daos/${encodeURIComponent(daoId)}`);
}
/**
* List DAOs.
*/
async listDaos(limit?: number, offset?: number): Promise<Dao[]> {
const params = new URLSearchParams();
if (limit) params.set('limit', limit.toString());
if (offset) params.set('offset', offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<DaosResponse>(`/daos${query}`);
return response.daos ?? [];
}
/**
* Get DAO members.
*/
async getDaoMembers(
daoId: string,
limit?: number,
offset?: number
): Promise<{ address: string; power: string }[]> {
const params = new URLSearchParams();
if (limit) params.set('limit', limit.toString());
if (offset) params.set('offset', offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<{ members: { address: string; power: string }[] }>(
`/daos/${encodeURIComponent(daoId)}/members${query}`
);
return response.members ?? [];
}
// ==================== Vesting Operations ====================
/**
* Create a vesting schedule.
*/
async createVestingSchedule(schedule: VestingSchedule): Promise<VestingContract> {
return await this.post<VestingContract>('/vesting', schedule);
}
/**
* Get a vesting contract.
*/
async getVestingContract(contractId: string): Promise<VestingContract> {
return await this.get<VestingContract>(`/vesting/${encodeURIComponent(contractId)}`);
}
/**
* List vesting contracts.
*/
async listVestingContracts(beneficiary?: string): Promise<VestingContract[]> {
const query = beneficiary ? `?beneficiary=${encodeURIComponent(beneficiary)}` : '';
const response = await this.get<VestingContractsResponse>(`/vesting${query}`);
return response.contracts ?? [];
}
/**
* Claim vested tokens.
*/
async claimVested(contractId: string): Promise<ClaimResult> {
return await this.post<ClaimResult>(
`/vesting/${encodeURIComponent(contractId)}/claim`,
{}
);
}
/**
* Revoke a vesting contract (if revocable).
*/
async revokeVesting(contractId: string): Promise<VestingContract> {
return await this.post<VestingContract>(
`/vesting/${encodeURIComponent(contractId)}/revoke`,
{}
);
}
/**
* Get claimable amount for a vesting contract.
*/
async getClaimableAmount(contractId: string): Promise<{ claimable: string; vested: string }> {
return await this.get<{ claimable: string; vested: string }>(
`/vesting/${encodeURIComponent(contractId)}/claimable`
);
}
// ==================== Lifecycle ====================
/**
* Health check.
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.get<{ status: string }>('/health');
return response.status === 'healthy';
} catch {
return false;
}
}
/**
* Check if client is closed.
*/
get isClosed(): boolean {
return this.closed;
}
/**
* Close the client.
*/
close(): void {
this.closed = true;
}
// ==================== Private HTTP Methods ====================
private async get<T>(path: string): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'GET',
headers: this.headers(),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
return await response.json();
});
}
private async post<T>(path: string, body: unknown): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(body),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
return await response.json();
});
}
private headers(): Record<string, string> {
return {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-SDK-Version': 'js/0.1.0',
};
}
private async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.closed) {
throw new GovernanceError('Client has been closed');
}
let lastError: Error | undefined;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (this.config.debug) {
console.log(`Attempt ${attempt + 1} failed:`, error);
}
if (attempt < this.config.retries - 1) {
await this.sleep(1000 * Math.pow(2, attempt));
}
}
}
throw lastError!;
}
private async ensureSuccess(response: Response): Promise<void> {
if (!response.ok) {
const body = await response.text();
let message = `HTTP ${response.status}`;
let code: string | undefined;
try {
const error = JSON.parse(body);
message = error.message ?? message;
code = error.code;
} catch {
// ignore parse errors
}
throw new GovernanceError(message, code, response.status);
}
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* Governance SDK Error
*/
export class GovernanceError extends Error {
constructor(
message: string,
public readonly code?: string,
public readonly statusCode?: number
) {
super(message);
this.name = 'GovernanceError';
}
}
// Re-export types
export * from './types';

View file

@ -0,0 +1,7 @@
/**
* Synor Governance SDK
* Proposals, voting, DAOs, and vesting.
*/
export { SynorGovernance, GovernanceError } from './client';
export * from './types';

View file

@ -0,0 +1,222 @@
/**
* Synor Governance SDK Types
* Proposals, voting, DAOs, and vesting.
*/
// Proposal status
export type ProposalStatus =
| 'draft'
| 'active'
| 'passed'
| 'rejected'
| 'executed'
| 'cancelled'
| 'expired';
// Vote choice
export type VoteChoice = 'for' | 'against' | 'abstain';
// Proposal type
export type ProposalType =
| 'parameter_change'
| 'treasury_spend'
| 'upgrade'
| 'text'
| 'custom';
// DAO type
export type DaoType = 'token' | 'multisig' | 'hybrid';
// Vesting status
export type VestingStatus = 'active' | 'paused' | 'completed' | 'cancelled';
// Proposal draft
export interface ProposalDraft {
title: string;
description: string;
type: ProposalType;
actions?: ProposalAction[];
startTime?: number;
endTime?: number;
quorum?: string;
threshold?: string;
}
// Proposal action
export interface ProposalAction {
target: string;
value: string;
calldata: string;
description?: string;
}
// Proposal
export interface Proposal {
id: string;
title: string;
description: string;
type: ProposalType;
status: ProposalStatus;
proposer: string;
actions: ProposalAction[];
startTime: number;
endTime: number;
quorum: string;
threshold: string;
forVotes: string;
againstVotes: string;
abstainVotes: string;
totalVotes: string;
createdAt: number;
executedAt?: number;
executionTxHash?: string;
}
// Proposal filter
export interface ProposalFilter {
status?: ProposalStatus;
type?: ProposalType;
proposer?: string;
limit?: number;
offset?: number;
}
// Vote
export interface Vote {
choice: VoteChoice;
reason?: string;
}
// Vote receipt
export interface VoteReceipt {
proposalId: string;
voter: string;
choice: VoteChoice;
weight: string;
reason?: string;
timestamp: number;
txHash: string;
}
// Delegation receipt
export interface DelegationReceipt {
delegator: string;
delegatee: string;
amount: string;
timestamp: number;
txHash: string;
}
// Voting power
export interface VotingPower {
address: string;
ownPower: string;
delegatedPower: string;
totalPower: string;
delegatedFrom: DelegationInfo[];
delegatedTo?: DelegationInfo;
}
// Delegation info
export interface DelegationInfo {
address: string;
amount: string;
timestamp: number;
}
// DAO config
export interface DaoConfig {
name: string;
type: DaoType;
tokenAddress?: string;
signers?: string[];
threshold?: number;
votingPeriod: number;
quorum: string;
proposalThreshold: string;
timelockDelay?: number;
}
// DAO
export interface Dao {
id: string;
name: string;
type: DaoType;
tokenAddress?: string;
signers?: string[];
threshold?: number;
votingPeriod: number;
quorum: string;
proposalThreshold: string;
timelockDelay?: number;
treasury: string;
totalProposals: number;
activeProposals: number;
totalMembers: number;
createdAt: number;
}
// Vesting schedule
export interface VestingSchedule {
beneficiary: string;
totalAmount: string;
startTime: number;
cliffDuration: number;
vestingDuration: number;
revocable?: boolean;
}
// Vesting contract
export interface VestingContract {
id: string;
beneficiary: string;
totalAmount: string;
releasedAmount: string;
vestedAmount: string;
startTime: number;
cliffTime: number;
endTime: number;
revocable: boolean;
status: VestingStatus;
createdAt: number;
txHash: string;
}
// Claim result
export interface ClaimResult {
vestingId: string;
amount: string;
recipient: string;
timestamp: number;
txHash: string;
}
// Transaction result
export interface GovernanceTransaction {
txHash: string;
timestamp: number;
blockNumber: number;
status: 'pending' | 'confirmed' | 'failed';
}
// Governance config
export interface GovernanceConfig {
apiKey: string;
endpoint?: string;
timeout?: number;
retries?: number;
debug?: boolean;
}
// Internal response types
export interface ProposalsResponse {
proposals: Proposal[];
}
export interface DaosResponse {
daos: Dao[];
}
export interface VestingContractsResponse {
contracts: VestingContract[];
}

409
sdk/js/src/mining/client.ts Normal file
View file

@ -0,0 +1,409 @@
/**
* Synor Mining SDK Client
* Pool connections, block templates, hashrate stats, and GPU management.
*/
import {
MiningConfig,
PoolConfig,
StratumConnection,
BlockTemplate,
MinedWork,
SubmitResult,
Hashrate,
MiningStats,
TimePeriod,
Earnings,
MiningDevice,
DeviceConfig,
WorkerInfo,
PoolStats,
MiningAlgorithm,
DevicesResponse,
WorkersResponse,
AlgorithmsResponse,
} from './types';
const DEFAULT_ENDPOINT = 'https://mining.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;
export class SynorMining {
private config: Required<MiningConfig>;
private closed = false;
private activeConnection?: StratumConnection;
constructor(config: MiningConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
timeout: config.timeout ?? DEFAULT_TIMEOUT,
retries: config.retries ?? DEFAULT_RETRIES,
debug: config.debug ?? false,
};
}
// ==================== Pool Connection ====================
/**
* Connect to a mining pool.
*/
async connect(pool: PoolConfig): Promise<StratumConnection> {
const connection = await this.post<StratumConnection>('/pool/connect', pool);
this.activeConnection = connection;
return connection;
}
/**
* Disconnect from the current pool.
*/
async disconnect(): Promise<void> {
if (this.activeConnection) {
await this.post<void>(`/pool/disconnect/${this.activeConnection.id}`, {});
this.activeConnection = undefined;
}
}
/**
* Get current connection status.
*/
async getConnectionStatus(): Promise<StratumConnection | null> {
if (!this.activeConnection) return null;
try {
return await this.get<StratumConnection>(`/pool/status/${this.activeConnection.id}`);
} catch {
return null;
}
}
/**
* Reconnect to the pool.
*/
async reconnect(): Promise<StratumConnection> {
if (!this.activeConnection) {
throw new MiningError('No active connection to reconnect');
}
return await this.post<StratumConnection>(`/pool/reconnect/${this.activeConnection.id}`, {});
}
// ==================== Mining Operations ====================
/**
* Get the current block template.
*/
async getBlockTemplate(): Promise<BlockTemplate> {
return await this.get<BlockTemplate>('/mining/template');
}
/**
* Submit mined work.
*/
async submitWork(work: MinedWork): Promise<SubmitResult> {
return await this.post<SubmitResult>('/mining/submit', work);
}
/**
* Get work from pool (stratum getwork).
*/
async getWork(): Promise<{ work: string; target: string; algorithm: string }> {
return await this.get<{ work: string; target: string; algorithm: string }>('/mining/getwork');
}
/**
* Start mining on all enabled devices.
*/
async startMining(algorithm?: string): Promise<{ started: boolean; devices: string[] }> {
const body = algorithm ? { algorithm } : {};
return await this.post<{ started: boolean; devices: string[] }>('/mining/start', body);
}
/**
* Stop mining on all devices.
*/
async stopMining(): Promise<{ stopped: boolean }> {
return await this.post<{ stopped: boolean }>('/mining/stop', {});
}
// ==================== Stats ====================
/**
* Get current hashrate.
*/
async getHashrate(): Promise<Hashrate> {
return await this.get<Hashrate>('/stats/hashrate');
}
/**
* Get mining stats.
*/
async getStats(): Promise<MiningStats> {
return await this.get<MiningStats>('/stats');
}
/**
* Get earnings for a time period.
*/
async getEarnings(period?: TimePeriod): Promise<Earnings> {
const query = period ? `?period=${period}` : '';
return await this.get<Earnings>(`/stats/earnings${query}`);
}
/**
* Get earnings history.
*/
async getEarningsHistory(limit?: number, offset?: number): Promise<Earnings[]> {
const params = new URLSearchParams();
if (limit) params.set('limit', limit.toString());
if (offset) params.set('offset', offset.toString());
const query = params.toString() ? `?${params.toString()}` : '';
const response = await this.get<{ earnings: Earnings[] }>(`/stats/earnings/history${query}`);
return response.earnings ?? [];
}
/**
* Get pool stats.
*/
async getPoolStats(): Promise<PoolStats> {
return await this.get<PoolStats>('/pool/stats');
}
// ==================== GPU Management ====================
/**
* List all mining devices.
*/
async listDevices(): Promise<MiningDevice[]> {
const response = await this.get<DevicesResponse>('/devices');
return response.devices ?? [];
}
/**
* Get device details.
*/
async getDevice(deviceId: string): Promise<MiningDevice> {
return await this.get<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}`);
}
/**
* Set device configuration.
*/
async setDeviceConfig(deviceId: string, config: DeviceConfig): Promise<MiningDevice> {
return await this.post<MiningDevice>(
`/devices/${encodeURIComponent(deviceId)}/config`,
config
);
}
/**
* Enable a device for mining.
*/
async enableDevice(deviceId: string): Promise<MiningDevice> {
return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/enable`, {});
}
/**
* Disable a device.
*/
async disableDevice(deviceId: string): Promise<MiningDevice> {
return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/disable`, {});
}
/**
* Reset device to default settings.
*/
async resetDevice(deviceId: string): Promise<MiningDevice> {
return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/reset`, {});
}
// ==================== Workers ====================
/**
* List all workers.
*/
async listWorkers(): Promise<WorkerInfo[]> {
const response = await this.get<WorkersResponse>('/workers');
return response.workers ?? [];
}
/**
* Get worker details.
*/
async getWorker(workerId: string): Promise<WorkerInfo> {
return await this.get<WorkerInfo>(`/workers/${encodeURIComponent(workerId)}`);
}
/**
* Create a new worker.
*/
async createWorker(name: string): Promise<WorkerInfo> {
return await this.post<WorkerInfo>('/workers', { name });
}
/**
* Delete a worker.
*/
async deleteWorker(workerId: string): Promise<void> {
await this.delete(`/workers/${encodeURIComponent(workerId)}`);
}
// ==================== Algorithms ====================
/**
* Get supported mining algorithms.
*/
async getSupportedAlgorithms(): Promise<MiningAlgorithm[]> {
const response = await this.get<AlgorithmsResponse>('/algorithms');
return response.algorithms ?? [];
}
/**
* Get current algorithm.
*/
async getCurrentAlgorithm(): Promise<MiningAlgorithm> {
return await this.get<MiningAlgorithm>('/algorithms/current');
}
/**
* Switch to a different algorithm.
*/
async switchAlgorithm(algorithm: string): Promise<{ switched: boolean }> {
return await this.post<{ switched: boolean }>('/algorithms/switch', { algorithm });
}
// ==================== Lifecycle ====================
/**
* Health check.
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.get<{ status: string }>('/health');
return response.status === 'healthy';
} catch {
return false;
}
}
/**
* Check if client is closed.
*/
get isClosed(): boolean {
return this.closed;
}
/**
* Close the client.
*/
async close(): Promise<void> {
if (this.activeConnection) {
await this.disconnect();
}
this.closed = true;
}
// ==================== Private HTTP Methods ====================
private async get<T>(path: string): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'GET',
headers: this.headers(),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
return await response.json();
});
}
private async post<T>(path: string, body: unknown): Promise<T> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify(body),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
const text = await response.text();
return text ? JSON.parse(text) : undefined;
});
}
private async delete(path: string): Promise<void> {
return await this.execute(async () => {
const response = await fetch(`${this.config.endpoint}${path}`, {
method: 'DELETE',
headers: this.headers(),
signal: AbortSignal.timeout(this.config.timeout),
});
await this.ensureSuccess(response);
});
}
private headers(): Record<string, string> {
return {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-SDK-Version': 'js/0.1.0',
};
}
private async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.closed) {
throw new MiningError('Client has been closed');
}
let lastError: Error | undefined;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (this.config.debug) {
console.log(`Attempt ${attempt + 1} failed:`, error);
}
if (attempt < this.config.retries - 1) {
await this.sleep(1000 * Math.pow(2, attempt));
}
}
}
throw lastError!;
}
private async ensureSuccess(response: Response): Promise<void> {
if (!response.ok) {
const body = await response.text();
let message = `HTTP ${response.status}`;
let code: string | undefined;
try {
const error = JSON.parse(body);
message = error.message ?? message;
code = error.code;
} catch {
// ignore parse errors
}
throw new MiningError(message, code, response.status);
}
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/**
* Mining SDK Error
*/
export class MiningError extends Error {
constructor(
message: string,
public readonly code?: string,
public readonly statusCode?: number
) {
super(message);
this.name = 'MiningError';
}
}
// Re-export types
export * from './types';

View file

@ -0,0 +1,7 @@
/**
* Synor Mining SDK
* Pool connections, block templates, hashrate stats, and GPU management.
*/
export { SynorMining, MiningError } from './client';
export * from './types';

242
sdk/js/src/mining/types.ts Normal file
View file

@ -0,0 +1,242 @@
/**
* Synor Mining SDK Types
* Pool connections, block templates, hashrate stats, and GPU management.
*/
// Device type
export type DeviceType = 'cpu' | 'gpu_nvidia' | 'gpu_amd' | 'asic';
// Device status
export type DeviceStatus = 'idle' | 'mining' | 'error' | 'offline';
// Connection status
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
// Time period for stats
export type TimePeriod = 'hour' | 'day' | 'week' | 'month' | 'all';
// Submit result status
export type SubmitResultStatus = 'accepted' | 'rejected' | 'stale';
// Pool configuration
export interface PoolConfig {
url: string;
user: string;
password?: string;
algorithm?: string;
difficulty?: number;
}
// Stratum connection
export interface StratumConnection {
id: string;
pool: string;
status: ConnectionStatus;
algorithm: string;
difficulty: number;
connectedAt: number;
lastShareAt?: number;
acceptedShares: number;
rejectedShares: number;
staleShares: number;
}
// Block template
export interface BlockTemplate {
id: string;
previousBlockHash: string;
merkleRoot: string;
timestamp: number;
bits: string;
height: number;
coinbaseValue: string;
transactions: TemplateTransaction[];
target: string;
algorithm: string;
extraNonce: string;
}
// Template transaction
export interface TemplateTransaction {
txid: string;
data: string;
fee: string;
weight: number;
}
// Mined work
export interface MinedWork {
templateId: string;
nonce: string;
extraNonce: string;
timestamp: number;
hash: string;
}
// Submit result
export interface SubmitResult {
status: SubmitResultStatus;
reason?: string;
share: ShareInfo;
blockFound: boolean;
blockHash?: string;
reward?: string;
}
// Share info
export interface ShareInfo {
hash: string;
difficulty: number;
timestamp: number;
accepted: boolean;
}
// Hashrate
export interface Hashrate {
current: number;
average1h: number;
average24h: number;
peak: number;
unit: string;
}
// Mining stats
export interface MiningStats {
hashrate: Hashrate;
shares: ShareStats;
uptime: number;
efficiency: number;
powerConsumption?: number;
temperature?: DeviceTemperature;
earnings: EarningsSnapshot;
}
// Share stats
export interface ShareStats {
accepted: number;
rejected: number;
stale: number;
total: number;
acceptRate: number;
}
// Device temperature
export interface DeviceTemperature {
current: number;
max: number;
throttling: boolean;
}
// Earnings snapshot
export interface EarningsSnapshot {
today: string;
yesterday: string;
thisWeek: string;
thisMonth: string;
total: string;
currency: string;
}
// Detailed earnings
export interface Earnings {
period: TimePeriod;
startDate: number;
endDate: number;
amount: string;
blocks: number;
shares: number;
averageHashrate: number;
currency: string;
breakdown: EarningsBreakdown[];
}
// Earnings breakdown
export interface EarningsBreakdown {
date: number;
amount: string;
blocks: number;
shares: number;
hashrate: number;
}
// Mining device
export interface MiningDevice {
id: string;
name: string;
type: DeviceType;
status: DeviceStatus;
hashrate: number;
temperature: number;
fanSpeed: number;
powerDraw: number;
memoryUsed: number;
memoryTotal: number;
driver?: string;
firmware?: string;
}
// Device config
export interface DeviceConfig {
enabled: boolean;
intensity?: number;
powerLimit?: number;
coreClockOffset?: number;
memoryClockOffset?: number;
fanSpeed?: number;
}
// Worker info
export interface WorkerInfo {
id: string;
name: string;
status: ConnectionStatus;
hashrate: Hashrate;
shares: ShareStats;
devices: MiningDevice[];
lastSeen: number;
uptime: number;
}
// Pool stats
export interface PoolStats {
url: string;
workers: number;
hashrate: number;
difficulty: number;
lastBlock: number;
blocksFound24h: number;
luck: number;
}
// Mining algorithm
export interface MiningAlgorithm {
name: string;
displayName: string;
hashUnit: string;
profitability: string;
difficulty: number;
blockReward: string;
blockTime: number;
}
// Mining config
export interface MiningConfig {
apiKey: string;
endpoint?: string;
timeout?: number;
retries?: number;
debug?: boolean;
}
// Internal response types
export interface DevicesResponse {
devices: MiningDevice[];
}
export interface WorkersResponse {
workers: WorkerInfo[];
}
export interface AlgorithmsResponse {
algorithms: MiningAlgorithm[];
}

View file

@ -0,0 +1,66 @@
"""
Synor Economics SDK for Python.
Pricing, billing, staking, and discount management.
"""
from .client import SynorEconomics, EconomicsError
from .types import (
ServiceType,
BillingPeriod,
StakeStatus,
DiscountType,
InvoiceStatus,
UsageMetrics,
UsagePlan,
ServiceUsage,
Price,
CostEstimate,
TaxAmount,
AppliedDiscount,
Usage,
ServiceUsageRecord,
UsageDetail,
Invoice,
InvoiceItem,
AccountBalance,
StakeReceipt,
UnstakeReceipt,
StakeInfo,
StakingRewards,
StakeRewardDetail,
Discount,
StakeOptions,
EconomicsConfig,
)
__version__ = "0.1.0"
__all__ = [
"SynorEconomics",
"EconomicsError",
"ServiceType",
"BillingPeriod",
"StakeStatus",
"DiscountType",
"InvoiceStatus",
"UsageMetrics",
"UsagePlan",
"ServiceUsage",
"Price",
"CostEstimate",
"TaxAmount",
"AppliedDiscount",
"Usage",
"ServiceUsageRecord",
"UsageDetail",
"Invoice",
"InvoiceItem",
"AccountBalance",
"StakeReceipt",
"UnstakeReceipt",
"StakeInfo",
"StakingRewards",
"StakeRewardDetail",
"Discount",
"StakeOptions",
"EconomicsConfig",
]

View file

@ -0,0 +1,499 @@
"""
Synor Economics SDK Client.
Pricing, billing, staking, and discount management.
"""
import asyncio
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode, quote
import httpx
from .types import (
EconomicsConfig,
ServiceType,
UsageMetrics,
Price,
UsagePlan,
CostEstimate,
BillingPeriod,
Usage,
Invoice,
AccountBalance,
StakeReceipt,
UnstakeReceipt,
StakingRewards,
StakeInfo,
StakeOptions,
Discount,
AppliedDiscount,
TaxAmount,
ServiceUsageRecord,
UsageDetail,
InvoiceItem,
InvoiceStatus,
StakeStatus,
DiscountType,
StakeRewardDetail,
)
class EconomicsError(Exception):
"""Economics SDK Error."""
def __init__(self, message: str, code: Optional[str] = None, status_code: int = 0):
super().__init__(message)
self.code = code
self.status_code = status_code
class SynorEconomics:
"""
Synor Economics SDK client for Python.
Pricing, billing, staking, and discount management.
"""
def __init__(self, config: EconomicsConfig):
self._config = config
self._closed = False
self._client = httpx.AsyncClient(
base_url=config.endpoint,
timeout=config.timeout,
headers={
"Authorization": f"Bearer {config.api_key}",
"Content-Type": "application/json",
"X-SDK-Version": "python/0.1.0",
},
)
# ==================== Pricing Operations ====================
async def get_price(self, service: ServiceType, usage: UsageMetrics) -> Price:
"""Get price for a service based on usage."""
response = await self._post(
"/pricing/calculate",
{"service": service.value, "usage": self._usage_to_dict(usage)},
)
return self._parse_price(response["price"])
async def estimate_cost(self, plan: UsagePlan) -> CostEstimate:
"""Estimate cost for a usage plan."""
response = await self._post("/pricing/estimate", self._plan_to_dict(plan))
return self._parse_cost_estimate(response)
async def get_pricing_tiers(self, service: ServiceType) -> List[Price]:
"""Get pricing tiers for a service."""
response = await self._get(f"/pricing/{service.value}/tiers")
return [self._parse_price(p) for p in response.get("tiers", [])]
# ==================== Usage & Billing Operations ====================
async def get_usage(self, period: Optional[BillingPeriod] = None) -> Usage:
"""Get usage for a billing period."""
params = f"?period={period.value}" if period else ""
response = await self._get(f"/usage{params}")
return self._parse_usage(response)
async def get_usage_history(
self, limit: Optional[int] = None, offset: Optional[int] = None
) -> List[Usage]:
"""Get usage history."""
params = {}
if limit:
params["limit"] = str(limit)
if offset:
params["offset"] = str(offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/usage/history{query}")
return [self._parse_usage(u) for u in response.get("usage", [])]
async def get_invoices(self) -> List[Invoice]:
"""Get invoices."""
response = await self._get("/invoices")
return [self._parse_invoice(i) for i in response.get("invoices", [])]
async def get_invoice(self, invoice_id: str) -> Invoice:
"""Get a specific invoice."""
response = await self._get(f"/invoices/{quote(invoice_id)}")
return self._parse_invoice(response)
async def pay_invoice(
self, invoice_id: str, payment_method: Optional[str] = None
) -> Invoice:
"""Pay an invoice."""
response = await self._post(
f"/invoices/{quote(invoice_id)}/pay",
{"payment_method": payment_method} if payment_method else {},
)
return self._parse_invoice(response)
async def get_balance(self) -> AccountBalance:
"""Get account balance."""
response = await self._get("/balance")
return self._parse_balance(response)
async def add_funds(self, amount: str, payment_method: str) -> AccountBalance:
"""Add funds to account."""
response = await self._post(
"/balance/deposit", {"amount": amount, "payment_method": payment_method}
)
return self._parse_balance(response)
# ==================== Staking Operations ====================
async def stake(
self, amount: str, options: Optional[StakeOptions] = None
) -> StakeReceipt:
"""Stake tokens."""
body: Dict[str, Any] = {"amount": amount}
if options:
if options.validator:
body["validator"] = options.validator
if options.auto_compound is not None:
body["auto_compound"] = options.auto_compound
if options.lock_duration:
body["lock_duration"] = options.lock_duration
response = await self._post("/staking/stake", body)
return self._parse_stake_receipt(response)
async def unstake(self, stake_id: str) -> UnstakeReceipt:
"""Unstake tokens."""
response = await self._post(f"/staking/stakes/{quote(stake_id)}/unstake", {})
return self._parse_unstake_receipt(response)
async def get_staking_rewards(self) -> StakingRewards:
"""Get staking rewards."""
response = await self._get("/staking/rewards")
return self._parse_staking_rewards(response)
async def claim_rewards(self, stake_id: Optional[str] = None) -> StakingRewards:
"""Claim staking rewards."""
path = (
f"/staking/stakes/{quote(stake_id)}/claim"
if stake_id
else "/staking/rewards/claim"
)
response = await self._post(path, {})
return self._parse_staking_rewards(response)
async def list_stakes(self) -> List[StakeInfo]:
"""List active stakes."""
response = await self._get("/staking/stakes")
return [self._parse_stake_info(s) for s in response.get("stakes", [])]
async def get_stake(self, stake_id: str) -> StakeInfo:
"""Get stake details."""
response = await self._get(f"/staking/stakes/{quote(stake_id)}")
return self._parse_stake_info(response)
async def get_staking_apy(self) -> Dict[str, Any]:
"""Get current APY for staking."""
return await self._get("/staking/apy")
# ==================== Discount Operations ====================
async def apply_discount(self, code: str) -> Discount:
"""Apply a discount code."""
response = await self._post("/discounts/apply", {"code": code})
return self._parse_discount(response)
async def get_available_discounts(self) -> List[Discount]:
"""Get available discounts."""
response = await self._get("/discounts")
return [self._parse_discount(d) for d in response.get("discounts", [])]
async def get_active_discounts(self) -> List[Discount]:
"""Get active discounts on account."""
response = await self._get("/discounts/active")
return [self._parse_discount(d) for d in response.get("discounts", [])]
async def remove_discount(self, code: str) -> None:
"""Remove a discount."""
await self._delete(f"/discounts/{quote(code)}")
# ==================== Lifecycle ====================
async def health_check(self) -> bool:
"""Health check."""
try:
response = await self._get("/health")
return response.get("status") == "healthy"
except Exception:
return False
@property
def is_closed(self) -> bool:
"""Check if client is closed."""
return self._closed
async def close(self) -> None:
"""Close the client."""
self._closed = True
await self._client.aclose()
async def __aenter__(self) -> "SynorEconomics":
return self
async def __aexit__(self, *args) -> None:
await self.close()
# ==================== Private Methods ====================
async def _get(self, path: str) -> Dict[str, Any]:
return await self._execute(lambda: self._client.get(path))
async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
return await self._execute(lambda: self._client.post(path, json=body))
async def _delete(self, path: str) -> None:
await self._execute(lambda: self._client.delete(path))
async def _execute(self, operation) -> Dict[str, Any]:
if self._closed:
raise EconomicsError("Client has been closed")
last_error = None
for attempt in range(self._config.retries):
try:
response = await operation()
self._ensure_success(response)
if response.content:
return response.json()
return {}
except Exception as e:
last_error = e
if self._config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < self._config.retries - 1:
await asyncio.sleep(2**attempt)
raise last_error
def _ensure_success(self, response: httpx.Response) -> None:
if response.status_code >= 400:
message = f"HTTP {response.status_code}"
code = None
try:
error = response.json()
message = error.get("message", message)
code = error.get("code")
except Exception:
pass
raise EconomicsError(message, code, response.status_code)
# ==================== Parsing Methods ====================
def _usage_to_dict(self, usage: UsageMetrics) -> Dict[str, Any]:
result = {}
if usage.compute_hours is not None:
result["compute_hours"] = usage.compute_hours
if usage.storage_bytes is not None:
result["storage_bytes"] = usage.storage_bytes
if usage.database_ops is not None:
result["database_ops"] = usage.database_ops
if usage.hosting_requests is not None:
result["hosting_requests"] = usage.hosting_requests
if usage.bridge_transfers is not None:
result["bridge_transfers"] = usage.bridge_transfers
if usage.rpc_calls is not None:
result["rpc_calls"] = usage.rpc_calls
return result
def _plan_to_dict(self, plan: UsagePlan) -> Dict[str, Any]:
result: Dict[str, Any] = {
"services": [
{
"service": s.service.value,
"metrics": self._usage_to_dict(s.metrics),
"tier": s.tier,
}
for s in plan.services
],
"period": plan.period.value,
}
if plan.start_date:
result["start_date"] = plan.start_date
if plan.end_date:
result["end_date"] = plan.end_date
return result
def _parse_price(self, data: Dict[str, Any]) -> Price:
return Price(
service=ServiceType(data["service"]),
base_price=data["base_price"],
quantity=data["quantity"],
unit=data["unit"],
subtotal=data["subtotal"],
discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
total=data["total"],
currency=data["currency"],
)
def _parse_applied_discount(self, data: Dict[str, Any]) -> AppliedDiscount:
return AppliedDiscount(
code=data["code"],
type=DiscountType(data["type"]),
value=data["value"],
savings=data["savings"],
description=data.get("description"),
)
def _parse_cost_estimate(self, data: Dict[str, Any]) -> CostEstimate:
return CostEstimate(
services=[self._parse_price(p) for p in data.get("services", [])],
subtotal=data["subtotal"],
discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
taxes=[
TaxAmount(name=t["name"], rate=t["rate"], amount=t["amount"])
for t in data.get("taxes", [])
],
total=data["total"],
currency=data["currency"],
valid_until=data["valid_until"],
)
def _parse_usage(self, data: Dict[str, Any]) -> Usage:
return Usage(
period=BillingPeriod(data["period"]),
start_date=data["start_date"],
end_date=data["end_date"],
services=[self._parse_service_usage(s) for s in data.get("services", [])],
total_cost=data["total_cost"],
currency=data["currency"],
)
def _parse_service_usage(self, data: Dict[str, Any]) -> ServiceUsageRecord:
metrics_data = data.get("metrics", {})
return ServiceUsageRecord(
service=ServiceType(data["service"]),
metrics=UsageMetrics(
compute_hours=metrics_data.get("compute_hours"),
storage_bytes=metrics_data.get("storage_bytes"),
database_ops=metrics_data.get("database_ops"),
hosting_requests=metrics_data.get("hosting_requests"),
bridge_transfers=metrics_data.get("bridge_transfers"),
rpc_calls=metrics_data.get("rpc_calls"),
),
cost=data["cost"],
details=[
UsageDetail(
timestamp=d["timestamp"],
operation=d["operation"],
quantity=d["quantity"],
unit=d["unit"],
cost=d["cost"],
)
for d in data.get("details", [])
],
)
def _parse_invoice(self, data: Dict[str, Any]) -> Invoice:
return Invoice(
id=data["id"],
status=InvoiceStatus(data["status"]),
period=BillingPeriod(data["period"]),
start_date=data["start_date"],
end_date=data["end_date"],
items=[
InvoiceItem(
service=ServiceType(i["service"]),
description=i["description"],
quantity=i["quantity"],
unit_price=i["unit_price"],
total=i["total"],
)
for i in data.get("items", [])
],
subtotal=data["subtotal"],
discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
taxes=[
TaxAmount(name=t["name"], rate=t["rate"], amount=t["amount"])
for t in data.get("taxes", [])
],
total=data["total"],
currency=data["currency"],
due_date=data["due_date"],
paid_at=data.get("paid_at"),
payment_method=data.get("payment_method"),
)
def _parse_balance(self, data: Dict[str, Any]) -> AccountBalance:
return AccountBalance(
available=data["available"],
pending=data["pending"],
reserved=data["reserved"],
total=data["total"],
currency=data["currency"],
last_updated=data["last_updated"],
credit_limit=data.get("credit_limit"),
)
def _parse_stake_receipt(self, data: Dict[str, Any]) -> StakeReceipt:
return StakeReceipt(
id=data["id"],
amount=data["amount"],
lock_duration=data["lock_duration"],
start_date=data["start_date"],
end_date=data["end_date"],
apy=data["apy"],
estimated_rewards=data["estimated_rewards"],
tx_hash=data["tx_hash"],
)
def _parse_unstake_receipt(self, data: Dict[str, Any]) -> UnstakeReceipt:
return UnstakeReceipt(
id=data["id"],
stake_id=data["stake_id"],
amount=data["amount"],
rewards=data["rewards"],
total=data["total"],
unbonding_period=data["unbonding_period"],
available_at=data["available_at"],
tx_hash=data["tx_hash"],
)
def _parse_stake_info(self, data: Dict[str, Any]) -> StakeInfo:
return StakeInfo(
id=data["id"],
amount=data["amount"],
status=StakeStatus(data["status"]),
lock_duration=data["lock_duration"],
start_date=data["start_date"],
end_date=data["end_date"],
apy=data["apy"],
earned_rewards=data["earned_rewards"],
pending_rewards=data["pending_rewards"],
validator=data.get("validator"),
)
def _parse_staking_rewards(self, data: Dict[str, Any]) -> StakingRewards:
return StakingRewards(
total_earned=data["total_earned"],
pending=data["pending"],
claimed=data["claimed"],
stakes=[
StakeRewardDetail(
stake_id=s["stake_id"],
earned=s["earned"],
pending=s["pending"],
apy=s["apy"],
)
for s in data.get("stakes", [])
],
last_claim_date=data.get("last_claim_date"),
next_claim_available=data.get("next_claim_available"),
)
def _parse_discount(self, data: Dict[str, Any]) -> Discount:
return Discount(
code=data["code"],
type=DiscountType(data["type"]),
value=data["value"],
description=data["description"],
valid_from=data["valid_from"],
valid_until=data["valid_until"],
applicable_services=[ServiceType(s) for s in data.get("applicable_services", [])],
used_count=data["used_count"],
min_purchase=data.get("min_purchase"),
max_discount=data.get("max_discount"),
usage_limit=data.get("usage_limit"),
)

View file

@ -0,0 +1,286 @@
"""
Synor Economics SDK Types.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List
class ServiceType(str, Enum):
"""Service types for pricing."""
COMPUTE = "compute"
STORAGE = "storage"
DATABASE = "database"
HOSTING = "hosting"
BRIDGE = "bridge"
RPC = "rpc"
class BillingPeriod(str, Enum):
"""Billing period."""
HOURLY = "hourly"
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
class StakeStatus(str, Enum):
"""Stake status."""
ACTIVE = "active"
UNSTAKING = "unstaking"
WITHDRAWN = "withdrawn"
SLASHED = "slashed"
class DiscountType(str, Enum):
"""Discount type."""
PERCENTAGE = "percentage"
FIXED = "fixed"
VOLUME = "volume"
REFERRAL = "referral"
class InvoiceStatus(str, Enum):
"""Invoice status."""
PENDING = "pending"
PAID = "paid"
OVERDUE = "overdue"
CANCELLED = "cancelled"
@dataclass
class UsageMetrics:
"""Usage metrics for pricing."""
compute_hours: Optional[float] = None
storage_bytes: Optional[int] = None
database_ops: Optional[int] = None
hosting_requests: Optional[int] = None
bridge_transfers: Optional[int] = None
rpc_calls: Optional[int] = None
@dataclass
class ServiceUsage:
"""Service usage in plan."""
service: ServiceType
metrics: UsageMetrics
tier: Optional[str] = None
@dataclass
class UsagePlan:
"""Usage plan for cost estimation."""
services: List[ServiceUsage]
period: BillingPeriod
start_date: Optional[int] = None
end_date: Optional[int] = None
@dataclass
class AppliedDiscount:
"""Applied discount."""
code: str
type: DiscountType
value: str
savings: str
description: Optional[str] = None
@dataclass
class TaxAmount:
"""Tax amount."""
name: str
rate: float
amount: str
@dataclass
class Price:
"""Price breakdown."""
service: ServiceType
base_price: str
quantity: str
unit: str
subtotal: str
discounts: List[AppliedDiscount]
total: str
currency: str
@dataclass
class CostEstimate:
"""Cost estimate."""
services: List[Price]
subtotal: str
discounts: List[AppliedDiscount]
taxes: List[TaxAmount]
total: str
currency: str
valid_until: int
@dataclass
class UsageDetail:
"""Usage detail."""
timestamp: int
operation: str
quantity: float
unit: str
cost: str
@dataclass
class ServiceUsageRecord:
"""Service usage record."""
service: ServiceType
metrics: UsageMetrics
cost: str
details: List[UsageDetail]
@dataclass
class Usage:
"""Usage record."""
period: BillingPeriod
start_date: int
end_date: int
services: List[ServiceUsageRecord]
total_cost: str
currency: str
@dataclass
class InvoiceItem:
"""Invoice item."""
service: ServiceType
description: str
quantity: str
unit_price: str
total: str
@dataclass
class Invoice:
"""Invoice."""
id: str
status: InvoiceStatus
period: BillingPeriod
start_date: int
end_date: int
items: List[InvoiceItem]
subtotal: str
discounts: List[AppliedDiscount]
taxes: List[TaxAmount]
total: str
currency: str
due_date: int
paid_at: Optional[int] = None
payment_method: Optional[str] = None
@dataclass
class AccountBalance:
"""Account balance."""
available: str
pending: str
reserved: str
total: str
currency: str
last_updated: int
credit_limit: Optional[str] = None
@dataclass
class StakeReceipt:
"""Stake receipt."""
id: str
amount: str
lock_duration: int
start_date: int
end_date: int
apy: str
estimated_rewards: str
tx_hash: str
@dataclass
class UnstakeReceipt:
"""Unstake receipt."""
id: str
stake_id: str
amount: str
rewards: str
total: str
unbonding_period: int
available_at: int
tx_hash: str
@dataclass
class StakeInfo:
"""Stake info."""
id: str
amount: str
status: StakeStatus
lock_duration: int
start_date: int
end_date: int
apy: str
earned_rewards: str
pending_rewards: str
validator: Optional[str] = None
@dataclass
class StakeRewardDetail:
"""Stake reward detail."""
stake_id: str
earned: str
pending: str
apy: str
@dataclass
class StakingRewards:
"""Staking rewards."""
total_earned: str
pending: str
claimed: str
stakes: List[StakeRewardDetail]
last_claim_date: Optional[int] = None
next_claim_available: Optional[int] = None
@dataclass
class Discount:
"""Discount."""
code: str
type: DiscountType
value: str
description: str
valid_from: int
valid_until: int
applicable_services: List[ServiceType]
used_count: int
min_purchase: Optional[str] = None
max_discount: Optional[str] = None
usage_limit: Optional[int] = None
@dataclass
class StakeOptions:
"""Staking options."""
validator: Optional[str] = None
auto_compound: Optional[bool] = None
lock_duration: Optional[int] = None
@dataclass
class EconomicsConfig:
"""Economics SDK configuration."""
api_key: str
endpoint: str = "https://economics.synor.io/v1"
timeout: float = 30.0
retries: int = 3
debug: bool = False

View file

@ -0,0 +1,56 @@
"""
Synor Governance SDK for Python.
Proposals, voting, DAOs, and vesting.
"""
from .client import SynorGovernance, GovernanceError
from .types import (
ProposalStatus,
VoteChoice,
ProposalType,
DaoType,
VestingStatus,
ProposalDraft,
ProposalAction,
Proposal,
ProposalFilter,
Vote,
VoteReceipt,
DelegationReceipt,
VotingPower,
DelegationInfo,
DaoConfig,
Dao,
VestingSchedule,
VestingContract,
ClaimResult,
GovernanceTransaction,
GovernanceConfig,
)
__version__ = "0.1.0"
__all__ = [
"SynorGovernance",
"GovernanceError",
"ProposalStatus",
"VoteChoice",
"ProposalType",
"DaoType",
"VestingStatus",
"ProposalDraft",
"ProposalAction",
"Proposal",
"ProposalFilter",
"Vote",
"VoteReceipt",
"DelegationReceipt",
"VotingPower",
"DelegationInfo",
"DaoConfig",
"Dao",
"VestingSchedule",
"VestingContract",
"ClaimResult",
"GovernanceTransaction",
"GovernanceConfig",
]

View file

@ -0,0 +1,478 @@
"""
Synor Governance SDK Client.
Proposals, voting, DAOs, and vesting.
"""
import asyncio
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode, quote
import httpx
from .types import (
GovernanceConfig,
ProposalDraft,
Proposal,
ProposalFilter,
ProposalAction,
ProposalStatus,
ProposalType,
Vote,
VoteReceipt,
VoteChoice,
DelegationReceipt,
VotingPower,
DelegationInfo,
DaoConfig,
Dao,
DaoType,
VestingSchedule,
VestingContract,
VestingStatus,
ClaimResult,
GovernanceTransaction,
)
class GovernanceError(Exception):
"""Governance SDK Error."""
def __init__(self, message: str, code: Optional[str] = None, status_code: int = 0):
super().__init__(message)
self.code = code
self.status_code = status_code
class SynorGovernance:
"""
Synor Governance SDK client for Python.
Proposals, voting, DAOs, and vesting.
"""
def __init__(self, config: GovernanceConfig):
self._config = config
self._closed = False
self._client = httpx.AsyncClient(
base_url=config.endpoint,
timeout=config.timeout,
headers={
"Authorization": f"Bearer {config.api_key}",
"Content-Type": "application/json",
"X-SDK-Version": "python/0.1.0",
},
)
# ==================== Proposal Operations ====================
async def create_proposal(self, draft: ProposalDraft) -> Proposal:
"""Create a new proposal."""
body = self._draft_to_dict(draft)
response = await self._post("/proposals", body)
return self._parse_proposal(response)
async def get_proposal(self, proposal_id: str) -> Proposal:
"""Get a proposal by ID."""
response = await self._get(f"/proposals/{quote(proposal_id)}")
return self._parse_proposal(response)
async def list_proposals(self, filter: Optional[ProposalFilter] = None) -> List[Proposal]:
"""List proposals with optional filtering."""
params = {}
if filter:
if filter.status:
params["status"] = filter.status.value
if filter.type:
params["type"] = filter.type.value
if filter.proposer:
params["proposer"] = filter.proposer
if filter.limit:
params["limit"] = str(filter.limit)
if filter.offset:
params["offset"] = str(filter.offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/proposals{query}")
return [self._parse_proposal(p) for p in response.get("proposals", [])]
async def cancel_proposal(self, proposal_id: str) -> Proposal:
"""Cancel a proposal."""
response = await self._post(f"/proposals/{quote(proposal_id)}/cancel", {})
return self._parse_proposal(response)
async def execute_proposal(self, proposal_id: str) -> GovernanceTransaction:
"""Execute a passed proposal."""
response = await self._post(f"/proposals/{quote(proposal_id)}/execute", {})
return GovernanceTransaction(
tx_hash=response["tx_hash"],
timestamp=response["timestamp"],
block_number=response["block_number"],
status=response["status"],
)
# ==================== Voting Operations ====================
async def vote(
self, proposal_id: str, vote: Vote, weight: Optional[str] = None
) -> VoteReceipt:
"""Vote on a proposal."""
body: Dict[str, Any] = {"choice": vote.choice.value}
if vote.reason:
body["reason"] = vote.reason
if weight:
body["weight"] = weight
response = await self._post(f"/proposals/{quote(proposal_id)}/vote", body)
return self._parse_vote_receipt(response)
async def get_votes(
self,
proposal_id: str,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> List[VoteReceipt]:
"""Get votes for a proposal."""
params = {}
if limit:
params["limit"] = str(limit)
if offset:
params["offset"] = str(offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/proposals/{quote(proposal_id)}/votes{query}")
return [self._parse_vote_receipt(v) for v in response.get("votes", [])]
async def delegate(
self, to: str, amount: Optional[str] = None
) -> DelegationReceipt:
"""Delegate voting power to another address."""
body: Dict[str, Any] = {"to": to}
if amount:
body["amount"] = amount
response = await self._post("/voting/delegate", body)
return self._parse_delegation_receipt(response)
async def undelegate(self, from_addr: Optional[str] = None) -> DelegationReceipt:
"""Undelegate voting power."""
body: Dict[str, Any] = {}
if from_addr:
body["from"] = from_addr
response = await self._post("/voting/undelegate", body)
return self._parse_delegation_receipt(response)
async def get_voting_power(self, address: str) -> VotingPower:
"""Get voting power for an address."""
response = await self._get(f"/voting/power/{quote(address)}")
return self._parse_voting_power(response)
async def get_my_voting_power(self) -> VotingPower:
"""Get current voting power for the authenticated user."""
response = await self._get("/voting/power")
return self._parse_voting_power(response)
# ==================== DAO Operations ====================
async def create_dao(self, config: DaoConfig) -> Dao:
"""Create a new DAO."""
body = self._dao_config_to_dict(config)
response = await self._post("/daos", body)
return self._parse_dao(response)
async def get_dao(self, dao_id: str) -> Dao:
"""Get a DAO by ID."""
response = await self._get(f"/daos/{quote(dao_id)}")
return self._parse_dao(response)
async def list_daos(
self, limit: Optional[int] = None, offset: Optional[int] = None
) -> List[Dao]:
"""List DAOs."""
params = {}
if limit:
params["limit"] = str(limit)
if offset:
params["offset"] = str(offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/daos{query}")
return [self._parse_dao(d) for d in response.get("daos", [])]
async def get_dao_members(
self,
dao_id: str,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> List[Dict[str, str]]:
"""Get DAO members."""
params = {}
if limit:
params["limit"] = str(limit)
if offset:
params["offset"] = str(offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/daos/{quote(dao_id)}/members{query}")
return response.get("members", [])
# ==================== Vesting Operations ====================
async def create_vesting_schedule(self, schedule: VestingSchedule) -> VestingContract:
"""Create a vesting schedule."""
body = {
"beneficiary": schedule.beneficiary,
"total_amount": schedule.total_amount,
"start_time": schedule.start_time,
"cliff_duration": schedule.cliff_duration,
"vesting_duration": schedule.vesting_duration,
"revocable": schedule.revocable,
}
response = await self._post("/vesting", body)
return self._parse_vesting_contract(response)
async def get_vesting_contract(self, contract_id: str) -> VestingContract:
"""Get a vesting contract."""
response = await self._get(f"/vesting/{quote(contract_id)}")
return self._parse_vesting_contract(response)
async def list_vesting_contracts(
self, beneficiary: Optional[str] = None
) -> List[VestingContract]:
"""List vesting contracts."""
query = f"?beneficiary={quote(beneficiary)}" if beneficiary else ""
response = await self._get(f"/vesting{query}")
return [self._parse_vesting_contract(c) for c in response.get("contracts", [])]
async def claim_vested(self, contract_id: str) -> ClaimResult:
"""Claim vested tokens."""
response = await self._post(f"/vesting/{quote(contract_id)}/claim", {})
return ClaimResult(
vesting_id=response["vesting_id"],
amount=response["amount"],
recipient=response["recipient"],
timestamp=response["timestamp"],
tx_hash=response["tx_hash"],
)
async def revoke_vesting(self, contract_id: str) -> VestingContract:
"""Revoke a vesting contract (if revocable)."""
response = await self._post(f"/vesting/{quote(contract_id)}/revoke", {})
return self._parse_vesting_contract(response)
async def get_claimable_amount(self, contract_id: str) -> Dict[str, str]:
"""Get claimable amount for a vesting contract."""
return await self._get(f"/vesting/{quote(contract_id)}/claimable")
# ==================== Lifecycle ====================
async def health_check(self) -> bool:
"""Health check."""
try:
response = await self._get("/health")
return response.get("status") == "healthy"
except Exception:
return False
@property
def is_closed(self) -> bool:
"""Check if client is closed."""
return self._closed
async def close(self) -> None:
"""Close the client."""
self._closed = True
await self._client.aclose()
async def __aenter__(self) -> "SynorGovernance":
return self
async def __aexit__(self, *args) -> None:
await self.close()
# ==================== Private Methods ====================
async def _get(self, path: str) -> Dict[str, Any]:
return await self._execute(lambda: self._client.get(path))
async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
return await self._execute(lambda: self._client.post(path, json=body))
async def _execute(self, operation) -> Dict[str, Any]:
if self._closed:
raise GovernanceError("Client has been closed")
last_error = None
for attempt in range(self._config.retries):
try:
response = await operation()
self._ensure_success(response)
if response.content:
return response.json()
return {}
except Exception as e:
last_error = e
if self._config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < self._config.retries - 1:
await asyncio.sleep(2**attempt)
raise last_error
def _ensure_success(self, response: httpx.Response) -> None:
if response.status_code >= 400:
message = f"HTTP {response.status_code}"
code = None
try:
error = response.json()
message = error.get("message", message)
code = error.get("code")
except Exception:
pass
raise GovernanceError(message, code, response.status_code)
# ==================== Parsing Methods ====================
def _draft_to_dict(self, draft: ProposalDraft) -> Dict[str, Any]:
result: Dict[str, Any] = {
"title": draft.title,
"description": draft.description,
"type": draft.type.value,
}
if draft.actions:
result["actions"] = [
{
"target": a.target,
"value": a.value,
"calldata": a.calldata,
"description": a.description,
}
for a in draft.actions
]
if draft.start_time:
result["start_time"] = draft.start_time
if draft.end_time:
result["end_time"] = draft.end_time
if draft.quorum:
result["quorum"] = draft.quorum
if draft.threshold:
result["threshold"] = draft.threshold
return result
def _dao_config_to_dict(self, config: DaoConfig) -> Dict[str, Any]:
result: Dict[str, Any] = {
"name": config.name,
"type": config.type.value,
"voting_period": config.voting_period,
"quorum": config.quorum,
"proposal_threshold": config.proposal_threshold,
}
if config.token_address:
result["token_address"] = config.token_address
if config.signers:
result["signers"] = config.signers
if config.threshold:
result["threshold"] = config.threshold
if config.timelock_delay:
result["timelock_delay"] = config.timelock_delay
return result
def _parse_proposal(self, data: Dict[str, Any]) -> Proposal:
return Proposal(
id=data["id"],
title=data["title"],
description=data["description"],
type=ProposalType(data["type"]),
status=ProposalStatus(data["status"]),
proposer=data["proposer"],
actions=[
ProposalAction(
target=a["target"],
value=a["value"],
calldata=a["calldata"],
description=a.get("description"),
)
for a in data.get("actions", [])
],
start_time=data["start_time"],
end_time=data["end_time"],
quorum=data["quorum"],
threshold=data["threshold"],
for_votes=data["for_votes"],
against_votes=data["against_votes"],
abstain_votes=data["abstain_votes"],
total_votes=data["total_votes"],
created_at=data["created_at"],
executed_at=data.get("executed_at"),
execution_tx_hash=data.get("execution_tx_hash"),
)
def _parse_vote_receipt(self, data: Dict[str, Any]) -> VoteReceipt:
return VoteReceipt(
proposal_id=data["proposal_id"],
voter=data["voter"],
choice=VoteChoice(data["choice"]),
weight=data["weight"],
timestamp=data["timestamp"],
tx_hash=data["tx_hash"],
reason=data.get("reason"),
)
def _parse_delegation_receipt(self, data: Dict[str, Any]) -> DelegationReceipt:
return DelegationReceipt(
delegator=data["delegator"],
delegatee=data["delegatee"],
amount=data["amount"],
timestamp=data["timestamp"],
tx_hash=data["tx_hash"],
)
def _parse_voting_power(self, data: Dict[str, Any]) -> VotingPower:
delegated_to = None
if data.get("delegated_to"):
delegated_to = DelegationInfo(
address=data["delegated_to"]["address"],
amount=data["delegated_to"]["amount"],
timestamp=data["delegated_to"]["timestamp"],
)
return VotingPower(
address=data["address"],
own_power=data["own_power"],
delegated_power=data["delegated_power"],
total_power=data["total_power"],
delegated_from=[
DelegationInfo(
address=d["address"],
amount=d["amount"],
timestamp=d["timestamp"],
)
for d in data.get("delegated_from", [])
],
delegated_to=delegated_to,
)
def _parse_dao(self, data: Dict[str, Any]) -> Dao:
return Dao(
id=data["id"],
name=data["name"],
type=DaoType(data["type"]),
voting_period=data["voting_period"],
quorum=data["quorum"],
proposal_threshold=data["proposal_threshold"],
treasury=data["treasury"],
total_proposals=data["total_proposals"],
active_proposals=data["active_proposals"],
total_members=data["total_members"],
created_at=data["created_at"],
token_address=data.get("token_address"),
signers=data.get("signers"),
threshold=data.get("threshold"),
timelock_delay=data.get("timelock_delay"),
)
def _parse_vesting_contract(self, data: Dict[str, Any]) -> VestingContract:
return VestingContract(
id=data["id"],
beneficiary=data["beneficiary"],
total_amount=data["total_amount"],
released_amount=data["released_amount"],
vested_amount=data["vested_amount"],
start_time=data["start_time"],
cliff_time=data["cliff_time"],
end_time=data["end_time"],
revocable=data["revocable"],
status=VestingStatus(data["status"]),
created_at=data["created_at"],
tx_hash=data["tx_hash"],
)

View file

@ -0,0 +1,243 @@
"""
Synor Governance SDK Types.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List
class ProposalStatus(str, Enum):
"""Proposal status."""
DRAFT = "draft"
ACTIVE = "active"
PASSED = "passed"
REJECTED = "rejected"
EXECUTED = "executed"
CANCELLED = "cancelled"
EXPIRED = "expired"
class VoteChoice(str, Enum):
"""Vote choice."""
FOR = "for"
AGAINST = "against"
ABSTAIN = "abstain"
class ProposalType(str, Enum):
"""Proposal type."""
PARAMETER_CHANGE = "parameter_change"
TREASURY_SPEND = "treasury_spend"
UPGRADE = "upgrade"
TEXT = "text"
CUSTOM = "custom"
class DaoType(str, Enum):
"""DAO type."""
TOKEN = "token"
MULTISIG = "multisig"
HYBRID = "hybrid"
class VestingStatus(str, Enum):
"""Vesting status."""
ACTIVE = "active"
PAUSED = "paused"
COMPLETED = "completed"
CANCELLED = "cancelled"
@dataclass
class ProposalAction:
"""Proposal action."""
target: str
value: str
calldata: str
description: Optional[str] = None
@dataclass
class ProposalDraft:
"""Proposal draft."""
title: str
description: str
type: ProposalType
actions: List[ProposalAction] = field(default_factory=list)
start_time: Optional[int] = None
end_time: Optional[int] = None
quorum: Optional[str] = None
threshold: Optional[str] = None
@dataclass
class Proposal:
"""Proposal."""
id: str
title: str
description: str
type: ProposalType
status: ProposalStatus
proposer: str
actions: List[ProposalAction]
start_time: int
end_time: int
quorum: str
threshold: str
for_votes: str
against_votes: str
abstain_votes: str
total_votes: str
created_at: int
executed_at: Optional[int] = None
execution_tx_hash: Optional[str] = None
@dataclass
class ProposalFilter:
"""Proposal filter."""
status: Optional[ProposalStatus] = None
type: Optional[ProposalType] = None
proposer: Optional[str] = None
limit: Optional[int] = None
offset: Optional[int] = None
@dataclass
class Vote:
"""Vote."""
choice: VoteChoice
reason: Optional[str] = None
@dataclass
class VoteReceipt:
"""Vote receipt."""
proposal_id: str
voter: str
choice: VoteChoice
weight: str
timestamp: int
tx_hash: str
reason: Optional[str] = None
@dataclass
class DelegationInfo:
"""Delegation info."""
address: str
amount: str
timestamp: int
@dataclass
class DelegationReceipt:
"""Delegation receipt."""
delegator: str
delegatee: str
amount: str
timestamp: int
tx_hash: str
@dataclass
class VotingPower:
"""Voting power."""
address: str
own_power: str
delegated_power: str
total_power: str
delegated_from: List[DelegationInfo]
delegated_to: Optional[DelegationInfo] = None
@dataclass
class DaoConfig:
"""DAO configuration."""
name: str
type: DaoType
voting_period: int
quorum: str
proposal_threshold: str
token_address: Optional[str] = None
signers: Optional[List[str]] = None
threshold: Optional[int] = None
timelock_delay: Optional[int] = None
@dataclass
class Dao:
"""DAO."""
id: str
name: str
type: DaoType
voting_period: int
quorum: str
proposal_threshold: str
treasury: str
total_proposals: int
active_proposals: int
total_members: int
created_at: int
token_address: Optional[str] = None
signers: Optional[List[str]] = None
threshold: Optional[int] = None
timelock_delay: Optional[int] = None
@dataclass
class VestingSchedule:
"""Vesting schedule."""
beneficiary: str
total_amount: str
start_time: int
cliff_duration: int
vesting_duration: int
revocable: bool = False
@dataclass
class VestingContract:
"""Vesting contract."""
id: str
beneficiary: str
total_amount: str
released_amount: str
vested_amount: str
start_time: int
cliff_time: int
end_time: int
revocable: bool
status: VestingStatus
created_at: int
tx_hash: str
@dataclass
class ClaimResult:
"""Claim result."""
vesting_id: str
amount: str
recipient: str
timestamp: int
tx_hash: str
@dataclass
class GovernanceTransaction:
"""Governance transaction."""
tx_hash: str
timestamp: int
block_number: int
status: str
@dataclass
class GovernanceConfig:
"""Governance SDK configuration."""
api_key: str
endpoint: str = "https://governance.synor.io/v1"
timeout: float = 30.0
retries: int = 3
debug: bool = False

View file

@ -0,0 +1,64 @@
"""
Synor Mining SDK for Python.
Pool connections, block templates, hashrate stats, and GPU management.
"""
from .client import SynorMining, MiningError
from .types import (
DeviceType,
DeviceStatus,
ConnectionStatus,
TimePeriod,
SubmitResultStatus,
PoolConfig,
StratumConnection,
BlockTemplate,
TemplateTransaction,
MinedWork,
SubmitResult,
ShareInfo,
Hashrate,
MiningStats,
ShareStats,
DeviceTemperature,
EarningsSnapshot,
Earnings,
EarningsBreakdown,
MiningDevice,
DeviceConfig,
WorkerInfo,
PoolStats,
MiningAlgorithm,
MiningConfig,
)
__version__ = "0.1.0"
__all__ = [
"SynorMining",
"MiningError",
"DeviceType",
"DeviceStatus",
"ConnectionStatus",
"TimePeriod",
"SubmitResultStatus",
"PoolConfig",
"StratumConnection",
"BlockTemplate",
"TemplateTransaction",
"MinedWork",
"SubmitResult",
"ShareInfo",
"Hashrate",
"MiningStats",
"ShareStats",
"DeviceTemperature",
"EarningsSnapshot",
"Earnings",
"EarningsBreakdown",
"MiningDevice",
"DeviceConfig",
"WorkerInfo",
"PoolStats",
"MiningAlgorithm",
"MiningConfig",
]

View file

@ -0,0 +1,501 @@
"""
Synor Mining SDK Client.
Pool connections, block templates, hashrate stats, and GPU management.
"""
import asyncio
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode, quote
import httpx
from .types import (
MiningConfig,
PoolConfig,
StratumConnection,
BlockTemplate,
TemplateTransaction,
MinedWork,
SubmitResult,
ShareInfo,
SubmitResultStatus,
Hashrate,
MiningStats,
ShareStats,
DeviceTemperature,
EarningsSnapshot,
TimePeriod,
Earnings,
EarningsBreakdown,
MiningDevice,
DeviceConfig,
DeviceType,
DeviceStatus,
ConnectionStatus,
WorkerInfo,
PoolStats,
MiningAlgorithm,
)
class MiningError(Exception):
"""Mining SDK Error."""
def __init__(self, message: str, code: Optional[str] = None, status_code: int = 0):
super().__init__(message)
self.code = code
self.status_code = status_code
class SynorMining:
"""
Synor Mining SDK client for Python.
Pool connections, block templates, hashrate stats, and GPU management.
"""
def __init__(self, config: MiningConfig):
self._config = config
self._closed = False
self._active_connection: Optional[StratumConnection] = None
self._client = httpx.AsyncClient(
base_url=config.endpoint,
timeout=config.timeout,
headers={
"Authorization": f"Bearer {config.api_key}",
"Content-Type": "application/json",
"X-SDK-Version": "python/0.1.0",
},
)
# ==================== Pool Connection ====================
async def connect(self, pool: PoolConfig) -> StratumConnection:
"""Connect to a mining pool."""
body = {
"url": pool.url,
"user": pool.user,
}
if pool.password:
body["password"] = pool.password
if pool.algorithm:
body["algorithm"] = pool.algorithm
if pool.difficulty:
body["difficulty"] = pool.difficulty
response = await self._post("/pool/connect", body)
connection = self._parse_connection(response)
self._active_connection = connection
return connection
async def disconnect(self) -> None:
"""Disconnect from the current pool."""
if self._active_connection:
await self._post(f"/pool/disconnect/{self._active_connection.id}", {})
self._active_connection = None
async def get_connection_status(self) -> Optional[StratumConnection]:
"""Get current connection status."""
if not self._active_connection:
return None
try:
response = await self._get(f"/pool/status/{self._active_connection.id}")
return self._parse_connection(response)
except Exception:
return None
async def reconnect(self) -> StratumConnection:
"""Reconnect to the pool."""
if not self._active_connection:
raise MiningError("No active connection to reconnect")
response = await self._post(f"/pool/reconnect/{self._active_connection.id}", {})
return self._parse_connection(response)
# ==================== Mining Operations ====================
async def get_block_template(self) -> BlockTemplate:
"""Get the current block template."""
response = await self._get("/mining/template")
return self._parse_block_template(response)
async def submit_work(self, work: MinedWork) -> SubmitResult:
"""Submit mined work."""
body = {
"template_id": work.template_id,
"nonce": work.nonce,
"extra_nonce": work.extra_nonce,
"timestamp": work.timestamp,
"hash": work.hash,
}
response = await self._post("/mining/submit", body)
return self._parse_submit_result(response)
async def get_work(self) -> Dict[str, str]:
"""Get work from pool (stratum getwork)."""
return await self._get("/mining/getwork")
async def start_mining(self, algorithm: Optional[str] = None) -> Dict[str, Any]:
"""Start mining on all enabled devices."""
body = {"algorithm": algorithm} if algorithm else {}
return await self._post("/mining/start", body)
async def stop_mining(self) -> Dict[str, bool]:
"""Stop mining on all devices."""
return await self._post("/mining/stop", {})
# ==================== Stats ====================
async def get_hashrate(self) -> Hashrate:
"""Get current hashrate."""
response = await self._get("/stats/hashrate")
return self._parse_hashrate(response)
async def get_stats(self) -> MiningStats:
"""Get mining stats."""
response = await self._get("/stats")
return self._parse_mining_stats(response)
async def get_earnings(self, period: Optional[TimePeriod] = None) -> Earnings:
"""Get earnings for a time period."""
query = f"?period={period.value}" if period else ""
response = await self._get(f"/stats/earnings{query}")
return self._parse_earnings(response)
async def get_earnings_history(
self, limit: Optional[int] = None, offset: Optional[int] = None
) -> List[Earnings]:
"""Get earnings history."""
params = {}
if limit:
params["limit"] = str(limit)
if offset:
params["offset"] = str(offset)
query = f"?{urlencode(params)}" if params else ""
response = await self._get(f"/stats/earnings/history{query}")
return [self._parse_earnings(e) for e in response.get("earnings", [])]
async def get_pool_stats(self) -> PoolStats:
"""Get pool stats."""
response = await self._get("/pool/stats")
return PoolStats(
url=response["url"],
workers=response["workers"],
hashrate=response["hashrate"],
difficulty=response["difficulty"],
last_block=response["last_block"],
blocks_found_24h=response["blocks_found_24h"],
luck=response["luck"],
)
# ==================== GPU Management ====================
async def list_devices(self) -> List[MiningDevice]:
"""List all mining devices."""
response = await self._get("/devices")
return [self._parse_device(d) for d in response.get("devices", [])]
async def get_device(self, device_id: str) -> MiningDevice:
"""Get device details."""
response = await self._get(f"/devices/{quote(device_id)}")
return self._parse_device(response)
async def set_device_config(
self, device_id: str, config: DeviceConfig
) -> MiningDevice:
"""Set device configuration."""
body: Dict[str, Any] = {"enabled": config.enabled}
if config.intensity is not None:
body["intensity"] = config.intensity
if config.power_limit is not None:
body["power_limit"] = config.power_limit
if config.core_clock_offset is not None:
body["core_clock_offset"] = config.core_clock_offset
if config.memory_clock_offset is not None:
body["memory_clock_offset"] = config.memory_clock_offset
if config.fan_speed is not None:
body["fan_speed"] = config.fan_speed
response = await self._post(f"/devices/{quote(device_id)}/config", body)
return self._parse_device(response)
async def enable_device(self, device_id: str) -> MiningDevice:
"""Enable a device for mining."""
response = await self._post(f"/devices/{quote(device_id)}/enable", {})
return self._parse_device(response)
async def disable_device(self, device_id: str) -> MiningDevice:
"""Disable a device."""
response = await self._post(f"/devices/{quote(device_id)}/disable", {})
return self._parse_device(response)
async def reset_device(self, device_id: str) -> MiningDevice:
"""Reset device to default settings."""
response = await self._post(f"/devices/{quote(device_id)}/reset", {})
return self._parse_device(response)
# ==================== Workers ====================
async def list_workers(self) -> List[WorkerInfo]:
"""List all workers."""
response = await self._get("/workers")
return [self._parse_worker(w) for w in response.get("workers", [])]
async def get_worker(self, worker_id: str) -> WorkerInfo:
"""Get worker details."""
response = await self._get(f"/workers/{quote(worker_id)}")
return self._parse_worker(response)
async def create_worker(self, name: str) -> WorkerInfo:
"""Create a new worker."""
response = await self._post("/workers", {"name": name})
return self._parse_worker(response)
async def delete_worker(self, worker_id: str) -> None:
"""Delete a worker."""
await self._delete(f"/workers/{quote(worker_id)}")
# ==================== Algorithms ====================
async def get_supported_algorithms(self) -> List[MiningAlgorithm]:
"""Get supported mining algorithms."""
response = await self._get("/algorithms")
return [self._parse_algorithm(a) for a in response.get("algorithms", [])]
async def get_current_algorithm(self) -> MiningAlgorithm:
"""Get current algorithm."""
response = await self._get("/algorithms/current")
return self._parse_algorithm(response)
async def switch_algorithm(self, algorithm: str) -> Dict[str, bool]:
"""Switch to a different algorithm."""
return await self._post("/algorithms/switch", {"algorithm": algorithm})
# ==================== Lifecycle ====================
async def health_check(self) -> bool:
"""Health check."""
try:
response = await self._get("/health")
return response.get("status") == "healthy"
except Exception:
return False
@property
def is_closed(self) -> bool:
"""Check if client is closed."""
return self._closed
async def close(self) -> None:
"""Close the client."""
if self._active_connection:
await self.disconnect()
self._closed = True
await self._client.aclose()
async def __aenter__(self) -> "SynorMining":
return self
async def __aexit__(self, *args) -> None:
await self.close()
# ==================== Private Methods ====================
async def _get(self, path: str) -> Dict[str, Any]:
return await self._execute(lambda: self._client.get(path))
async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
return await self._execute(lambda: self._client.post(path, json=body))
async def _delete(self, path: str) -> None:
await self._execute(lambda: self._client.delete(path))
async def _execute(self, operation) -> Dict[str, Any]:
if self._closed:
raise MiningError("Client has been closed")
last_error = None
for attempt in range(self._config.retries):
try:
response = await operation()
self._ensure_success(response)
if response.content:
return response.json()
return {}
except Exception as e:
last_error = e
if self._config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < self._config.retries - 1:
await asyncio.sleep(2**attempt)
raise last_error
def _ensure_success(self, response: httpx.Response) -> None:
if response.status_code >= 400:
message = f"HTTP {response.status_code}"
code = None
try:
error = response.json()
message = error.get("message", message)
code = error.get("code")
except Exception:
pass
raise MiningError(message, code, response.status_code)
# ==================== Parsing Methods ====================
def _parse_connection(self, data: Dict[str, Any]) -> StratumConnection:
return StratumConnection(
id=data["id"],
pool=data["pool"],
status=ConnectionStatus(data["status"]),
algorithm=data["algorithm"],
difficulty=data["difficulty"],
connected_at=data["connected_at"],
accepted_shares=data["accepted_shares"],
rejected_shares=data["rejected_shares"],
stale_shares=data["stale_shares"],
last_share_at=data.get("last_share_at"),
)
def _parse_block_template(self, data: Dict[str, Any]) -> BlockTemplate:
return BlockTemplate(
id=data["id"],
previous_block_hash=data["previous_block_hash"],
merkle_root=data["merkle_root"],
timestamp=data["timestamp"],
bits=data["bits"],
height=data["height"],
coinbase_value=data["coinbase_value"],
transactions=[
TemplateTransaction(
txid=t["txid"],
data=t["data"],
fee=t["fee"],
weight=t["weight"],
)
for t in data.get("transactions", [])
],
target=data["target"],
algorithm=data["algorithm"],
extra_nonce=data["extra_nonce"],
)
def _parse_submit_result(self, data: Dict[str, Any]) -> SubmitResult:
return SubmitResult(
status=SubmitResultStatus(data["status"]),
share=ShareInfo(
hash=data["share"]["hash"],
difficulty=data["share"]["difficulty"],
timestamp=data["share"]["timestamp"],
accepted=data["share"]["accepted"],
),
block_found=data["block_found"],
reason=data.get("reason"),
block_hash=data.get("block_hash"),
reward=data.get("reward"),
)
def _parse_hashrate(self, data: Dict[str, Any]) -> Hashrate:
return Hashrate(
current=data["current"],
average_1h=data["average_1h"],
average_24h=data["average_24h"],
peak=data["peak"],
unit=data["unit"],
)
def _parse_mining_stats(self, data: Dict[str, Any]) -> MiningStats:
temperature = None
if data.get("temperature"):
temperature = DeviceTemperature(
current=data["temperature"]["current"],
max=data["temperature"]["max"],
throttling=data["temperature"]["throttling"],
)
return MiningStats(
hashrate=self._parse_hashrate(data["hashrate"]),
shares=ShareStats(
accepted=data["shares"]["accepted"],
rejected=data["shares"]["rejected"],
stale=data["shares"]["stale"],
total=data["shares"]["total"],
accept_rate=data["shares"]["accept_rate"],
),
uptime=data["uptime"],
efficiency=data["efficiency"],
power_consumption=data.get("power_consumption"),
temperature=temperature,
earnings=EarningsSnapshot(
today=data["earnings"]["today"],
yesterday=data["earnings"]["yesterday"],
this_week=data["earnings"]["this_week"],
this_month=data["earnings"]["this_month"],
total=data["earnings"]["total"],
currency=data["earnings"]["currency"],
),
)
def _parse_earnings(self, data: Dict[str, Any]) -> Earnings:
return Earnings(
period=TimePeriod(data["period"]),
start_date=data["start_date"],
end_date=data["end_date"],
amount=data["amount"],
blocks=data["blocks"],
shares=data["shares"],
average_hashrate=data["average_hashrate"],
currency=data["currency"],
breakdown=[
EarningsBreakdown(
date=b["date"],
amount=b["amount"],
blocks=b["blocks"],
shares=b["shares"],
hashrate=b["hashrate"],
)
for b in data.get("breakdown", [])
],
)
def _parse_device(self, data: Dict[str, Any]) -> MiningDevice:
return MiningDevice(
id=data["id"],
name=data["name"],
type=DeviceType(data["type"]),
status=DeviceStatus(data["status"]),
hashrate=data["hashrate"],
temperature=data["temperature"],
fan_speed=data["fan_speed"],
power_draw=data["power_draw"],
memory_used=data["memory_used"],
memory_total=data["memory_total"],
driver=data.get("driver"),
firmware=data.get("firmware"),
)
def _parse_worker(self, data: Dict[str, Any]) -> WorkerInfo:
return WorkerInfo(
id=data["id"],
name=data["name"],
status=ConnectionStatus(data["status"]),
hashrate=self._parse_hashrate(data["hashrate"]),
shares=ShareStats(
accepted=data["shares"]["accepted"],
rejected=data["shares"]["rejected"],
stale=data["shares"]["stale"],
total=data["shares"]["total"],
accept_rate=data["shares"]["accept_rate"],
),
devices=[self._parse_device(d) for d in data.get("devices", [])],
last_seen=data["last_seen"],
uptime=data["uptime"],
)
def _parse_algorithm(self, data: Dict[str, Any]) -> MiningAlgorithm:
return MiningAlgorithm(
name=data["name"],
display_name=data["display_name"],
hash_unit=data["hash_unit"],
profitability=data["profitability"],
difficulty=data["difficulty"],
block_reward=data["block_reward"],
block_time=data["block_time"],
)

View file

@ -0,0 +1,277 @@
"""
Synor Mining SDK Types.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List
class DeviceType(str, Enum):
"""Device type."""
CPU = "cpu"
GPU_NVIDIA = "gpu_nvidia"
GPU_AMD = "gpu_amd"
ASIC = "asic"
class DeviceStatus(str, Enum):
"""Device status."""
IDLE = "idle"
MINING = "mining"
ERROR = "error"
OFFLINE = "offline"
class ConnectionStatus(str, Enum):
"""Connection status."""
DISCONNECTED = "disconnected"
CONNECTING = "connecting"
CONNECTED = "connected"
RECONNECTING = "reconnecting"
class TimePeriod(str, Enum):
"""Time period for stats."""
HOUR = "hour"
DAY = "day"
WEEK = "week"
MONTH = "month"
ALL = "all"
class SubmitResultStatus(str, Enum):
"""Submit result status."""
ACCEPTED = "accepted"
REJECTED = "rejected"
STALE = "stale"
@dataclass
class PoolConfig:
"""Pool configuration."""
url: str
user: str
password: Optional[str] = None
algorithm: Optional[str] = None
difficulty: Optional[float] = None
@dataclass
class StratumConnection:
"""Stratum connection."""
id: str
pool: str
status: ConnectionStatus
algorithm: str
difficulty: float
connected_at: int
accepted_shares: int
rejected_shares: int
stale_shares: int
last_share_at: Optional[int] = None
@dataclass
class TemplateTransaction:
"""Template transaction."""
txid: str
data: str
fee: str
weight: int
@dataclass
class BlockTemplate:
"""Block template."""
id: str
previous_block_hash: str
merkle_root: str
timestamp: int
bits: str
height: int
coinbase_value: str
transactions: List[TemplateTransaction]
target: str
algorithm: str
extra_nonce: str
@dataclass
class MinedWork:
"""Mined work to submit."""
template_id: str
nonce: str
extra_nonce: str
timestamp: int
hash: str
@dataclass
class ShareInfo:
"""Share info."""
hash: str
difficulty: float
timestamp: int
accepted: bool
@dataclass
class SubmitResult:
"""Submit result."""
status: SubmitResultStatus
share: ShareInfo
block_found: bool
reason: Optional[str] = None
block_hash: Optional[str] = None
reward: Optional[str] = None
@dataclass
class Hashrate:
"""Hashrate."""
current: float
average_1h: float
average_24h: float
peak: float
unit: str
@dataclass
class ShareStats:
"""Share stats."""
accepted: int
rejected: int
stale: int
total: int
accept_rate: float
@dataclass
class DeviceTemperature:
"""Device temperature."""
current: float
max: float
throttling: bool
@dataclass
class EarningsSnapshot:
"""Earnings snapshot."""
today: str
yesterday: str
this_week: str
this_month: str
total: str
currency: str
@dataclass
class MiningStats:
"""Mining stats."""
hashrate: Hashrate
shares: ShareStats
uptime: int
efficiency: float
earnings: EarningsSnapshot
power_consumption: Optional[float] = None
temperature: Optional[DeviceTemperature] = None
@dataclass
class EarningsBreakdown:
"""Earnings breakdown."""
date: int
amount: str
blocks: int
shares: int
hashrate: float
@dataclass
class Earnings:
"""Detailed earnings."""
period: TimePeriod
start_date: int
end_date: int
amount: str
blocks: int
shares: int
average_hashrate: float
currency: str
breakdown: List[EarningsBreakdown]
@dataclass
class MiningDevice:
"""Mining device."""
id: str
name: str
type: DeviceType
status: DeviceStatus
hashrate: float
temperature: float
fan_speed: float
power_draw: float
memory_used: int
memory_total: int
driver: Optional[str] = None
firmware: Optional[str] = None
@dataclass
class DeviceConfig:
"""Device configuration."""
enabled: bool
intensity: Optional[int] = None
power_limit: Optional[int] = None
core_clock_offset: Optional[int] = None
memory_clock_offset: Optional[int] = None
fan_speed: Optional[int] = None
@dataclass
class WorkerInfo:
"""Worker info."""
id: str
name: str
status: ConnectionStatus
hashrate: Hashrate
shares: ShareStats
devices: List[MiningDevice]
last_seen: int
uptime: int
@dataclass
class PoolStats:
"""Pool stats."""
url: str
workers: int
hashrate: float
difficulty: float
last_block: int
blocks_found_24h: int
luck: float
@dataclass
class MiningAlgorithm:
"""Mining algorithm."""
name: str
display_name: str
hash_unit: str
profitability: str
difficulty: float
block_reward: str
block_time: int
@dataclass
class MiningConfig:
"""Mining SDK configuration."""
api_key: str
endpoint: str = "https://mining.synor.io/v1"
timeout: float = 30.0
retries: int = 3
debug: bool = False

View file

@ -0,0 +1,809 @@
//! Synor Economics SDK for Rust.
//! Pricing, billing, staking, and discount management.
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
const DEFAULT_ENDPOINT: &str = "https://economics.synor.io/v1";
const DEFAULT_TIMEOUT: u64 = 30;
const DEFAULT_RETRIES: u32 = 3;
/// Service type for pricing.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ServiceType {
Compute,
Storage,
Database,
Hosting,
Bridge,
Rpc,
}
/// Billing period.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BillingPeriod {
Hourly,
Daily,
Weekly,
Monthly,
}
/// Stake status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum StakeStatus {
Active,
Unstaking,
Withdrawn,
Slashed,
}
/// Discount type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DiscountType {
Percentage,
Fixed,
Volume,
Referral,
}
/// Invoice status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum InvoiceStatus {
Pending,
Paid,
Overdue,
Cancelled,
}
/// Economics SDK configuration.
#[derive(Debug, Clone)]
pub struct EconomicsConfig {
pub api_key: String,
pub endpoint: String,
pub timeout: Duration,
pub retries: u32,
pub debug: bool,
}
impl EconomicsConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: DEFAULT_ENDPOINT.to_string(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT),
retries: DEFAULT_RETRIES,
debug: false,
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn with_retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
/// Usage metrics for pricing.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UsageMetrics {
#[serde(skip_serializing_if = "Option::is_none")]
pub compute_hours: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_bytes: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub database_ops: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hosting_requests: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bridge_transfers: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rpc_calls: Option<i64>,
}
/// Service usage in a plan.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceUsage {
pub service: ServiceType,
pub metrics: UsageMetrics,
#[serde(skip_serializing_if = "Option::is_none")]
pub tier: Option<String>,
}
/// Usage plan for cost estimation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsagePlan {
pub services: Vec<ServiceUsage>,
pub period: BillingPeriod,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_date: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_date: Option<i64>,
}
/// Applied discount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppliedDiscount {
pub code: String,
#[serde(rename = "type")]
pub discount_type: DiscountType,
pub value: String,
pub savings: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
/// Tax amount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaxAmount {
pub name: String,
pub rate: f64,
pub amount: String,
}
/// Price breakdown.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Price {
pub service: ServiceType,
pub base_price: String,
pub quantity: String,
pub unit: String,
pub subtotal: String,
pub discounts: Vec<AppliedDiscount>,
pub total: String,
pub currency: String,
}
/// Cost estimate for a usage plan.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostEstimate {
pub services: Vec<Price>,
pub subtotal: String,
pub discounts: Vec<AppliedDiscount>,
pub taxes: Vec<TaxAmount>,
pub total: String,
pub currency: String,
pub valid_until: i64,
}
/// Usage detail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsageDetail {
pub timestamp: i64,
pub operation: String,
pub quantity: f64,
pub unit: String,
pub cost: String,
}
/// Service usage record.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceUsageRecord {
pub service: ServiceType,
pub metrics: UsageMetrics,
pub cost: String,
pub details: Vec<UsageDetail>,
}
/// Usage record.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
pub period: BillingPeriod,
pub start_date: i64,
pub end_date: i64,
pub services: Vec<ServiceUsageRecord>,
pub total_cost: String,
pub currency: String,
}
/// Invoice item.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvoiceItem {
pub service: ServiceType,
pub description: String,
pub quantity: String,
pub unit_price: String,
pub total: String,
}
/// Invoice.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invoice {
pub id: String,
pub status: InvoiceStatus,
pub period: BillingPeriod,
pub start_date: i64,
pub end_date: i64,
pub items: Vec<InvoiceItem>,
pub subtotal: String,
pub discounts: Vec<AppliedDiscount>,
pub taxes: Vec<TaxAmount>,
pub total: String,
pub currency: String,
pub due_date: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub paid_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_method: Option<String>,
}
/// Account balance.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountBalance {
pub available: String,
pub pending: String,
pub reserved: String,
pub total: String,
pub currency: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub credit_limit: Option<String>,
pub last_updated: i64,
}
/// Stake receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeReceipt {
pub id: String,
pub amount: String,
pub lock_duration: i32,
pub start_date: i64,
pub end_date: i64,
pub apy: String,
pub estimated_rewards: String,
pub tx_hash: String,
}
/// Unstake receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnstakeReceipt {
pub id: String,
pub stake_id: String,
pub amount: String,
pub rewards: String,
pub total: String,
pub unbonding_period: i32,
pub available_at: i64,
pub tx_hash: String,
}
/// Stake info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeInfo {
pub id: String,
pub amount: String,
pub status: StakeStatus,
pub lock_duration: i32,
pub start_date: i64,
pub end_date: i64,
pub apy: String,
pub earned_rewards: String,
pub pending_rewards: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub validator: Option<String>,
}
/// Stake reward detail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeRewardDetail {
pub stake_id: String,
pub earned: String,
pub pending: String,
pub apy: String,
}
/// Staking rewards.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakingRewards {
pub total_earned: String,
pub pending: String,
pub claimed: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_claim_date: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_claim_available: Option<i64>,
pub stakes: Vec<StakeRewardDetail>,
}
/// Discount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Discount {
pub code: String,
#[serde(rename = "type")]
pub discount_type: DiscountType,
pub value: String,
pub description: String,
pub valid_from: i64,
pub valid_until: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_purchase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_discount: Option<String>,
pub applicable_services: Vec<ServiceType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_limit: Option<i32>,
pub used_count: i32,
}
/// Stake options.
#[derive(Debug, Clone, Default, Serialize)]
pub struct StakeOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub validator: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_compound: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lock_duration: Option<i32>,
}
/// Staking APY response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakingApy {
pub apy: String,
pub min_lock_duration: i32,
pub max_lock_duration: i32,
}
/// Economics SDK error.
#[derive(Error, Debug)]
pub enum EconomicsError {
#[error("HTTP error: {message}")]
Http {
message: String,
code: Option<String>,
status_code: u16,
},
#[error("Client closed")]
ClientClosed,
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
/// Synor Economics SDK client.
pub struct SynorEconomics {
config: EconomicsConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl SynorEconomics {
/// Create a new Economics client.
pub fn new(config: EconomicsConfig) -> Result<Self, EconomicsError> {
let client = Client::builder()
.timeout(config.timeout)
.build()?;
Ok(Self {
config,
client,
closed: Arc::new(AtomicBool::new(false)),
})
}
// ==================== Pricing Operations ====================
/// Get price for a service based on usage.
pub async fn get_price(
&self,
service: ServiceType,
usage: UsageMetrics,
) -> Result<Price, EconomicsError> {
#[derive(Serialize)]
struct Request {
service: ServiceType,
usage: UsageMetrics,
}
#[derive(Deserialize)]
struct Response {
price: Price,
}
let resp: Response = self
.post("/pricing/calculate", &Request { service, usage })
.await?;
Ok(resp.price)
}
/// Estimate cost for a usage plan.
pub async fn estimate_cost(&self, plan: UsagePlan) -> Result<CostEstimate, EconomicsError> {
self.post("/pricing/estimate", &plan).await
}
/// Get pricing tiers for a service.
pub async fn get_pricing_tiers(
&self,
service: ServiceType,
) -> Result<Vec<Price>, EconomicsError> {
#[derive(Deserialize)]
struct Response {
tiers: Vec<Price>,
}
let resp: Response = self
.get(&format!("/pricing/{:?}/tiers", service).to_lowercase())
.await?;
Ok(resp.tiers)
}
// ==================== Usage & Billing Operations ====================
/// Get usage for a billing period.
pub async fn get_usage(
&self,
period: Option<BillingPeriod>,
) -> Result<Usage, EconomicsError> {
let path = match period {
Some(p) => format!("/usage?period={:?}", p).to_lowercase(),
None => "/usage".to_string(),
};
self.get(&path).await
}
/// Get usage history.
pub async fn get_usage_history(
&self,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<Usage>, EconomicsError> {
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
let path = if params.is_empty() {
"/usage/history".to_string()
} else {
format!("/usage/history?{}", params.join("&"))
};
#[derive(Deserialize)]
struct Response {
usage: Vec<Usage>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.usage)
}
/// Get invoices.
pub async fn get_invoices(&self) -> Result<Vec<Invoice>, EconomicsError> {
#[derive(Deserialize)]
struct Response {
invoices: Vec<Invoice>,
}
let resp: Response = self.get("/invoices").await?;
Ok(resp.invoices)
}
/// Get a specific invoice.
pub async fn get_invoice(&self, invoice_id: &str) -> Result<Invoice, EconomicsError> {
self.get(&format!("/invoices/{}", urlencoding::encode(invoice_id)))
.await
}
/// Pay an invoice.
pub async fn pay_invoice(
&self,
invoice_id: &str,
payment_method: Option<&str>,
) -> Result<Invoice, EconomicsError> {
#[derive(Serialize)]
struct Request {
#[serde(skip_serializing_if = "Option::is_none")]
payment_method: Option<String>,
}
self.post(
&format!("/invoices/{}/pay", urlencoding::encode(invoice_id)),
&Request {
payment_method: payment_method.map(String::from),
},
)
.await
}
/// Get account balance.
pub async fn get_balance(&self) -> Result<AccountBalance, EconomicsError> {
self.get("/balance").await
}
/// Add funds to account.
pub async fn add_funds(
&self,
amount: &str,
payment_method: &str,
) -> Result<AccountBalance, EconomicsError> {
#[derive(Serialize)]
struct Request {
amount: String,
payment_method: String,
}
self.post(
"/balance/deposit",
&Request {
amount: amount.to_string(),
payment_method: payment_method.to_string(),
},
)
.await
}
// ==================== Staking Operations ====================
/// Stake tokens.
pub async fn stake(
&self,
amount: &str,
options: Option<StakeOptions>,
) -> Result<StakeReceipt, EconomicsError> {
#[derive(Serialize)]
struct Request {
amount: String,
#[serde(flatten)]
options: Option<StakeOptions>,
}
self.post(
"/staking/stake",
&Request {
amount: amount.to_string(),
options,
},
)
.await
}
/// Unstake tokens.
pub async fn unstake(&self, stake_id: &str) -> Result<UnstakeReceipt, EconomicsError> {
self.post::<(), UnstakeReceipt>(
&format!("/staking/stakes/{}/unstake", urlencoding::encode(stake_id)),
&(),
)
.await
}
/// Get staking rewards.
pub async fn get_staking_rewards(&self) -> Result<StakingRewards, EconomicsError> {
self.get("/staking/rewards").await
}
/// Claim staking rewards.
pub async fn claim_rewards(
&self,
stake_id: Option<&str>,
) -> Result<StakingRewards, EconomicsError> {
let path = match stake_id {
Some(id) => format!("/staking/stakes/{}/claim", urlencoding::encode(id)),
None => "/staking/rewards/claim".to_string(),
};
self.post::<(), StakingRewards>(&path, &()).await
}
/// List active stakes.
pub async fn list_stakes(&self) -> Result<Vec<StakeInfo>, EconomicsError> {
#[derive(Deserialize)]
struct Response {
stakes: Vec<StakeInfo>,
}
let resp: Response = self.get("/staking/stakes").await?;
Ok(resp.stakes)
}
/// Get stake details.
pub async fn get_stake(&self, stake_id: &str) -> Result<StakeInfo, EconomicsError> {
self.get(&format!(
"/staking/stakes/{}",
urlencoding::encode(stake_id)
))
.await
}
/// Get current APY for staking.
pub async fn get_staking_apy(&self) -> Result<StakingApy, EconomicsError> {
self.get("/staking/apy").await
}
// ==================== Discount Operations ====================
/// Apply a discount code.
pub async fn apply_discount(&self, code: &str) -> Result<Discount, EconomicsError> {
#[derive(Serialize)]
struct Request {
code: String,
}
self.post(
"/discounts/apply",
&Request {
code: code.to_string(),
},
)
.await
}
/// Get available discounts.
pub async fn get_available_discounts(&self) -> Result<Vec<Discount>, EconomicsError> {
#[derive(Deserialize)]
struct Response {
discounts: Vec<Discount>,
}
let resp: Response = self.get("/discounts").await?;
Ok(resp.discounts)
}
/// Get active discounts on account.
pub async fn get_active_discounts(&self) -> Result<Vec<Discount>, EconomicsError> {
#[derive(Deserialize)]
struct Response {
discounts: Vec<Discount>,
}
let resp: Response = self.get("/discounts/active").await?;
Ok(resp.discounts)
}
/// Remove a discount.
pub async fn remove_discount(&self, code: &str) -> Result<(), EconomicsError> {
self.delete(&format!("/discounts/{}", urlencoding::encode(code)))
.await
}
// ==================== Lifecycle ====================
/// Health check.
pub async fn health_check(&self) -> bool {
#[derive(Deserialize)]
struct Response {
status: String,
}
match self.get::<Response>("/health").await {
Ok(resp) => resp.status == "healthy",
Err(_) => false,
}
}
/// Check if client is closed.
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
/// Close the client.
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
}
// ==================== Private Methods ====================
async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, EconomicsError> {
self.execute(|| async {
let resp = self
.client
.get(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
&self,
path: &str,
body: &B,
) -> Result<T, EconomicsError> {
self.execute(|| async {
let resp = self
.client
.post(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.json(body)
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn delete(&self, path: &str) -> Result<(), EconomicsError> {
self.execute(|| async {
let resp = self
.client
.delete(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if resp.status().is_success() {
Ok(())
} else {
Err(self.parse_error(resp).await)
}
})
.await
}
async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, EconomicsError>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, EconomicsError>>,
{
if self.closed.load(Ordering::SeqCst) {
return Err(EconomicsError::ClientClosed);
}
let mut last_error = None;
for attempt in 0..self.config.retries {
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
if self.config.debug {
eprintln!("Attempt {} failed: {:?}", attempt + 1, e);
}
last_error = Some(e);
if attempt < self.config.retries - 1 {
tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
}
}
}
}
Err(last_error.unwrap())
}
async fn handle_response<T: for<'de> Deserialize<'de>>(
&self,
resp: reqwest::Response,
) -> Result<T, EconomicsError> {
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(self.parse_error(resp).await)
}
}
async fn parse_error(&self, resp: reqwest::Response) -> EconomicsError {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
#[derive(Deserialize)]
struct ErrorResponse {
message: Option<String>,
code: Option<String>,
}
let (message, code) = match serde_json::from_str::<ErrorResponse>(&body) {
Ok(err) => (
err.message.unwrap_or_else(|| format!("HTTP {}", status)),
err.code,
),
Err(_) => (format!("HTTP {}", status), None),
};
EconomicsError::Http {
message,
code,
status_code: status,
}
}
}

View file

@ -0,0 +1,796 @@
//! Synor Governance SDK for Rust.
//! Proposals, voting, DAOs, and vesting.
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
const DEFAULT_ENDPOINT: &str = "https://governance.synor.io/v1";
const DEFAULT_TIMEOUT: u64 = 30;
const DEFAULT_RETRIES: u32 = 3;
/// Proposal status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ProposalStatus {
Draft,
Active,
Passed,
Rejected,
Executed,
Cancelled,
Expired,
}
/// Vote choice.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum VoteChoice {
For,
Against,
Abstain,
}
/// Proposal type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProposalType {
ParameterChange,
TreasurySpend,
Upgrade,
Text,
Custom,
}
/// DAO type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DaoType {
Token,
Multisig,
Hybrid,
}
/// Vesting status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum VestingStatus {
Active,
Paused,
Completed,
Cancelled,
}
/// Governance SDK configuration.
#[derive(Debug, Clone)]
pub struct GovernanceConfig {
pub api_key: String,
pub endpoint: String,
pub timeout: Duration,
pub retries: u32,
pub debug: bool,
}
impl GovernanceConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: DEFAULT_ENDPOINT.to_string(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT),
retries: DEFAULT_RETRIES,
debug: false,
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
}
/// Proposal action.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProposalAction {
pub target: String,
pub value: String,
pub calldata: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
/// Proposal draft for creating proposals.
#[derive(Debug, Clone, Serialize)]
pub struct ProposalDraft {
pub title: String,
pub description: String,
#[serde(rename = "type")]
pub proposal_type: ProposalType,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<ProposalAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quorum: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<String>,
}
/// Proposal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proposal {
pub id: String,
pub title: String,
pub description: String,
#[serde(rename = "type")]
pub proposal_type: ProposalType,
pub status: ProposalStatus,
pub proposer: String,
pub actions: Vec<ProposalAction>,
pub start_time: i64,
pub end_time: i64,
pub quorum: String,
pub threshold: String,
pub for_votes: String,
pub against_votes: String,
pub abstain_votes: String,
pub total_votes: String,
pub created_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub executed_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_tx_hash: Option<String>,
}
/// Proposal filter.
#[derive(Debug, Clone, Default)]
pub struct ProposalFilter {
pub status: Option<ProposalStatus>,
pub proposal_type: Option<ProposalType>,
pub proposer: Option<String>,
pub limit: Option<i32>,
pub offset: Option<i32>,
}
/// Vote.
#[derive(Debug, Clone, Serialize)]
pub struct Vote {
pub choice: VoteChoice,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
/// Vote receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoteReceipt {
pub proposal_id: String,
pub voter: String,
pub choice: VoteChoice,
pub weight: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub timestamp: i64,
pub tx_hash: String,
}
/// Delegation info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationInfo {
pub address: String,
pub amount: String,
pub timestamp: i64,
}
/// Delegation receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationReceipt {
pub delegator: String,
pub delegatee: String,
pub amount: String,
pub timestamp: i64,
pub tx_hash: String,
}
/// Voting power.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VotingPower {
pub address: String,
pub own_power: String,
pub delegated_power: String,
pub total_power: String,
pub delegated_from: Vec<DelegationInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegated_to: Option<DelegationInfo>,
}
/// DAO config for creating DAOs.
#[derive(Debug, Clone, Serialize)]
pub struct DaoConfig {
pub name: String,
#[serde(rename = "type")]
pub dao_type: DaoType,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<i32>,
pub voting_period: i32,
pub quorum: String,
pub proposal_threshold: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock_delay: Option<i32>,
}
/// DAO.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dao {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub dao_type: DaoType,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<i32>,
pub voting_period: i32,
pub quorum: String,
pub proposal_threshold: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock_delay: Option<i32>,
pub treasury: String,
pub total_proposals: i32,
pub active_proposals: i32,
pub total_members: i32,
pub created_at: i64,
}
/// Vesting schedule for creating vesting contracts.
#[derive(Debug, Clone, Serialize)]
pub struct VestingSchedule {
pub beneficiary: String,
pub total_amount: String,
pub start_time: i64,
pub cliff_duration: i32,
pub vesting_duration: i32,
#[serde(default)]
pub revocable: bool,
}
/// Vesting contract.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VestingContract {
pub id: String,
pub beneficiary: String,
pub total_amount: String,
pub released_amount: String,
pub vested_amount: String,
pub start_time: i64,
pub cliff_time: i64,
pub end_time: i64,
pub revocable: bool,
pub status: VestingStatus,
pub created_at: i64,
pub tx_hash: String,
}
/// Claim result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimResult {
pub vesting_id: String,
pub amount: String,
pub recipient: String,
pub timestamp: i64,
pub tx_hash: String,
}
/// Governance transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GovernanceTransaction {
pub tx_hash: String,
pub timestamp: i64,
pub block_number: i64,
pub status: String,
}
/// DAO member.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaoMember {
pub address: String,
pub power: String,
}
/// Claimable amount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimableAmount {
pub claimable: String,
pub vested: String,
}
/// Governance SDK error.
#[derive(Error, Debug)]
pub enum GovernanceError {
#[error("HTTP error: {message}")]
Http {
message: String,
code: Option<String>,
status_code: u16,
},
#[error("Client closed")]
ClientClosed,
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
/// Synor Governance SDK client.
pub struct SynorGovernance {
config: GovernanceConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl SynorGovernance {
/// Create a new Governance client.
pub fn new(config: GovernanceConfig) -> Result<Self, GovernanceError> {
let client = Client::builder().timeout(config.timeout).build()?;
Ok(Self {
config,
client,
closed: Arc::new(AtomicBool::new(false)),
})
}
// ==================== Proposal Operations ====================
/// Create a new proposal.
pub async fn create_proposal(&self, draft: ProposalDraft) -> Result<Proposal, GovernanceError> {
self.post("/proposals", &draft).await
}
/// Get a proposal by ID.
pub async fn get_proposal(&self, proposal_id: &str) -> Result<Proposal, GovernanceError> {
self.get(&format!("/proposals/{}", urlencoding::encode(proposal_id)))
.await
}
/// List proposals with optional filtering.
pub async fn list_proposals(
&self,
filter: Option<ProposalFilter>,
) -> Result<Vec<Proposal>, GovernanceError> {
let mut params = Vec::new();
if let Some(f) = filter {
if let Some(s) = f.status {
params.push(format!("status={:?}", s).to_lowercase());
}
if let Some(t) = f.proposal_type {
params.push(format!("type={:?}", t).to_lowercase());
}
if let Some(p) = f.proposer {
params.push(format!("proposer={}", p));
}
if let Some(l) = f.limit {
params.push(format!("limit={}", l));
}
if let Some(o) = f.offset {
params.push(format!("offset={}", o));
}
}
let path = if params.is_empty() {
"/proposals".to_string()
} else {
format!("/proposals?{}", params.join("&"))
};
#[derive(Deserialize)]
struct Response {
proposals: Vec<Proposal>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.proposals)
}
/// Cancel a proposal.
pub async fn cancel_proposal(&self, proposal_id: &str) -> Result<Proposal, GovernanceError> {
self.post::<(), Proposal>(
&format!("/proposals/{}/cancel", urlencoding::encode(proposal_id)),
&(),
)
.await
}
/// Execute a passed proposal.
pub async fn execute_proposal(
&self,
proposal_id: &str,
) -> Result<GovernanceTransaction, GovernanceError> {
self.post::<(), GovernanceTransaction>(
&format!("/proposals/{}/execute", urlencoding::encode(proposal_id)),
&(),
)
.await
}
// ==================== Voting Operations ====================
/// Vote on a proposal.
pub async fn vote(
&self,
proposal_id: &str,
vote: Vote,
weight: Option<&str>,
) -> Result<VoteReceipt, GovernanceError> {
#[derive(Serialize)]
struct VoteRequest {
choice: VoteChoice,
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
weight: Option<String>,
}
let req = VoteRequest {
choice: vote.choice,
reason: vote.reason,
weight: weight.map(String::from),
};
self.post(
&format!("/proposals/{}/vote", urlencoding::encode(proposal_id)),
&req,
)
.await
}
/// Get votes for a proposal.
pub async fn get_votes(
&self,
proposal_id: &str,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<VoteReceipt>, GovernanceError> {
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
let path = if params.is_empty() {
format!("/proposals/{}/votes", urlencoding::encode(proposal_id))
} else {
format!(
"/proposals/{}/votes?{}",
urlencoding::encode(proposal_id),
params.join("&")
)
};
#[derive(Deserialize)]
struct Response {
votes: Vec<VoteReceipt>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.votes)
}
/// Delegate voting power.
pub async fn delegate(
&self,
to: &str,
amount: Option<&str>,
) -> Result<DelegationReceipt, GovernanceError> {
#[derive(Serialize)]
struct Request {
to: String,
#[serde(skip_serializing_if = "Option::is_none")]
amount: Option<String>,
}
self.post(
"/voting/delegate",
&Request {
to: to.to_string(),
amount: amount.map(String::from),
},
)
.await
}
/// Undelegate voting power.
pub async fn undelegate(
&self,
from: Option<&str>,
) -> Result<DelegationReceipt, GovernanceError> {
#[derive(Serialize)]
struct Request {
#[serde(skip_serializing_if = "Option::is_none")]
from: Option<String>,
}
self.post(
"/voting/undelegate",
&Request {
from: from.map(String::from),
},
)
.await
}
/// Get voting power for an address.
pub async fn get_voting_power(&self, address: &str) -> Result<VotingPower, GovernanceError> {
self.get(&format!("/voting/power/{}", urlencoding::encode(address)))
.await
}
/// Get voting power for the authenticated user.
pub async fn get_my_voting_power(&self) -> Result<VotingPower, GovernanceError> {
self.get("/voting/power").await
}
// ==================== DAO Operations ====================
/// Create a new DAO.
pub async fn create_dao(&self, config: DaoConfig) -> Result<Dao, GovernanceError> {
self.post("/daos", &config).await
}
/// Get a DAO by ID.
pub async fn get_dao(&self, dao_id: &str) -> Result<Dao, GovernanceError> {
self.get(&format!("/daos/{}", urlencoding::encode(dao_id)))
.await
}
/// List DAOs.
pub async fn list_daos(
&self,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<Dao>, GovernanceError> {
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
let path = if params.is_empty() {
"/daos".to_string()
} else {
format!("/daos?{}", params.join("&"))
};
#[derive(Deserialize)]
struct Response {
daos: Vec<Dao>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.daos)
}
/// Get DAO members.
pub async fn get_dao_members(
&self,
dao_id: &str,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<DaoMember>, GovernanceError> {
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
let path = if params.is_empty() {
format!("/daos/{}/members", urlencoding::encode(dao_id))
} else {
format!(
"/daos/{}/members?{}",
urlencoding::encode(dao_id),
params.join("&")
)
};
#[derive(Deserialize)]
struct Response {
members: Vec<DaoMember>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.members)
}
// ==================== Vesting Operations ====================
/// Create a vesting schedule.
pub async fn create_vesting_schedule(
&self,
schedule: VestingSchedule,
) -> Result<VestingContract, GovernanceError> {
self.post("/vesting", &schedule).await
}
/// Get a vesting contract.
pub async fn get_vesting_contract(
&self,
contract_id: &str,
) -> Result<VestingContract, GovernanceError> {
self.get(&format!("/vesting/{}", urlencoding::encode(contract_id)))
.await
}
/// List vesting contracts.
pub async fn list_vesting_contracts(
&self,
beneficiary: Option<&str>,
) -> Result<Vec<VestingContract>, GovernanceError> {
let path = match beneficiary {
Some(b) => format!("/vesting?beneficiary={}", urlencoding::encode(b)),
None => "/vesting".to_string(),
};
#[derive(Deserialize)]
struct Response {
contracts: Vec<VestingContract>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.contracts)
}
/// Claim vested tokens.
pub async fn claim_vested(&self, contract_id: &str) -> Result<ClaimResult, GovernanceError> {
self.post::<(), ClaimResult>(
&format!("/vesting/{}/claim", urlencoding::encode(contract_id)),
&(),
)
.await
}
/// Revoke a vesting contract.
pub async fn revoke_vesting(
&self,
contract_id: &str,
) -> Result<VestingContract, GovernanceError> {
self.post::<(), VestingContract>(
&format!("/vesting/{}/revoke", urlencoding::encode(contract_id)),
&(),
)
.await
}
/// Get claimable amount for a vesting contract.
pub async fn get_claimable_amount(
&self,
contract_id: &str,
) -> Result<ClaimableAmount, GovernanceError> {
self.get(&format!(
"/vesting/{}/claimable",
urlencoding::encode(contract_id)
))
.await
}
// ==================== Lifecycle ====================
/// Health check.
pub async fn health_check(&self) -> bool {
#[derive(Deserialize)]
struct Response {
status: String,
}
match self.get::<Response>("/health").await {
Ok(resp) => resp.status == "healthy",
Err(_) => false,
}
}
/// Check if client is closed.
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
/// Close the client.
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
}
// ==================== Private Methods ====================
async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, GovernanceError> {
self.execute(|| async {
let resp = self
.client
.get(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
&self,
path: &str,
body: &B,
) -> Result<T, GovernanceError> {
self.execute(|| async {
let resp = self
.client
.post(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.json(body)
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, GovernanceError>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, GovernanceError>>,
{
if self.closed.load(Ordering::SeqCst) {
return Err(GovernanceError::ClientClosed);
}
let mut last_error = None;
for attempt in 0..self.config.retries {
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
if self.config.debug {
eprintln!("Attempt {} failed: {:?}", attempt + 1, e);
}
last_error = Some(e);
if attempt < self.config.retries - 1 {
tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
}
}
}
}
Err(last_error.unwrap())
}
async fn handle_response<T: for<'de> Deserialize<'de>>(
&self,
resp: reqwest::Response,
) -> Result<T, GovernanceError> {
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(self.parse_error(resp).await)
}
}
async fn parse_error(&self, resp: reqwest::Response) -> GovernanceError {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
#[derive(Deserialize)]
struct ErrorResponse {
message: Option<String>,
code: Option<String>,
}
let (message, code) = match serde_json::from_str::<ErrorResponse>(&body) {
Ok(err) => (
err.message.unwrap_or_else(|| format!("HTTP {}", status)),
err.code,
),
Err(_) => (format!("HTTP {}", status), None),
};
GovernanceError::Http {
message,
code,
status_code: status,
}
}
}

802
sdk/rust/src/mining/mod.rs Normal file
View file

@ -0,0 +1,802 @@
//! Synor Mining SDK for Rust.
//! Pool connections, block templates, hashrate stats, and GPU management.
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use thiserror::Error;
const DEFAULT_ENDPOINT: &str = "https://mining.synor.io/v1";
const DEFAULT_TIMEOUT: u64 = 30;
const DEFAULT_RETRIES: u32 = 3;
/// Device type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DeviceType {
Cpu,
GpuNvidia,
GpuAmd,
Asic,
}
/// Device status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DeviceStatus {
Idle,
Mining,
Error,
Offline,
}
/// Connection status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ConnectionStatus {
Disconnected,
Connecting,
Connected,
Reconnecting,
}
/// Time period for stats.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TimePeriod {
Hour,
Day,
Week,
Month,
All,
}
/// Submit result status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SubmitResultStatus {
Accepted,
Rejected,
Stale,
}
/// Mining SDK configuration.
#[derive(Debug, Clone)]
pub struct MiningConfig {
pub api_key: String,
pub endpoint: String,
pub timeout: Duration,
pub retries: u32,
pub debug: bool,
}
impl MiningConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: DEFAULT_ENDPOINT.to_string(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT),
retries: DEFAULT_RETRIES,
debug: false,
}
}
pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
}
/// Pool configuration.
#[derive(Debug, Clone, Serialize)]
pub struct PoolConfig {
pub url: String,
pub user: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub difficulty: Option<f64>,
}
/// Stratum connection.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StratumConnection {
pub id: String,
pub pool: String,
pub status: ConnectionStatus,
pub algorithm: String,
pub difficulty: f64,
pub connected_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_share_at: Option<i64>,
pub accepted_shares: i64,
pub rejected_shares: i64,
pub stale_shares: i64,
}
/// Template transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateTransaction {
pub txid: String,
pub data: String,
pub fee: String,
pub weight: i32,
}
/// Block template.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockTemplate {
pub id: String,
pub previous_block_hash: String,
pub merkle_root: String,
pub timestamp: i64,
pub bits: String,
pub height: i64,
pub coinbase_value: String,
pub transactions: Vec<TemplateTransaction>,
pub target: String,
pub algorithm: String,
pub extra_nonce: String,
}
/// Mined work to submit.
#[derive(Debug, Clone, Serialize)]
pub struct MinedWork {
pub template_id: String,
pub nonce: String,
pub extra_nonce: String,
pub timestamp: i64,
pub hash: String,
}
/// Share info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShareInfo {
pub hash: String,
pub difficulty: f64,
pub timestamp: i64,
pub accepted: bool,
}
/// Submit result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitResult {
pub status: SubmitResultStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub share: ShareInfo,
pub block_found: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reward: Option<String>,
}
/// Hashrate statistics.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hashrate {
pub current: f64,
pub average_1h: f64,
pub average_24h: f64,
pub peak: f64,
pub unit: String,
}
/// Share stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShareStats {
pub accepted: i64,
pub rejected: i64,
pub stale: i64,
pub total: i64,
pub accept_rate: f64,
}
/// Device temperature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceTemperature {
pub current: f64,
pub max: f64,
pub throttling: bool,
}
/// Earnings snapshot.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EarningsSnapshot {
pub today: String,
pub yesterday: String,
pub this_week: String,
pub this_month: String,
pub total: String,
pub currency: String,
}
/// Mining stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MiningStats {
pub hashrate: Hashrate,
pub shares: ShareStats,
pub uptime: i64,
pub efficiency: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub power_consumption: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<DeviceTemperature>,
pub earnings: EarningsSnapshot,
}
/// Earnings breakdown.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EarningsBreakdown {
pub date: i64,
pub amount: String,
pub blocks: i32,
pub shares: i64,
pub hashrate: f64,
}
/// Detailed earnings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Earnings {
pub period: TimePeriod,
pub start_date: i64,
pub end_date: i64,
pub amount: String,
pub blocks: i32,
pub shares: i64,
pub average_hashrate: f64,
pub currency: String,
pub breakdown: Vec<EarningsBreakdown>,
}
/// Mining device.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MiningDevice {
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub device_type: DeviceType,
pub status: DeviceStatus,
pub hashrate: f64,
pub temperature: f64,
pub fan_speed: f64,
pub power_draw: f64,
pub memory_used: i64,
pub memory_total: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firmware: Option<String>,
}
/// Device configuration.
#[derive(Debug, Clone, Serialize)]
pub struct DeviceConfig {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub intensity: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub power_limit: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub core_clock_offset: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_clock_offset: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fan_speed: Option<i32>,
}
/// Worker info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerInfo {
pub id: String,
pub name: String,
pub status: ConnectionStatus,
pub hashrate: Hashrate,
pub shares: ShareStats,
pub devices: Vec<MiningDevice>,
pub last_seen: i64,
pub uptime: i64,
}
/// Pool stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolStats {
pub url: String,
pub workers: i32,
pub hashrate: f64,
pub difficulty: f64,
pub last_block: i64,
pub blocks_found_24h: i32,
pub luck: f64,
}
/// Mining algorithm.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MiningAlgorithm {
pub name: String,
pub display_name: String,
pub hash_unit: String,
pub profitability: String,
pub difficulty: f64,
pub block_reward: String,
pub block_time: i32,
}
/// Work result from getwork.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkResult {
pub work: String,
pub target: String,
pub algorithm: String,
}
/// Start mining result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StartMiningResult {
pub started: bool,
pub devices: Vec<String>,
}
/// Mining SDK error.
#[derive(Error, Debug)]
pub enum MiningError {
#[error("HTTP error: {message}")]
Http {
message: String,
code: Option<String>,
status_code: u16,
},
#[error("Client closed")]
ClientClosed,
#[error("No active connection")]
NoActiveConnection,
#[error("Request error: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
/// Synor Mining SDK client.
pub struct SynorMining {
config: MiningConfig,
client: Client,
active_connection: Arc<RwLock<Option<StratumConnection>>>,
closed: Arc<AtomicBool>,
}
impl SynorMining {
/// Create a new Mining client.
pub fn new(config: MiningConfig) -> Result<Self, MiningError> {
let client = Client::builder().timeout(config.timeout).build()?;
Ok(Self {
config,
client,
active_connection: Arc::new(RwLock::new(None)),
closed: Arc::new(AtomicBool::new(false)),
})
}
// ==================== Pool Connection ====================
/// Connect to a mining pool.
pub async fn connect(&self, pool: PoolConfig) -> Result<StratumConnection, MiningError> {
let conn: StratumConnection = self.post("/pool/connect", &pool).await?;
*self.active_connection.write().unwrap() = Some(conn.clone());
Ok(conn)
}
/// Disconnect from the current pool.
pub async fn disconnect(&self) -> Result<(), MiningError> {
let conn = self.active_connection.read().unwrap().clone();
if let Some(c) = conn {
self.post::<(), ()>(&format!("/pool/disconnect/{}", c.id), &())
.await?;
*self.active_connection.write().unwrap() = None;
}
Ok(())
}
/// Get current connection status.
pub async fn get_connection_status(&self) -> Result<Option<StratumConnection>, MiningError> {
let conn = self.active_connection.read().unwrap().clone();
if let Some(c) = conn {
let status: StratumConnection =
self.get(&format!("/pool/status/{}", c.id)).await?;
Ok(Some(status))
} else {
Ok(None)
}
}
/// Reconnect to the pool.
pub async fn reconnect(&self) -> Result<StratumConnection, MiningError> {
let conn = self.active_connection.read().unwrap().clone();
if let Some(c) = conn {
self.post(&format!("/pool/reconnect/{}", c.id), &()).await
} else {
Err(MiningError::NoActiveConnection)
}
}
// ==================== Mining Operations ====================
/// Get the current block template.
pub async fn get_block_template(&self) -> Result<BlockTemplate, MiningError> {
self.get("/mining/template").await
}
/// Submit mined work.
pub async fn submit_work(&self, work: MinedWork) -> Result<SubmitResult, MiningError> {
self.post("/mining/submit", &work).await
}
/// Get work from pool.
pub async fn get_work(&self) -> Result<WorkResult, MiningError> {
self.get("/mining/getwork").await
}
/// Start mining on all enabled devices.
pub async fn start_mining(
&self,
algorithm: Option<&str>,
) -> Result<StartMiningResult, MiningError> {
#[derive(Serialize)]
struct Request {
#[serde(skip_serializing_if = "Option::is_none")]
algorithm: Option<String>,
}
self.post(
"/mining/start",
&Request {
algorithm: algorithm.map(String::from),
},
)
.await
}
/// Stop mining on all devices.
pub async fn stop_mining(&self) -> Result<bool, MiningError> {
#[derive(Deserialize)]
struct Response {
stopped: bool,
}
let resp: Response = self.post("/mining/stop", &()).await?;
Ok(resp.stopped)
}
// ==================== Stats ====================
/// Get current hashrate.
pub async fn get_hashrate(&self) -> Result<Hashrate, MiningError> {
self.get("/stats/hashrate").await
}
/// Get mining stats.
pub async fn get_stats(&self) -> Result<MiningStats, MiningError> {
self.get("/stats").await
}
/// Get earnings for a time period.
pub async fn get_earnings(&self, period: Option<TimePeriod>) -> Result<Earnings, MiningError> {
let path = match period {
Some(p) => format!("/stats/earnings?period={:?}", p).to_lowercase(),
None => "/stats/earnings".to_string(),
};
self.get(&path).await
}
/// Get earnings history.
pub async fn get_earnings_history(
&self,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<Earnings>, MiningError> {
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
let path = if params.is_empty() {
"/stats/earnings/history".to_string()
} else {
format!("/stats/earnings/history?{}", params.join("&"))
};
#[derive(Deserialize)]
struct Response {
earnings: Vec<Earnings>,
}
let resp: Response = self.get(&path).await?;
Ok(resp.earnings)
}
/// Get pool stats.
pub async fn get_pool_stats(&self) -> Result<PoolStats, MiningError> {
self.get("/pool/stats").await
}
// ==================== GPU Management ====================
/// List all mining devices.
pub async fn list_devices(&self) -> Result<Vec<MiningDevice>, MiningError> {
#[derive(Deserialize)]
struct Response {
devices: Vec<MiningDevice>,
}
let resp: Response = self.get("/devices").await?;
Ok(resp.devices)
}
/// Get device details.
pub async fn get_device(&self, device_id: &str) -> Result<MiningDevice, MiningError> {
self.get(&format!("/devices/{}", urlencoding::encode(device_id)))
.await
}
/// Set device configuration.
pub async fn set_device_config(
&self,
device_id: &str,
config: DeviceConfig,
) -> Result<MiningDevice, MiningError> {
self.post(
&format!("/devices/{}/config", urlencoding::encode(device_id)),
&config,
)
.await
}
/// Enable a device for mining.
pub async fn enable_device(&self, device_id: &str) -> Result<MiningDevice, MiningError> {
self.post::<(), MiningDevice>(
&format!("/devices/{}/enable", urlencoding::encode(device_id)),
&(),
)
.await
}
/// Disable a device.
pub async fn disable_device(&self, device_id: &str) -> Result<MiningDevice, MiningError> {
self.post::<(), MiningDevice>(
&format!("/devices/{}/disable", urlencoding::encode(device_id)),
&(),
)
.await
}
/// Reset device to default settings.
pub async fn reset_device(&self, device_id: &str) -> Result<MiningDevice, MiningError> {
self.post::<(), MiningDevice>(
&format!("/devices/{}/reset", urlencoding::encode(device_id)),
&(),
)
.await
}
// ==================== Workers ====================
/// List all workers.
pub async fn list_workers(&self) -> Result<Vec<WorkerInfo>, MiningError> {
#[derive(Deserialize)]
struct Response {
workers: Vec<WorkerInfo>,
}
let resp: Response = self.get("/workers").await?;
Ok(resp.workers)
}
/// Get worker details.
pub async fn get_worker(&self, worker_id: &str) -> Result<WorkerInfo, MiningError> {
self.get(&format!("/workers/{}", urlencoding::encode(worker_id)))
.await
}
/// Create a new worker.
pub async fn create_worker(&self, name: &str) -> Result<WorkerInfo, MiningError> {
#[derive(Serialize)]
struct Request {
name: String,
}
self.post(
"/workers",
&Request {
name: name.to_string(),
},
)
.await
}
/// Delete a worker.
pub async fn delete_worker(&self, worker_id: &str) -> Result<(), MiningError> {
self.delete(&format!("/workers/{}", urlencoding::encode(worker_id)))
.await
}
// ==================== Algorithms ====================
/// Get supported mining algorithms.
pub async fn get_supported_algorithms(&self) -> Result<Vec<MiningAlgorithm>, MiningError> {
#[derive(Deserialize)]
struct Response {
algorithms: Vec<MiningAlgorithm>,
}
let resp: Response = self.get("/algorithms").await?;
Ok(resp.algorithms)
}
/// Get current algorithm.
pub async fn get_current_algorithm(&self) -> Result<MiningAlgorithm, MiningError> {
self.get("/algorithms/current").await
}
/// Switch to a different algorithm.
pub async fn switch_algorithm(&self, algorithm: &str) -> Result<bool, MiningError> {
#[derive(Serialize)]
struct Request {
algorithm: String,
}
#[derive(Deserialize)]
struct Response {
switched: bool,
}
let resp: Response = self
.post(
"/algorithms/switch",
&Request {
algorithm: algorithm.to_string(),
},
)
.await?;
Ok(resp.switched)
}
// ==================== Lifecycle ====================
/// Health check.
pub async fn health_check(&self) -> bool {
#[derive(Deserialize)]
struct Response {
status: String,
}
match self.get::<Response>("/health").await {
Ok(resp) => resp.status == "healthy",
Err(_) => false,
}
}
/// Check if client is closed.
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
/// Close the client.
pub async fn close(&self) -> Result<(), MiningError> {
self.disconnect().await?;
self.closed.store(true, Ordering::SeqCst);
Ok(())
}
// ==================== Private Methods ====================
async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, MiningError> {
self.execute(|| async {
let resp = self
.client
.get(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
&self,
path: &str,
body: &B,
) -> Result<T, MiningError> {
self.execute(|| async {
let resp = self
.client
.post(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", "rust/0.1.0")
.json(body)
.send()
.await?;
self.handle_response(resp).await
})
.await
}
async fn delete(&self, path: &str) -> Result<(), MiningError> {
self.execute(|| async {
let resp = self
.client
.delete(format!("{}{}", self.config.endpoint, path))
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-SDK-Version", "rust/0.1.0")
.send()
.await?;
if resp.status().is_success() {
Ok(())
} else {
Err(self.parse_error(resp).await)
}
})
.await
}
async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, MiningError>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, MiningError>>,
{
if self.closed.load(Ordering::SeqCst) {
return Err(MiningError::ClientClosed);
}
let mut last_error = None;
for attempt in 0..self.config.retries {
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
if self.config.debug {
eprintln!("Attempt {} failed: {:?}", attempt + 1, e);
}
last_error = Some(e);
if attempt < self.config.retries - 1 {
tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
}
}
}
}
Err(last_error.unwrap())
}
async fn handle_response<T: for<'de> Deserialize<'de>>(
&self,
resp: reqwest::Response,
) -> Result<T, MiningError> {
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(self.parse_error(resp).await)
}
}
async fn parse_error(&self, resp: reqwest::Response) -> MiningError {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
#[derive(Deserialize)]
struct ErrorResponse {
message: Option<String>,
code: Option<String>,
}
let (message, code) = match serde_json::from_str::<ErrorResponse>(&body) {
Ok(err) => (
err.message.unwrap_or_else(|| format!("HTTP {}", status)),
err.code,
),
Err(_) => (format!("HTTP {}", status), None),
};
MiningError::Http {
message,
code,
status_code: status,
}
}
}