gRPC Go
This page shows how to query the chain, and how to broadcast a transaction, from a Go program. The pattern is the same in both cases: open a gRPC connection, then use the Protobuf-generated client for the service you want.
Setting up
Hub v12 is built against Cosmos SDK v0.47, so pin the same minor version:
go get github.com/cosmos/cosmos-sdk@v0.47.17
go get github.com/sentinel-official/sentinelhub/v12
Querying state
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
func queryState() error {
myAddress, err := sdk.AccAddressFromBech32("sent1...")
if err != nil {
return err
}
// Create a connection to the gRPC server. The public endpoint is served over
// TLS on port 443; for a local node use insecure.NewCredentials() and :9090.
grpcConn, err := grpc.Dial(
"fullgrpc-sentinel.busurnode.com:443",
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
// This instantiates a general gRPC codec which handles proto bytes. We pass in a nil interface registry
// if the request/response types contain interface instead of 'nil' you should pass the application specific codec.
grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(nil).GRPCCodec())),
)
if err != nil {
return err
}
defer grpcConn.Close()
// This creates a gRPC client to query the x/bank service.
bankClient := banktypes.NewQueryClient(grpcConn)
bankRes, err := bankClient.Balance(
context.Background(),
&banktypes.QueryBalanceRequest{Address: myAddress.String(), Denom: "udvpn"},
)
if err != nil {
return err
}
fmt.Println(bankRes.GetBalance()) // Prints the account balance
return nil
}
Swap the query client for one generated from any other Protobuf service. For the Sentinel modules, import the generated types from the hub, for example nodetypes "github.com/sentinel-official/sentinelhub/v12/x/node/types/v3", and call nodetypes.NewQueryServiceClient(grpcConn). The gRPCurl page lists every service the chain serves and the version each module is on.
Connecting to a local node
A node you run yourself exposes plaintext gRPC on port 9090:
import "google.golang.org/grpc/credentials/insecure"
grpcConn, err := grpc.Dial(
"localhost:9090",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(nil).GRPCCodec())),
)
Querying historical state
Add the block height metadata to the request:
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
func queryState() error {
// --snip--
var header metadata.MD
bankRes, err = bankClient.Balance(
metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "30810000"), // Add metadata to request
&banktypes.QueryBalanceRequest{Address: myAddress.String(), Denom: "udvpn"},
grpc.Header(&header), // Retrieve header from response
)
if err != nil {
return err
}
blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
fmt.Println(blockHeight) // Prints the block height the query was answered at
return nil
}
The public endpoints prune old state, so pick a recent height. See the caveat on the gRPCurl page.
Sending transactions
The hub does not serve MsgService over gRPC, so there is no nodetypes.NewMsgServiceClient(grpcConn) to call. Messages are assembled into a transaction, signed locally, encoded, and submitted through the Cosmos Tx service:
import (
"context"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
)
func broadcast(grpcConn *grpc.ClientConn, txBytes []byte) error {
txClient := txtypes.NewServiceClient(grpcConn)
// Estimate gas first. Simulate runs the transaction without committing it.
sim, err := txClient.Simulate(
context.Background(),
&txtypes.SimulateRequest{TxBytes: txBytes},
)
if err != nil {
return err
}
_ = sim.GasInfo.GasUsed
res, err := txClient.BroadcastTx(
context.Background(),
&txtypes.BroadcastTxRequest{
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
TxBytes: txBytes,
},
)
if err != nil {
return err
}
// res.TxResponse.Code == 0 means the transaction passed CheckTx and entered
// the mempool. Poll txClient.GetTx with res.TxResponse.TxHash for the result.
_ = res
return nil
}
Sentinel message types live next to the query types, in the module that owns the action rather than the one named after it: MsgRegisterNodeRequest and MsgStartSessionRequest are both in x/node/types/v3, while x/session/types/v3 holds MsgUpdateSessionRequest and MsgCancelSessionRequest. Building txBytes from them (account number, sequence, fee, signature) is the standard Cosmos SDK flow, documented under Generating, signing and broadcasting transactions.
Unless you specifically need to drive the wire protocol yourself, the official SDKs already handle key management, fee estimation, signing and broadcasting for Go, JavaScript and Python.