TzGo is Blockwatch's low-level Tezos Golang SDK for reliable, high-performance applications with a focus on correctness, stability, and compliance with the Tezos protocol. Available on GitHub blockwatch-cc/tzgo.

New: TzGo SDK v1.17

TzGo now ships with support for the latest Tezos protocol live on mainnet – v17 Nairobi – including support for all of its data types, hashes, RPC calls, and transaction encoding. Other than with more heavy-weight protocol upgrades, Nairobi fortunately required only few changes in TzGo. This gave us the opportunity to improve two major areas:

1) Integration of a new transaction simulation RPC endpoint for more precise estimations on how much gas, storage, and fees a transaction must pay to not fail.

2) Integration of the TzGen code generator which enables creating Golang struct types and smart contract interface bindings based on a contract's entrypoint specification. The generated code can be used to deploy, bootstrap, and call any chosen smart contract and interact with all of its entrypoints including views. The bindings read/decode data found in transaction parameters, transaction receipts (storage and bigmap updates), and contract storage as well as write/encode type-conform parameters for sending transactions.

TzGen started as an external project developed by Jean Schmitt (Ubisoft). It was released as OSS under the MIT license and is using TzGo primitives. Since TzGen is less known and unmaintained, we decided to integrate TzGen into TzGo by refactoring and extending it towards common type and naming conventions to avoid code duplication. The integration will be improved further in future TzGo releases.

💡 TzGen is a game changer for Tezos Go developers as they no longer have to work with complicated Micheline types or any Tezos internals. Instead you can use native Go language features to easily interact with Tezos contracts as if they were just another network service.

Simulating Transactions

Tx simulation executes a complete transaction with a fake signature against the current state of the blockchain and returns the result. It can be used for different reasons such as predicting contract execution results, but in TzGo we use it to estimate optimal costs. Each time you send a transaction, TzGo simulates its execution to identify the actual execution costs in terms of gas usage and storage allocation. It's important to know this because a transaction has to set gas limit and storage limit. Too high limits cost more fees and when limits are too low, the transaction fails and the user wasted all fees.

For a long time, the only way to simulate was to call the RunOperation() RPC. This still works and is still used in TzGo when you request simulation on an old block, however, it cannot look into the future. Why is that important, though? To optimize gas costs, Tezos introduced a contract cache a while back, which essentially keeps the code of a few often used contracts in memory and avoids loading & deserializing the contract from disk. When a contract is not in the cache, the first transaction that loads it has to pay extra gas for deserialization.

Now, RunOperation might implicitly use the cache, but it does not know which contracts may be cached at the time when the transaction is later included on-chain. A new RPC called SimulateOperation() is specifically made for this case. By default it looks 5 blocks ahead, trying to precisely predict the future state of the contract cache based on heuristics. Starting from v1.17.2, TzGo makes use of this new RPC whenever you send a transaction under default settings. You can control how far into the future an estimation is done via the SimulationOffset parameter in CallOptions (default of 5 is fine).

// create and init a new TzGo RPC client
c, _ := rpc.NewClient("https://rpc.tzpro.io", nil)
_ = c.Init(ctx)

// create a signer from an in-memory private key sk
sign := signer.NewFromKey(sk)

// you can attach the key to your client or pass it in CallOptions like below
c.Signer = sign

// create simple tez transfer
_ = codec.NewOp().WithTransfer(recv, amount)

// Single-step operation sending (recommended by TzGo)
//
// This completes, simulates, signs, broadcasts the transaction.
// internally, the new RPC call SimulateOperation is used.
_, _ = c.Send(ctx, op, &rpc.CallOptions{
    Confirmations: 2,
    MaxFee: 10000,
    TTL: 100,
    SimulationOffset: 5, // control how many blocks in the future to simulate
    Signer: sign,
})

Note that alternatively, if you want more control over the transaction construction process, or if you'd like to simulate only, instead of calling Send() you can run:

// you probably want to have TzGo fill in missing data like counter and reveal
c.Complete(ctx, op, sk.Public())

// then simulate the call (this calls RunOperation internally)
c.Simulate(ctx, op, &rpc.CallOptions{
    Confirmations: 2,
    MaxFee: 10000,
    TTL: 100,
    SimulationBlockID: rpc.Head,
})

With the new simulation method, we were able to run complex contract calls and even large payout batches with 400 accounts as destination with the exact gas limit value that came from the simulation (no extra safety margin). In practice it is still a good idea to keep the small default margin of 100 because contract state may change between the time you simulate and the time of the execution.

Using TzGen

TzGen is a code generator for smart contract interface bindings that can take the code of your contract (or just the address if it is already deployed) to create interface methods and related types in Go. It's similar to how Protobuf or MsgPack generators work, but instead of a protocol spec it uses the contract's Micheline entrypoint type definitions. When using TzGen you never ever have to worry about details of Micheline or TzGo. TzGen produces all interfaces and types you'll need to call any of your contract's entrypoints, read its storage and bigmap entries.

So how does it work? You can run tzgen manually which will write an auto-generated Go source file that you can build together with your project and check in to your repository.

From a deployed contract
tzgen -name <name> -pkg <pkg> -address <addr> -o <file.go>
tzgen -name hdao -pkg contracts -address KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW -o ./examples/hdao/contract.go

From a Micheline file
tzgen -name <name> -pkg <pkg> -src <file.json> -o <file.go>
tzgen -name hello -pkg contracts -src ./hello.json -o ./examples/tzgen/hello.go

You can also use tzgen in combination with the go generate tool when you like to create fresh interface definitions at build time. To use go generate you need to do two things:

// 1. Add a go generate comment into a Go source code file
//go:generate go run -mod=mod blockwatch.cc/tzgo/cmd/tzgen -name <name> -pkg <pkg> -address <addr> -o <file.go>

// 2. Call `go generate ./path-to-your-pkg-or-cmd` to make Go call whatever script you have defined in 1.
go generate ./cmd/myprog

TzGen is currently a technology preview available in TzGo's master branch (untagged). We're collecting feedback from the community and will release it with the next protocol upgrade if everything works smoothly.

Changelog

For the full list of changes, please consult the changelog file in the TzGo repo.

unreleased

  • add TzGen tech preview
  • remove Mumbainet hashes, config and references

v1.17.1

  • change gas simulation to /helpers/scripts/simulate_operation for better future estimates
  • new CallOptions.ExtraGasMargin arg for manual override of the default (100)
  • new CallOptions.SimulationOffset arg to control future block offset
  • new Client.SimulateOperation() method to simulate execution at a future block

v1.17.0

  • add Nairobi and Nairobinet constants
  • update smart_rollup_cement parameters to Nairobi changes
  • fix accounting for internal origination allocation burn
  • update secp256k1 package
  • add storage limit safety margin (100 byte)
  • fix some Micheline translation bugs for nested list/comb-pair ambiguities
  • fix decoding for some single-value entrypoints
  • support TZGO_API_KEY env variable

🌶️ What's Hot? Meet TzPro.

We've recently announced the launch of TzPro: professional Tezos infrastructure for developers. With TzPro, we offer a suite of powerful APIs and SDKs to work with Tezos. Our aim is to provide you with infrastructure and tooling that enables a more efficient and joyful workflow so you can focus on building better software for our decentralized future.  

While the TzPro waitlist is still open, you can sign up for TzPro on https://tzpro.io. Learn more via the announcement post and don't hesitate to get started reading the developer documentation.

Get In Touch

Come talk to us on our Discord developer support channel, DM us on Twitter or send a mail to hello at blockwatch.cc. We're very interested in your feedback and suggestions. For software licensing details, contact us via licensing at blockwatch.cc.