synor/sdk/ruby/lib/synor_mining/client.rb
Gulshan Yadav 6607223c9e feat(sdk): complete Phase 4 SDKs for all remaining languages
Add Economics, Governance, and Mining SDKs for:
- Java: Full SDK with CompletableFuture async operations
- Kotlin: Coroutine-based SDK with suspend functions
- Swift: Modern Swift SDK with async/await
- Flutter/Dart: Complete Dart SDK with Future-based API
- C: Header files and implementations with opaque handles
- C++: Modern C++17 with std::future and PIMPL pattern
- C#: Records, async/await Tasks, and IDisposable
- Ruby: Struct-based types with Faraday HTTP client

Also includes minor Dart lint fixes (const exceptions).
2026-01-28 08:33:20 +05:30

437 lines
12 KiB
Ruby

# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorMining
# Synor Mining SDK client for Ruby.
# Pool connections, block templates, hashrate stats, and GPU management.
class Client
attr_reader :closed, :active_connection
def initialize(config)
@config = config
@closed = false
@active_connection = nil
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Pool Operations ====================
def connect(pool:)
body = { url: pool.url, user: pool.user }
body[:password] = pool.password if pool.password
body[:algorithm] = pool.algorithm if pool.algorithm
body[:difficulty] = pool.difficulty if pool.difficulty
response = post("/pool/connect", body)
@active_connection = parse_stratum_connection(response)
end
def disconnect
return unless @active_connection
post("/pool/disconnect", {})
@active_connection = nil
end
def get_connection
response = get("/pool/connection")
@active_connection = parse_stratum_connection(response)
end
def get_pool_stats
response = get("/pool/stats")
parse_pool_stats(response)
end
def connected?
@active_connection&.status == ConnectionStatus::CONNECTED
end
# ==================== Mining Operations ====================
def get_block_template
response = get("/mining/template")
parse_block_template(response)
end
def submit_work(work:)
body = {
template_id: work.template_id,
nonce: work.nonce,
extra_nonce: work.extra_nonce,
timestamp: work.timestamp,
hash: work.hash
}
response = post("/mining/submit", body)
parse_submit_result(response)
end
def start_mining(algorithm: nil)
body = algorithm ? { algorithm: algorithm } : {}
post("/mining/start", body)
end
def stop_mining
post("/mining/stop", {})
end
def mining?
response = get("/mining/status")
response["mining"] == true
end
# ==================== Stats Operations ====================
def get_hashrate
response = get("/stats/hashrate")
parse_hashrate(response)
end
def get_stats
response = get("/stats")
parse_mining_stats(response)
end
def get_earnings(period: nil)
path = period ? "/stats/earnings?period=#{period}" : "/stats/earnings"
response = get(path)
parse_earnings(response)
end
def get_share_stats
response = get("/stats/shares")
parse_share_stats(response)
end
# ==================== Device Operations ====================
def list_devices
response = get("/devices")
(response["devices"] || []).map { |d| parse_mining_device(d) }
end
def get_device(device_id)
response = get("/devices/#{encode(device_id)}")
parse_mining_device(response)
end
def set_device_config(device_id:, config:)
body = { enabled: config.enabled }
body[:intensity] = config.intensity if config.intensity
body[:power_limit] = config.power_limit if config.power_limit
body[:core_clock_offset] = config.core_clock_offset if config.core_clock_offset
body[:memory_clock_offset] = config.memory_clock_offset if config.memory_clock_offset
body[:fan_speed] = config.fan_speed if config.fan_speed
response = put("/devices/#{encode(device_id)}/config", body)
parse_mining_device(response)
end
def enable_device(device_id)
set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: true))
end
def disable_device(device_id)
set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: false))
end
# ==================== Worker Operations ====================
def list_workers
response = get("/workers")
(response["workers"] || []).map { |w| parse_worker_info(w) }
end
def get_worker(worker_id)
response = get("/workers/#{encode(worker_id)}")
parse_worker_info(response)
end
def remove_worker(worker_id)
delete("/workers/#{encode(worker_id)}")
end
# ==================== Algorithm Operations ====================
def list_algorithms
response = get("/algorithms")
(response["algorithms"] || []).map { |a| parse_mining_algorithm(a) }
end
def get_algorithm(name)
response = get("/algorithms/#{encode(name)}")
parse_mining_algorithm(response)
end
def switch_algorithm(algorithm)
post("/algorithms/switch", { algorithm: algorithm })
end
def get_most_profitable
response = get("/algorithms/profitable")
parse_mining_algorithm(response)
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@active_connection = nil
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def put(path, body)
execute { @conn.put(path, body).body }
end
def delete(path)
execute { @conn.delete(path).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
# Parsing methods
def parse_stratum_connection(data)
StratumConnection.new(
id: data["id"],
pool: data["pool"],
status: 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["last_share_at"]
)
end
def parse_pool_stats(data)
PoolStats.new(
url: data["url"],
workers: data["workers"],
hashrate: data["hashrate"],
difficulty: data["difficulty"],
last_block: data["last_block"],
blocks_found_24h: data["blocks_found_24h"],
luck: data["luck"]
)
end
def parse_block_template(data)
BlockTemplate.new(
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: (data["transactions"] || []).map { |t| parse_template_transaction(t) },
target: data["target"],
algorithm: data["algorithm"],
extra_nonce: data["extra_nonce"]
)
end
def parse_template_transaction(data)
TemplateTransaction.new(
txid: data["txid"],
data: data["data"],
fee: data["fee"],
weight: data["weight"]
)
end
def parse_submit_result(data)
SubmitResult.new(
status: data["status"],
share: data["share"] ? parse_share_info(data["share"]) : nil,
block_found: data["block_found"],
reason: data["reason"],
block_hash: data["block_hash"],
reward: data["reward"]
)
end
def parse_share_info(data)
ShareInfo.new(
hash: data["hash"],
difficulty: data["difficulty"],
timestamp: data["timestamp"],
accepted: data["accepted"]
)
end
def parse_hashrate(data)
Hashrate.new(
current: data["current"],
average_1h: data["average_1h"],
average_24h: data["average_24h"],
peak: data["peak"],
unit: data["unit"]
)
end
def parse_share_stats(data)
ShareStats.new(
accepted: data["accepted"],
rejected: data["rejected"],
stale: data["stale"],
total: data["total"],
accept_rate: data["accept_rate"]
)
end
def parse_mining_stats(data)
MiningStats.new(
hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
uptime: data["uptime"],
efficiency: data["efficiency"],
earnings: data["earnings"] ? parse_earnings_snapshot(data["earnings"]) : nil,
power_consumption: data["power_consumption"],
temperature: data["temperature"] ? parse_device_temperature(data["temperature"]) : nil
)
end
def parse_earnings_snapshot(data)
EarningsSnapshot.new(
today: data["today"],
yesterday: data["yesterday"],
this_week: data["this_week"],
this_month: data["this_month"],
total: data["total"],
currency: data["currency"]
)
end
def parse_device_temperature(data)
DeviceTemperature.new(
current: data["current"],
max: data["max"],
throttling: data["throttling"]
)
end
def parse_earnings(data)
Earnings.new(
period: 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: (data["breakdown"] || []).map { |b| parse_earnings_breakdown(b) }
)
end
def parse_earnings_breakdown(data)
EarningsBreakdown.new(
date: data["date"],
amount: data["amount"],
blocks: data["blocks"],
shares: data["shares"],
hashrate: data["hashrate"]
)
end
def parse_mining_device(data)
MiningDevice.new(
id: data["id"],
name: data["name"],
type: data["type"],
status: 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["driver"],
firmware: data["firmware"]
)
end
def parse_worker_info(data)
WorkerInfo.new(
id: data["id"],
name: data["name"],
status: data["status"],
hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
devices: (data["devices"] || []).map { |d| parse_mining_device(d) },
last_seen: data["last_seen"],
uptime: data["uptime"]
)
end
def parse_mining_algorithm(data)
MiningAlgorithm.new(
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"]
)
end
end
end