A Go client for UTxO RPC servers, with generic Query, Submit, Sync, and Watch clients and optional Cardano helpers. The default package targets v1beta and uses Connect RPC in gRPC mode over HTTP/2.
Requires Go 1.26.10 or later, as declared in go.mod.
go get github.com/utxorpc/go-sdk| Package | Purpose |
|---|---|
github.com/utxorpc/go-sdk (package sdk) |
Generic v1beta client, header management, streaming, and pagination |
github.com/utxorpc/go-sdk/cardano |
Cardano UTxO searches, transaction helpers, and block helpers |
github.com/utxorpc/go-sdk/v1alpha |
Frozen legacy client for v1alpha servers |
Set UTXORPC_URL to your server's base URL. If your provider uses the
dmtr-api-key header, set DMTR_API_KEY as well; other authentication headers
can be supplied with WithHeaders.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"connectrpc.com/connect"
"github.com/utxorpc/go-codegen/utxorpc/v1beta/query"
sdk "github.com/utxorpc/go-sdk"
)
func main() {
baseURL := os.Getenv("UTXORPC_URL")
if baseURL == "" {
log.Fatal("set UTXORPC_URL to your UTxO RPC server URL")
}
client := sdk.NewClient(sdk.WithBaseUrl(baseURL))
if key := os.Getenv("DMTR_API_KEY"); key != "" {
client.SetHeader("dmtr-api-key", key)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req := connect.NewRequest(&query.ReadParamsRequest{})
resp, err := client.ReadParamsWithContext(ctx, req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Ledger tip slot: %d\n", resp.Msg.GetLedgerTip().GetSlot())
}Every generic RPC wrapper has two forms: ReadParams(req) uses
context.Background(), and ReadParamsWithContext(ctx, req) uses the caller's
context. Both inject configured headers before dispatching.
Use the same SDK options for a Cardano client. With baseURL and ctx from
above, and github.com/utxorpc/go-sdk/cardano imported:
client := cardano.NewClient(sdk.WithBaseUrl(baseURL))
if key := os.Getenv("DMTR_API_KEY"); key != "" {
client.UtxorpcClient.SetHeader("dmtr-api-key", key)
}
resp, err := client.GetTipWithContext(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tip slot: %d\n", resp.Msg.GetTip().GetSlot())The generic client is available through client.UtxorpcClient, including its
service wrappers and header management methods.
client := sdk.NewClient(
sdk.WithBaseUrl(os.Getenv("UTXORPC_URL")),
sdk.WithDialTimeout(10*time.Second),
sdk.WithRequestTimeout(30*time.Second),
)| Option | Behavior |
|---|---|
WithBaseUrl(url) |
Server base URL; https:// uses TLS, http:// uses cleartext HTTP/2 (h2c) |
WithHeaders(headers) |
Initial headers injected by SDK wrappers |
WithDialTimeout(duration) |
Connection establishment timeout for the default HTTP client |
WithRequestTimeout(duration) |
Timeout for the entire request, including reading the response |
WithHttpClient(client) |
Supply a connect.HTTPClient; SDK dial and request timeout options then have no effect |
WithConnectOptions(options...) |
Apply Connect client options to all four services |
The default transport uses Go's net/http HTTP/2 support, requires TLS 1.2 or
later for HTTPS, and does not follow redirects. With no request timeout set,
there is no client-wide request deadline. A nonzero request timeout also
limits streaming responses; use a cancellable context and a suitable timeout
for long-running streams.
SetURL rebuilds all four service clients with the new URL and existing
Connect options. It reuses the HTTP client, so create a new client when
switching between HTTPS and cleartext HTTP/2.
client.SetHeader("dmtr-api-key", os.Getenv("DMTR_API_KEY"))
client.SetHeaders(map[string]string{"Authorization": "Bearer " + token})
client.RemoveHeader("Authorization")
headers := client.Headers()SetHeaders replaces the entire stored map. WithHeaders and SetHeaders
retain the supplied map; Headers returns the live map when one is set.
Configure headers and URLs before concurrent use, or synchronize mutations
with calls yourself. Use separate request objects for concurrent calls.
The exported Query, Submit, Sync, and Watch fields are generated
Connect clients. Direct calls require explicit header injection:
req := connect.NewRequest(&query.ReadParamsRequest{})
client.AddHeadersToRequest(req)
resp, err := client.Query.ReadParams(ctx, req)Prefer client.ReadParamsWithContext(ctx, req) for automatic header injection.
client := sdk.NewClient(
sdk.WithBaseUrl(os.Getenv("UTXORPC_URL")),
sdk.WithConnectOptions(connect.WithInterceptors(metricsInterceptor)),
)Connect options apply to all services and survive SetURL. They are applied
in order after the default connect.WithGRPC() option. Repeated
WithConnectOptions calls append options.
The SDK does not retry calls automatically. Limit retry interceptors to transient failures on idempotent reads. Retrying transaction submission needs an application-level deduplication guarantee.
These methods are available on *sdk.UtxorpcClient. Each has a WithContext
variant and takes a *connect.Request containing the corresponding v1beta
protobuf request from github.com/utxorpc/go-codegen/utxorpc/v1beta/....
| Service | Unary methods | Streaming methods |
|---|---|---|
| Query | ReadData, ReadEraSummary, ReadGenesis, ReadParams, ReadState, ReadTx, ReadUtxos, SearchUtxos |
— |
| Submit | EvalTx, SubmitTx, ReadMempool |
WaitForTx, WatchMempool |
| Sync | DumpHistory, FetchBlock, ReadTip |
FollowTip |
| Watch | — | WatchTx |
See the package reference for signatures and the UTxO RPC specification for request and response semantics. Server support for individual methods may vary.
SearchUtxos and DumpHistory return one page. Use SearchUtxosPages or
DumpHistoryPages (and their WithContext variants) to iterate all pages:
// import "google.golang.org/protobuf/proto"
// predicate is a *query.UtxoPredicate describing the UTxOs to match.
req := connect.NewRequest(&query.SearchUtxosRequest{
Predicate: predicate,
MaxItems: proto.Int32(100),
})
for page, err := range client.SearchUtxosPagesWithContext(ctx, req) {
if err != nil {
log.Fatal(err)
}
for _, item := range page.Msg.GetItems() {
fmt.Println(item.GetTxoRef())
}
}The helpers return lazy iter.Seq2 sequences of responses and errors. Breaking
iteration prevents further page requests. Each helper clones the protobuf
request, copies request headers to each page, and injects stored client headers.
The original request is unchanged. An RPC error is yielded once and ends
iteration.
Iteration starts at the request's StartToken. UTxO searches follow string
NextToken values until empty; history pages follow *sync.BlockRef tokens
until nil. For manual pagination, save a response's NextToken and pass it
as the next request's StartToken.
See the Cardano package reference for full signatures. Helpers return Connect responses or server streams.
| Purpose | Helpers |
|---|---|
| Protocol parameters | GetProtocolParameters |
| UTxOs by reference | GetUtxoByRef, GetUtxosByRefs |
| UTxO search | GetUtxosByAddress, GetUtxosByAddressWithAsset, GetUtxosByAsset |
| Paginated UTxO search | GetUtxosByAddressPages, GetUtxosByAddressWithAssetPages, GetUtxosByAssetPages |
| Transactions | SubmitTransaction, EvaluateTransaction, GetMempoolTransactions |
| Transaction streams | WaitForTransaction, WatchMempoolTransactions, WatchTransaction |
| Blocks | GetTip, GetBlockByRef, ReadBlock, WatchBlocksByRef |
GetUtxoByReftries a transaction hash as hex first, then standard base64.GetUtxosByRefstakes[]*query.TxoRefwith raw hash bytes.- Address searches take raw bytes. Decode bech32 addresses first, for example
with
common.NewAddressfromgithub.com/blinklabs-io/gouroboros/ledger/common, then callBytes(). - Asset policy IDs and asset names are raw bytes. Decode hex strings before
passing them.
GetUtxosByAssetrequires at least one non-nil filter; address-plus-asset searches allow both filters to be empty. SubmitTransactionandEvaluateTransactiontake signed transaction CBOR as hex.WaitForTransactiontakes a hex transaction hash.GetBlockByRef,WatchBlocksByRef, andWatchTransactiontake a hex block hash and a slot. An empty hash and slot-1omit the reference or intersect; the server determines the behavior.WatchTransactionwatches transactions from a block intersect, rather than selecting a transaction by its hash.ReadBlocktakes a*sync.BlockRefand checks that the first returned block is a Cardano block with a header. It returns the fullFetchBlockresponse.
Cardano search helpers return a single page of up to 100 items by default,
starting with an empty token and requesting all fields. All three search
helpers and their Pages variants accept these options:
| Option | Purpose |
|---|---|
cardano.WithSearchMaxItems(n) |
Set the maximum number of results per page |
cardano.WithSearchStartToken(token) |
Resume from a search response's NextToken |
cardano.WithSearchFieldMask(paths...) |
Select protobuf field-mask paths; no paths requests all fields |
For a Cardano client and decoded addressBytes:
for page, err := range client.GetUtxosByAddressPagesWithContext(
ctx,
addressBytes,
cardano.WithSearchMaxItems(50),
) {
if err != nil {
log.Fatal(err)
}
for _, item := range page.Msg.GetItems() {
fmt.Printf("Coin: %v\n", item.GetCardano().GetCoin())
}
}Every helper has a WithContext variant, but some take a protobuf request
instead of the convenience arguments:
| Convenience call | Context call's arguments after ctx |
|---|---|
SubmitTransaction(txCborHex) |
*submit.SubmitTxRequest |
EvaluateTransaction(txCborHex) |
*submit.EvalTxRequest |
WaitForTransaction(txHashHex) |
*submit.WaitForTxRequest |
GetBlockByRef(blockHashHex, slot) |
*sync.FetchBlockRequest |
WatchBlocksByRef(blockHashHex, slot) |
*sync.FollowTipRequest |
WatchTransaction(blockHashHex, slot) |
*watch.WatchTxRequest |
These context variants take hashes and transaction CBOR already decoded to
bytes in the protobuf request. Other helpers retain their convenience
arguments after ctx.
Streams use Connect RPC server streaming over HTTP/2. Close each stream,
process messages with Receive and Msg, and check Err after the loop.
For a generic SDK client, with the v1beta sync package imported:
req := connect.NewRequest(&sync.FollowTipRequest{})
stream, err := client.FollowTipWithContext(ctx, req)
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for stream.Receive() {
switch action := stream.Msg().GetAction().(type) {
case *sync.FollowTipResponse_Apply:
fmt.Println("Apply:", action.Apply)
case *sync.FollowTipResponse_Undo:
fmt.Println("Undo:", action.Undo)
case *sync.FollowTipResponse_Reset_:
fmt.Println("Reset:", action.Reset_)
}
}
if err := stream.Err(); err != nil {
log.Print(err)
}Handle Apply, Undo, and Reset events to keep local chain state consistent. Use a context whose lifetime covers the stream, and cancel it when finished. The SDK does not automatically reconnect or resume a failed stream.
RPC methods return Go errors. Inspect underlying transport failures with
errors.As and RPC status information with sdk.AsConnectError:
// import "errors" and "net"
resp, err := client.ReadParamsWithContext(ctx, req)
if err != nil {
var transportErr net.Error
if errors.As(err, &transportErr) {
log.Printf("transport error: %v", transportErr)
} else if rpcErr, ok := sdk.AsConnectError(err); ok {
log.Printf("RPC error: code=%s message=%q", rpcErr.Code(), rpcErr.Message())
} else {
log.Printf("local error: %v", err)
}
return
}
fmt.Printf("Ledger tip slot: %d\n", resp.Msg.GetLedgerTip().GetSlot())The returned *connect.Error also exposes Details() and Meta().
sdk.HandleError is a deprecated compatibility helper that prints and panics;
handle returned errors explicitly in new code.
For a v1alpha server, use matching SDK and generated protobuf imports:
import (
"github.com/utxorpc/go-codegen/utxorpc/v1alpha/query"
sdk "github.com/utxorpc/go-sdk/v1alpha"
)The legacy package retains service wrappers, header management, and
background/context method pairs for v1alpha. It is frozen for new features
and has no Cardano helper package. It does not provide v1beta additions such
as ReadState, DumpHistory, page iterators, WithConnectOptions, or
AsConnectError; inspect *connect.Error with errors.As instead.
When migrating to v1beta, update both SDK and generated protobuf import paths,
and adapt requests and response handling to the v1beta schema. The packages'
ClientOption types are distinct.
Run examples from the repository root after setting UTXORPC_URL and, if
needed, DMTR_API_KEY:
go run ./examples/query
go run ./examples/submit
go run ./examples/sync- Query: protocol parameters, era summaries, genesis, UTxOs by reference, address searches, and native asset filters.
- Submit: reads the mempool by default. Edit the
modevariable to selectsubmitTx,waitForTx, orwatchMempool; replace the transaction CBOR or hash with your own data for submission or waiting. - Sync: fetches a block, then follows chain-tip events including Apply, Undo, and Reset.
The examples contain sample Cardano addresses and block/transaction references.
Replace them with data for your server's network. They fall back to a Demeter
preview endpoint when UTXORPC_URL is unset.
make test # go mod tidy, then go test -v -race ./...
make format # go fmt ./... and gofmt -s -w
make build # build example binaries in the repository root
make mod-tidy # tidy module dependencies
make golines # optional 80-column formatting; requires golines
golangci-lint runCI runs golangci-lint with the linters and formatters in
.golangci.yml; there is no make lint target.
Tests use local fixtures and test servers. Live server checks are separate;
building examples does not run them. See AGENTS.md for repository
conventions and contribution guidance.
- UTxO RPC — protocol documentation and ecosystem
- go-codegen — generated protobuf and Connect clients
- gouroboros — Cardano primitives used by the examples