📢 Steem Go Libraries Update: steemutil v0.0.22–v0.0.25 & steemgosdk v0.0.23steemCreated with Sketch.

in #steem6 days ago (edited)

Date: 2026-07-14
Affected modules: github.com/steemit/steemutil, github.com/steemit/steemgosdk
Audience: Go developers building on Steem (conveyor, wallets, indexers, bots)

A batch of releases across our two Go libraries lays the groundwork for the
upcoming conveyor Go rewrite. steemutil gained correctness fixes to its
protocol encoder, real RPC signature verification, and chain-aligned Asset/Price
primitives. steemgosdk builds on top of these to ship server-side __signed
verification and typed chain-query wrappers that conveyor needs.

ModuleFrom → ToHighlights
steemutilv0.0.21 → v0.0.25Encoder fixes (pointer-optional, pubkey serialization), rpc.VerifySignedRpc, integer Asset/Price matching steemd semantics
steemgosdkv0.0.22 → v0.0.23API.VerifySignedRequest, eight typed chain-query wrappers, bumps steemutil to v0.0.25

No new third-party dependencies were introduced in any of these releases.
Both libraries remain on the Go standard library + btcec/btcutil (steemutil)
and pkg/errors only.


steemutil releases

v0.0.22 — Encoder: stop treating all pointer fields as optional<T>

PR #10. The protocol encoder previously treated every pointer field as
optional<T>, emitting an extra presence tag byte even for fields that are
plain required pointers. This produced malformed serialization for several
operation types. The encoder now only applies optional encoding to fields that
are genuinely declared optional.

  • Impact: Fixes transaction/operation serialization for affected types.
    If you were hand-padding or working around extra bytes in serialized ops,
    remove those workarounds.
  • Includes golden-hex serialization tests covering the optional path.

v0.0.23 — Encode public_key_type as 33-byte binary blob

PR #11. public_key_type fields were serialized incorrectly (as a
hex/varint form), which broke any operation carrying a public key — most
notably account_update and account_create. They are now encoded as the
canonical 33-byte compressed public key blob, matching steemd's wire
format.

  • Impact: Fixes signing/broadcast of key-bearing operations.
  • Includes golden-hex tests for all public-key-bearing fields.

v0.0.24 — RPC signature verification (VerifySignedRpc)

PR #12. This is the core of the server-side __signed authentication that
conveyor (and any JSON-RPC 2.0 server authenticating callers by Steem account
ownership) needs. It mirrors @steemit/koa-jsonrpc's verifier byte-for-byte.

New exported API:

// rpc package
type AccountFetcher func(account string) (api.Authority, error)

func VerifySignedRpc(message []byte, signatures []string, account string,
    accountFetcher AccountFetcher) error

Key design points:

  • Posting authority only. Only the account's posting key is checked
    (active/owner/memo ignored), matching the JS verifier.
  • Single-key, single-signature. Accounts with multiple posting keys or
    requests with multiple signatures are rejected (Unsupported posting key configuration / Multisig not supported) — the JS verifier has the same
    limitation.
  • Exact error wording matches koa-jsonrpc so Go and JS servers
    reject/accept the same requests.
  • AccountFetcher is a callback (returns api.Authority) so the rpc package
    stays free of any chain-access dependency; the caller wires it to
    get_accounts.
  • The compact-signature byte layout used by dsteem/libcrypto
    (recovery + 31) is byte-identical to btcec's compressed layout
    (27 + recoveryId + 4) for compressed keys, so verification needed no
    conversion glue — just a format guard (wif.ValidateCompactSignature).
  • rpc.Validate already handled the envelope (jsonrpc/method, 60-second
    freshness, nonce, message digest); v0.0.24 plugs real crypto verification
    into it.

Also shipped: custom MarshalJSON/UnmarshalJSON for KeyAuth and
AccountAuthEntry, so the wire form [["STMxxx", 1], ...] flattens into typed
fields.

v0.0.25 — Asset/Price integer primitives

PR #13. Adds Asset, Price, and ComputePrices to protocol/api, with
arithmetic that matches steemd's C++ semantics exactly — zero floating point.

This replaces the float64 approach used by dsteem (a JS limitation, not a
correct design). Assets are int64 atom units; price conversion uses
math/big.Int to replicate steemd's 128-bit cross-multiply-divide.

// protocol/api package
type Asset struct {
    Amount int64   // atom units, e.g. 1500 == "1.500 STEEM"
    Symbol string  // STEEM | SBD | VESTS | TESTS | TBD
}

type Price struct {
    Base, Quote Asset
}

func ParseAsset(s string) (Asset, error)
func ParsePrice(base, quote string) (Price, error)
func (p Price) Convert(a Asset) (Asset, error)  // replicates steemd asset*price
func (p Price) Compare(q Price) int             // cross-multiply, no division
func (p Price) Invert() Price
func (a Asset) Add(b Asset) (Asset, error)      // overflow-checked
func (a Asset) Sub(b Asset) (Asset, error)      // rejects negatives

Why integer, and why math/big:

  • steemd stores assets as share_type (safe<int64_t>); prices are ratios of
    two assets; asset * price computes (uint128(a) * numerator) / denominator
    with a high-bits-zero overflow check. There is no double anywhere in
    steemd's consensus code.
  • Asset.Amount is therefore int64. Price.Convert does the same
    cross-multiply-divide in math/big.Int (which trivially covers 128 bits),
    truncating toward zero and asserting the result fits — bit-identical results
    to the C++.
  • Consequence: no 0.1 + 0.2 != 0.3 class of errors. Amounts are exact.

ComputePrices wraps the three price computations conveyor's get_prices
needs (internal market SBD, witness USD feed, VESTS-per-STEEM), returning exact
Asset/Price values rather than float64 display numbers. Conveyor decides
how/whether to round for display.

Precision (steemd-aligned): STEEM/SBD/TESTS/TBD = 3 decimals; VESTS = 6.
MaxSatoshis = 2^62 - 1.

Wire-bridge helpers OrderPrice.ToPrice() and
CurrentMedianHistoryPrice.ToPrice() were added so the existing string-asset
wire structs feed straight into the new primitives.


steemgosdk v0.0.23

PR #11. Bumps steemutil to v0.0.25 and adds the two pieces conveyor needed
that were missing from the SDK.

G1: Server-side __signed verification

func (a *API) VerifySignedRequest(signedReq *rpc.SignedRequest) (
    params []interface{}, account string, err error)

This is the server-side counterpart to the existing SignedCall (which only
signs and sends). It builds an AccountFetcher that resolves the account's
posting authority via get_accounts and returns accts[0].Posting directly —
its type (protocolapi.Authority) is exactly what AccountFetcher expects, so
there's zero conversion glue. The request is then validated with
rpc.Validate + rpc.VerifySignedRpc.

G2: Typed chain-query wrappers

Eight typed wrappers over CallWithResult, replacing the hand-written result
structs callers previously needed:

MethodRPCParams (positional array)
GetAccountscondenser_api.get_accounts[["n1","n2"]]
GetFollowCountcondenser_api.get_follow_count[account]
GetFollowerscondenser_api.get_followers[account, start, type, limit]
GetFollowingcondenser_api.get_following[account, start, type, limit]
GetAccountHistorycondenser_api.get_account_history[account, from, limit]
LookupAccountscondenser_api.lookup_accounts[lowerBound, limit]
GetOrderBookcondenser_api.get_order_book[limit]
GetFeedHistorycondenser_api.get_feed_history[]

Result structs reuse steemutil's protocol/api types; only AccountHistoryEntry
is defined locally (its wire form is a [index, {op:[type,payload],timestamp}]
tuple, with a custom UnmarshalJSON).

get_account_history pagination matches conveyor's
accountHistoryGenerator: first page uses from = -1, limit = 1000 (-1 =
newest); the SDK returns one page and the caller drives the paging loop.

A note on the condenser_api vs database_api choice

GetOrderBook and GetFeedHistory target condenser_api, not
database_api. This is deliberate: condenser_api handlers take positional
arrays, which is what the SDK's jsonrpc2 layer (RpcSendData.Params []any)
emits. database_api handlers take named objects ({"limit": N}), which a
[]any slice cannot represent. An audit confirmed the condenser_api shapes
are accepted by api.steemit.com, and ComputePrices runs end-to-end on real
data.

Both APIs are reachable via steemd's preferred dotted form
(method: "api.method"); the legacy method: "call" form (api/method packed
into params) is retained only for backwards compatibility.

Tests

  • Offline unit tests (default go test): httptest.Server-based,
    deterministic, no network dependency. Includes params-shape regression tests
    that assert every wrapper emits the correct positional-array shape and
    targets the correct API.
  • Integration tests (go test -tags=integration): run against
    api.steemit.com, verifying the wire shapes are accepted by a real node and
    ComputePrices produces sane values (e.g. 1.000 STEEM → ~1622 VESTS).

Upgrading

# steemutil consumers
go get github.com/steemit/[email protected]

# steemgosdk consumers
go get github.com/steemit/[email protected]

Breaking changes

None at the API level. The encoder fixes (steemutil v0.0.22, v0.0.23) change
the bytes produced for affected operation/key types — this is a bug fix
(meaningful output was previously malformed), but if you have persisted or
pinned any serialized bytes from older versions for those specific types, they
will differ after upgrading.

Go version

Both modules require Go 1.18+ (unchanged).


What's next

These releases unblock the conveyor Go rewrite (Gin + steemgosdk). The
authentication path (VerifySignedRequest), the high-frequency chain queries
(G2), and the price arithmetic (steemutil ComputePrices) are all now
available off the shelf.

Sort:  

I am using these on Steem Virtual Machine for Bridge Module Packed inside the chain Binary itself . Great work so far .

Upvoted! Thank you for supporting witness @jswit.