Arquitetura de dApps: on-chain, off-chain e híbrido na prática
Decisão arquitetural que importa: o que fica on-chain, o que fica off-chain e por quê.
Alienhub Team
Web3 Engineering

Toda decisão arquitetural em blockchain é: "isso vai on-chain ou off-chain?"
Resposta errada custa milhões em gas desnecessário, latência degradada ou perda de garantias de imutabilidade. Resposta certa? Seu produto rode rápido, barato e seguro. Vamos destrinchar.
O trade-off fundamental
On-chain: dados na blockchain. Imutável, verifiable, público. Caro (gas). Lento (13s+ por bloco).
Off-chain: dados em servidor/banco de dados. Rápido, barato, privado. Requer confiança. Pode sofrer censura.
Nada é "certo" universalmente. Dependência: seu problema, seu contexto.
Decisão: o que colocar on-chain?
Critério 1: State critico que precisa imutabilidade
Sim, on-chain:
- Saldo de usuário (token balance)
- Ownership (quem é dono de NFT/ativo)
- Regras de votação (governance)
- Proof-of-reserve (quanto você tem em vault)
Não, off-chain:
- Historico de transacao do usuario
- Metadata pesada (descrição, imagem, atributos)
- Dados de analytics (quando user clicou, quanto tempo ficou)
- User profile (nome, bio, avatar)
Regra: se mudar state deve impactar toda a rede de forma verificável, on-chain. Se for apenas referencia, off-chain.
Critério 2: Frequência de acesso
Alto acesso (muitas transacoes por segundo)? Off-chain. Gas soma rápido.
Exemplo: order book de DEX. Entrada/saída de ordens: centenas por segundo. Impossível on-chain. Solução: order book off-chain, settlement on-chain. Você vê ordens em servidor rápido; quando match, escreve na blockchain.
Baixo acesso (transacao por dia/semana)? On-chain é viável.
Exemplo: você votando em governance. 1 vez por proposta. Gas caro, mas tá tudo bem.
Critério 3: Necessidade de privacidade
Privado? Off-chain.
Blockchain é público. Cada transacao vista. Se seu app coleta dados sensíveis (medicacao, preferencia política, localizacao), off-chain (com HTTPS/TLS). Nunca publique.
Público/semi-publico? On-chain é OK.
Votação em DAO? Público. Saldo de USDC? Semi-público (só endereço vê detalhes).
Arquitetura típica de dApp em 2026
USUARIO
↓
┌─────────────┐
│ Frontend │ (React/Next.js)
│ (Viem.js) │
└──────┬──────┘
│
├───────────→ Smart Contracts (ETH/L2)
│
└───────────→ API BFF (Backend For Frontend)
├─→ The Graph / Indexer (GraphQL)
├─→ Database (user data, metadata)
└─→ External services (email, storage)
Explicando cada layer:
Layer 1: Frontend (Viem, Wagmi)
Codigo rodando no browser. Responsabilidades:
- Conectar wallet (MetaMask, Coinbase Wallet, etc)
- Assinar transacoes
- Ler estado local da blockchain (balance, allowance)
- Chamar smart contracts
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: http(),
});
// Ler saldo de usuario
const balance = await client.readContract({
address: "0xUSDC",
abi: ERC20_ABI,
functionName: "balanceOf",
args: ["0xUserAddress"],
});
// Escrever na blockchain (precisa de assinatura)
await walletClient.writeContract({
account: userAddress,
address: "0xUSDC",
abi: ERC20_ABI,
functionName: "transfer",
args: ["0xRecipient", parseUnits("100", 6)],
});
Trade-off: frontend ve estado "fresh" da blockchain, mas opera sempre com lag (não ve memppool em tempo real, só blocks).
Layer 2: Smart Contracts
Regras de negocio que precisam verificabilidade.
Exemplo (Solidity):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleTransfer {
mapping(address => uint256) public balances;
function deposit(uint256 amount) external {
require(amount > 0, "Amount > 0");
balances[msg.sender] += amount;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Aqui: balances (state critico, imutavel, publicamente auditavel).
Layer 3: Indexer (The Graph, Goldsky)
Blockchain é "escreve nao indexa". Você pode escrever dados, mas ler e filtrar é lento.
Indexer: serviço que lê blocos, extrai dados relevantes, indexa em database.
# Exemplo: listar todas as transferencias de USDC de um usuario
query GetTransfers($user: String!) {
transfers(where: { from: $user }) {
id
from
to
amount
timestamp
}
}
Sem indexer: teria que iterar cada bloco (millions), filtrar logs manualmente. 10+ segundos. Com indexer: resposta em <100ms.
Trade-off: indexer é centralizado (risco). The Graph é descentralizado (mais lento). Escolha seu poison.
Layer 4: API BFF (Backend For Frontend)
Lógica que não cabe no frontend, não precisa ser on-chain.
Responsabilidades:
- Autenticacao (JWT, session)
- Rate limiting
- User profiling (qual usuario é quem)
- Business logic privada (calculo de fee, matchmaking)
- Integracao com externos (Stripe, SendGrid, S3)
// Express.js BFF
app.get("/api/user/profile", authenticateJWT, async (req, res) => {
const userDb = await db.users.findOne({ address: req.user.address });
// Combina dados de blockchain com dados privados
const onChainBalance = await ethClient.readContract({...});
res.json({
address: req.user.address,
name: userDb.name,
onChainBalance,
registeredAt: userDb.createdAt,
});
});
Este é o lugar onde metadata pesada fica.
Padrões híbridos comuns
Padrão 1: Order Book Off-chain, Settlement On-chain
User A espera vender 100 USDC por 0.05 ETH
→ Cria ordem no servidor (off-chain book)
User B espera comprar 100 USDC por 0.05 ETH
→ Cria ordem no servidor (match!)
→ Servidor emite "settlement order" assinada
→ Frontend de A ou B clica "executar"
→ Smart contract transfere USDC e ETH atomicamente
→ Tudo final em blockchain
Exemplo: Uniswap V4 Hooks, CoW Protocol (Coincidence of Wants).
Padrão 2: State Channels (Plasma anterior)
User cria "canal" com provider (2-of-2 multisig)
→ Transacoes só requerem assinatura, não blockchain
→ Estado atualizado off-chain
→ Quando fechar canal, estado final vai on-chain
Exemplo: Lightning Network (Bitcoin), Connext (Ethereum).
Real em 2026? Pouco. Layer 2s (Arbitrum, Optimism) fizeram isso obsoleto.
Padrão 3: Merkle Trees pra provar inclusão
Off-chain: lista de 1 milhão de endereços elegíveis pra airdrop
On-chain: só root hash da Merkle tree (~32 bytes)
User clama: prova inclusion em 20 hashes (~640 bytes)
Smart contract valida: √(claim legítimo)
Barato, escalavel, imutável.
L1 vs L2: onde rodar smart contracts?
| Metrica | Mainnet (L1) | Arbitrum | Optimism | zkSync |
|---|---|---|---|---|
| Gas por transfer | 21k | ~400 | ~1800 | ~300 |
| Custo em $ | $3-15 | $0.01-0.05 | $0.05-0.2 | $0.01-0.03 |
| Latência | 13s | 250ms confirm | 2s confirm | 100ms confirm |
| Segurança | Max (settlment L1) | High (fraud proofs) | High (optimism) | High (zk-proof) |
| Liquidez | Max | High | High | Growing |
Regra 2026:
- Muito gas necessário? L2. Arbitrum (liquidez, volume). Optimism (Coinbase + ecosystem). zkSync (caso super-específico com volume extremo).
- Low gas OK, precisa max segurança? Mainnet L1.
- Low gas, novo experimento? Arbitrum Sepolia, Optimism Sepolia (testnets).
Evitar overengineering
Aqui vai a realidade brutal de 2026:
Problema: você quer escala extrema, imagina solução super-complexa (zk-rollup customizado, state channels, etc).
Realidade: Arbitrum resolve 90% dos casos com 100 linhas de Solidity e 1 deploy.
Overengineering síndrome: pensar que precisas inovar na infraestrutura. Não. Use o que existe (Arbitrum, Optimism, Safe). Inova no seu produto.
Exceção: você é app de "1 milhão de transacoes por segundo" (high-frequency trading, gaming AAA, social media on-chain). Aí sim, custom L2 pode fazer sense.
"O melhor sistema é sempre o mais simples que resolve o problema." — Occam, blade ainda afiada em 2026.
Workflow real: seu dApp
1. User conecta wallet (frontend)
2. Frontend lê saldo (smart contract via Viem)
3. User clica "comprar" (frontend)
4. Frontend manda intent pra BFF (API call)
5. BFF valida, checa inventário, calcula fee
6. BFF retorna transacao assinada (ERC-2612 pra gasless exemplo)
7. Frontend submete transacao (blockchain)
8. Blockchain executa, emite evento
9. Indexer pega evento, actualiza database
10. BFF retorna "order criada" (latência ~2s)
11. User ve confirmacao em tempo real
Cada camada serve propósito. Nenhuma é desnecessária.
Checklist: design arquitetura sua dApp
- Identifiquei state que PRECISA on-chain (ownership, balances, rules)
- Identifiquei dados que podem ficar off-chain (metadata, user profile, history)
- Escolhi L1 ou L2 baseado em gas esperado
- Decidi como indexar dados (The Graph? Goldsky? Custom?)
- Decidi BFF/API pattern (ou frontend-only pra MVP)
- Mapeei fluxo usuario (user→frontend→BFF→chain→indexer→frontend)
- Considerei latência em cada hop
- Planejei fallback se indexer down (cache frontend? Usar on-chain data?)
Na Alienhub, fazemos isso todo dia: desenhar arquitetura que e rápida, barata e segura. Escolhemos camadas certas pra seu produto. Se você tá construindo dApp e achando que precisa de estado channel + zk-proof + L2 customizada, conversa com a gente. Provavelmente só precisa de Viem + Arbitrum + The Graph.
Construindo seu SaaS?
Receba insights semanais sobre produto, tecnologia e negócios para fundadores de SaaS e Micro-SaaS.
Continue Lendo

