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