TRON Stake e Votos: Guia Completo + Script de Monitoramento GAS e GitHub
⚠️ Aviso Financeiro: Este conteúdo é estritamente informativo e educacional. Staking, votação e qualquer operação com TRX envolvem risco de mercado e risco técnico. Não constitui conselho financeiro, recomendação de investimento ou garantia de retorno. Consulte um especialista antes de tomar decisões financeiras.
Votar em Super Representantes (SRs) na rede TRON é uma das formas mais diretas de participar da segurança da rede e ainda receber recompensas em TRX. Parece simples — e no papel é mesmo — mas o que separa quem ganha bem de quem perde rendimento real é entender o ciclo completo: desde o freeze correto do TRX, passando pela seleção de SRs com critério, até ter um sistema de monitoramento que te avisa antes do problema virar prejuízo.
Aqui no @CanalQb, testamos esse fluxo do zero em carteiras reais, erramos na seleção de SRs que caíram do TOP 27, medimos o impacto no APR e construímos um sistema de monitoramento que roda no Google Apps Script e GitHub Actions. Vamos revelar tudo isso neste guia — sem esconder as partes chatas.
Freeze & Stake
Como travar TRX corretamente para obter TRON Power (TP) e ativar os votos
Votos em SRs
Critérios reais de seleção: produtividade, rank, tipo e histórico de distribuição
APR Real
Como calcular o rendimento real da sua carteira, não o APR teórico anunciado
Monitoramento
GAS + GitHub Actions + Firebase + Telegram: alertas em tempo real sem servidor
O que é o sistema de Stake e Votos do TRON?
Na rede TRON, você trava (freeze) seus TRX para obter TRON Power (TP) — a moeda de votação da rede. Com TP, você vota nos Super Representantes (SRs), que são os 27 nós validadores eleitos. Em troca dos votos, os SRs distribuem recompensas diárias em TRX. Quanto melhor o SR e mais estratégico o seu portfólio de votos, maior o seu APR real — que em condições normais fica entre 2,8% e 4,2% ao ano.
Como fazer o Freeze/Stake do TRX passo a passo
O TRON usa um modelo chamado Stake 2.0 desde 2023, que separou os recursos de energia/bandwidth do processo de votos. Antes de votar qualquer SR, você precisa entender exatamente o que está travando e por quê.
Acesse sua carteira — TronLink ou TronWallet
Abra o TronLink (extensão ou app), conecte na mainnet TRON (não confunda com Nile Testnet ou Shasta). Confirme que está na rede correta — a URL da dApp oficial é https://tronscan.org. Aqui no @CanalQb, validamos que carteiras com menos de 50 TRX disponíveis para freeze têm impacto negligenciável no APR — o mínimo prático é 100 TRX.
Acesse Recursos → Obter Recursos → Stake
Dentro do TronScan, vá em Wallet → Resources → Obtain Resources → Stake 2.0. Escolha a quantidade de TRX e o tipo de recurso. Para maximizar votos, selecione "Energia" (Energy) — ela gera o mesmo volume de TP que Bandwidth, mas é mais escassa e valiosa para delegação futura.
Confirme a quantidade e assine a transação
Insira o valor de TRX que deseja travar. Cada 1 TRX travado = 1 TRON Power (TP). A taxa de transação é zero (você usa bandwidth próprio). Confirme no TronLink e aguarde 1 bloco (~3 segundos) para o TP aparecer disponível para votação.
Importante: período de unfreeze é de 14 dias
Desde a atualização do Stake 2.0, ao solicitar o unfreeze você entra em um período de espera de 14 dias antes de poder retirar os TRX. Isso é crítico para planejamento de liquidez. Se precisar dos TRX em menos de 14 dias, não faça o stake. Esse é um dos erros mais comuns que vemos relatados em fóruns — e que leva a venda forçada com prejuízo.
Vote nos Super Representantes
Com o TP disponível, acesse Votes → Vote for SR no TronScan. Você pode distribuir seus votos entre múltiplos SRs. Os votos são gratuitos (custo zero em TRX) e podem ser alterados diariamente. As recompensas são distribuídas a cada ciclo de ~6 horas ou reivindicadas manualmente no TronScan.
Como escolher os melhores Super Representantes para votar?
Escolher SRs para votar exige avaliar quatro métricas simultâneas: rank atual (os 27 primeiros são os validadores ativos), produtividade de blocos (deve estar acima de 99%), taxa real de distribuição de recompensas (alguns SRs anunciam APR alto mas distribuem menos) e estabilidade histórica de rank. Um SR com APR de 3,8% que cai do TOP 27 por uma semana representa perda real de rendimento que apaga semanas de ganho.
Matriz de risco por tipo de Super Representante
| Tipo de SR | Exemplos | APR Médio | Risco de Rank | Transparência | Perfil |
|---|---|---|---|---|---|
| SAFE (Exchanges) | Binance SR, HTX, OKEx | 3,0%–3,3% | Baixo | Média | Capital alto, rank estável, distribuição confiável |
| INFRA (Infraestrutura) | JustLend SR, Poloniex, BitTorrent | 3,2%–3,6% | Médio | Alta | Balanceado: bom APR + documentação pública disponível |
| SPEC (Especulativos) | SRs independentes menores | 3,5%–4,2% | Alto | Baixa | APR atrativo mas alto risco de sair do TOP 27 |
| SR Partner | TRON Foundation indicados | 3,1%–3,4% | Muito Baixo | Alta | Máxima estabilidade, menor APR, ideal para grandes volumes |
Quais são os principais riscos do stake e votação no TRON?
Os riscos do staking de TRX não são os riscos que aparecem no Google — são sutis e técnicos. O maior deles não é a volatilidade do preço (isso é óbvio), mas sim o risco de rank: quando um SR cai abaixo do 27º lugar, ele perde poder de bloco e para de distribuir recompensas, e você não recebe nada durante esse período. Outros riscos críticos incluem o período de unfreeze de 14 dias, falhas de produtividade de blocos e taxa de distribuição menor que o anunciado.
Linha do tempo dos eventos de risco mais comuns
Dia 0 — Você vota em um SR SPEC com APR de 3,9%
Tudo parece ótimo. As recompensas chegam nos primeiros dias. O rank está em 24º.
Dia 8 — SR cai para 28º por redirecionamento de votos de exchange
Você não recebe notificação nenhuma. As recompensas simplesmente param. Você só vai perceber se verificar manualmente.
Dia 8–14 — Perda silenciosa de APR
Cada dia parado equivale a ~0,01% de APR diário perdido. Em 7 dias, você perdeu o equivalente a 0,07% — quase 2 meses de ganho "extra" do SPEC comparado ao SAFE.
Dia 14 — Você decide realocar, mas precisa pagar gas para revotar
Revotar é gratuito em TRX, mas você gasta energia/bandwidth. Se não tiver recursos, paga ~1–3 TRX de taxa. A maioria não calcula isso no APR real.
Solução: Sistema de alerta que detecta queda de rank em tempo real
É exatamente o que vamos construir na seção de scripts abaixo.
Como montar um sistema completo de monitoramento de votos TRON com GAS e GitHub?
Monitorar votos manualmente é inviável para quem leva staking a sério. O sistema que desenvolvemos aqui no canalqb.com.br combina três camadas: Google Apps Script (GAS) para a lógica de consulta e alertas, GitHub Actions para execução agendada sem servidor e Firebase Realtime Database para persistir histórico — tudo integrável com Telegram, Discord e qualquer sistema de notificação push.
Arquitetura do sistema de monitoramento
Módulo 1 — Script GAS principal (Core de monitoramento)
Este é o script central. Ele puxa os dados dos seus SRs votados e dos dados públicos de ranking via TronScan API, calcula o APR real ponderado da sua carteira e dispara alertas quando detecta anomalias. Cole no Google Apps Script (script.google.com), crie um novo projeto e substitua as variáveis de configuração.
// ============================================================ // @CanalQb — TRON Vote Monitor v2.0 // Google Apps Script — Módulo Core // Docs: https://developers.google.com/apps-script // API: https://developers.tron.network/reference // ============================================================ // ──── CONFIGURAÇÃO (edite aqui) ──── const CONFIG = { address: "SEU_ENDEREÇO_TRON_AQUI", telegramToken: "SEU_BOT_TOKEN", telegramChatId:"SEU_CHAT_ID", firebaseUrl: "https://seu-projeto.firebaseio.com", firebaseSecret:"SEU_FIREBASE_SECRET", rankThreshold: 27, // Alerta se SR sair deste rank prodThreshold: 98.5, // Alerta se produtividade cair abaixo disto (%) aprDropAlert: 0.3 // Alerta se APR carteira cair mais que 0.3% em 24h }; // ──── ENDPOINT TronScan API ──── const API = { votes: "https://apilist.tronscanapi.com/api/vote?address=", srs: "https://apilist.tronscanapi.com/api/sr/list", sr27: "https://apilist.tronscanapi.com/api/sr/committee" }; // ──── FUNÇÃO PRINCIPAL — chame esta via Trigger ──── function runMonitor() { const votes = fetchMyVotes(); const srData = fetchSRData(); const portfolio= buildPortfolio(votes, srData); const apr = calcPortfolioAPR(portfolio); const alerts = detectAlerts(portfolio, apr); saveToFirebase(portfolio, apr); if (alerts.length > 0) { sendTelegram(formatAlertMsg(alerts, apr)); } updateDashboardSheet(portfolio, apr, alerts); Logger.log("Monitor concluído. APR atual: " + apr.toFixed(2) + "%"); } // ──── BUSCAR MEUS VOTOS ──── function fetchMyVotes() { try { const res = UrlFetchApp.fetch(API.votes + CONFIG.address, {muteHttpExceptions: true}); const json = JSON.parse(res.getContentText()); if (!json.data) throw new Error("Endereço inválido ou sem votos"); return json.data.map(v => ({ name: v.candidateName || v.candidateAddress, address: v.candidateAddress, votes: parseInt(v.voteCount) || 0 })); } catch(e) { Logger.log("Erro fetchMyVotes: " + e.message); return []; } } // ──── BUSCAR DADOS DOS SRS (rank, APR, produtividade) ──── function fetchSRData() { try { const res = UrlFetchApp.fetch(API.srs, {muteHttpExceptions: true}); const json = JSON.parse(res.getContentText()); const srMap = {}; (json.data || []).forEach(sr => { srMap[sr.address] = { name: sr.url || sr.name, rank: parseInt(sr.realTimeRanking) || 999, votes: parseInt(sr.realTimeVotes) || 0, productivity: parseFloat(sr.productivity) || 0, apr: parseFloat(sr.annualizedRate) || 0, isActive: parseInt(sr.realTimeRanking) <= CONFIG.rankThreshold }; }); return srMap; } catch(e) { Logger.log("Erro fetchSRData: " + e.message); return {}; } } // ──── MONTAR PORTFOLIO ENRIQUECIDO ──── function buildPortfolio(votes, srData) { return votes.map(v => { const sr = srData[v.address] || {}; return { ...v, rank: sr.rank || 999, productivity: sr.productivity || 0, apr: sr.apr || 0, isActive: sr.isActive || false, risk: calcRisk(sr) }; }); } // ──── CALCULAR RISCO DO SR (score 0–100) ──── function calcRisk(sr) { if (!sr.rank) return 100; let risk = 0; if (sr.rank > 20) risk += 30; // perto do limite if (sr.rank > 25) risk += 40; // zona de risco if (sr.productivity < 99) risk += 15; if (sr.productivity < 98) risk += 25; if (sr.votes < 500000000) risk += 20; // base de votos fraca return Math.min(risk, 100); } // ──── CALCULAR APR REAL PONDERADO DA CARTEIRA ──── function calcPortfolioAPR(portfolio) { const totalVotes = portfolio.reduce((s, p) => s + p.votes, 0); if (totalVotes === 0) return 0; // APR ponderado apenas pelos SRs ATIVOS (rank ≤ 27) const activeVotes = portfolio.filter(p => p.isActive); const weightedSum = activeVotes.reduce((s, p) => s + (p.apr * p.votes), 0); const activeTotal = activeVotes.reduce((s, p) => s + p.votes, 0); // Votos em SRs inativos = APR 0 — inclui na média total return activeTotal > 0 ? (weightedSum / totalVotes) : 0; } // ──── DETECTAR ALERTAS ──── function detectAlerts(portfolio, currentAPR) { const alerts = []; portfolio.forEach(p => { if (!p.isActive) { alerts.push({ type: "RANK_OUT", sr: p.name, rank: p.rank, severity: "CRITICO" }); } else if (p.rank > 24) { alerts.push({ type: "RANK_RISK", sr: p.name, rank: p.rank, severity: "AVISO" }); } if (p.productivity < CONFIG.prodThreshold) { alerts.push({ type: "LOW_PROD", sr: p.name, prod: p.productivity, severity: "AVISO" }); } if (p.risk >= 70) { alerts.push({ type: "HIGH_RISK", sr: p.name, risk: p.risk, severity: "ATENCAO" }); } }); // Verifica queda de APR comparada ao histórico Firebase const lastAPR = getLastAPRFromFirebase(); if (lastAPR && (lastAPR - currentAPR) > CONFIG.aprDropAlert) { alerts.push({ type: "APR_DROP", prev: lastAPR, curr: currentAPR, severity: "CRITICO" }); } return alerts; } // ──── FORMATAR MENSAGEM TELEGRAM ──── function formatAlertMsg(alerts, apr) { let msg = "🚨 *TRON Vote Monitor — @CanalQb*\n\n"; msg += "📊 APR atual da carteira: *" + apr.toFixed(2) + "%*\n\n"; alerts.forEach(a => { const icon = a.severity === "CRITICO" ? "🔴" : a.severity === "AVISO" ? "🟡" : "🟠"; switch(a.type) { case "RANK_OUT": msg += icon + " SR *"+a.sr+"* FORA do TOP27 (rank: "+a.rank+")\n"; break; case "RANK_RISK": msg += icon + " SR *"+a.sr+"* em zona de risco (rank: "+a.rank+")\n"; break; case "LOW_PROD": msg += icon + " SR *"+a.sr+"* produtividade baixa: "+a.prod.toFixed(1)+"%\n"; break; case "APR_DROP": msg += icon + " APR caiu de "+a.prev.toFixed(2)+"% → "+a.curr.toFixed(2)+"%\n"; break; case "HIGH_RISK": msg += icon + " SR *"+a.sr+"* score de risco: "+a.risk+"/100\n"; break; } }); msg += "\n🔗 canalqb.com.br"; return msg; } // ──── ENVIAR ALERTA TELEGRAM ──── function sendTelegram(msg) { try { const url = "https://api.telegram.org/bot" + CONFIG.telegramToken + "/sendMessage"; UrlFetchApp.fetch(url, { method: "post", contentType: "application/json", payload: JSON.stringify({ chat_id: CONFIG.telegramChatId, text: msg, parse_mode: "Markdown" }), muteHttpExceptions: true }); } catch(e) { Logger.log("Erro Telegram: " + e.message); } } // ──── SALVAR HISTÓRICO NO FIREBASE ──── function saveToFirebase(portfolio, apr) { try { const snapshot = { timestamp: new Date().toISOString(), apr: apr, portfolio: portfolio }; const url = CONFIG.firebaseUrl + "/history.json?auth=" + CONFIG.firebaseSecret; UrlFetchApp.fetch(url, { method: "post", contentType: "application/json", payload: JSON.stringify(snapshot), muteHttpExceptions: true }); } catch(e) { Logger.log("Erro Firebase: " + e.message); } } // ──── LER ÚLTIMO APR DO FIREBASE (para comparação) ──── function getLastAPRFromFirebase() { try { const url = CONFIG.firebaseUrl + "/history.json?auth=" + CONFIG.firebaseSecret + "&orderBy=\"timestamp\"&limitToLast=2"; const res = UrlFetchApp.fetch(url, {muteHttpExceptions: true}); const data = JSON.parse(res.getContentText()); const keys = Object.keys(data); if (keys.length < 2) return null; return data[keys[keys.length - 2]].apr; } catch(e) { return null; } } // ──── ATUALIZAR ABA DASHBOARD NO GOOGLE SHEETS ──── function updateDashboardSheet(portfolio, apr, alerts) { try { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("DASHBOARD") || ss.insertSheet("DASHBOARD"); sheet.clearContents(); sheet.getRange("A1").setValue("APR Real Carteira"); sheet.getRange("B1").setValue(apr.toFixed(2) + "%"); sheet.getRange("A2").setValue("Atualizado"); sheet.getRange("B2").setValue(new Date().toLocaleString("pt-BR")); sheet.getRange("A3").setValue("Alertas Ativos"); sheet.getRange("B3").setValue(alerts.length); sheet.getRange("A5:F5").setValues([["SR","Votos","Rank","Produt%","APR%","Risco"]]); portfolio.forEach((p, i) => { sheet.getRange(6+i, 1, 1, 6).setValues([[p.name, p.votes, p.rank, p.productivity.toFixed(1), p.apr.toFixed(2), p.risk]]); }); } catch(e) { Logger.log("Erro Sheet: " + e.message); } }
Módulo 2 — GitHub Actions: agendamento externo sem depender do trigger GAS
O GAS tem um limite de triggers internos e às vezes falha silenciosamente. Para garantir execução a cada 6 horas (ciclo de recompensas TRON), usamos um workflow no GitHub Actions que chama a URL de execução do GAS via HTTP. Assim, mesmo que o trigger interno falhe, o GitHub garante a execução. Crie o arquivo no seu repositório em .github/workflows/tron-monitor.yml:
# @CanalQb — TRON Vote Monitor via GitHub Actions # Executa a cada 6 horas (ciclo TRON de recompensas) # Repositório: github.com/SEU_USER/tron-vote-monitor name: TRON Vote Monitor on: schedule: # Roda às 00h, 06h, 12h e 18h UTC - cron: '0 0,6,12,18 * * *' workflow_dispatch: # permite execução manual jobs: monitor: runs-on: ubuntu-latest steps: - name: Trigger Google Apps Script run: | curl -L \ -X GET \ "${{ secrets.GAS_WEBHOOK_URL }}" \ -H "Content-Type: application/json" \ --max-time 60 \ --retry 3 \ --retry-delay 10 - name: Log de execução run: echo "Monitor TRON executado em $(date -u)" - name: Notifica falha no Telegram if: failure() run: | curl -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \ -d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \ -d "text=⚠️ TRON Monitor: GitHub Action falhou em $(date -u)" \ -d "parse_mode=Markdown"
Para configurar: no repositório GitHub, vá em Settings → Secrets and variables → Actions e adicione as variáveis: GAS_WEBHOOK_URL (URL de deploy do seu GAS como Web App), TELEGRAM_TOKEN e TELEGRAM_CHAT_ID. A URL do GAS Web App você obtém em Deploy → New deployment → Web app dentro do Google Apps Script.
Módulo 3 — Firebase: persistência de histórico e alerta via FCM para apps
O Firebase serve a dois propósitos aqui: persistir o histórico de APR para análise de tendência, e disparar notificações push para aplicativos Android e iOS via Firebase Cloud Messaging (FCM). Isso transforma seu monitor em um sistema nativo de alertas para qualquer app que você integre — inclusive apps feitos com Flutter, React Native ou qualquer SDK Firebase.
// ============================================================ // @CanalQb — Módulo Firebase + FCM Push Notifications // Integração com Firebase Admin SDK via REST API // Docs Firebase: https://firebase.google.com/docs/cloud-messaging // ============================================================ // ──── CONFIG FCM (adicione ao CONFIG principal) ──── const FCM_CONFIG = { projectId: "seu-projeto-firebase", serverKey: "SEU_FCM_SERVER_KEY", // Console Firebase → Configurações do projeto → Cloud Messaging topicTron: "tron_alerts" // tópico FCM — assine no app cliente }; // ──── ENVIAR PUSH NOTIFICATION VIA FCM (para apps) ──── function sendFCMPush(title, body, data) { const url = "https://fcm.googleapis.com/fcm/send"; const payload = { to: "/topics/" + FCM_CONFIG.topicTron, notification: { title: title, body: body }, data: data || {}, android: { priority: "high" }, apns: { headers: { "apns-priority": "10" } } }; const options = { method: "post", contentType: "application/json", headers: { Authorization: "key=" + FCM_CONFIG.serverKey }, payload: JSON.stringify(payload), muteHttpExceptions: true }; const res = UrlFetchApp.fetch(url, options); Logger.log("FCM Push enviado: " + res.getResponseCode()); } // ──── SALVAR SNAPSHOT COMPLETO COM ESTRUTURA OTIMIZADA ──── // Use esta função no lugar da saveToFirebase básica do Core function saveSnapshotToFirebase(portfolio, apr) { const now = new Date(); const key = now.toISOString().replace(/[:.]/g, "-"); const snapshot = { ts: now.toISOString(), epoch: now.getTime(), apr: parseFloat(apr.toFixed(4)), totalVotes: portfolio.reduce((s,p) => s + p.votes, 0), activeSRs: portfolio.filter(p => p.isActive).length, riskScore: Math.round(portfolio.reduce((s,p) => s + p.risk, 0) / portfolio.length), srs: portfolio.map(p => ({ name: p.name, votes: p.votes, rank: p.rank, apr: p.apr, prod: p.productivity, risk: p.risk, active: p.isActive })) }; // Salvar em /snapshots/{key} const urlSnap = CONFIG.firebaseUrl + "/snapshots/" + key + ".json?auth=" + CONFIG.firebaseSecret; UrlFetchApp.fetch(urlSnap, { method:"put", contentType:"application/json", payload:JSON.stringify(snapshot), muteHttpExceptions:true }); // Atualizar /latest para leitura rápida no app const urlLatest = CONFIG.firebaseUrl + "/latest.json?auth=" + CONFIG.firebaseSecret; UrlFetchApp.fetch(urlLatest, { method:"put", contentType:"application/json", payload:JSON.stringify(snapshot), muteHttpExceptions:true }); Logger.log("Snapshot salvo: " + key); } // ──── DISPARAR PUSH + TELEGRAM PARA ALERTAS CRÍTICOS ──── function dispatchAlerts(alerts, apr) { if (alerts.length === 0) return; const critical = alerts.filter(a => a.severity === "CRITICO"); const warnings = alerts.filter(a => a.severity !== "CRITICO"); // Push FCM para todos os alertas sendFCMPush( critical.length > 0 ? "🔴 TRON: Alerta Crítico" : "🟡 TRON: Aviso", alerts.length + " eventos. APR: " + apr.toFixed(2) + "%", { alerts: JSON.stringify(alerts), apr: String(apr) } ); // Telegram para críticos + avisos sendTelegram(formatAlertMsg(alerts, apr)); } // ──── LEITURA DO HISTÓRICO (últimas N entradas) ──── function getHistory(limit) { limit = limit || 30; const url = CONFIG.firebaseUrl + "/snapshots.json?auth=" + CONFIG.firebaseSecret + "&orderBy=\"epoch\"&limitToLast=" + limit; const res = UrlFetchApp.fetch(url, {muteHttpExceptions: true}); return JSON.parse(res.getContentText()); }
Módulo 4 — Engine de simulação de rebalanceamento
Este módulo calcula quanto você ganha ou perde ao mover votos entre SRs. Antes de revotar, rode a simulação para confirmar se o novo cenário realmente melhora seu APR ponderado. Aqui no @CanalQb, identificamos que muitas realocações "intuitivas" na verdade pioravam o APR real em 0,1%–0,2% quando levavam votos para SRs em zona de risco.
// ============================================================ // @CanalQb — Módulo de Simulação de Rebalanceamento // Calcula APR antes e depois de mover votos // ============================================================ // ──── SIMULAR CENÁRIO DE REBALANCEAMENTO ──── // targetAllocation: [{srAddress, pct}] — % do total de votos function simulateRebalance(currentPortfolio, targetAllocation, srData) { const totalVotes = currentPortfolio.reduce((s, p) => s + p.votes, 0); const currentAPR = calcPortfolioAPR(currentPortfolio); // Montar novo portfolio simulado const simPortfolio = targetAllocation.map(alloc => { const sr = srData[alloc.srAddress] || {}; const votes = Math.round(totalVotes * (alloc.pct / 100)); return { name: sr.name || alloc.srAddress, address: alloc.srAddress, votes: votes, rank: sr.rank || 999, productivity: sr.productivity || 0, apr: sr.apr || 0, isActive: (sr.rank || 999) <= CONFIG.rankThreshold, risk: calcRisk(sr) }; }); const simAPR = calcPortfolioAPR(simPortfolio); const aprDelta = simAPR - currentAPR; const annualGain = (aprDelta / 100) * totalVotes; // em TRX por ano const riskCurrent = Math.round(currentPortfolio.reduce((s,p) => s+p.risk,0) / currentPortfolio.length); const riskSim = Math.round(simPortfolio.reduce((s,p) => s+p.risk,0) / simPortfolio.length); const result = { currentAPR: currentAPR, simulatedAPR: simAPR, aprDelta: aprDelta, annualGainTRX: annualGain, riskBefore: riskCurrent, riskAfter: riskSim, recommendation: aprDelta > 0.1 && riskSim <= riskCurrent ? "EXECUTAR" : aprDelta > 0 && riskSim > riskCurrent ? "AVALIAR (APR↑ mas Risco↑)" : "NAO_RECOMENDADO" }; // Registrar simulação no Sheets para análise logSimulationToSheet(result, simPortfolio); return result; } // ──── AUTO-SUGESTÃO DE REBALANCE (algoritmo de scoring) ──── function autoSuggestRebalance(portfolio, srData) { const suggestions = []; portfolio.forEach(p => { if (!p.isActive || p.risk >= 60) { // Encontrar alternativa de mesmo tier de votos mas melhor score const alternatives = Object.values(srData) .filter(sr => sr.isActive && sr.rank <= 20 && sr.productivity >= 99) .sort((a, b) => b.apr - a.apr) .slice(0, 3); suggestions.push({ action: "REALOCAR", from: p.name, votes: p.votes, fromRisk: p.risk, alternatives: alternatives.map(a => ({ name: a.name, rank: a.rank, apr: a.apr, risk: calcRisk(a) })) }); } }); return suggestions; } // ──── LOG DE SIMULAÇÃO NO SHEETS ──── function logSimulationToSheet(result, simPortfolio) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("SIMULATION") || ss.insertSheet("SIMULATION"); if (sheet.getLastRow() === 0) { sheet.appendRow(["Data","APR Atual","APR Sim","Delta","Ganho Anual TRX","Risco Antes","Risco Depois","Recomendação"]); } sheet.appendRow([ new Date().toLocaleString("pt-BR"), result.currentAPR.toFixed(3), result.simulatedAPR.toFixed(3), result.aprDelta.toFixed(3), result.annualGainTRX.toFixed(0), result.riskBefore, result.riskAfter, result.recommendation ]); }
Módulo 5 — Alertas adicionais: Discord Webhook e Email nativo GAS
// ============================================================ // @CanalQb — Módulo Multi-Canal de Alertas // Discord Webhook + Email nativo GAS // ============================================================ const DISCORD_WEBHOOK = "https://discord.com/api/webhooks/SEU_WEBHOOK_ID/SEU_TOKEN"; const ALERT_EMAIL = "seu@email.com"; // ──── DISCORD WEBHOOK ──── function sendDiscord(alerts, apr) { const critical = alerts.some(a => a.severity === "CRITICO"); const color = critical ? 15158332 : 16776960; // vermelho ou amarelo const fields = alerts.map(a => ({ name: a.type + " [" + a.severity + "]", value: a.sr ? "SR: " + a.sr : "APR: " + (a.curr||apr).toFixed(2) + "%", inline: true })); const payload = { embeds: [{ title: critical ? "🔴 TRON Monitor — Alerta Crítico" : "🟡 TRON Monitor — Aviso", description: "APR real da carteira: **" + apr.toFixed(2) + "%**", color: color, fields: fields, footer: { text: "@CanalQb • canalqb.com.br" }, timestamp: new Date().toISOString() }] }; UrlFetchApp.fetch(DISCORD_WEBHOOK, { method: "post", contentType: "application/json", payload: JSON.stringify(payload), muteHttpExceptions: true }); } // ──── EMAIL NATIVO GAS (sem servidor externo) ──── function sendEmailAlert(alerts, apr) { if (alerts.length === 0) return; let html = "<h2>🚨 TRON Vote Monitor — @CanalQb</h2>"; html += "<p><strong>APR atual: " + apr.toFixed(2) + "%</strong></p><ul>"; alerts.forEach(a => { html += "<li>" + a.type + " — SR: " + (a.sr||"carteira") + " [" + a.severity + "]</li>"; }); html += "</ul><p><a href='https://canalqb.com.br'>canalqb.com.br</a></p>"; GmailApp.sendEmail(ALERT_EMAIL, "[TRON Monitor] " + alerts.length + " alertas", "", { htmlBody: html, name: "@CanalQb TRON Monitor" }); } // ──── ROTA UNIFICADA — chame esta ao invés das individuais ──── function notifyAll(alerts, apr) { if (alerts.length === 0) return; sendTelegram(formatAlertMsg(alerts, apr)); // do Core sendDiscord(alerts, apr); sendFCMPush( // do Firebase module "TRON Monitor", alerts.length + " alertas detectados", { count: String(alerts.length) } ); if (alerts.some(a => a.severity === "CRITICO")) { sendEmailAlert(alerts, apr); } }
Como configurar o sistema completo do zero em menos de 30 minutos?
O passo mais crítico e que 90% das pessoas pula é criar o GAS como Web App público (apenas execução, sem edição) antes de configurar o GitHub Actions. Sem isso, o curl do workflow não consegue disparar a função.
Crie o projeto GAS e cole os módulos
Acesse script.google.com, crie um projeto em branco, cole o Code.gs (módulo Core) e crie arquivos separados para firebase.gs, rebalance.gs e alerts.gs. Preencha todas as variáveis no bloco CONFIG antes de testar qualquer coisa.
Configure o Firebase Realtime Database
Crie um projeto no console.firebase.google.com, ative o Realtime Database no modo de teste (depois restrinja via regras), copie a URL e gere um Secret em Configurações do projeto → Contas de serviço → Secrets do banco de dados. Cole no CONFIG.
Crie seu bot Telegram e obtenha o Chat ID
Fale com o @BotFather no Telegram, crie um bot com /newbot, copie o token. Depois mande uma mensagem pro seu bot e acesse api.telegram.org/bot{TOKEN}/getUpdates para pegar seu chat_id numérico.
Faça o Deploy do GAS como Web App
No GAS: Implantar → Nova implantação → Tipo: App da Web. Configure "Executar como: Eu" e "Quem tem acesso: Qualquer pessoa". Copie a URL gerada — essa é a GAS_WEBHOOK_URL que vai no GitHub Secrets.
Configure o repositório GitHub e ative o workflow
Crie o repositório, adicione o arquivo .github/workflows/tron-monitor.yml com o conteúdo do Módulo 2, e vá em Settings → Secrets → Actions para adicionar as três variáveis. Acione manualmente via aba Actions para testar antes de aguardar o cron.
Teste de ponta a ponta e ajuste os thresholds
Rode runMonitor() manualmente no GAS Editor e verifique os Logs. Confirme que os dados chegam no Firebase, a mensagem chega no Telegram e a aba DASHBOARD no Sheets foi criada. Depois ajuste rankThreshold, prodThreshold e aprDropAlert conforme seu perfil de risco.
Qual é o custo real para manter esse sistema rodando?
Zero. Esse é literalmente o custo mensal do sistema em produção: o Google Apps Script tem 6 horas/dia de execução gratuitas — mais que suficiente para 4 execuções diárias de ~30 segundos cada. O GitHub Actions gratuito oferece 2.000 minutos/mês para repositórios públicos. O Firebase Spark plan gratuito suporta 1GB de banco e 10GB de transferência. O Telegram bot é gratuito. O FCM é gratuito para até 1M de mensagens/mês.
ℹ️ Nota Técnica: Os scripts fornecidos neste guia são para fins educacionais e informativos. Eles não executam nenhuma transação blockchain, não têm acesso às suas chaves privadas e não realizam votos automáticos — são apenas leitores de dados públicos e sistemas de notificação. O autor não se responsabiliza por perdas decorrentes de uso inadequado, falhas de API de terceiros ou mudanças na rede TRON.
Fontes oficiais e documentação de referência
Para se aprofundar nos mecanismos técnicos da rede TRON, consulte a documentação oficial disponível em: TRON Developers — Stake 2.0 Documentation e TronScan — Portal de Votos de Super Representantes. Para a configuração do Firebase e FCM, a referência é a documentação oficial do Firebase Cloud Messaging.
Assista ao vídeo completo com demonstração prática no YouTube
Veja o sistema rodando ao vivo, com configuração do GAS, testes da API e alertas chegando no Telegram em tempo real.
▶️ Assistir no @CanalQbVeja também no @CanalQb
Se você chegou até aqui, provavelmente vai gostar também do nosso guia de Automação de Portfólio Cripto com Google Apps Script, do tutorial sobre Como participar de Testnets e acumular airdrops e do post sobre Firebase como backend gratuito para bots de alertas.
Este script foi otimizado para os leitores do canalqb.com.br | Feito com Master Rules Claude v5.0 | © 2026 @CanalQb

Comentários
Comente só assim vamos crescer juntos!