Разработка бэкенда dApp на Go

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка бэкенда dApp на Go
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка бэкенда dApp на Go

Go в блокчейн-контексте — не очевидный выбор на первый взгляд: большинство туториалов по dApp используют Node.js/TypeScript. Но на практике Go выигрывает там, где важна надёжность под нагрузкой: indexing событий, обработка вебхуков от нод, off-chain компоненты keeper'ов и bot'ов. Geth написан на Go, и go-ethereum — самая зрелая low-level библиотека для работы с EVM.

go-ethereum: ключевые паттерны

Подключение и чтение

client, err := ethclient.Dial("wss://eth-mainnet.g.alchemy.com/v2/KEY")
// Для production — fallback между несколькими провайдерами

// Чтение данных контракта через сгенерированный binding
tokenABI, _ := abi.JSON(strings.NewReader(ERC20ABI))
contract := bind.NewBoundContract(tokenAddress, tokenABI, client, client, client)

var balanceResult []interface{}
contract.Call(nil, &balanceResult, "balanceOf", userAddress)
balance := balanceResult[0].(*big.Int)

На практике — использовать abigen для генерации Go-биндингов из ABI. Это даёт типизированные методы вместо interface{}:

abigen --abi=./abi/Token.json --pkg=token --out=./contracts/token.go
token, _ := token.NewToken(tokenAddress, client)
balance, _ := token.BalanceOf(nil, userAddress)  // типизировано

Event subscriptions

WebSocket подписка на события — основа indexer'ов:

query := ethereum.FilterQuery{
    Addresses: []common.Address{contractAddress},
    Topics: [][]common.Hash{{
        crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")),
    }},
}

logs := make(chan types.Log)
sub, err := client.SubscribeFilterLogs(ctx, query, logs)

for {
    select {
    case err := <-sub.Err():
        // reconnect логика
    case log := <-logs:
        processTransferEvent(log)
    }
}

Критично: WebSocket соединение падает. Нужна reconnect-логика с exponential backoff. Для production — отдельная горутина, следящая за состоянием подписки и пересоздающая её при обрыве.

Архитектура indexer-сервиса

Типичный use case: собирать события смарт-контракта, хранить в PostgreSQL, предоставлять REST/GraphQL API для frontend.

Структура сервиса

cmd/
  indexer/main.go      — точка входа
  api/main.go          — HTTP сервер
internal/
  indexer/             — логика обработки событий
  repository/          — слой данных (PostgreSQL)
  blockchain/          — клиент go-ethereum
  api/handlers/        — HTTP handlers

Обработка реорганизаций (reorgs)

Это самое неочевидное для разработчиков без опыта в блокчейне. Блоки могут быть реорганизованы — транзакция, которая была в блоке 100, может исчезнуть, если произошёл reorg. Наивный indexer, не учитывающий reorg, накопит некорректные данные.

Решение: не помечать блоки как «финализированные» сразу. Ждать N подтверждений (12 для Ethereum, 3 для Polygon, 1 для Arbitrum с его финализацией). Хранить block_hash вместе с данными событий. При обнаружении reorg — откатить все записи с изменившимися block_hash.

type IndexedEvent struct {
    ID          int64
    BlockNumber uint64
    BlockHash   common.Hash
    TxHash      common.Hash
    LogIndex    uint
    Data        []byte
    Finalized   bool
}

Периодически запрашивать eth_getBlockByNumber для последних N блоков и сравнивать block_hash с сохранёнными.

Backfill исторических данных

При первом запуске или после gaps — нужно пройтись по историческим блокам. FilterLogs с диапазоном блоков, но с ограничением на размер диапазона (ноды обычно лимитируют до 2000-10000 блоков). Параллельный backfill с worker pool:

func backfill(ctx context.Context, from, to uint64, workers int) {
    chunks := make(chan [2]uint64, workers)
    var wg sync.WaitGroup

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for chunk := range chunks {
                logs, _ := client.FilterLogs(ctx, ethereum.FilterQuery{
                    FromBlock: new(big.Int).SetUint64(chunk[0]),
                    ToBlock:   new(big.Int).SetUint64(chunk[1]),
                    Addresses: []common.Address{contractAddress},
                })
                processLogs(logs)
            }
        }()
    }
    // Разбить [from, to] на chunks и отправить в канал
}

Transaction signing и отправка

Для off-chain компонентов (keeper'ы, автоматические транзакции) — управление приватным ключом в backend:

privateKey, _ := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))
auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, chainID)

// EIP-1559 ценообразование
tip, _ := client.SuggestGasTipCap(ctx)
auth.GasTipCap = tip
auth.GasFeeCap = new(big.Int).Add(baseFee, tip) // baseFee из последнего блока

tx, err := contract.SomeMethod(auth, arg1, arg2)

Для production: AWS KMS или HashiCorp Vault вместо env-переменной для хранения ключа. Nonce management — отдельная тема: при параллельной отправке транзакций нужен nonce manager, который атомарно выдаёт следующий nonce и обрабатывает dropped/stuck транзакции.

API слой

// Стандартный стек: chi или gin для роутинга, pgx/v5 для PostgreSQL
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.RealIP)
r.Use(cors.Handler(cors.Options{
    AllowedOrigins: []string{"https://app.example.com"},
    AllowedMethods: []string{"GET", "POST"},
}))

r.Get("/api/v1/events", handlers.GetEvents)
r.Get("/api/v1/user/{address}/positions", handlers.GetUserPositions)

WebSocket endpoint для real-time обновлений — gorilla/websocket или nhooyr.io/websocket. Одна горутина на подключение, channel-based broadcast от indexer'а к WebSocket клиентам.

Ориентиры по срокам

Простой indexer + REST API (один контракт, 3-5 endpoints): 3-4 дня. Полноценный сервис с backfill, reorg handling, WebSocket, nonce manager: 1,5-2 недели.