// Copyright (c) 2017-2026 Lethean (https://lt.hn) // SPDX-License-Identifier: EUPL-1.2 package node import ( "encoding/json" "fmt" "io" "net/http" "strings" ) // LetheanDefaults for chain connectivity. const ( LetheanTestnetDaemon = "http://127.0.0.1:46941" LetheanMainnetDaemon = "http://127.0.0.1:36941" LetheanTestnetPool = "stratum+tcp://pool.lthn.io:5555" LetheanMainnetPool = "stratum+tcp://pool.lthn.io:3333" ) // ChainInfo holds basic chain state from the daemon RPC. // // info, err := node.GetChainInfo("http://127.0.0.1:46941") type ChainInfo struct { Height uint64 `json:"height"` Aliases int `json:"alias_count"` Synced bool `json:"synced"` PoolSize int `json:"tx_pool_size"` Difficulty uint64 `json:"difficulty"` } // PoolGateway is a mining pool discovered from chain aliases. // // pools := node.DiscoverPools("http://127.0.0.1:46941") type PoolGateway struct { Name string `json:"name"` Address string `json:"address"` Caps string `json:"caps"` Endpoint string `json:"endpoint"` } // GetChainInfo queries the daemon for basic chain state. // // info, err := node.GetChainInfo("http://127.0.0.1:46941") func GetChainInfo(daemonURL string) (*ChainInfo, error) { body := `{"jsonrpc":"2.0","id":"0","method":"getinfo","params":{}}` response, err := http.Post(daemonURL+"/json_rpc", "application/json", strings.NewReader(body)) if err != nil { return nil, fmt.Errorf("daemon RPC failed: %w", err) } defer response.Body.Close() responseBody, _ := io.ReadAll(response.Body) var result struct { Result ChainInfo `json:"result"` } if err := json.Unmarshal(responseBody, &result); err != nil { return nil, fmt.Errorf("parse getinfo: %w", err) } result.Result.Synced = result.Result.Height > 0 return &result.Result, nil } // DiscoverPools queries the chain for aliases with cap=pool and returns // pool endpoints that miners can connect to. // // pools := node.DiscoverPools("http://127.0.0.1:46941") func DiscoverPools(daemonURL string) []PoolGateway { body := `{"jsonrpc":"2.0","id":"0","method":"get_all_alias_details","params":{}}` response, err := http.Post(daemonURL+"/json_rpc", "application/json", strings.NewReader(body)) if err != nil { return nil } defer response.Body.Close() responseBody, _ := io.ReadAll(response.Body) var result struct { Result struct { Aliases []struct { Alias string `json:"alias"` Address string `json:"address"` Comment string `json:"comment"` } `json:"aliases"` } `json:"result"` } json.Unmarshal(responseBody, &result) var pools []PoolGateway for _, alias := range result.Result.Aliases { if !strings.Contains(alias.Comment, "pool") { continue } parsed := parseComment(alias.Comment) pools = append(pools, PoolGateway{ Name: alias.Alias, Address: alias.Address, Caps: parsed["cap"], Endpoint: fmt.Sprintf("stratum+tcp://%s.lthn:5555", alias.Alias), }) } return pools } // DiscoverGateways returns all gateway nodes from chain aliases. // // gateways := node.DiscoverGateways("http://127.0.0.1:46941") func DiscoverGateways(daemonURL string) []PoolGateway { body := `{"jsonrpc":"2.0","id":"0","method":"get_all_alias_details","params":{}}` response, err := http.Post(daemonURL+"/json_rpc", "application/json", strings.NewReader(body)) if err != nil { return nil } defer response.Body.Close() responseBody, _ := io.ReadAll(response.Body) var result struct { Result struct { Aliases []struct { Alias string `json:"alias"` Address string `json:"address"` Comment string `json:"comment"` } `json:"aliases"` } `json:"result"` } json.Unmarshal(responseBody, &result) var gateways []PoolGateway for _, alias := range result.Result.Aliases { if !strings.Contains(alias.Comment, "type=gateway") { continue } parsed := parseComment(alias.Comment) gateways = append(gateways, PoolGateway{ Name: alias.Alias, Address: alias.Address, Caps: parsed["cap"], Endpoint: fmt.Sprintf("%s.lthn", alias.Alias), }) } return gateways } // parsed := parseComment("cap=pool;endpoint=stratum+tcp://pool.lthn.io:3333") // parsed["cap"] // "pool" // parsed["endpoint"] // "stratum+tcp://pool.lthn.io:3333" func parseComment(comment string) map[string]string { result := make(map[string]string) for _, part := range strings.Split(comment, ";") { separatorIndex := strings.IndexByte(part, '=') if separatorIndex > 0 { result[part[:separatorIndex]] = part[separatorIndex+1:] } } return result }