synor/sdk/go/governance/governance.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

722 lines
20 KiB
Go

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