package chain import ( "bytes" "encoding/json" "fmt" "io" "net/http" "sync/atomic" "time" ) type rpcRequest struct { JSONRPC string `json:"jsonrpc"` ID int64 `json:"id"` Method string `json:"method"` Params any `json:"params"` } type rpcResponse struct { JSONRPC string `json:"jsonrpc"` ID any `json:"id"` Result json.RawMessage `json:"result,omitempty"` Error *rpcError `json:"error,omitempty"` } type rpcError struct { Code int `json:"code"` Message string `json:"message"` } func (e *rpcError) Error() string { return fmt.Sprintf("RPC error %d: %s", e.Code, e.Message) } // Client is a JSON-RPC 2.0 client for the TOL Chain node. type Client struct { nodeURL string http *http.Client idSeq atomic.Int64 } func NewClient(nodeURL string) *Client { return &Client{ nodeURL: nodeURL, http: &http.Client{Timeout: 10 * time.Second}, } } // Call invokes a JSON-RPC method and unmarshals the result into out. func (c *Client) Call(method string, params any, out any) error { reqBody := rpcRequest{ JSONRPC: "2.0", ID: c.idSeq.Add(1), Method: method, Params: params, } data, err := json.Marshal(reqBody) if err != nil { return fmt.Errorf("marshal RPC request: %w", err) } resp, err := c.http.Post(c.nodeURL, "application/json", bytes.NewReader(data)) if err != nil { return fmt.Errorf("RPC network error: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read RPC response: %w", err) } var rpcResp rpcResponse if err := json.Unmarshal(body, &rpcResp); err != nil { return fmt.Errorf("unmarshal RPC response: %w", err) } if rpcResp.Error != nil { return rpcResp.Error } if out != nil { if err := json.Unmarshal(rpcResp.Result, out); err != nil { return fmt.Errorf("unmarshal RPC result: %w", err) } } return nil } // --- Typed convenience methods --- type BalanceResult struct { Address string `json:"address"` Balance uint64 `json:"balance"` Nonce uint64 `json:"nonce"` } func (c *Client) GetBalance(address string) (*BalanceResult, error) { var result BalanceResult err := c.Call("getBalance", map[string]string{"address": address}, &result) return &result, err } func (c *Client) GetAsset(id string) (json.RawMessage, error) { var result json.RawMessage err := c.Call("getAsset", map[string]string{"id": id}, &result) return result, err } func (c *Client) GetAssetsByOwner(owner string, offset, limit int) (json.RawMessage, error) { var result json.RawMessage err := c.Call("getAssetsByOwner", map[string]any{ "owner": owner, "offset": offset, "limit": limit, }, &result) return result, err } func (c *Client) GetInventory(owner string) (json.RawMessage, error) { var result json.RawMessage err := c.Call("getInventory", map[string]string{"owner": owner}, &result) return result, err } func (c *Client) GetActiveListings(offset, limit int) (json.RawMessage, error) { var result json.RawMessage err := c.Call("getActiveListings", map[string]any{ "offset": offset, "limit": limit, }, &result) return result, err } func (c *Client) GetListing(id string) (json.RawMessage, error) { var result json.RawMessage err := c.Call("getListing", map[string]string{"id": id}, &result) return result, err } type SendTxResult struct { TxID string `json:"tx_id"` } func (c *Client) SendTx(tx any) (*SendTxResult, error) { var result SendTxResult err := c.Call("sendTx", tx, &result) return &result, err }