Files
sonr/client/query/client.go
T

355 lines
12 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
// Package query provides query functionality for reading blockchain state.
package query
import (
"context"
"fmt"
"google.golang.org/grpc"
"github.com/cosmos/cosmos-sdk/types/query"
authTypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/sonr-io/sonr/client/config"
"github.com/sonr-io/sonr/client/errors"
)
// QueryClient provides an interface for querying blockchain state.
type QueryClient interface {
// Chain information
ChainInfo(ctx context.Context) (*ChainInfo, error)
NodeInfo(ctx context.Context) (*NodeInfo, error)
// Account queries
Account(ctx context.Context, address string) (*authTypes.QueryAccountResponse, error)
Accounts(ctx context.Context, pagination *PageRequest) (*authTypes.QueryAccountsResponse, error)
// Balance queries
Balance(ctx context.Context, address, denom string) (*banktypes.QueryBalanceResponse, error)
AllBalances(ctx context.Context, address string, pagination *PageRequest) (*banktypes.QueryAllBalancesResponse, error)
TotalSupply(ctx context.Context, pagination *PageRequest) (*banktypes.QueryTotalSupplyResponse, error)
SupplyOf(ctx context.Context, denom string) (*banktypes.QuerySupplyOfResponse, error)
// Staking queries
Validators(ctx context.Context, status string, pagination *PageRequest) (*stakingtypes.QueryValidatorsResponse, error)
Validator(ctx context.Context, validatorAddr string) (*stakingtypes.QueryValidatorResponse, error)
Delegations(ctx context.Context, delegatorAddr string, pagination *PageRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error)
Delegation(ctx context.Context, delegatorAddr, validatorAddr string) (*stakingtypes.QueryDelegationResponse, error)
// Transaction queries
Tx(ctx context.Context, hash string) (*TxResponse, error)
TxsByEvents(ctx context.Context, events []string, pagination *PageRequest) (*TxSearchResponse, error)
// Module-specific queries will be handled by module clients
}
// ChainInfo contains basic information about the blockchain.
type ChainInfo struct {
ChainID string `json:"chain_id"`
BlockHeight int64 `json:"block_height"`
BlockTime string `json:"block_time"`
NodeVersion string `json:"node_version"`
ApplicationVersion string `json:"application_version"`
}
// NodeInfo contains information about the connected node.
type NodeInfo struct {
NodeID string `json:"node_id"`
Network string `json:"network"`
Version string `json:"version"`
ListenAddr string `json:"listen_addr"`
Moniker string `json:"moniker"`
}
// TxResponse represents a transaction response.
type TxResponse struct {
Hash string `json:"hash"`
Height int64 `json:"height"`
Code uint32 `json:"code"`
Log string `json:"log"`
GasWanted int64 `json:"gas_wanted"`
GasUsed int64 `json:"gas_used"`
Events []Event `json:"events"`
}
// Event represents a transaction event.
type Event struct {
Type string `json:"type"`
Attributes []Attribute `json:"attributes"`
}
// Attribute represents an event attribute.
type Attribute struct {
Key string `json:"key"`
Value string `json:"value"`
}
// TxSearchResponse represents a transaction search response.
type TxSearchResponse struct {
Txs []*TxResponse `json:"txs"`
TotalCount int64 `json:"total_count"`
}
// PageRequest represents pagination parameters.
type PageRequest struct {
Key []byte `json:"key,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Limit uint64 `json:"limit,omitempty"`
CountTotal bool `json:"count_total,omitempty"`
Reverse bool `json:"reverse,omitempty"`
}
// queryClient implements QueryClient.
type queryClient struct {
grpcConn *grpc.ClientConn
config *config.NetworkConfig
// Cosmos SDK service clients
authQueryClient authTypes.QueryClient
bankQueryClient banktypes.QueryClient
stakingQueryClient stakingtypes.QueryClient
}
// NewQueryClient creates a new query client.
func NewQueryClient(grpcConn *grpc.ClientConn, cfg *config.NetworkConfig) (QueryClient, error) {
if grpcConn == nil {
return nil, fmt.Errorf("gRPC connection is required")
}
if cfg == nil {
return nil, fmt.Errorf("network configuration is required")
}
return &queryClient{
grpcConn: grpcConn,
config: cfg,
authQueryClient: authTypes.NewQueryClient(grpcConn),
bankQueryClient: banktypes.NewQueryClient(grpcConn),
stakingQueryClient: stakingtypes.NewQueryClient(grpcConn),
}, nil
}
// ChainInfo retrieves basic chain information.
func (qc *queryClient) ChainInfo(ctx context.Context) (*ChainInfo, error) {
// TODO: Implement proper chain info query using Tendermint RPC client
// Should query /status endpoint for current block height and time
// Get node version and application version from /abci_info
// Return comprehensive chain information with real-time data
return &ChainInfo{
ChainID: qc.config.ChainID,
NodeVersion: "unknown",
ApplicationVersion: "unknown",
}, nil
}
// NodeInfo retrieves node information.
func (qc *queryClient) NodeInfo(ctx context.Context) (*NodeInfo, error) {
// TODO: Implement proper node info query using Tendermint RPC client
// Should query /status endpoint for node ID and network info
// Get listen address and moniker from node status
// Include peer count and sync status information
return &NodeInfo{
NodeID: "unknown",
Network: qc.config.ChainID,
Version: "unknown",
}, nil
}
// Account retrieves account information by address.
func (qc *queryClient) Account(ctx context.Context, address string) (*authTypes.QueryAccountResponse, error) {
req := &authTypes.QueryAccountRequest{Address: address}
resp, err := qc.authQueryClient.Account(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query account %s", address)
}
return resp, nil
}
// Accounts retrieves all accounts with pagination.
func (qc *queryClient) Accounts(ctx context.Context, pagination *PageRequest) (*authTypes.QueryAccountsResponse, error) {
req := &authTypes.QueryAccountsRequest{}
if pagination != nil {
req.Pagination = &query.PageRequest{
Key: pagination.Key,
Offset: pagination.Offset,
Limit: pagination.Limit,
CountTotal: pagination.CountTotal,
Reverse: pagination.Reverse,
}
}
resp, err := qc.authQueryClient.Accounts(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query accounts")
}
return resp, nil
}
// Balance retrieves the balance of a specific denomination for an address.
func (qc *queryClient) Balance(ctx context.Context, address, denom string) (*banktypes.QueryBalanceResponse, error) {
req := &banktypes.QueryBalanceRequest{
Address: address,
Denom: denom,
}
resp, err := qc.bankQueryClient.Balance(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query balance for %s", address)
}
return resp, nil
}
// AllBalances retrieves all balances for an address.
func (qc *queryClient) AllBalances(ctx context.Context, address string, pagination *PageRequest) (*banktypes.QueryAllBalancesResponse, error) {
req := &banktypes.QueryAllBalancesRequest{Address: address}
if pagination != nil {
req.Pagination = &query.PageRequest{
Key: pagination.Key,
Offset: pagination.Offset,
Limit: pagination.Limit,
CountTotal: pagination.CountTotal,
Reverse: pagination.Reverse,
}
}
resp, err := qc.bankQueryClient.AllBalances(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query all balances for %s", address)
}
return resp, nil
}
// TotalSupply retrieves the total supply of all denominations.
func (qc *queryClient) TotalSupply(ctx context.Context, pagination *PageRequest) (*banktypes.QueryTotalSupplyResponse, error) {
req := &banktypes.QueryTotalSupplyRequest{}
if pagination != nil {
req.Pagination = &query.PageRequest{
Key: pagination.Key,
Offset: pagination.Offset,
Limit: pagination.Limit,
CountTotal: pagination.CountTotal,
Reverse: pagination.Reverse,
}
}
resp, err := qc.bankQueryClient.TotalSupply(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query total supply")
}
return resp, nil
}
// SupplyOf retrieves the supply of a specific denomination.
func (qc *queryClient) SupplyOf(ctx context.Context, denom string) (*banktypes.QuerySupplyOfResponse, error) {
req := &banktypes.QuerySupplyOfRequest{Denom: denom}
resp, err := qc.bankQueryClient.SupplyOf(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query supply of %s", denom)
}
return resp, nil
}
// Validators retrieves validators with optional status filter.
func (qc *queryClient) Validators(ctx context.Context, status string, pagination *PageRequest) (*stakingtypes.QueryValidatorsResponse, error) {
req := &stakingtypes.QueryValidatorsRequest{Status: status}
if pagination != nil {
req.Pagination = &query.PageRequest{
Key: pagination.Key,
Offset: pagination.Offset,
Limit: pagination.Limit,
CountTotal: pagination.CountTotal,
Reverse: pagination.Reverse,
}
}
resp, err := qc.stakingQueryClient.Validators(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query validators")
}
return resp, nil
}
// Validator retrieves a specific validator by address.
func (qc *queryClient) Validator(ctx context.Context, validatorAddr string) (*stakingtypes.QueryValidatorResponse, error) {
req := &stakingtypes.QueryValidatorRequest{ValidatorAddr: validatorAddr}
resp, err := qc.stakingQueryClient.Validator(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query validator %s", validatorAddr)
}
return resp, nil
}
// Delegations retrieves all delegations for a delegator.
func (qc *queryClient) Delegations(ctx context.Context, delegatorAddr string, pagination *PageRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error) {
req := &stakingtypes.QueryDelegatorDelegationsRequest{DelegatorAddr: delegatorAddr}
if pagination != nil {
req.Pagination = &query.PageRequest{
Key: pagination.Key,
Offset: pagination.Offset,
Limit: pagination.Limit,
CountTotal: pagination.CountTotal,
Reverse: pagination.Reverse,
}
}
resp, err := qc.stakingQueryClient.DelegatorDelegations(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query delegations for %s", delegatorAddr)
}
return resp, nil
}
// Delegation retrieves a specific delegation.
func (qc *queryClient) Delegation(ctx context.Context, delegatorAddr, validatorAddr string) (*stakingtypes.QueryDelegationResponse, error) {
req := &stakingtypes.QueryDelegationRequest{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
}
resp, err := qc.stakingQueryClient.Delegation(ctx, req)
if err != nil {
return nil, errors.WrapError(err, errors.ErrQueryFailed, "failed to query delegation from %s to %s", delegatorAddr, validatorAddr)
}
return resp, nil
}
// Tx retrieves a transaction by hash.
func (qc *queryClient) Tx(ctx context.Context, hash string) (*TxResponse, error) {
// TODO: Implement transaction query using Tendermint RPC client
// Should query /tx endpoint with transaction hash
// Parse transaction result and decode events
// Return formatted transaction response with gas usage
// Handle transaction not found errors gracefully
return nil, fmt.Errorf("transaction queries not yet implemented")
}
// TxsByEvents retrieves transactions by events.
func (qc *queryClient) TxsByEvents(ctx context.Context, events []string, pagination *PageRequest) (*TxSearchResponse, error) {
// TODO: Implement transaction search using Tendermint RPC client
// Should query /tx_search endpoint with event filters
// Support pagination with page and per_page parameters
// Parse and format transaction results with event data
// Handle complex event queries with AND/OR logic
return nil, fmt.Errorf("transaction search not yet implemented")
}