Lesson 02 · Web3 with Go & Polygon
Before an investor can buy a share of your client's property, your backend needs to
answer a very ordinary question: how much does this address hold? Does
the buyer have enough POL to pay the gas? Later — once shares are a token —
the exact same call tells you how many shares an address owns. Reading a balance
is the first read your product actually cares about.
wei, then converted to human POL. This is the pattern behind
every "your balance is…" line your app will ever show.
On Polygon (and every EVM chain) an account is just an entry in the
chain's giant ledger, named by a 20-byte address written as 40 hex characters:
0x71C7…976F. There are two kinds — and the difference matters for your
product:
BalanceAt works the same on both. Today we read an EOA's native
POL balance — the gas money.
The chain never stores decimals. A balance is always a whole number of wei,
the smallest unit, where 1 POL = 1,000,000,000,000,000,000 wei (that's
1018). Think of wei as "cents", except there are a quintillion of them
per coin. That's exactly why balances come back as *big.Int — the number is
far too big for a normal int64. You met big.Int in Lesson 01;
here's why it's unavoidable.
Pick any address that has funds on Amoy — your own faucet wallet, or a random active one copied from Amoy Polygonscan.
main.go
package main
import (
"context"
"fmt"
"log"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rpc-amoy.polygon.technology/")
if err != nil {
log.Fatalf("could not connect: %v", err)
}
defer client.Close()
// Turn the human hex string into a typed 20-byte address.
account := common.HexToAddress("0x0000000000000000000000000000000000001010")
// nil block number = "the latest block". Returns wei as *big.Int.
wei, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatalf("could not read balance: %v", err)
}
fmt.Printf("balance (wei): %s\n", wei)
// Convert wei -> POL for humans. big.Float, because we're dividing by 1e18.
fbalance := new(big.Float).SetInt(wei)
pol := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Printf("balance (POL): %s\n", pol.Text('f', 6))
}
Run it:
terminal
go run main.go
balance (wei): 1943500000000000000
balance (POL): 1.943500
common.HexToAddress is not just a cast. It parses the hex
into a fixed 20-byte value and is the type every go-ethereum method expects.
Passing a raw string won't compile — addresses are a real type here, which stops a whole
class of typo bugs.
The nil block number means "latest". Because balances live in
state, and state exists at every block, you can ask "what was this balance 10,000
blocks ago?" by passing a block number instead. That's how you'll later audit ownership at
the moment a sale closed. Powerful, and free to read.
0 — that's correct, not an error.
If you want a non-zero result, grab test POL from the
Amoy faucet for your own
wallet first (we generate that wallet in Lesson 05).
No peeking — recall from memory. The effort is the point.
1. How many wei are in one POL?
2. An account controlled by a private key is called a…
3. What does passing nil as the block number mean?
BalanceAt and wei conversion, straight from our backbone book.
I'm your teacher — ask me anything. Want to see the balance at a past block? Curious why the chain refuses to store decimals at all? Wondering how a token balance differs from this native balance? Ask away.