Fundamentos de Blockchain
Blockchain é uma estrutura de dados append-only, distribuída e imutável, formada por blocos encadeados via hash criptográfico. Cada bloco contém um conjunto de transações, um timestamp e o hash do bloco anterior — alterar qualquer byte quebra toda a cadeia subsequente.
Hash e encadeamento
A função de hash usada no Ethereum é Keccak-256 (variante do SHA-3). Ela transforma qualquer entrada em um digest de 32 bytes determinístico, unidirecional e resistente a colisões.
// Hash Keccak-256 com go-ethereum
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
data := []byte("hello blockchain")
// Keccak-256 — usado internamente pelo Ethereum
hash := crypto.Keccak256Hash(data)
fmt.Printf("Keccak-256: %s\n", hash.Hex())
// 0x... 32 bytes em hex
// Hash puro como []byte
raw := crypto.Keccak256(data)
fmt.Printf("raw bytes: %x (len=%d)\n", raw, len(raw))
// Converter para common.Hash
h := common.BytesToHash(raw)
fmt.Println("as Hash:", h)
}Merkle Tree
Árvore binária onde cada folha é o hash de uma transação e cada nó interno é o hash da concatenação de seus filhos. A raiz (Merkle root) resume todas as transações em 32 bytes — permite provar inclusão sem baixar o bloco inteiro (Merkle proofs usadas por light clients).
// Merkle root simples — demonstração didática
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/crypto"
)
func merkleRoot(leaves [][]byte) []byte {
if len(leaves) == 0 {
return nil
}
level := make([][]byte, len(leaves))
for i, leaf := range leaves {
level[i] = crypto.Keccak256(leaf)
}
for len(level) > 1 {
if len(level)%2 != 0 {
level = append(level, level[len(level)-1]) // duplica último
}
next := make([][]byte, 0, len(level)/2)
for i := 0; i < len(level); i += 2 {
combined := append(level[i], level[i+1]...)
next = append(next, crypto.Keccak256(combined))
}
level = next
}
return level[0]
}
func main() {
txs := [][]byte{[]byte("tx1"), []byte("tx2"), []byte("tx3"), []byte("tx4")}
root := merkleRoot(txs)
fmt.Printf("merkle root: %x\n", root)
}Estrutura de um bloco Ethereum
- Header: parentHash, stateRoot, txRoot, receiptsRoot, number, gasLimit, gasUsed, timestamp, baseFeePerGas (EIP-1559).
- Body: lista de transações e uncles (pré-Merge).
- stateRoot: raiz da Merkle Patricia Trie do estado global (saldos, código, storage de contratos).
Consenso: PoW vs PoS
- Proof of Work (Bitcoin, Ethereum até 2022): mineradores competem resolvendo puzzle criptográfico. Seguro mas consome muita energia.
- Proof of Stake (Ethereum pós-Merge): validadores são escolhidos proporcionalmente ao stake (32 ETH mínimo). Slashing penaliza mau comportamento. Gasta ~99,95% menos energia.
- Finalidade: PoS tem finalidade probabilística após ~2 épocas (~12,8 min no Ethereum).
Ethereum vs Bitcoin
Bitcoin
UTXO model, script limitado, foco em dinheiro digital. Bloco a cada ~10 min, ~7 TPS.
Ethereum
Account model, EVM Turing-completa, smart contracts. Bloco a cada ~12s, ~15-30 TPS na L1.
Alchemy, Infura ou QuickNode em vez de rodar seu próprio geth. Um node full sincronizado consome ~1TB de SSD e largura de banda considerável.Criptografia & Wallets
Ethereum usa ECDSA (Elliptic Curve Digital Signature Algorithm) sobre a curva secp256k1 — a mesma do Bitcoin. Cada conta tem uma chave privada de 32 bytes que deriva deterministicamente a chave pública e o endereço.
Gerar chave privada e endereço
package main
import (
"crypto/ecdsa"
"fmt"
"log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
// Gera chave privada secp256k1 aleatória
privateKey, err := crypto.GenerateKey()
if err != nil {
log.Fatal(err)
}
// Serializa privada em hex — NUNCA logar em produção
privBytes := crypto.FromECDSA(privateKey)
fmt.Println("Private:", hexutil.Encode(privBytes))
// Deriva chave pública
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("erro ao derivar pubkey")
}
pubBytes := crypto.FromECDSAPub(publicKeyECDSA)
fmt.Println("Public: ", hexutil.Encode(pubBytes))
// Endereço = últimos 20 bytes de Keccak256(pubKey[1:])
address := crypto.PubkeyToAddress(*publicKeyECDSA)
fmt.Println("Address:", address.Hex()) // 0x... checksum EIP-55
}Importar chave a partir de hex
import "github.com/ethereum/go-ethereum/crypto"
// A partir de string hex (sem o prefixo 0x)
priv, err := crypto.HexToECDSA("ac09...64 hex chars...bf")
if err != nil {
return err
}
// Também possível via crypto.ToECDSA(bytes)
// Use HexToECDSAUnsafe apenas em testes — ignora validação de comprimento.
addr := crypto.PubkeyToAddress(priv.PublicKey)
fmt.Println(addr.Hex())HD Wallets (BIP-32 / BIP-39 / BIP-44)
Wallets determinísticas hierárquicas derivam infinitas chaves a partir de uma seed de 12 ou 24 palavras (mnemonic). O path padrão Ethereum é m/44'/60'/0'/0/N.
// go get github.com/tyler-smith/go-bip39
// go get github.com/tyler-smith/go-bip32
package main
import (
"fmt"
"log"
"github.com/ethereum/go-ethereum/crypto"
bip32 "github.com/tyler-smith/go-bip32"
bip39 "github.com/tyler-smith/go-bip39"
)
func main() {
// 1) Gerar entropia de 128 bits → 12 palavras
entropy, _ := bip39.NewEntropy(128)
mnemonic, _ := bip39.NewMnemonic(entropy)
fmt.Println("Mnemonic:", mnemonic)
// 2) Seed a partir do mnemonic (com passphrase opcional)
seed := bip39.NewSeed(mnemonic, "")
// 3) Master key BIP-32
masterKey, err := bip32.NewMasterKey(seed)
if err != nil {
log.Fatal(err)
}
// 4) Derivar path m/44'/60'/0'/0/0 (Ethereum, conta 0, endereço 0)
purpose, _ := masterKey.NewChildKey(bip32.FirstHardenedChild + 44)
coinType, _ := purpose.NewChildKey(bip32.FirstHardenedChild + 60)
account, _ := coinType.NewChildKey(bip32.FirstHardenedChild + 0)
change, _ := account.NewChildKey(0)
addrKey, _ := change.NewChildKey(0)
// 5) Converter em *ecdsa.PrivateKey
priv, err := crypto.ToECDSA(addrKey.Key)
if err != nil {
log.Fatal(err)
}
fmt.Println("Address 0:", crypto.PubkeyToAddress(priv.PublicKey).Hex())
}Assinar e verificar mensagens
package main
import (
"fmt"
"log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
priv, _ := crypto.GenerateKey()
message := []byte("Kaique assina esta mensagem")
hash := crypto.Keccak256Hash(message)
// Assinatura ECDSA (65 bytes: R || S || V)
sig, err := crypto.Sign(hash.Bytes(), priv)
if err != nil {
log.Fatal(err)
}
fmt.Println("Signature:", hexutil.Encode(sig))
// Recuperar chave pública a partir da assinatura
recoveredPub, err := crypto.SigToPub(hash.Bytes(), sig)
if err != nil {
log.Fatal(err)
}
recoveredAddr := crypto.PubkeyToAddress(*recoveredPub)
fmt.Println("Recovered:", recoveredAddr.Hex())
// Verificar equivale a: recoveredAddr == crypto.PubkeyToAddress(priv.PublicKey)
ok := crypto.VerifySignature(
crypto.CompressPubkey(&priv.PublicKey),
hash.Bytes(),
sig[:64], // sem o byte V
)
fmt.Println("Valid:", ok)
}"\x19Ethereum Signed Message:\n" + len(msg) + msg antes do Keccak. Isso evita que a mensagem colida com o hash de uma transação real.Go + Ethereum com go-ethereum
A biblioteca oficial github.com/ethereum/go-ethereum é o próprio código do cliente geth exposto como módulos Go. O pacote ethclient fala JSON-RPC com qualquer node Ethereum (local, Infura, Alchemy).
Instalação
# Go 1.23+
go mod init meu-dapp
go get github.com/ethereum/[email protected]
# Subpacotes comumente importados:
# - github.com/ethereum/go-ethereum/ethclient
# - github.com/ethereum/go-ethereum/common
# - github.com/ethereum/go-ethereum/crypto
# - github.com/ethereum/go-ethereum/core/types
# - github.com/ethereum/go-ethereum/accounts/abi/bindConectar ao RPC
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// Alchemy, Infura, ou node local (http://localhost:8545)
rpcURL := "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
client, err := ethclient.Dial(rpcURL)
if err != nil {
log.Fatalf("dial: %v", err)
}
defer client.Close()
chainID, err := client.ChainID(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("Chain ID:", chainID) // mainnet=1, sepolia=11155111
}Consultar saldo de uma conta
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
)
func main() {
client, err := ethclient.Dial("https://eth-mainnet.g.alchemy.com/v2/KEY")
if err != nil {
log.Fatal(err)
}
// Vitalik.eth
addr := common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
// Saldo no bloco mais recente (nil = latest)
wei, err := client.BalanceAt(context.Background(), addr, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Wei: %s\n", wei.String())
// Converter para ETH (1 ether = 10^18 wei)
ether := new(big.Float).Quo(
new(big.Float).SetInt(wei),
new(big.Float).SetInt(big.NewInt(params.Ether)),
)
fmt.Printf("Ether: %s\n", ether.Text('f', 6))
}Ler blocos e transações
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, _ := ethclient.Dial("https://eth-mainnet.g.alchemy.com/v2/KEY")
ctx := context.Background()
// Header do último bloco (leve)
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Block: ", header.Number.String())
fmt.Println("BaseFee: ", header.BaseFee)
fmt.Println("GasUsed: ", header.GasUsed)
fmt.Println("Timestamp:", header.Time)
// Bloco completo (com txs) por número
block, err := client.BlockByNumber(ctx, big.NewInt(19_000_000))
if err != nil {
log.Fatal(err)
}
fmt.Println("Txs:", len(block.Transactions()))
// Ler transação específica
txHash := common.HexToHash("0xabc...")
tx, isPending, err := client.TransactionByHash(ctx, txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println("Pending:", isPending)
if tx.To() != nil {
fmt.Println("To: ", tx.To().Hex())
}
fmt.Println("Value: ", tx.Value())
fmt.Println("Nonce: ", tx.Nonce())
fmt.Println("GasLim: ", tx.Gas())
// Receipt = status + logs após execução
receipt, err := client.TransactionReceipt(ctx, txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println("Status: ", receipt.Status) // 1 = success, 0 = revert
fmt.Println("Logs: ", len(receipt.Logs))
}Subscribir novos blocos (WebSocket)
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// WebSocket (wss://) é obrigatório para subscriptions
client, err := ethclient.Dial("wss://eth-mainnet.g.alchemy.com/v2/KEY")
if err != nil {
log.Fatal(err)
}
defer client.Close()
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case h := <-headers:
fmt.Printf("novo bloco: %s hash=%s\n", h.Number, h.Hash().Hex())
}
}
}ethclient.Client é goroutine-safe. Crie um único por URL e reutilize — cada Dial abre conexão TCP/HTTP nova e custa handshake.Smart Contracts & ABI
Smart contracts no Ethereum são escritos geralmente em Solidity, compilados para bytecode EVM e executados determinísticamente por todos os nodes. A ABI (Application Binary Interface) descreve as funções do contrato em JSON — Go usa isso para codificar/decodificar chamadas.
Contrato Solidity simples
// Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Counter {
uint256 public count;
event Incremented(address indexed by, uint256 newCount);
function increment() external {
count += 1;
emit Incremented(msg.sender, count);
}
function add(uint256 n) external {
count += n;
emit Incremented(msg.sender, count);
}
}Compilar e gerar bindings Go com abigen
# 1) Compilar (solc 0.8.20+)
solc --abi --bin Counter.sol -o build/
# 2) Instalar abigen (vem com go-ethereum)
go install github.com/ethereum/go-ethereum/cmd/abigen@latest
# 3) Gerar Counter.go com bindings tipados
abigen \
--abi build/Counter.abi \
--bin build/Counter.bin \
--pkg contracts \
--type Counter \
--out contracts/Counter.goChamar funções view (sem gas)
package main
import (
"fmt"
"log"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"meu-dapp/contracts" // gerado pelo abigen
)
func main() {
client, err := ethclient.Dial("https://sepolia.infura.io/v3/KEY")
if err != nil {
log.Fatal(err)
}
// Endereço do contrato já deployado
addr := common.HexToAddress("0x1234...")
counter, err := contracts.NewCounter(addr, client)
if err != nil {
log.Fatal(err)
}
// Chamada view — sem tx, sem gas
count, err := counter.Count(&bind.CallOpts{})
if err != nil {
log.Fatal(err)
}
fmt.Println("Count atual:", count.String())
}Enviar transação (state-changing)
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"meu-dapp/contracts"
)
func main() {
client, _ := ethclient.Dial("https://sepolia.infura.io/v3/KEY")
ctx := context.Background()
priv, err := crypto.HexToECDSA("YOUR_PRIVATE_KEY_HEX")
if err != nil {
log.Fatal(err)
}
chainID, err := client.ChainID(ctx)
if err != nil {
log.Fatal(err)
}
// TransactOpts assina txs automaticamente
auth, err := bind.NewKeyedTransactorWithChainID(priv, chainID)
if err != nil {
log.Fatal(err)
}
auth.Context = ctx
auth.GasLimit = uint64(300_000) // opcional — estimado se 0
addr := common.HexToAddress("0xCONTRACT_ADDRESS")
counter, _ := contracts.NewCounter(addr, client)
// Enviar tx
tx, err := counter.Add(auth, big.NewInt(5))
if err != nil {
log.Fatal(err)
}
fmt.Println("Tx enviada:", tx.Hash().Hex())
// Esperar confirmação
receipt, err := bind.WaitMined(ctx, client, tx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Confirmada no bloco:", receipt.BlockNumber)
}Deploy de contrato via Go
import "meu-dapp/contracts"
// DeployCounter também é gerado pelo abigen quando .bin é fornecido
addr, tx, instance, err := contracts.DeployCounter(auth, client)
if err != nil {
log.Fatal(err)
}
fmt.Println("Deploy em:", addr.Hex())
// Aguardar mineração do deploy
if _, err := bind.WaitDeployed(ctx, client, tx); err != nil {
log.Fatal(err)
}
// instance já pode ser usado para chamadas imediatas
_ = instanceDecodificar eventos emitidos
// O abigen gera também helpers "Filter" e "Watch" para cada evento
iter, err := counter.FilterIncremented(
&bind.FilterOpts{Start: 19_000_000, End: nil, Context: ctx},
nil, // filtrar por address indexed (nil = todos)
)
if err != nil {
log.Fatal(err)
}
defer iter.Close()
for iter.Next() {
ev := iter.Event
fmt.Printf("by=%s newCount=%s block=%d\n",
ev.By.Hex(), ev.NewCount.String(), ev.Raw.BlockNumber)
}
if err := iter.Error(); err != nil {
log.Fatal(err)
}Sepolia para desenvolvimento (gas grátis via faucets). Evite mainnet até ter auditoria. Para local, rode anvil (Foundry) ou hardhat node — velocidade instantânea e contas pré-financiadas.Transações & Gas
Toda transação no Ethereum paga gas — combustível que mede o custo computacional. Desde EIP-1559 (agosto/2021), transações têm dois campos: MaxPriorityFeePerGas (tip para o validador) e MaxFeePerGas (teto total, incluindo baseFee queimada).
Tipos de transação
- Legacy (type 0x00): campo
gasPriceúnico — ainda aceito mas desencorajado. - EIP-2930 (type 0x01): legacy + access lists para economizar gas em storage.
- EIP-1559 (type 0x02): padrão moderno — baseFee + priority tip.
- EIP-4844 (type 0x03): blob transactions para rollups (Cancun, março/2024).
Enviar ETH bruto com EIP-1559
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, _ := ethclient.Dial("https://sepolia.infura.io/v3/KEY")
ctx := context.Background()
priv, _ := crypto.HexToECDSA("PRIV_HEX")
from := crypto.PubkeyToAddress(priv.PublicKey)
to := common.HexToAddress("0xRECEIVER")
// 1) Nonce atual (pending inclui txs já submetidas)
nonce, err := client.PendingNonceAt(ctx, from)
if err != nil {
log.Fatal(err)
}
// 2) Sugestões de gas (EIP-1559)
tip, err := client.SuggestGasTipCap(ctx)
if err != nil {
log.Fatal(err)
}
header, _ := client.HeaderByNumber(ctx, nil)
// maxFee = 2 * baseFee + tip (margem de segurança)
maxFee := new(big.Int).Add(
new(big.Int).Mul(header.BaseFee, big.NewInt(2)),
tip,
)
chainID, _ := client.ChainID(ctx)
// 3) Montar tx EIP-1559 (DynamicFeeTx)
value := new(big.Int).Mul(big.NewInt(10_000_000_000_000_000), big.NewInt(1)) // 0.01 ETH em wei
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: tip,
GasFeeCap: maxFee,
Gas: 21_000, // transfer simples
To: &to,
Value: value,
Data: nil,
})
// 4) Assinar
signed, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), priv)
if err != nil {
log.Fatal(err)
}
// 5) Enviar
if err := client.SendTransaction(ctx, signed); err != nil {
log.Fatal(err)
}
fmt.Println("Tx:", signed.Hash().Hex())
}Gerência de nonce
Nonce é o contador de transações da conta — deve ser sequencial. Erros comuns:
nonce too low: nonce já foi usado.nonce too high: pulou números — tx fica pendente até o gap fechar.- Várias txs em paralelo exigem gerenciar nonces localmente (mutex + contador).
// Nonce manager simples para envios concorrentes
package wallet
import (
"context"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
type NonceManager struct {
mu sync.Mutex
client *ethclient.Client
from common.Address
next uint64
inited bool
}
func (n *NonceManager) Next(ctx context.Context) (uint64, error) {
n.mu.Lock()
defer n.mu.Unlock()
if !n.inited {
cur, err := n.client.PendingNonceAt(ctx, n.from)
if err != nil {
return 0, err
}
n.next = cur
n.inited = true
}
nonce := n.next
n.next++
return nonce, nil
}Estimar gas de antemão
import "github.com/ethereum/go-ethereum"
msg := ethereum.CallMsg{
From: from,
To: &to,
Value: big.NewInt(0),
Data: calldata, // ABI-encoded
}
gasLimit, err := client.EstimateGas(ctx, msg)
if err != nil {
return fmt.Errorf("estimate: %w", err) // reverts vazam aqui
}
// Adicione ~20% de buffer contra mudanças de storage entre estimate e envio
gasLimit = gasLimit * 120 / 100EIP-712 — typed structured data signing
Padrão para assinatura de dados estruturados legíveis por humanos (usado em Permit, OpenSea orders, gasless txs).
import (
"math/big"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)
typed := apitypes.TypedData{
Types: apitypes.Types{
"EIP712Domain": {
{Name: "name", Type: "string"},
{Name: "version", Type: "string"},
{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"Mail": {
{Name: "from", Type: "address"},
{Name: "to", Type: "address"},
{Name: "body", Type: "string"},
},
},
PrimaryType: "Mail",
Domain: apitypes.TypedDataDomain{
Name: "MeuDApp",
Version: "1",
ChainId: (*math.HexOrDecimal256)(big.NewInt(1)),
VerifyingContract: "0xContract",
},
Message: apitypes.TypedDataMessage{
"from": from.Hex(),
"to": to.Hex(),
"body": "hello",
},
}
hash, _, err := apitypes.TypedDataAndHash(typed)
if err != nil {
return err
}
sig, err := crypto.Sign(hash, priv)gasPrice alto frequentemente pagam mais que o necessário — maxFeePerGas + baseFee dinâmico garante que você só paga o justo, com o tip controlando prioridade.Safe (antigo Gnosis Safe) — exige N de M assinaturas para mover fundos. Nunca deixe tesouraria sob EOA única.Web3 Avançado
Indexação, streaming de eventos, mempool watching, IPFS e otimizações MEV — a fronteira onde Go brilha por sua concorrência e performance.
Filtrar eventos históricos
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, _ := ethclient.Dial("https://eth-mainnet.g.alchemy.com/v2/KEY")
ctx := context.Background()
// USDC (ERC-20)
usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
// Topic[0] = keccak256("Transfer(address,address,uint256)")
transferSig := crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)"))
query := ethereum.FilterQuery{
FromBlock: big.NewInt(19_000_000),
ToBlock: big.NewInt(19_000_100),
Addresses: []common.Address{usdc},
Topics: [][]common.Hash{{transferSig}},
}
logs, err := client.FilterLogs(ctx, query)
if err != nil {
log.Fatal(err)
}
for _, l := range logs {
from := common.BytesToAddress(l.Topics[1].Bytes())
to := common.BytesToAddress(l.Topics[2].Bytes())
amount := new(big.Int).SetBytes(l.Data)
fmt.Printf("block %d %s → %s amt %s\n",
l.BlockNumber, from.Hex(), to.Hex(), amount.String())
}
}Stream ao vivo de eventos
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("wss://eth-mainnet.g.alchemy.com/v2/KEY")
if err != nil {
log.Fatal(err)
}
usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
sig := crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)"))
query := ethereum.FilterQuery{
Addresses: []common.Address{usdc},
Topics: [][]common.Hash{{sig}},
}
logs := make(chan types.Log)
sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case l := <-logs:
fmt.Printf("novo transfer no bloco %d tx %s\n",
l.BlockNumber, l.TxHash.Hex())
}
}
}Mempool — transações pendentes
package main
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)
func main() {
// RPC bruto — newPendingTransactions não tem wrapper tipado
rpcClient, err := rpc.Dial("wss://...")
if err != nil {
panic(err)
}
client := ethclient.NewClient(rpcClient)
ctx := context.Background()
ch := make(chan common.Hash)
sub, err := rpcClient.EthSubscribe(ctx, ch, "newPendingTransactions")
if err != nil {
panic(err)
}
defer sub.Unsubscribe()
whaleThreshold := new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) // 1 ETH
for hash := range ch {
tx, _, _ := client.TransactionByHash(ctx, hash)
if tx == nil || tx.To() == nil {
continue
}
if tx.Value().Cmp(whaleThreshold) > 0 {
fmt.Println("whale pending:", hash.Hex(), "value:", tx.Value())
}
}
}The Graph — indexação produtiva
Indexar manualmente milhões de eventos é lento e caro. The Graph roda subgraphs que indexam contratos e expõem uma API GraphQL. Consumir do Go:
package thegraph
import (
"bytes"
"encoding/json"
"io"
"net/http"
)
const url = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
type gqlReq struct {
Query string `json:"query"`
}
func Query(q string) ([]byte, error) {
body, _ := json.Marshal(gqlReq{Query: q})
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// Exemplo: top 10 pools Uniswap V3 por TVL
// data, _ := Query(`{
// pools(first: 10, orderBy: totalValueLockedUSD, orderDirection: desc) {
// id
// token0 { symbol }
// token1 { symbol }
// totalValueLockedUSD
// }
// }`)IPFS — armazenamento descentralizado
// go get github.com/ipfs/go-ipfs-api
package main
import (
"fmt"
"io"
"log"
"strings"
shell "github.com/ipfs/go-ipfs-api"
)
func main() {
sh := shell.NewShell("localhost:5001") // ou gateway (infura/pinata)
// Upload
cid, err := sh.Add(strings.NewReader("hello IPFS from Go"))
if err != nil {
log.Fatal(err)
}
fmt.Println("CID:", cid) // Qm... ou bafy...
// Download
r, err := sh.Cat(cid)
if err != nil {
log.Fatal(err)
}
content, _ := io.ReadAll(r)
fmt.Println("Content:", string(content))
}MEV — o lado obscuro
Maximal Extractable Value é o valor que validadores e searchers podem extrair reordenando, incluindo ou censurando transações.
- Sandwich: front-run + back-run de swaps grandes em DEX.
- Arbitragem: spread entre Uniswap / Sushi / Curve.
- Liquidações: competir para liquidar posições unhealthy no Aave / Compound.
- Flashbots / MEV-Boost: envio privado de bundles via
eth_sendBundle— evita mempool público.
Ferramentas do ecossistema Go
go-ethereum
Cliente oficial + SDK. ethclient, abigen, crypto, rlp.
Foundry (cast / anvil)
Toolchain em Rust — anvil como dev node, cast para RPC em CLI, forge para testes Solidity.
Flashbots mev-geth
Fork do geth com suporte a bundles privados. Base de quase todo searcher em Go.
Prysm / Lighthouse
Clientes de consensus layer (PoS). Prysm é escrito em Go, Lighthouse em Rust.
12+ confirmações antes de considerar uma tx final em mainnet. Indexadores devem armazenar blockHash junto com blockNumber — se o hash mudar numa leitura posterior, reverta o estado local.