Bot de Monitoramento para SushiSwap V3 em Go — Parte 1

Este guia descreve, de forma contínua e didática, como construir a primeira metade de um bot de trading em Go. Nesta etapa não há transações on-chain. O programa observa o preço em dólar de um token de referência, compara esse valor com limites configu…


This content originally appeared on DEV Community and was authored by Cláudio Filipe Lima Rapôso

Este guia descreve, de forma contínua e didática, como construir a primeira metade de um bot de trading em Go. Nesta etapa não há transações on-chain. O programa observa o preço em dólar de um token de referência, compara esse valor com limites configurados em um arquivo .env e decide, de maneira simulada, quando compraria e quando venderia. O objetivo é validar lógica e parâmetros sem risco financeiro, além de introduzir a persistência do estado em SQLite para que o bot possa ser interrompido e retomado sem perder contexto.

Conceito e fluxo

Arquitetura de Referência

O comportamento é simples. O aplicativo carrega as configurações do .env, consulta periodicamente o preço em USD do token de referência por HTTP e imprime no console o valor atual. Se o preço observado estiver abaixo ou igual ao alvo de compra e ainda não houver posição aberta, considera-se uma entrada e o estado é salvo no banco com a informação do preço de entrada. Se, com posição aberta, o preço atingir o alvo de lucro, considera-se a saída e o estado é atualizado para indicar que não há mais posição. Em qualquer outro caso, o programa apenas aguarda. Como não há transações nesta fase, tratamos essas decisões como um simulador de gatilhos, útil para ajustar parâmetros e entender o ciclo de execução.

Preparação do ambiente

É necessário ter Go instalado em versão recente. Crie uma pasta para o projeto, inicialize um módulo e instale as dependências. O pacote godotenv carrega variáveis do arquivo .env e o driver modernc.org/sqlite possibilita usar SQLite sem CGO, o que simplifica a portabilidade.

mkdir sushiswap-v3-go && cd sushiswap-v3-go
go mod init example.com/sushiswap-v3-go
go get github.com/joho/godotenv
go get modernc.org/sqlite

Configuração com .env

Crie um arquivo .env na raiz do projeto. Ele concentra variáveis de rede e parâmetros de estratégia. Também indicamos DB_PATH, o caminho do arquivo SQLite que manterá o estado.

CHAIN_ID=11155111
NETWORK=sepolia
INFURA_API_KEY=SUA_INFURA_KEY

TOKEN0_ADDRESS=0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14
TOKEN1_ADDRESS=0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238

QUOTE0_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

WALLET=0xSUA_WALLET
PRIVATE_KEY=0xSUA_PRIVATE_KEY

ROUTER_ADDRESS=0x3b0aa7d38bf3c103bf02d1de2e37568cbed3d6e8

PRICE_TO_BUY=5000
AMOUNT_TO_BUY=0.1
PROFITABILITY=1.1

DB_PATH=./bot.db

A variável QUOTE0_ADDRESS representa o token cujo preço em USD será observado; no exemplo, usa-se o WETH da mainnet por ser uma referência líquida e amplamente utilizada. As variáveis PRICE_TO_BUY e PROFITABILITY definem, respectivamente, o alvo de entrada e o multiplicador do alvo de lucro. As chaves de carteira e o endereço do router são armazenados desde já, embora só venham a ser utilizadas na fase seguinte, quando forem adicionadas transações reais. Recomenda-se manter o arquivo .env fora do controle de versão.

Implementação do código

A seguir estão os arquivos Go, organizados por responsabilidade. Basta criar cada arquivo com o conteúdo indicado.

config.go

Este módulo lê e valida o .env, convertendo valores numéricos e garantindo que os campos mínimos existam para a execução desta etapa. Se faltar algo essencial, a função sinaliza claramente o problema.

package main

import (
    "fmt"
    "os"
    "strconv"

    "github.com/joho/godotenv"
)

type Config struct {
    ChainID       int
    Network       string
    InfuraAPIKey  string
    Token0Address string
    Token1Address string
    Quote0Address string
    Wallet        string
    PrivateKey    string
    RouterAddress string
    PriceToBuy    float64
    AmountToBuy   string
    Profitability float64
    DBPath        string
}

func LoadConfig() (*Config, error) {
    _ = godotenv.Load()

    var err error
    cfg := &Config{}

    if v := os.Getenv("CHAIN_ID"); v != "" {
        if cfg.ChainID, err = strconv.Atoi(v); err != nil {
            return nil, fmt.Errorf("CHAIN_ID inválido: %w", err)
        }
    } else {
        cfg.ChainID = 11155111
    }

    cfg.Network = os.Getenv("NETWORK")
    cfg.InfuraAPIKey = os.Getenv("INFURA_API_KEY")
    cfg.Token0Address = os.Getenv("TOKEN0_ADDRESS")
    cfg.Token1Address = os.Getenv("TOKEN1_ADDRESS")
    cfg.Quote0Address = os.Getenv("QUOTE0_ADDRESS")
    cfg.Wallet = os.Getenv("WALLET")
    cfg.PrivateKey = os.Getenv("PRIVATE_KEY")
    cfg.RouterAddress = os.Getenv("ROUTER_ADDRESS")
    cfg.AmountToBuy = os.Getenv("AMOUNT_TO_BUY")

    if v := os.Getenv("PRICE_TO_BUY"); v != "" {
        if cfg.PriceToBuy, err = strconv.ParseFloat(v, 64); err != nil {
            return nil, fmt.Errorf("PRICE_TO_BUY inválido: %w", err)
        }
    } else {
        return nil, fmt.Errorf("PRICE_TO_BUY ausente")
    }

    if v := os.Getenv("PROFITABILITY"); v != "" {
        if cfg.Profitability, err = strconv.ParseFloat(v, 64); err != nil {
            return nil, fmt.Errorf("PROFITABILITY inválido: %w", err)
        }
    } else {
        return nil, fmt.Errorf("PROFITABILITY ausente")
    }

    if cfg.Quote0Address == "" {
        return nil, fmt.Errorf("QUOTE0_ADDRESS ausente")
    }

    if v := os.Getenv("DB_PATH"); v != "" {
        cfg.DBPath = v
    } else {
        cfg.DBPath = "./bot.db"
    }

    return cfg, nil
}

database.go

Este módulo cria e inicializa o banco SQLite, além de oferecer funções para ler e salvar o estado do bot. O estado se restringe a duas informações: se há ou não posição aberta e qual foi o preço de entrada, quando aplicável. Ao iniciar pela primeira vez, uma linha padrão é criada para garantir que o acesso seja simples e previsível nas execuções subsequentes.

package main

import (
    "database/sql"
    "fmt"

    _ "modernc.org/sqlite"
)

type BotState struct {
    IsOpened   bool
    EntryPrice *float64
}

func InitDB(path string) (*sql.DB, error) {
    db, err := sql.Open("sqlite", path)
    if err != nil {
        return nil, fmt.Errorf("erro abrindo sqlite: %w", err)
    }
    schema := `
    CREATE TABLE IF NOT EXISTS bot_state (
        id INTEGER PRIMARY KEY CHECK (id = 1),
        is_opened INTEGER NOT NULL,
        entry_price REAL
    );
    INSERT INTO bot_state (id, is_opened, entry_price)
        SELECT 1, 0, NULL
        WHERE NOT EXISTS (SELECT 1 FROM bot_state WHERE id = 1);
    `
    if _, err := db.Exec(schema); err != nil {
        return nil, fmt.Errorf("erro criando schema: %w", err)
    }
    return db, nil
}

func LoadBotState(db *sql.DB) (*BotState, error) {
    row := db.QueryRow(`SELECT is_opened, entry_price FROM bot_state WHERE id = 1`)
    var isOpenedInt int
    var entryPrice sql.NullFloat64
    if err := row.Scan(&isOpenedInt, &entryPrice); err != nil {
        return nil, fmt.Errorf("erro lendo estado: %w", err)
    }
    var ep *float64
    if entryPrice.Valid {
        v := entryPrice.Float64
        ep = &v
    }
    return &BotState{
        IsOpened:   isOpenedInt == 1,
        EntryPrice: ep,
    }, nil
}

func SaveBotState(db *sql.DB, st *BotState) error {
    var isOpenedInt int
    if st.IsOpened {
        isOpenedInt = 1
    } else {
        isOpenedInt = 0
    }

    var entry interface{}
    if st.EntryPrice != nil {
        entry = *st.EntryPrice
    }

    if _, err := db.Exec(
        `UPDATE bot_state SET is_opened = ?, entry_price = ? WHERE id = 1`,
        isOpenedInt, entry,
    ); err != nil {
        return fmt.Errorf("erro salvando estado: %w", err)
    }
    return nil
}

prices.go

Esta unidade contém a função que consulta a API pública de preços. O retorno esperado é um número de ponto flutuante representando o preço em USD do token indicado. A configuração de timeout e a verificação do status HTTP ajudam a tornar o comportamento previsível.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

func fetchPriceUSD(tokenAddress string) (float64, error) {
    url := fmt.Sprintf("https://api.sushi.com/price/v1/%d/%s", 1, tokenAddress)

    req, _ := http.NewRequest(http.MethodGet, url, nil)
    req.Header.Set("Accept", "application/json")

    client := &http.Client{Timeout: 10 * time.Second}
    res, err := client.Do(req)
    if err != nil {
        return 0, err
    }
    defer res.Body.Close()

    if res.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(res.Body)
        return 0, fmt.Errorf("pricing api status %d: %s", res.StatusCode, string(body))
    }

    var price float64
    if err := json.NewDecoder(res.Body).Decode(&price); err != nil {
        return 0, fmt.Errorf("falha ao decodificar preço: %w", err)
    }
    return price, nil
}

main.go

O arquivo principal integra as partes. Ao iniciar, o programa carrega a configuração, prepara o banco, recupera o estado e entra em um ciclo que consulta o preço, imprime o valor e executa a lógica de decisão, atualizando o SQLite sempre que houver mudanças de estado. Um comando para limpar a tela mantém o console com aparência de painel.

package main

import (
    "fmt"
    "log"
    "time"
)

func monitor(cfg *Config) (float64, error) {
    price, err := fetchPriceUSD(cfg.Quote0Address)
    if err != nil {
        return 0, err
    }
    fmt.Print("\033[2J\033[H")
    fmt.Println("Price:", price)
    return price, nil
}

func main() {
    cfg, err := LoadConfig()
    if err != nil {
        log.Fatalf("config erro: %v", err)
    }

    db, err := InitDB(cfg.DBPath)
    if err != nil {
        log.Fatalf("db erro: %v", err)
    }
    defer db.Close()

    state, err := LoadBotState(db)
    if err != nil {
        log.Fatalf("erro carregando estado: %v", err)
    }

    cycle := func() {
        price, err := monitor(cfg)
        if err != nil {
            log.Printf("erro monitorando preço: %v", err)
            return
        }

        if !state.IsOpened && price <= cfg.PriceToBuy {
            state.IsOpened = true
            state.EntryPrice = &price
            if err := SaveBotState(db, state); err != nil {
                log.Printf("erro salvando estado (swap in): %v", err)
            }
            fmt.Printf("Swap in (entry=%.6f)\n", price)
            return
        }

        target := cfg.PriceToBuy * cfg.Profitability
        if state.IsOpened && price >= target {
            state.IsOpened = false
            state.EntryPrice = nil
            if err := SaveBotState(db, state); err != nil {
                log.Printf("erro salvando estado (swap out): %v", err)
            }
            fmt.Printf("Swap out (target=%.6f)\n", target)
            return
        }

        if state.IsOpened && state.EntryPrice != nil {
            fmt.Printf("Wait... (opened at %.6f, target %.6f)\n", *state.EntryPrice, target)
            return
        }

        fmt.Println("Wait...")
    }

    cycle()
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    for range ticker.C {
        cycle()
    }
}

Execução

Com os arquivos criados e o .env preenchido, execute o projeto a partir da raiz. O console exibirá o preço e a decisão correspondente do ciclo. Quando o preço atingir o alvo de compra, aparecerá a indicação de entrada e o estado será salvo. Quando alcançar o alvo de lucro com posição aberta, a saída será registrada. Se o programa for encerrado e reiniciado, a leitura do banco restaurará a situação anterior.

go run .

Encerramento

Com esta base consolidada, você tem um esqueleto funcional capaz de observar preço, tomar decisões simuladas e manter consistência entre execuções por meio de SQLite. Na Parte 2, em vez de montar e enviar a transação on-chain localmente, o fluxo de swap será executado via gRPC: o cliente em Go fará chamadas a um serviço gRPC responsável por cotar e processar a operação de compra e venda. Esse serviço receberá parâmetros como chain_id, token_in, token_out, amount, max_slippage e wallet, retornará métricas de pré-troca (por exemplo, o assumedAmountOut) e cuidará da preparação/execução da transação, incluindo roteamento, montagem do payload, assinatura e envio, além do retorno do hash e do recibo quando confirmada.

Do ponto de vista de arquitetura, o cliente Go apenas disciplina a orquestração: abre o canal gRPC, envia a requisição de Quote/Execute, aguarda a resposta (com sucesso ou erro tratado) e registra estado/telemetria. O serviço gRPC concentra a lógica sensível: integração com o roteador (para encontrar a melhor rota), aplicação de slippage máxima, políticas de deadline e retries, idempotência por chave, auditoria, observabilidade e segurança de chaves (sem trafegar PRIVATE_KEY pelo cliente). A assinatura pode ficar no servidor (HSM, KMS ou cofre) ou ser separada em um signer dedicado, mantendo o cliente leve e sem material sensível.

Finalmente, a evolução natural é: o bot em Go continua decidindo quando agir, persiste o estado no SQLite, e a execução do swap passa a ser um contrato gRPC bem definido com protobufs para cotação e execução, entregando robustez operacional, isolamento de segredos e padronização de erros/logs para produção.

Referências

Go. (2025, 25 fevereiro). The Go programming language specification. go.dev. https://go.dev/ref/spec (Go)

Go. (n.d.). Documentation. go.dev. https://go.dev/doc/ (Go)

gRPC. (2024, 25 novembro). Basics tutorial | Go. grpc.io. https://grpc.io/docs/languages/go/basics/ (gRPC)

gRPC. (2024, 25 novembro). Quick start | Go. grpc.io. https://grpc.io/docs/languages/go/quickstart/ (gRPC)

Protocol Buffers. (n.d.). Protocol buffer basics: Go. protobuf.dev. https://protobuf.dev/getting-started/gotutorial/ (protobuf.dev)

Equipe go-ethereum (Geth). (2024, 24 maio). Getting started with Geth. geth.ethereum.org. https://geth.ethereum.org/docs/getting-started (go-ethereum)

Go Ethereum. (n.d.). github.com/ethereum/go-ethereum (API em Go). pkg.go.dev. https://pkg.go.dev/github.com/ethereum/go-ethereum (Go Packages)

modernc.org. (2025, 11 agosto). sqlite package (driver SQLite sem CGO). pkg.go.dev. https://pkg.go.dev/modernc.org/sqlite (Go Packages)

Joho. (n.d.). joho/godotenv [Repositório]. GitHub. https://github.com/joho/godotenv (GitHub)

Sushi. (2025, 13 setembro). Pricing API documentation. docs.sushi.com. https://docs.sushi.com/api/examples/pricing (docs.sushi.com)

gRPC-Go. (n.d.). google.golang.org/grpc (pacote em Go). pkg.go.dev. https://pkg.go.dev/google.golang.org/grpc (Go Packages)

💡Curtiu?

Se quiser trocar ideia sobre IA, cloud e arquitetura, me segue nas redes:

Publico conteúdos técnicos direto do campo de batalha. E quando descubro uma ferramenta que economiza tempo e resolve bem, como essa, você fica sabendo também.


This content originally appeared on DEV Community and was authored by Cláudio Filipe Lima Rapôso


Print Share Comment Cite Upload Translate Updates
APA

Cláudio Filipe Lima Rapôso | Sciencx (2025-09-30T20:13:30+00:00) Bot de Monitoramento para SushiSwap V3 em Go — Parte 1. Retrieved from https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/

MLA
" » Bot de Monitoramento para SushiSwap V3 em Go — Parte 1." Cláudio Filipe Lima Rapôso | Sciencx - Tuesday September 30, 2025, https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/
HARVARD
Cláudio Filipe Lima Rapôso | Sciencx Tuesday September 30, 2025 » Bot de Monitoramento para SushiSwap V3 em Go — Parte 1., viewed ,<https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/>
VANCOUVER
Cláudio Filipe Lima Rapôso | Sciencx - » Bot de Monitoramento para SushiSwap V3 em Go — Parte 1. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/
CHICAGO
" » Bot de Monitoramento para SushiSwap V3 em Go — Parte 1." Cláudio Filipe Lima Rapôso | Sciencx - Accessed . https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/
IEEE
" » Bot de Monitoramento para SushiSwap V3 em Go — Parte 1." Cláudio Filipe Lima Rapôso | Sciencx [Online]. Available: https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/. [Accessed: ]
rf:citation
» Bot de Monitoramento para SushiSwap V3 em Go — Parte 1 | Cláudio Filipe Lima Rapôso | Sciencx | https://www.scien.cx/2025/09/30/bot-de-monitoramento-para-sushiswap-v3-em-go-parte-1/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.