tronlib

package module
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: MIT Imports: 14 Imported by: 0

README

TronLib

Go Reference codecov tests checks

A typed Go SDK for the TRON blockchain. One import for the happy path, explicit subpackages when you need the full surface. Human developers and AI coding agents are equal first-class users — see Two audiences.

  • Module: github.com/kslamph/tronlib/v2
  • Go: 1.27.1 or newer
  • Transport: gRPC to a TRON node (grpc:// plaintext, grpcs:// TLS)

Install

go get github.com/kslamph/tronlib/v2@v2.0.0

Quickstart

package main

import (
	"context"
	"fmt"

	tronlib "github.com/kslamph/tronlib/v2"
)

func main() {
	ctx := context.Background()

	cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
	if err != nil {
		panic(err)
	}
	defer cli.Close()

	signer, err := tronlib.KeyFromHex("<private-key-hex>")
	if err != nil {
		panic(err)
	}
	to := tronlib.MustAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")

	// Amounts are integer SUN: 1 TRX = 1_000_000 SUN. Wrap literals in
	// tronlib.TRX — a bare 1 here would mean 1 SUN, not 1 TRX.
	transfer, err := cli.Account(signer.Address()).TransferTRX(ctx, to, tronlib.TRX(1))
	if err != nil {
		panic(err)
	}
	signed, err := transfer.Sign(signer)
	if err != nil {
		panic(err)
	}

	rec, err := cli.Broadcast(ctx, signed)
	if err != nil {
		panic(err)
	}
	if !rec.OK() {
		panic("node rejected: " + rec.NodeCode)
	}
	// Inclusion is not finality; WaitForSolid waits for solidification.
	if _, err := cli.WaitForSolid(ctx, rec.TxID); err != nil {
		panic(err)
	}
	fmt.Println("confirmed", rec.TxID)
}

Dial is lazy — it performs no network I/O, so reachability is proven by the first call. If you declared a network with WithNetwork, call Client.VerifyNetwork before broadcasting.

Packages

Package Purpose
github.com/kslamph/tronlib/v2 Facade: dial, chain client, account handles, type aliases.
.../v2/account Account-scoped handle: state, staking and delegation, permissions (multi-sig), voting.
.../v2/key Signers (private key, mnemonic) and message signing.
.../v2/rpc Full 1:1 gRPC wrapper surface.
.../v2/tx Transaction builders, signing, broadcast, receipts, cost preview.
.../v2/contract ABI-driven contract calls and deploys.
.../v2/token TRC-20 token handle with decimal-aware amounts — Client.Token(ctx, addr). TRC-10 legacy assets go through Client.Account(owner).TransferTRC10.
.../v2/event Log and event decoding.
.../v2/tron Core types: addresses, amounts (SUN), errors.

Notes

  • Client is the chain handle (dial, broadcast, wait, contract and token reads). Everything bound to one address lives on cli.Account(owner); the handle holds no key and never signs, which is what lets a multi-signature signer authorize someone else's account. The token handle is the one deliberate exception: it is bound to the token contract, not an owner, so it hangs off Client.Token and its Transfer/Approve take the sending account as an explicit first argument.
  • Amounts are integer SUN (1 TRX = 1_000_000 SUN). Use tronlib.TRX for literals and constants; dynamic decimal input must go through tronlib.ParseTRX. A bare integer where a SUN is expected compiles and means SUN — TransferTRX(ctx, to, 10) sends 0.00001 TRX.
  • Staking amounts are TRX in SUN, never Energy or Bandwidth quantities, and Unstake starts the chain's cooldown rather than returning TRX — WithdrawUnstaked claims the matured balance. ClaimRewards is voting rewards, a different balance again.
  • Client.Broadcast returns a Receipt for node-level rejections — rec.OK() reports them; they are not Go errors. Check rec.OK() before waiting: a rejected transaction never lands, so Wait on it polls until the context deadline.
  • Client.Wait reports inclusion; use WaitForSolid for custody or deposit-crediting semantics.
  • Multi-signature transactions can travel between signers: tronlib.Encode / tronlib.Decode carry a partially signed transaction, tronlib.Sign adds a signature, and Account(owner).Permissions().SignWeight asks the node whether the collected weight meets the threshold before broadcast.
  • tx.With* options are part of raw_data, so they are set before signing: every option mutator rejects an already-signed transaction with tx.already_signed rather than producing a transaction the node rejects with SIGERROR.
  • TotalCostOf includes the governance fees a transaction triggers (multi-signature surcharge, permission-update fee), read live from the chain parameters. CostPreview (pre-sign) prices energy and bandwidth — TotalFloor is the all-in floor on a single-signature estimate of the broadcast bytes; sign and call TotalCost for the all-in answer.

Examples

The examples live in example_test.go and are rendered into compiled examples. Each one is a whole task rather than a single API call — a contract call is simulated, priced and its logs decoded in the same example — and go test compiles every one, so they cannot drift from the code.

Example Covers
Example Quickstart: dial, read the balance, transfer TRX, price it, sign, broadcast, wait for solidification. Hex key, one signer.
ExampleClient_Account Account and resource state: balance, Energy/Bandwidth, TRON Power, stake/unstake/delegation summary, and an all-in cost preview before spending.
ExampleClient_Contract One contract interaction end to end: view call, Invoke, Simulate, CostPreview, broadcast, and event decoding from the receipt.
ExampleClient_Token TRC-20 through the decimal-aware handle: symbol/name/decimals/totalSupply, balance and allowance reads, Approve, transferFrom, Transfer. Mnemonic key.
ExampleResources_Stake Staking lifecycle and delegation: stake, unstake into the cooldown, harvest, delegate with a block-based lock, track the delegation index.
ExamplePermissions_Current Permission configuration: read the whole set, add an operations-bitmap-scoped active permission, submit the complete replacement and its fee.
ExamplePermissions_SignWeight Offline multi-signature: portable envelope between two machines and two key forms, node-verified sign weight, plus the SignHash/AttachSignature path for a remote signer.

They carry no // Output: comment, so they compile without contacting a node (CODING_STANDARDS.md §6.4). To run one against Nile, copy its body into a main and set the environment it reads: TRON_PRIVATE_KEY (hex, 64 characters), TRON_MNEMONIC, TRON_SIGNER_A_KEY, TRON_SIGNER_B_MNEMONIC.

Checking them against a live node

Compilation proves the examples type-check, not that they are correct. The harness behind them, cmd/examplecheck, walks every documented flow against a real node — reads, builds, local signing, simulation, cost pricing, the multi-signature envelope round trip and the node's signature-weight verdict — and spends nothing unless asked to:

go run ./cmd/examplecheck   # every flow, live node, zero spend

Recorded runs live in the verification ledger: R9 (spend-free, 34 steps OK), R10 (on-chain, 55 steps OK / 0 failed) with every txid — along with the flags for a broadcasting run, the run-to-run cost measurements, the example bug only execution could find (a reverting simulation returns revert data, not the method's return value), and what is deliberately not yet proven.

Two audiences

Human developers and AI coding agents are equal first-class users, and the documentation serves each natively:

  • Errors are machine-readable remediation. Every failure is a *tron.Error with a stable Code, a Hint that names the fix, and a Next action (retry, wait, fix_call, fix_transaction, fund). The error table is generated from the source by cmd/docgen, so it cannot drift from the code it documents.
  • Examples are compiled and were executed. Every example in docs/examples.md is a Go example compiled by go test, and the verification ledger records them running against a live node, with txids.
  • llms.txt is the curated reading order — the four documents an agent (or a human in a hurry) needs, plus the API's few non-negotiable rules restated where they cannot be missed.

Documentation

Documentation

Overview

Package tronlib is a Go SDK for the TRON blockchain.

v2 is a clean-room redesign. See docs/architecture.md for the design. for the design and the reasoning behind each breaking change.

The root facade

This package is the facade: ONE import for the happy path (architecture §10). The facade is an on-ramp — every data type is a type alias (zero conversion tax between facade and subpackage code), every amount and address constructor is a one-line re-export, and every Client method is a one-line delegation to the subpackage owner. The facade never reimplements; anything beyond the happy path is one Raw() call away in the subpackages (rpc for the full 1:1 gRPC surface, tx for builders/options, contract for ABI-driven calls, key for message signing).

The happy path (architecture §10):

cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
defer cli.Close()

signer, err := tronlib.KeyFromHex(os.Getenv("TRON_PRIVATE_KEY"))
to, err := tronlib.ParseAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")

transfer, err := cli.Account(signer.Address()).TransferTRX(ctx, to, tronlib.TRX(1))
signed, err := transfer.Sign(signer)
rec, err := cli.Broadcast(ctx, signed)

One import; v1 needed four.

Network identity is explicit configuration, not a derivation. TRON has no chain ID, and the 21-byte address prefix is 0x41 on Mainnet, Shasta and Nile alike, so an address byte cannot discriminate a network. Declare it with WithNetwork; Client.Network reports the declaration with no I/O, and Client.VerifyNetwork compares the endpoint's genesis block id against a recorded table (heuristic: a redeployed testnet changes its genesis and a private chain matches nothing). Dial does not verify automatically — Dial is lazy by design, so verification is an explicit call.

Example

Example is the quickstart: dial, read the balance, transfer TRX, price the transaction before signing it, broadcast, and wait for solidification.

The stages are explicit on purpose — build, sign, broadcast are separate calls, so a mistake (wrong recipient, unexpected cost) is caught before anything is irreversibly sent. `Sign` returns a copy: the unsigned transaction stays valid for a second attempt.

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	tronlib "github.com/kslamph/tronlib/v2"
)

func main() {
	// One deadline for the whole send: an unbounded broadcast call is how a
	// program hangs on a node that stopped answering.
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
	if err != nil {
		fmt.Println("dial:", err)
		return
	}
	defer cli.Close()

	// A hex private key: 64 hex characters, with or without 0x.
	signer, err := tronlib.KeyFromHex(os.Getenv("TRON_PRIVATE_KEY"))
	if err != nil {
		fmt.Println("key:", err)
		return
	}
	to, err := tronlib.ParseAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")
	if err != nil {
		fmt.Println("address:", err)
		return
	}

	acct := cli.Account(signer.Address())
	bal, err := acct.Balance(ctx)
	if err != nil {
		fmt.Println("balance:", err)
		return
	}
	fmt.Println("balance:", bal.Formatted(), "TRX")

	transfer, err := acct.TransferTRX(ctx, to, tronlib.TRX(1))
	if err != nil {
		fmt.Println("build:", err)
		return
	}
	signed, err := transfer.Sign(signer)
	if err != nil {
		fmt.Println("sign:", err)
		return
	}

	// Price the exact bytes that will be broadcast: energy, bandwidth,
	// account-creation and any governance fee. Call it after Sign — the
	// bandwidth half is measured on the signed transaction.
	cost, err := acct.TotalCost(ctx, signed)
	if err != nil {
		fmt.Println("cost:", err)
		return
	}
	fmt.Println(cost.String())

	rec, err := cli.Broadcast(ctx, signed)
	if err != nil {
		fmt.Println("broadcast:", err)
		return
	}
	if !rec.OK() {
		fmt.Println("node rejected:", rec.NodeCode)
		return
	}
	// Inclusion is not finality; custody and deposit-crediting wait for
	// solidification (confirmed by the super representatives).
	if _, err := cli.WaitForSolid(ctx, rec.TxID); err != nil {
		fmt.Println("wait:", err)
	}
}

Index

Examples

Constants

View Source
const (
	Energy    = tx.ResourceEnergy
	Bandwidth = tx.ResourceBandwidth
)

Resource names a stakeable/delegatable resource. TRON Power is deliberately absent: it is not delegatable, and staking grants it under the current Mainnet resource model.

View Source
const (
	TypeAccountCreate           = tx.TypeAccountCreate
	TypeTransfer                = tx.TypeTransfer
	TypeTransferAsset           = tx.TypeTransferAsset
	TypeVoteWitness             = tx.TypeVoteWitness
	TypeCreateWitness           = tx.TypeCreateWitness
	TypeUpdateWitness           = tx.TypeUpdateWitness
	TypeFreezeBalance           = tx.TypeFreezeBalance
	TypeUnfreezeBalance         = tx.TypeUnfreezeBalance
	TypeWithdrawBalance         = tx.TypeWithdrawBalance
	TypeProposalCreate          = tx.TypeProposalCreate
	TypeProposalApprove         = tx.TypeProposalApprove
	TypeProposalDelete          = tx.TypeProposalDelete
	TypeCreateSmartContract     = tx.TypeCreateSmartContract
	TypeTriggerSmartContract    = tx.TypeTriggerSmartContract
	TypeUpdateSetting           = tx.TypeUpdateSetting
	TypeUpdateEnergyLimit       = tx.TypeUpdateEnergyLimit
	TypeAccountPermissionUpdate = tx.TypeAccountPermissionUpdate
	TypeClearABI                = tx.TypeClearABI
	TypeUpdateBrokerage         = tx.TypeUpdateBrokerage
	TypeFreezeBalanceV2         = tx.TypeFreezeBalanceV2
	TypeUnfreezeBalanceV2       = tx.TypeUnfreezeBalanceV2
	TypeWithdrawExpireUnfreeze  = tx.TypeWithdrawExpireUnfreeze
	TypeDelegateResource        = tx.TypeDelegateResource
	TypeUnDelegateResource      = tx.TypeUnDelegateResource
	TypeCancelAllUnfreezeV2     = tx.TypeCancelAllUnfreezeV2
)

Contract types for an active permission's operations bitmap (tx.OperationsBitmap). These are the operations this SDK can build; the bitmap itself accepts any protocol id through ContractType.

Variables

This section is empty.

Functions

func Encode

func Encode(t Tx) ([]byte, error)

Encode renders a transaction as a portable, versioned envelope that carries the declared kind and every signature attached so far (tx.Encode). It is the interchange format for offline multi-signing: persist it, or hand it to the next signer on another machine.

func OperationsBitmap

func OperationsBitmap(types ...ContractType) ([]byte, error)

OperationsBitmap builds the 32-byte active-permission bitmap for the given contract types (tx.OperationsBitmap). Re-exported as a one-line wrapper because a type alias cannot carry a function.

func SignHash

func SignHash(t Tx) ([]byte, error)

SignHash returns the 32-byte digest a signature must cover for t (tx.SignHash): sha256 of raw_data. A hardware wallet or remote signer signs it, and AttachSignature attaches the result — the private key never enters this process.

Types

type Account

type Account = account.Handle

Account-scoped handles, reached through Client.Account(owner).

type AccountState

type AccountState = account.State

Account-scoped values.

type Action

type Action = tron.Action

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Address

type Address = tron.Address

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func MustAddress

func MustAddress(s string) Address

MustAddress parses s and panics on error. For package-level address literals in tests and examples; anything that handles user input must use ParseAddress.

func ParseAddress

func ParseAddress(s string) (Address, error)

ParseAddress parses a base58check-encoded TRON address ("T...", 34 chars).

type BandwidthCost added in v2.0.1

type BandwidthCost = tx.BandwidthCost

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type ChainParams

type ChainParams = tx.ChainParams

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func ChainParamsOf

func ChainParamsOf(ctx context.Context, cp *rpc.Client) (*ChainParams, error)

ChainParamsOf reads the governance parameters that price and bound transactions: the permission-update and multi-signature fees, the unstake cooldown, and the maximum delegation lock (tx.ChainParamsOf). They are live values, not constants.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the happy-path handle to one TRON node. It wraps *rpc.Client; every method is a one-line delegation to the subpackage owner (architecture D7). The declared network is explicit configuration recorded here by WithNetwork; VerifyNetwork checks it against the endpoint's genesis. The energy-price cache is one memoised read per maintenance period.

func Dial

func Dial(ctx context.Context, endpoint string, opts ...DialOption) (*Client, error)

Dial creates a Client to a TRON node at endpoint, which must be grpc://host:port (plaintext) or grpcs://host:port (TLS). Dial is lazy: it builds the connection factory without network I/O, so reachability is proven by the first call. Options: WithTimeout (default 30s), WithPool (default 1..5), WithNetwork. Dial does NOT verify the network — call Client.VerifyNetwork explicitly when a declaration was made.

func (*Client) Account

func (c *Client) Account(owner Address) *account.Handle

Account returns the account-scoped handle for owner: transfers, deployment, staking and delegation, permissions and voting, plus the account-shaped reads. It performs no I/O and stores no key material — the account being operated on is not the key that signs, which is what makes multi-signature work.

The shared pipeline is unchanged: every state-changing method returns an unsigned transaction, and signing and broadcasting stay on the transaction and on Client.

Example

ExampleClient_Account reads everything a decision needs before spending: the account state, its staked resources (Energy and Bandwidth), the stake/unstake/delegation summary, and the all-in cost of a transaction. Nothing is broadcast here — this is the "should I send this?" pass.

Resource amounts are always TRX in SUN; how much Energy a stake actually buys depends on the network-wide staked total, so it is read, never calculated from a fixed rate.

package main

import (
	"context"
	"fmt"
	"os"

	tronlib "github.com/kslamph/tronlib/v2"
)

func main() {
	ctx := context.Background()

	cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
	if err != nil {
		fmt.Println("dial:", err)
		return
	}
	defer cli.Close()

	signer, err := tronlib.KeyFromHex(os.Getenv("TRON_PRIVATE_KEY"))
	if err != nil {
		fmt.Println("key:", err)
		return
	}
	acct := cli.Account(signer.Address())

	// Balance, stake positions still in their cooldown, current votes and the
	// TRX lent to / borrowed from other accounts.
	state, err := acct.State(ctx)
	if err != nil {
		fmt.Println("state:", err)
		return
	}
	fmt.Println(state.Balance.Formatted(), "TRX,", len(state.Stakes), "stake(s),",
		len(state.Unstakes), "unstake(s),", len(state.Votes), "vote(s)")

	// Energy and Bandwidth limits, current usage, and the TRON Power that
	// voting consumes.
	resources, err := acct.Resources().State(ctx)
	if err != nil {
		fmt.Println("resources:", err)
		return
	}
	fmt.Println("energy", resources.EnergyAvailable(), "of", resources.EnergyLimit,
		"| bandwidth", resources.BandwidthAvailable(), "of",
		resources.BandwidthLimit+resources.FreeBandwidthLimit,
		"| tron power", resources.TronPowerAvailable())

	// One call that folds the account read and the resource read into the
	// numbers a staking UI shows.
	summary, err := acct.Resources().Summary(ctx)
	if err != nil {
		fmt.Println("summary:", err)
		return
	}
	fmt.Println("staked", summary.StakedByResource[tronlib.Energy].Formatted(), "TRX for energy,",
		summary.UnstakeWithdrawable.Formatted(), "TRX withdrawable,",
		summary.UnstakeSlots, "unstake slots left")

	to, err := tronlib.ParseAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")
	if err != nil {
		fmt.Println("address:", err)
		return
	}
	transfer, err := acct.TransferTRX(ctx, to, tronlib.TRX(1))
	if err != nil {
		fmt.Println("build:", err)
		return
	}
	signed, err := transfer.Sign(signer)
	if err != nil {
		fmt.Println("sign:", err)
		return
	}
	cost, err := acct.TotalCost(ctx, signed)
	if err != nil {
		fmt.Println("cost:", err)
		return
	}
	fmt.Println("this transfer costs:", cost.Total.Formatted(), "TRX —", cost.String())
}

func (*Client) Broadcast

func (c *Client) Broadcast(ctx context.Context, t Tx) (*Receipt, error)

Broadcast submits a signed transaction to the node and returns a Receipt. A node-level rejection is a Receipt (r.OK() reports it), not an error.

func (*Client) ChainTip

func (c *Client) ChainTip(ctx context.Context) (uint64, error)

ChainTip returns the latest known block number.

func (*Client) Close

func (c *Client) Close() error

Close closes the client and all pooled connections. Idempotent.

func (*Client) Contract

func (c *Client) Contract(_ context.Context, addr Address) (*contract.Instance, error)

Contract returns a typed view of the deployed contract at addr. The ABI loads lazily on first use unless the instance is given one with UseABI.

Example

ExampleClient_Contract is one contract interaction from end to end: read a view function, build a state-changing call, dry-run it, price it, broadcast it, and decode the events it emitted. A wrong ABI argument or an under-priced call is caught by the simulation, before signing.

package main

import (
	"context"
	"fmt"
	"math/big"
	"os"

	tronlib "github.com/kslamph/tronlib/v2"
	"github.com/kslamph/tronlib/v2/contract"
)

func main() {
	ctx := context.Background()

	cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
	if err != nil {
		fmt.Println("dial:", err)
		return
	}
	defer cli.Close()

	signer, err := tronlib.KeyFromHex(os.Getenv("TRON_PRIVATE_KEY"))
	if err != nil {
		fmt.Println("key:", err)
		return
	}
	to, err := tronlib.ParseAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")
	if err != nil {
		fmt.Println("address:", err)
		return
	}
	// Nile's official USDT contract (docs/verification.md, address appendix).
	token, err := tronlib.ParseAddress("TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj")
	if err != nil {
		fmt.Println("address:", err)
		return
	}
	inst, err := cli.Contract(ctx, token)
	if err != nil {
		fmt.Println("contract:", err)
		return
	}

	// 1. Read-only call. Results carry their ABI type; accessors return an
	// error rather than a zero value when the type does not match.
	supply, err := inst.Call(ctx, "balanceOf", contract.AddressArg(signer.Address()))
	if err != nil {
		fmt.Println("call:", err)
		return
	}
	held, err := supply.BigInt()
	if err != nil {
		fmt.Println("decode:", err)
		return
	}
	fmt.Println("token balance:", held.String())

	// 2. Build the state-changing call. The ABI is fetched from the node on
	// first use (Instance.UseABI supplies one offline). USDT has 6 decimals, so
	// one token is 1_000_000 raw units; the generic contract layer takes the
	// raw ABI value, and token.Handle does the scaling for you (see
	// ExampleClient_Token).
	units := new(big.Int).Mul(big.NewInt(1), big.NewInt(1_000_000))
	call, err := inst.Invoke(ctx, signer.Address(), 0, "transfer",
		contract.AddressArg(to), contract.BigIntArg(units))
	if err != nil {
		fmt.Println("invoke:", err)
		return
	}

	// 3. Dry-run. A revert is an answer, not an error: the node reports it in
	// est.Revert, and the constant result then carries the revert payload rather
	// than the method's return value — so the revert MUST be checked before the
	// result is decoded, or Decode fails with contract.arg_mismatch. (A live run
	// of this example against Nile caught exactly that: an unfunded owner made
	// transfer revert, and the unconditional decode below reported arg_mismatch
	// instead of the revert.)
	est, err := call.Simulate(ctx)
	if err != nil {
		fmt.Println("simulate:", err)
		return
	}
	if est.Revert != "" {
		fmt.Println("the call would revert:", est.Revert, "— energy", est.Energy, "code", est.Code)
	} else {
		fmt.Println("energy:", est.Energy, "penalty:", est.Penalty)
		if est.HasResult() {
			result, err := inst.Decode("transfer", est.ConstantResult[0])
			if err != nil {
				fmt.Println("decode result:", err)
				return
			}
			if ok, err := result.Bool(); err == nil {
				fmt.Println("transfer would succeed:", ok)
			}
		}
	}

	// 4. Price it against this account's staked energy and bandwidth, then
	// broadcast. TotalFloor is the all-in floor: energy burn + bandwidth
	// burn, with no governance fees (TotalCost on the signed transaction is
	// the all-in answer).
	acct := cli.Account(signer.Address())
	preview, err := acct.CostPreview(ctx, call)
	if err != nil {
		fmt.Println("preview:", err)
		return
	}
	fmt.Println("burn", preview.TronToBurn.Formatted(), "TRX of", preview.SunPerEnergy, "sun/energy;")
	fmt.Println(preview.Bandwidth.String(), "; total floor", preview.TotalFloor.Formatted(), "TRX",
		"("+preview.BandwidthNote+")")

	signed, err := call.Sign(signer)
	if err != nil {
		fmt.Println("sign:", err)
		return
	}
	rec, err := cli.Broadcast(ctx, signed)
	if err != nil {
		fmt.Println("broadcast:", err)
		return
	}
	if !rec.OK() {
		fmt.Println("node rejected:", rec.NodeCode, rec.Revert)
		return
	}

	// 5. Decode what the transaction emitted. Receipt logs are decoded against
	// every ABI registered for the emitting contract — Instance.UseABI (and
	// the token handle) register theirs — and unknown signatures stay in the
	// receipt with their raw topics instead of being dropped.
	for _, lg := range rec.Logs {
		fmt.Println("event", lg.EventName, "from", lg.Address)
		for _, p := range lg.Parameters {
			fmt.Println("   ", p.Name, "=", p.Value)
		}
	}
	// The same logs are retrievable later, by txid, without a receipt.
	logs, err := cli.Events(ctx, rec.TxID)
	if err != nil {
		fmt.Println("events:", err)
		return
	}
	fmt.Println("stored logs:", len(logs))
}

func (*Client) Endpoint

func (c *Client) Endpoint() string

Endpoint returns the configured endpoint (scheme://host:port).

func (*Client) EnergyPrice

func (c *Client) EnergyPrice(ctx context.Context) (*tx.EnergyPrice, error)

EnergyPrice returns the current energy unit price (the latest governance "ts:price" entry). The unit price changes only via governance proposal, so the read is cached for one maintenance period (architecture §7.3, risk G5): the cache is TTL-only, never keyed on the head block. A caller wanting a guaranteed-fresh read calls tx.EnergyPriceOf(ctx, c.Raw()) directly.

func (*Client) Events

func (c *Client) Events(ctx context.Context, txid string) ([]Log, error)

Events fetches the transaction's logs, decoded leniently — unknown signatures materialize with EventName empty and raw bytes preserved; never dropped. One non-polling fetch: a transaction that is not yet included yields no logs (poll Wait/WaitForSolid first if inclusion is required).

func (*Client) Network

func (c *Client) Network() Network

Network returns the network declared at Dial time. It is a pure accessor: no I/O, and no inference from the endpoint.

func (*Client) Raw

func (c *Client) Raw() *rpc.Client

Raw returns the underlying *rpc.Client — the escape hatch to the full 1:1 gRPC wrapper surface without leaving the facade's connection pool.

func (*Client) Token

func (c *Client) Token(ctx context.Context, address Address) (*token.Handle, error)

Token pins a TRC-20 handle for the token contract at address, fetching its decimals with one eager view call. Amounts minted by the Handle carry that scale.

Example

ExampleClient_Token uses the TRC-20 handle, which owns the token's scale: every Amount it mints carries the token's decimals, so "1.5" means 1.5 USDT and not 1.5 raw units.

It also shows the two-step approval flow: the owner approves a spender, and the spender pulls the funds with transferFrom — a plain `transfer` moves only the owner's own tokens.

The key here is a BIP-39 mnemonic, the other form the SDK accepts.

package main

import (
	"context"
	"fmt"
	"os"

	tronlib "github.com/kslamph/tronlib/v2"
	"github.com/kslamph/tronlib/v2/contract"
)

func main() {
	ctx := context.Background()

	cli, err := tronlib.Dial(ctx, "grpc://grpc.nile.trongrid.io:50051")
	if err != nil {
		fmt.Println("dial:", err)
		return
	}
	defer cli.Close()

	// A mnemonic plus derivation path (TRON uses coin type 195).
	ownerKey, err := tronlib.KeyFromMnemonic(os.Getenv("TRON_MNEMONIC"), "", "m/44'/195'/0'/0/0")
	if err != nil {
		fmt.Println("mnemonic:", err)
		return
	}
	spenderKey, err := tronlib.KeyFromMnemonic(os.Getenv("TRON_MNEMONIC"), "", "m/44'/195'/0'/0/1")
	if err != nil {
		fmt.Println("mnemonic:", err)
		return
	}
	usdt, err := tronlib.ParseAddress("TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj")
	if err != nil {
		fmt.Println("address:", err)
		return
	}
	handle, err := cli.Token(ctx, usdt)
	if err != nil {
		fmt.Println("token:", err)
		return
	}

	// Metadata and reads. Decimals are read once, eagerly, by Token.
	symbol, err := handle.Symbol(ctx)
	if err != nil {
		fmt.Println("symbol:", err)
		return
	}
	name, err := handle.Name(ctx)
	if err != nil {
		fmt.Println("name:", err)
		return
	}
	supply, err := handle.TotalSupply(ctx)
	if err != nil {
		fmt.Println("totalSupply:", err)
		return
	}
	fmt.Println(name, "("+symbol+")", "decimals:", handle.Decimals(), "supply:", supply.Formatted())

	owner := ownerKey.Address()
	spender := spenderKey.Address()
	balance, err := handle.BalanceOf(ctx, owner)
	if err != nil {
		fmt.Println("balanceOf:", err)
		return
	}
	allowance, err := handle.Allowance(ctx, owner, spender)
	if err != nil {
		fmt.Println("allowance:", err)
		return
	}
	fmt.Println("owner holds", balance.Formatted(), "| spender may pull", allowance.Formatted())

	// Step 1: the owner approves a limited allowance. Decimal input goes
	// through the handle ("1.5" -> 1500000 raw units at 6 decimals); the
	// integer-only Whole is for whole tokens.
	spend, err := handle.Whole(1)
	if err != nil {
		fmt.Println("amount:", err)
		return
	}
	approve, err := handle.Approve(ctx, owner, spender, spend)
	if err != nil {
		fmt.Println("approve:", err)
		return
	}
	signedApprove, err := approve.Sign(ownerKey)
	if err != nil {
		fmt.Println("sign approve:", err)
		return
	}
	if _, err := cli.Broadcast(ctx, signedApprove); err != nil {
		fmt.Println("broadcast approve:", err)
		return
	}

	// Step 2: the spender pulls the approved amount. transferFrom is a plain
	// ABI method, so it goes through the generic contract instance.
	to, err := tronlib.ParseAddress("TBkfmcE7pM8cwxEhATtkMFwAf1FeQcwY9x")
	if err != nil {
		fmt.Println("address:", err)
		return
	}
	inst, err := cli.Contract(ctx, usdt)
	if err != nil {
		fmt.Println("contract:", err)
		return
	}
	pull, err := inst.Invoke(ctx, spender, 0, "transferFrom",
		contract.AddressArg(owner), contract.AddressArg(to), contract.BigIntArg(spend.Raw()))
	if err != nil {
		fmt.Println("transferFrom:", err)
		return
	}
	signedPull, err := pull.Sign(spenderKey)
	if err != nil {
		fmt.Println("sign transferFrom:", err)
		return
	}
	if _, err := cli.Broadcast(ctx, signedPull); err != nil {
		fmt.Println("broadcast transferFrom:", err)
		return
	}

	// Or move your own tokens directly, no approval needed.
	move, err := handle.Transfer(ctx, owner, to, spend)
	if err != nil {
		fmt.Println("transfer:", err)
		return
	}
	signedMove, err := move.Sign(ownerKey)
	if err != nil {
		fmt.Println("sign transfer:", err)
		return
	}
	if _, err := cli.Broadcast(ctx, signedMove); err != nil {
		fmt.Println("broadcast transfer:", err)
	}
}

func (*Client) VerifyNetwork

func (c *Client) VerifyNetwork(ctx context.Context) error

VerifyNetwork compares the endpoint's genesis block id against the recorded table for the declared network and returns chain.network_mismatch when they disagree. It is heuristic: a redeployed testnet changes its genesis and a private chain matches nothing, so WithNetwork is the source of truth — this only detects a mismatch, it never infers identity (architecture §10, risk R7).

The undeclared zero value and Private return nil without a read — there is no declaration to contradict. A declared network absent from the table fails closed as a mismatch. Dial does not call this automatically: Dial is lazy by design, so verification is an explicit call.

func (*Client) Wait

func (c *Client) Wait(ctx context.Context, txid string) (*Receipt, error)

Wait polls until the transaction is included and executed, and returns the parsed Receipt. Call it only on a broadcast the node accepted (Receipt.OK()) — a rejected transaction never lands and Wait would poll until the context deadline. Inclusion is not finality; use WaitForSolid for custody or deposit-crediting semantics.

func (*Client) WaitForSolid

func (c *Client) WaitForSolid(ctx context.Context, txid string) (*Receipt, error)

WaitForSolid polls the Solidity endpoint until the transaction appears there — solidified semantics, the finality-aware variant of Wait.

func (*Client) Witnesses

func (c *Client) Witnesses(ctx context.Context, page Page) ([]Witness, error)

Witnesses returns one page of the current witness list. page.Offset and page.Limit pass through to the node; Limit 0 means the node's rpc default, never "all" (architecture §10.1).

type Code

type Code = tron.Code

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type ContractTx

type ContractTx = tx.ContractTx

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type ContractType

type ContractType = tx.ContractType

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func OperationsList

func OperationsList(bitmap []byte) ([]ContractType, error)

OperationsList decodes an active-permission bitmap back into the contract types it allows, in ascending order (tx.OperationsList) — the read side of OperationsBitmap, for showing what a permission actually grants.

type CostPreview added in v2.0.1

type CostPreview = tx.CostPreview

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type DelegateParams

type DelegateParams = tx.DelegateOptions

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Delegation

type Delegation = tx.Delegation

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type DelegationList

type DelegationList = tx.DelegationIndex

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type DialOption

type DialOption struct {
	// contains filtered or unexported fields
}

DialOption configures a Client at Dial time. It is opaque: build one with WithTimeout, WithPool, or WithNetwork.

func WithNetwork

func WithNetwork(n Network) DialOption

WithNetwork declares the endpoint's network. The undeclared zero value and Private skip verification; a public declaration opts into the genesis-fingerprint check (architecture §10).

func WithPool

func WithPool(initConnections, maxConnections int) DialOption

WithPool configures the initial and maximum connections for the pool (default 1..5; sizes <= 0 fall back to the defaults).

func WithTimeout

func WithTimeout(d time.Duration) DialOption

WithTimeout sets the default timeout for client operations when the context has no deadline (default 30 seconds).

type Error

type Error = tron.Error

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Log

type Log = event.Log

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type NativeTx

type NativeTx = tx.NativeTx

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Network

type Network string

Network is a declared TRON network identity. It is explicit configuration, never inferred: TRON has no chain ID, and the 21-byte address prefix is 0x41 on Mainnet, Shasta and Nile alike, so an address byte cannot discriminate a network (architecture §10).

const (
	Mainnet Network = "mainnet"
	Shasta  Network = "shasta"
	Nile    Network = "nile"
	Private Network = "private"
)

type Page

type Page struct {
	Offset int64
	Limit  int64 // 0 means the rpc default; never means "all"
}

Page is one explicit pagination cursor: the cursor is a parameter, never hidden client state (architecture §10.1). Limit 0 delegates to the rpc default — a caller cannot express "give me everything"; that is the point of the List verb contract.

type Permission

type Permission = tx.Permission

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type PermissionKey

type PermissionKey = tx.PermissionKey

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type PermissionSet

type PermissionSet = tx.PermissionSet

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Permissions

type Permissions = account.Permissions

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Receipt

type Receipt = tx.Receipt

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Resource

type Resource = tx.Resource

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type ResourceState

type ResourceState = tx.ResourceState

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Resources

type Resources = account.Resources

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type SUN

type SUN = tron.SUN

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func MustTRX

func MustTRX(s string) SUN

MustTRX parses s and panics on error. For package-level amount literals in tests and examples; anything that handles user input must use ParseTRX.

func ParseTRX

func ParseTRX(s string) (SUN, error)

ParseTRX parses an exact decimal TRX string into SUN (at most 6 decimal places; no float64 ever touches this path).

func TRX

func TRX[T tron.Whole](n T) SUN

TRX converts a whole-number TRX literal to SUN. It panics on overflow and is for literals and constants only; dynamic input must use ParseTRX. Re-exported as a one-line wrapper because generics cannot be aliased.

type SignatureState

type SignatureState = account.SignatureStatus

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Signer

type Signer = key.Signer

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func KeyFromHex

func KeyFromHex(hexKey string) (Signer, error)

KeyFromHex builds a Signer from a secp256k1 private key hex string (with or without a 0x prefix).

func KeyFromMnemonic

func KeyFromMnemonic(mnemonic, passphrase, path string) (Signer, error)

KeyFromMnemonic builds a Signer from a BIP-39 mnemonic, passphrase and derivation path.

type Stake

type Stake = account.Stake

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type TotalCost

type TotalCost = tx.TotalCost

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Tx

type Tx = tx.Tx

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

func AttachSignature

func AttachSignature(t Tx, addr Address, sig []byte) (Tx, error)

AttachSignature returns a copy of t with the raw 65-byte [R || S || V] signature attached for addr, rejecting a signature that does not recover to that address and a duplicate signer (tx.AttachSignature).

func Decode

func Decode(data []byte) (Tx, error)

Decode rebuilds a transaction from an Encode envelope, restoring the concrete kind and any partial signatures, and rejecting an envelope whose declared kind contradicts the contract it wraps (tx.Decode).

func Sign

func Sign(t Tx, signers ...Signer) (Tx, error)

Sign adds a signature from each signer to t and returns the same concrete kind (tx.Sign). It is the entry point for a transaction recovered by Decode, whose static type is Tx rather than a named kind, and it rejects a signer that is already present (a duplicate signature invalidates the transaction).

type Unstake

type Unstake = account.Unstake

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Vote

type Vote = tx.Vote

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Voting

type Voting = account.Voting

Aliases, not wrappers: a tron.Address and a tronlib.Address are the same type, so facade and subpackage calls interoperate with zero conversion. This is only possible because v2 is a single module (architecture C1).

type Witness

type Witness = rpc.Witness

Witness is one super-representative candidate (a decoded view of the node's core.Witness: address, vote count, isJobs).

Directories

Path Synopsis
Package account is the account-scoped facade: everything that acts on, or reads the state of, ONE account.
Package account is the account-scoped facade: everything that acts on, or reads the state of, ONE account.
cmd
docgen command
Command docgen keeps v2 documentation mechanically in sync with code.
Command docgen keeps v2 documentation mechanically in sync with code.
eventtool command
Command eventtool maintains tronlib's 32-byte event corpus and the built-in table generated from it.
Command eventtool maintains tronlib's 32-byte event corpus and the built-in table generated from it.
examplecheck command
Command examplecheck exercises the flows documented in the examples (example_test.go / docs/examples.md) against a LIVE node, so the examples' code paths are proven to work and not merely to compile.
Command examplecheck exercises the flows documented in the examples (example_test.go / docs/examples.md) against a LIVE node, so the examples' code paths are proven to work and not merely to compile.
tip491probe command
Command tip491probe is a manual harness for architecture §7.5 item 6: it checks whether a node's simulated energy estimate already includes the TIP-491 dynamic-energy penalty, and cross-checks the node's two independent factor reports (GetContractInfo's stored factor vs the factor derived from Simulate's energy/penalty pair) against the per-opcode formula the library implements (tx.DynamicEnergy.PredictPenalty).
Command tip491probe is a manual harness for architecture §7.5 item 6: it checks whether a node's simulated energy estimate already includes the TIP-491 dynamic-energy penalty, and cross-checks the node's two independent factor reports (GetContractInfo's stored factor vs the factor derived from Simulate's energy/penalty pair) against the per-opcode formula the library implements (tx.DynamicEnergy.PredictPenalty).
Package contract binds a deployed smart contract's ABI to typed Go calls: Instance encodes sealed Arg values into call data, executes reads (Call), state-changing calls (Invoke — producing a *tx.ContractTx), and decodes raw ABI output into typed Result values with enumerable accessors (architecture §9).
Package contract binds a deployed smart contract's ABI to typed Go calls: Instance encodes sealed Arg values into call data, executes reads (Call), state-changing calls (Invoke — producing a *tx.ContractTx), and decodes raw ABI output into typed Result values with enumerable accessors (architecture §9).
Package event decodes TRON transaction log entries into typed event data.
Package event decodes TRON transaction log entries into typed event data.
internal
eventdata
Package eventdata holds the tracked data the event tooling operates on: the curated event-signature corpus that generates event/builtin_gen.go, the seed ABI files, and the snapshotted TronScan contract ranking.
Package eventdata holds the tracked data the event tooling operates on: the curated event-signature corpus that generates event/builtin_gen.go, the seed ABI files, and the snapshotted TronScan contract ranking.
eventtool
Package eventtool holds the reusable logic behind cmd/eventtool: the 32-byte event corpus store, the v1->v2 corpus migration, ABI ingestion, the TronScan contract ranking snapshot, contract ABI capture, and the builtin_gen.go generator.
Package eventtool holds the reusable logic behind cmd/eventtool: the 32-byte event corpus store, the v1->v2 corpus migration, ABI ingestion, the TronScan contract ranking snapshot, contract ABI capture, and the builtin_gen.go generator.
format
Package format holds display-only string helpers shared by the tron and token packages.
Package format holds display-only string helpers shared by the tron and token packages.
Package key provides message signing for TRON: Signer implementations backed by a hex private key or an HD wallet (BIP-39 mnemonic + derivation path), plus TIP-191 v2 message sign/verify.
Package key provides message signing for TRON: Signer implementations backed by a hex private key or an HD wallet (BIP-39 mnemonic + derivation path), plus TIP-191 v2 message sign/verify.
pb
api
Smart contract related gRPC calls (1:1 port of lowlevel/contract.go).
Smart contract related gRPC calls (1:1 port of lowlevel/contract.go).
Package token is the TRC-20 layer of tronlib v2: a Handle pins one token contract with its decimals fetched eagerly at construction, and Amount is an exact immutable amount in the token's atomic unit.
Package token is the TRC-20 layer of tronlib v2: a Handle pins one token contract with its decimals fetched eagerly at construction, and Amount is an exact immutable amount in the token's atomic unit.
Package tron is the v2 vocabulary package: the shared types every other v2 package speaks.
Package tron is the v2 vocabulary package: the shared types every other v2 package speaks.
Package tx is the v2 transaction pipeline: build → optionally simulate → sign → broadcast, expressed as types so each stage's output is the next stage's input and illegal transitions do not compile (architecture §6).
Package tx is the v2 transaction pipeline: build → optionally simulate → sign → broadcast, expressed as types so each stage's output is the next stage's input and illegal transitions do not compile (architecture §6).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL