BNB Smart Chain

BNB Smart Chain

Go

For Go developers, go-ethereum (universally known as geth) is the natural choice for working with BNB Smart Chain. It is the reference EVM implementation, so its client tracks protocol changes faster and more completely than any third-party Go binding, and BSC's geth-equivalent nodes accept its calls verbatim. Add it to your module with go get github.com/ethereum/go-ethereum and you gain typed access to blocks, BNB balances, and BEP-20 contracts on chain 56.

go-ethereum is maintained directly by the Ethereum Foundation, which is exactly why it carries weight for production Go services on BSC: bug fixes and new RPC methods land here first. For anyone building on BNB Smart Chain in Go, it delivers the most complete and current EVM toolkit available, from low-level transaction encoding to high-level contract bindings.

Go-Ethereum (geth)

One line connects you: ethclient.Dial("https://bsc.therpc.io/YOUR_API_KEY") returns a client bound to BNB Smart Chain that you can immediately use for balance and block queries. Rather than threading that raw client through every function, wrap it in your own struct. A small BSCClient type holding the *ethclient.Client keeps call sites tidy and lets you swap in a mock during tests.

package ethereum
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type EthereumClient struct {
client *ethclient.Client
}
func NewEthereumClient(url string) (*EthereumClient, error) {
client, err := ethclient.Dial(url)
if err != nil {
return nil, err
}
return &EthereumClient{client: client}, nil
}
func (ec *EthereumClient) GetBalance(address string) (*big.Float, error) {
account := common.HexToAddress(address)
balance, err := ec.client.BalanceAt(context.Background(), account, nil)
if err != nil {
return nil, err
}
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(1e18))
return ethValue, nil
}
func (ec *EthereumClient) SendTransaction(from, to common.Address, value *big.Int) (*types.Transaction, error) {
nonce, err := ec.client.PendingNonceAt(context.Background(), from)
if err != nil {
return nil, err
}
gasPrice, err := ec.client.SuggestGasPrice(context.Background())
if err != nil {
return nil, err
}
tx := types.NewTransaction(nonce, to, value, 21000, gasPrice, nil)
return tx, nil
}
  • GitHub: https://github.com/ethereum/go-ethereum
  • Docs: https://geth.ethereum.org/
  • A full node implementation covering the complete BNB Smart Chain protocol
  • High-performance, concurrency-friendly Go APIs suited to backend services
  • A suite of CLI tools, including abigen for contract bindings
  • Mobile build targets for embedding chain-56 access in apps

Smart Contract Integration

Go favours generated, type-safe contract access over dynamic dispatch. Run abigen against a Solidity ABI (for instance a BEP-20 token or a Venus market) and it emits a Go package with strongly typed methods mirroring the contract. Under the hood those wrappers lean on bind.BoundContract, which marries the ABI to a deployed address and gives you Call for read-only views and Transact for state-changing transactions on chain 56.

package contracts
import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type SmartContractClient struct {
contract *bind.BoundContract
address common.Address
}
func NewContract(address common.Address, client *ethclient.Client) (*SmartContractClient, error) {
parsed, err := abi.JSON(strings.NewReader(ContractABI))
if err != nil {
return nil, err
}
contract := bind.NewBoundContract(address, parsed, client, client, client)
return &SmartContractClient{
contract: contract,
address: address,
}, nil
}
func (sc *SmartContractClient) CallMethod(method string, args ...interface{}) error {
opts := &bind.CallOpts{
Pending: false,
Context: context.Background(),
}
return sc.contract.Call(opts, method, args...)
}

Event Monitoring

Live event streams such as SubscribeNewHead are push-based, which the HTTP transport simply cannot do. Subscriptions require a WebSocket connection to BNB Smart Chain, so dial the wss:// form of your endpoint when you need them. Once subscribed, drive the stream from a goroutine and use a select to react to two channels at once: one delivering each new chain-56 header, the other carrying any subscription error so you can reconnect cleanly.

func (ec *EthereumClient) MonitorBlocks() (<-chan *types.Header, error) {
headers := make(chan *types.Header)
sub, err := ec.client.SubscribeNewHead(context.Background(), headers)
if err != nil {
return nil, err
}
go func() {
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
block, err := ec.client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println("New block:", block.Number().Uint64())
}
}
}()
return headers, nil
}

Utils and Helpers

A word of caution on arithmetic: BNB amounts are denominated in wei, and a single BNB is 10^18 wei, far beyond what a float64 can represent without silent rounding errors. Always carry wei values as big.Int, and reach for big.Float only when you deliberately convert to a human-readable BNB figure for display. Store balances as plain floats and you take the classic route to losing dust or, worse, miscomputing a transfer on chain 56.

package utils
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// WeiToEther converts wei to ether
func WeiToEther(wei *big.Int) *big.Float {
return new(big.Float).Quo(
new(big.Float).SetInt(wei),
new(big.Float).SetInt(big.NewInt(1e18)),
)
}
// EtherToWei converts ether to wei
func EtherToWei(ether *big.Float) *big.Int {
truncInt, _ := new(big.Float).Mul(ether, big.NewFloat(1e18)).Int(nil)
return truncInt
}
// IsValidAddress checks if the address is valid
func IsValidAddress(address string) bool {
return common.IsHexAddress(address)
}

Ready to call this in production?

Free tier covers personal projects. Pay-as-you-go scales without a card.