Toda transação Solana define (implícita ou explicitamente) dois parâmetros: um limite de unidades de computação (máximo de CUs que a tx pode consumir; padrão 200.000 × número de instruções até um limite por tx) e uma taxa de prioridade em micro-lamports por CU. Dimensionar incorretamente qualquer um deles mata a transação — limites de CU muito baixos causam ProgramFailedToComplete; taxas de prioridade muito baixas fazem a tx ficar não confirmada até expirar.
As duas configurações
import { ComputeBudgetProgram } from "@solana/web3.js";
const tx = new Transaction()
.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }))
.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 }))
.add(yourRaydiumSwapIx);
setComputeUnitLimit(units) — limita computação; a transação paga por no máximo units CUs.
setComputeUnitPrice(microLamports) — lance de taxa de prioridade, em micro-lamports por CU. Taxa de prioridade total = units × microLamports × 1e-6 lamports.
Cálculo de custo: um limite de 250k CU a 50k micro-lamports/CU oferece 250_000 × 50_000 / 1e6 = 12.500 lamports ≈ 0,0000125 SOL ≈ $0,003 a $200 SOL. Taxas de prioridade nessa escala são ruído para a maioria dos swaps de usuários, mas significativas para bots fazendo 1000 txs/dia.
Benchmarks de CU por instrução
Benchmarks de logs de execução da mainnet, obtidos pela média de execuções recentes. Os números são aproximados (±15%); re-meça seus fluxos específicos.
| Instrução | SPL Token | Token-2022 (simples) | Token-2022 (taxa de transferência) |
|---|
| CPMM initialize_pool | 180.000 | 200.000 | — |
| CPMM swap_base_input | 140.000 | 180.000 | 200.000 |
| CPMM swap_base_output | 150.000 | 185.000 | 205.000 |
| CPMM deposit | 130.000 | 160.000 | 180.000 |
| CPMM withdraw | 120.000 | 150.000 | 170.000 |
| CLMM create_pool | 70.000 | 85.000 | — |
| CLMM open_position_v2 | 120.000 | 140.000 | 160.000 |
| CLMM increase_liquidity_v2 | 150.000 | 175.000 | 195.000 |
| CLMM decrease_liquidity_v2 | 140.000 | 165.000 | 185.000 |
| CLMM swap_v2 (0 cruzamentos de tick) | 170.000 | 205.000 | 225.000 |
| CLMM swap_v2 (1 cruzamento de tick) | 220.000 | 255.000 | 275.000 |
| CLMM swap_v2 (3 cruzamentos de tick) | 320.000 | 355.000 | 375.000 |
| CLMM collect_fee | 80.000 | 95.000 | 105.000 |
| AMM v4 swap_base_in | 140.000 | — | — |
| AMM v4 deposit | 120.000 | — | — |
| AMM v4 withdraw | 110.000 | — | — |
| Farm v6 create_farm | 70.000 | 85.000 | — |
| Farm v6 deposit (1 slot de recompensa) | 130.000 | 155.000 | 175.000 |
| Farm v6 deposit (3 slots de recompensa) | 220.000 | 255.000 | 275.000 |
| Farm v6 withdraw | igual ao deposit | | |
| Farm v6 harvest | igual ao deposit | | |
| Farm v3/v5 deposit | 100.000 | — | — |
| LaunchLab initialize | 100.000 | — | — |
| LaunchLab buy_exact_in | 140.000 | — | — |
| LaunchLab graduate | 250.000 | — | — |
A linha “cruzamentos de tick” para CLMM é a maior variável de CU. Se você não souber quantos ticks o swap vai cruzar, orce pelo pior caso — 8 cruzamentos é o limite rígido (o programa carrega no máximo 8 arrays de tick).
Transações compostas
Some os orçamentos individuais e adicione:
- +1.500 CU por frame de CPI — sobrecarga fixa do runtime para cada chamada entre programas.
- +20.000 CU por criação de ATA —
create_associated_token_account não é grátis.
- +5.000 CU para
setComputeUnitLimit / setComputeUnitPrice cada uma.
Exemplo: um swap de usuário que cria a ATA de saída e envolve SOL nativo:
wrap_sol (create_ata + system transfer + sync_native) ≈ 30.000
CPMM swap_base_input (SPL) ≈ 140.000
close_account (unwrap) ≈ 5.000
ComputeBudget instructions ≈ 10.000
────────────────────────────────────────────────────────
Total ≈ 185.000 → orçamento 250.000
Margem de segurança: defina o limite de CU ~25% acima do uso esperado. Subestimar custos mata toda a tx; superestimar apenas aumenta o custo de taxa de prioridade proporcionalmente (taxa de prioridade é units × microLamports, então ~25% acima do orçamento custa 25% extra em taxa de prioridade).
Estimativa de taxa de prioridade
O mercado de taxas local do Solana significa que as taxas de prioridade são por conta gravável. Uma tx que grava em uma conta quente (estado de pool popular) paga mais do que uma tx que grava em uma conta fria. O nível de taxa global não é a métrica certa para swaps Raydium; você quer taxas nas pools específicas que está tocando.
Estratégia 1: Estimador do provedor RPC
Cada grande provedor RPC publica um estimador de taxa de prioridade que consulta taxas recentes em contas específicas:
// Helius
const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${apiKey}`, {
method: "POST",
body: JSON.stringify({
jsonrpc: "2.0",
id: "fee-estimate",
method: "getPriorityFeeEstimate",
params: [{
accountKeys: [poolStatePubkey.toBase58()],
options: { priorityLevel: "High" },
}],
}),
});
const { result } = await response.json();
const microLamports = result.priorityFeeEstimate;
Níveis de prioridade na maioria dos provedores: Min / Low / Medium / High / VeryHigh / UnsafeMax. Mapeie-os para percentis:
| Nível | Percentil | Caso de uso |
|---|
| Min | 25º | Tráfego de bot em background, não urgente |
| Low | 50º | Swaps normais de usuários |
| Medium | 60º | Padrão para UIs de carteira |
| High | 75º | Arbitragem sensível ao tempo |
| VeryHigh | 95º | Liquidações, saídas de última chance |
Provedores: Helius (getPriorityFeeEstimate), Triton (getRecentPrioritizationFees com lista de contas), QuickNode (similar).
Estratégia 2: Consulta RPC direta
Use o RPC padrão getRecentPrioritizationFees:
const fees = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: [poolStatePubkey],
});
// fees: Array<{ slot, prioritizationFee }>
// Slots recentes; padrão ~150 slots.
const median = percentile(fees.map(f => f.prioritizationFee), 0.5);
Este é o método RPC vanilla do Solana; funciona com qualquer provedor. Desvantagem: a amostra é pequena (150 slots ≈ 60 segundos) e ruidosa. Para estimativas mais suaves, use a agregação de um provedor.
Estratégia 3: Auto-ajuste histórico
Para bots com fluxo constante, rastreie suas próprias taxas de sucesso vs. expiração:
alvo por pool: taxa de sucesso de 80% em <30s
se taxa_sucesso_atual < 80%: priorityFee += 10%
se taxa_sucesso_atual > 95%: priorityFee -= 5%
Isto se auto-corrige mais rápido que estimadores públicos e captura a estrutura por pool que os estimadores públicos nem sempre veem.
Tratamento de falhas por esgotamento de CU
Sintoma: tx falha com exceeded maximum number of instructions allowed (200000) ou ProgramFailedToComplete.
Diagnóstico:
solana confirm <tx-sig> -v
# Procure por "consumed N of M compute units" e qual instrução esgotou.
Soluções:
- Aumente o limite de CU. Se sua tx estava usando 195k de um orçamento de 200k, suba para 300k.
- Divida a transação. Se você está batendo no limite de 1,4M por tx, quebre em duas txs. Farm
harvest then stake é um clássico para dividir quando há muitas recompensas.
- Aparar contas. Cada conta gravável adicional adiciona ~2.000 CU. Podar contas não utilizadas ajuda em casos marginais.
- Use tabelas de lookup. Lookups de LUT são ~50 CU por endereço resolvido, economizando os 5.000 CU de uma referência de conta completa por entrada.
Tratamento de transações travadas
Sintoma: tx submetida, nunca confirma, eventualmente expira com BlockhashNotFound.
Diagnóstico:
getSignatureStatuses([sig]) retorna null → líder nunca viu.
- Retorna
{ confirmationStatus: null } → líder viu mas não incluiu.
Soluções:
- Aumente a taxa de prioridade. Resubmeta com 2× a taxa atual.
- Reconstrua com blockhash fresco. A vida útil do blockhash é ~60 segundos; além disso a tx é inválida independentemente de taxas.
- Broadcast multi-RPC. Alguns RPCs têm melhor conectividade com líderes. Submeta a 3–5 em paralelo.
- Mude para bundles Jito. Veja
integration-guides/routing-and-mev. Bundles ignoram filas de pacotes públicas.
Esqueleto de lógica de retry:
async function submitWithRetry(buildTx, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const tx = await buildTx({
priorityFee: basePriorityFee * Math.pow(1.5, attempt),
blockhash: (await connection.getLatestBlockhash()).blockhash,
});
try {
const sig = await connection.sendRawTransaction(tx.serialize(), {
skipPreflight: attempt > 0, // pula após primeira tentativa para economizar latência
});
const result = await connection.confirmTransaction(sig, "confirmed");
if (result.value.err) {
// Erro de lógica; não retry.
throw result.value.err;
}
return sig;
} catch (e) {
if (isExpiredError(e)) continue; // retry
if (isRevertError(e)) throw e; // não retry; falha determinística
throw e;
}
}
throw new Error("submit: exhausted retries");
}
Sob congestionamento
Quando a rede está congestionada (dashboards Jupiter / Jito bundle mostram backlog, latência RPC dispara, taxas de expiração de tx sobem), ajuste:
| Parâmetro | Condições normais | Condições de congestionamento |
|---|
| Limite de CU | +25% acima da estimativa | +25% acima da estimativa (sem mudança) |
| Percentil de taxa de prioridade | 50º | 75º–95º |
| Contagem de retry | 3 | 5–7 |
| Backoff de retry | 500ms | 1000ms |
| Usar bundles Jito | Opcional | Fortemente recomendado |
| Refresh de blockhash no retry | Sim | Sim, obrigatório |
Monitorando sinais de congestionamento:
- Percentil 75º de taxa de prioridade > 500k micro-lamports: congestionamento.
- Percentil 50º de dica Jito > 0,001 SOL: congestionamento.
- p99 de resposta RPC > 2s: problema específico do RPC ou congestionamento.
Orçamento de taxa para bots
Um bot de trading rodando ~1000 txs/dia precisa de um orçamento de taxa de prioridade. Estimativa rápida:
CU médio por tx: ~250.000
Taxa do 50º percentil: ~20.000 micro-lamports/CU
Custo por tx: 250_000 × 20_000 × 1e-6 = 5_000 lamports = 5e-6 SOL
Custo diário (1000 tx): 5e-3 SOL ≈ $1 @ $200 SOL
Custo mensal: ~$30
Esse é o mínimo. Durante congestionamento, multiplique por 5–10×. Planeje ~$150–300/mês em taxas de prioridade para um bot de fluxo constante.
Bots que devem pousar em slots específicos (liquidações, arb) pagam percentil 95º continuamente e gastam ~10× mais. Dicas de bundle Jito dominam nessa escala — frequentemente $1000+/mês — mas a alternativa (ser front-run ou expirar) é pior.
Armadilhas
1. Esquecer o limite de CU
O padrão é 200k CUs × (instruções em tx). Um swap de instrução única padrão é 200k; isso é suficiente para CPMM em SPL Token, mas não para CLMM com cruzamentos de tick ou qualquer coisa Token-2022. Sempre defina explicitamente.
2. Taxa de prioridade na conta errada
Se você estimar taxa de prioridade contra o mint do token, mas a conta quente é o estado do pool, sua estimativa é muito baixa. O estado do pool é a conta gravável certa para mirar para Raydium.
taxa_prioridade_total = units × microLamports. Aumentar units de 200k para 1M a 50k micro-lamports/CU multiplica a taxa de prioridade 5×. Não sobre-orce CU só no caso; meça.
4. Versão padrão de tx
Transações legacy têm limites de conta mais baixos; transações V0 com tabelas de lookup de endereço desbloqueiam rotas maiores. O SDK usa V0 por padrão em txVersion: TxVersion.V0. Não caia para legacy a menos que você precise de compatibilidade de carteira.
5. skipPreflight esconde erros de CU
skipPreflight: true envia a tx sem simulação local. Você economiza ~100ms, mas perde o feedback antecipado sobre esgotamento de CU. Use apenas em retries, não na primeira tentativa.
Referências
Fontes: