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

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

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

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

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

786 lines
21 KiB
Go

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