Blockchain

Guia Completo de Go — Blockchain

Blockchain em Go: criptografia, go-ethereum (ethclient), abigen, EIP-1559, mempool, The Graph.

Progresso
0%

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.

Dica: Para produção, use um node gerenciado como 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)
}
EIP-191 / personal_sign: para assinar mensagens humanas (MetaMask), prefixe com "\x19Ethereum Signed Message:\n" + len(msg) + msg antes do Keccak. Isso evita que a mensagem colida com o hash de uma transação real.
Nunca commite chaves privadas em Git, logs ou variáveis de ambiente expostas. Use KMS (AWS KMS, GCP KMS, HashiCorp Vault) ou HSM em produção. Uma chave vazada é game over — não há recuperação.

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/bind

Conectar 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())
    }
  }
}
Cache do client: o 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.go

Chamar 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
_ = instance

Decodificar 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)
}
Testnets: use 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 / 100

EIP-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)
Sempre use EIP-1559 em mainnet. Transações legacy com 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.
Multi-sig: para fundos maiores, use 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.

Reorgs: sempre espere 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.
Segurança: contratos imutáveis significam que bugs são permanentes. Sempre faça auditoria (OpenZeppelin, Trail of Bits), use testes de fuzz (Foundry), e considere upgradeable patterns (UUPS proxy) para contratos críticos.
Go — Básico← Todos os treinamentos