We're happy to announce the first release of a long-awaited Golang SDK for TzStats. Now, backend developers can finally integrate Tezos data with the ease and efficiency Golang is widely known for. TzStats-Go is the sister library to TzGo, our low-level Tezos library for native Golang interactions which powers the built-in Micheline data handling and all kinds of core Tezos data types.
TzStats-Go initially ships with the following features
- explorer API access to every on-chain data model the TzStats API supports
- table API access to everything except voting-related tables
- metadata read/write access
- efficient pagination using easy-to-use cursor and offset methods
- IPFS read access for JSON and media data
- fully open-source with a permissive license for free commercial and non-commercial use
TzStats-Go is compatible with the recently announced TzStats API v009-2021-04-16. We'll publish regular updates to keep track of changes to the Tezos network and our own API features.
Excited? Let's jump right in to see a few examples. We'll just show the relevant lines of code and leave error handling to the interested reader.
Installation
go get -u blockwatch.cc/tzstats-go
Then import, using
import (
"context" // we use Go's context on all calls
"blockwatch.cc/tzstats-go" // the SDK
"blockwatch.cc/tzgo/tezos" // for common Go types
)
Initializing the TzStats SDK Client
All functions are exported through a Client
object. For convenience, we have defined two default clients tzstats.DefaultClient
for Tezos mainnet and tzstats.IpfsClient
for our IPFS gateway at ipfs.tzstats.com. You may construct custom clients for different API URLs like so:
c, err := tzstats.NewClient("https://api.edo.tzstats.com", nil)
The default configuration should work just fine, but if you need special timeouts, proxy, or TLS settings you may use a custom http.Client
as second argument.
Working with TzGo Types
We use TzGo under the hood to have a common base for types and hashes. One important thing to note is that the entire TzStats SDK relies on typed hashes to avoid mistakes that may happen with untyped strings. You always pass typed hashes into functions and on return structs will contain typed hashes whenever an address, block, operation, or protocol is referenced.
Most examples below use a tezos.Address
value which can be initialized as follows (or taken from a previous API response object):
// addresses and other hashes are typed, so we parse from string,
addr := tezos.MustParseAddress("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9")
// MustParse* panics on error but is easy, for better error handling use
addr, err := tezos.ParseAddress("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9")
Reading a single Tezos Account
Reading individual objects is straightforward. Optional call parameters allow you to control what extra features the response will contain. Each API object such as an account, block, or bigmap has its own custom *Param
struct defined. Params are always passed by value. When calling a With*
function on a param it returns a copy of the original, so the original can be reused.
// to get account data and embed metadata (if available) we define call
// params and add features using With* methods (available are
// WithLimit(), WithCursor(), WithOffset(), WithOrder(), WithPrim(), ..)
params := tzstats.NewAccountParams().WithMeta()
// fetch data from the API, returns a *tzstats.Account
a, err := client.GetAccount(ctx, addr, params)
Listing Account Transactions
Listing data from the explorer API will always return an array of objects. Params work in the same way as in the single object case above.
// list operations sent and received by this account
params := tzstats.NewOpParams().
WithLimit(100).
WithOrder(tzstats.OrderDesc)
ops, err := client.GetAccountOps(ctx, addr, params)
Cursoring through results
The SDK has a convenient way for fetching results longer than the default maximum of 500 entries. We use the more efficient cursor method here, but offset would work similarly. To obtain the next batch (or page) of results, you simply overwrite the cursor property in params as shown below. An empty result means there is no more data available right now. As the chain grows you may fetch only fresh data when you keep the last row_id
around and use it as cursor in your next call.
params := tzstats.NewOpParams()
for {
// fetch next batch from the explorer API
ops, err := client.GetAccountOps(ctx, addr, params)
// handle error if necessary
// stop when result is empty
if len(ops) == 0 {
break
}
// handle ops here
// prepare for next iteration
params = params.WithCursor(ops[len(ops)-1].RowId)
}
Decoding smart contract data into Go types
The TzStats API supports unfolding of smart contract and bigmap data into JSON objects that are derived from Micheline type annotations in the contract. Below we pull such unfolded data from the API and unmarshal it directly into a Go type.
A similar example that shows how binary data is read from the Table API and then TzGo is used to perform client-side unfolding can be found in the Github repository.
addr := tezos.MustParseAddress("KT1Puc9St8wdNoGtLiD2WXaHbWU7styaxYhD")
params := tzstats.NewContractParams()
// read storage from API
raw, err := client.GetContractStorage(ctx, addr, params)
// decode into custom Go struct type
type DexterStorage struct {
Accounts int64 `json:"accounts"`
SelfIsUpdatingTokenPool bool `json:"selfIsUpdatingTokenPool"`
FreezeBaker bool `json:"freezeBaker"`
LqtTotal *big.Int `json:"lqtTotal"`
Manager tezos.Address `json:"manager"`
TokenAddress tezos.Address `json:"tokenAddress"`
TokenPool *big.Int `json:"tokenPool"`
XtzPool *big.Int `json:"xtzPool"`
}
dexterPool := &DexterStorage{}
err := raw.Unmarshal(dexterPool)
Wrapping up
We hope you are inspired by our work and can greatly improve your Tezos integrations. For questions and help please join our Discord, send us an email, or file an issue on Github.