Skip to main content

Steam Trade Bot

1. Summary

Goal: Автоматизированный вывод виртуальных предметов (скинов) из инвентаря пользователя в его Steam аккаунт через trade offers. Бот работает без участия администратора — от запроса до получения скина в Steam.

User Value: Получение реальных скинов Rust в Steam без ожидания ручной обработки администратором. Пользователь нажимает "Вывести" → принимает трейд в Steam → скин в инвентаре.


2. Business Logic

Withdrawal Lifecycle

Жизненный цикл вывода проходит через 5 стадий:

PENDING → PROCESSING → SENT → COMPLETED

FAILED

Резервация предмета

  • Пользователь запрашивает вывод через POST /api/withdraw
  • Создаётся запись Withdrawal со статусом PENDING
  • Предмет НЕ удаляется из виртуального инвентаря (protection)
  • Job добавляется в очередь обработки
Item Reservation Pattern

Виртуальный предмет НЕ удаляется до подтверждения Steam. Это защита от double-spend: если трейд упадёт — предмет останется.

Withdraw Readiness Check

Перед выводом система выполняет 7-точечную проверку готовности:

#ПроверкаОписаниеБлокирует
1hasTradeUrlTrade URL установлен в профиле
2isSteamVerifiedSteam аккаунт верифицирован
3isInventoryPublicИнвентарь Steam публичный
4noActiveWithdrawalНет активного вывода этого предмета
5itemExistsПредмет есть в виртуальном инвентаре
6itemWithdrawableТип предмета — SKIN
7itemAvailableOnBotПредмет tradable на боте

Результат: canWithdraw: boolean + детальный breakdown для UI.

Protection

ДействиеRate LimitAuthValidationAtomic
Создать выводmutations (5/min)telegramWithdrawalSchema
Проверить готовностьgeneral (100/min)telegramReadinessSchema
Отменить выводtelegram
Детали реализации

См. Security Matrix для полного обзора защит.

Incoming Trades (Пополнение бота)

Бот автоматически принимает входящие трейды для пополнения инвентаря. Только от доверенных аккаунтов, только подарки (бот ничего не отдаёт).

Decision Logic:

Новый входящий трейд
├── Отправитель НЕ в whitelist → DECLINE
├── Бот должен отдать предметы → DECLINE (safety)
├── Пустой трейд (0 предметов) → DECLINE
└── Gift от admin → ACCEPT + 2FA confirm + Telegram notify
Критичная защита

Бот никогда не отдаёт предметы через авто-принятие. Даже если отправитель в whitelist — трейд, где itemsToGive > 0, будет отклонён. Это защита от компрометации admin-аккаунта.

Конфигурация:

ПеременнаяФорматПример
STEAM_ADMIN_IDSComma-separated SteamID6476561198175539447,76561198000000000

Если STEAM_ADMIN_IDS не задан — все входящие трейды отклоняются.

Telegram уведомления:

При успешном принятии трейда бот отправляет уведомление в топик TRADES:

  • Trade ID
  • Steam ID отправителя
  • Количество и список предметов (до 20)

Edge Cases

СитуацияUI поведение
❌ Trade URL не установленКнопка "Вывести" → редирект в настройки профиля
❌ Steam не верифицированКнопка disabled, tooltip "Требуется верификация Steam"
❌ Инвентарь приватныйКнопка disabled, tooltip "Сделайте инвентарь публичным"
⏱️ Предмет в trade holdПоказать дату разблокировки "Доступен через 5д 12ч"
🔄 Уже есть активный выводПоказать статус текущего вывода вместо кнопки
✅ Трейд отправленКнопка "Открыть Steam" → ссылка на trade offer
Backend Error Codes
КодHTTPОписание
NO_TRADE_URL400Trade URL не установлен
NOT_VERIFIED400Steam аккаунт не верифицирован
ITEM_NOT_FOUND404Предмет не найден в инвентаре
ITEM_NOT_WITHDRAWABLE400Тип предмета не поддерживает вывод
ACTIVE_WITHDRAWAL_EXISTS400Уже есть активный вывод
ITEM_NOT_ON_BOT400Предмет отсутствует на боте
TRADE_HOLD400Предмет в trade hold
BOT_UNAVAILABLE503Бот не авторизован в Steam

3. ADR (Architectural Decisions)

Почему Item Reservation Pattern?

Проблема: При выводе нужно синхронизировать два источника правды — виртуальный инвентарь (БД) и реальный Steam инвентарь. Если удалить предмет сразу при запросе, а трейд упадёт — предмет потерян.

Решение: Предмет удаляется ТОЛЬКО после подтверждения Steam (state=3 Accepted). До этого — резервация: предмет помечен как "в процессе вывода", но физически остаётся в UserInventory.

Альтернативы (отклонены):

  • Удалять сразу + восстанавливать при ошибке → race conditions, сложная логика отката
  • Не удалять никогда, полагаться на Steam → рассинхронизация данных

Последствия: Надёжность выше, но код сложнее. Нужен отдельный confirmWithdrawal() метод.

Критично для консистентности

Никогда не удалять виртуальный предмет до TradeStatusTrackerService.handleAccepted(). Все пути ошибок должны вызывать releaseItemOnError().

Почему 3-Layer Smart Caching?

Проблема: Steam API имеет жёсткие лимиты. При частых запросах инвентаря бота — бан IP.

Решение: Трёхуровневое кеширование:

  1. Cache Hit — возврат кеша если TTL валиден (5 мин)
  2. Smart Freshness — если forceRefresh но кеш моложе 3 сек, всё равно возврат кеша
  3. In-Flight Deduplication — одновременные запросы получают один Promise
Техническая реализация
getInventory(appid, contextid, forceRefresh):
1. Check cache → HIT? return cached
2. forceRefresh? Check age < 3sec → return cached anyway
3. Check in-flight Map → same request pending? return same Promise
4. Fetch from Steam API → store in cache → return
ПараметрЗначениеОписание
CACHE_TTL5 минВремя жизни кеша инвентаря
MIN_INTERVAL3 секМинимальный интервал между запросами Steam

Почему In-Memory Queue?

Проблема: Trade jobs нужно обрабатывать последовательно (Steam rate limits).

Решение: In-memory очередь с интервалом 1 сек между jobs.

Альтернативы (отклонены):

  • Redis/PostgreSQL queue → overhead для простой задачи
  • BullMQ → лишняя зависимость

Последствия:

  • ✅ Простота, нет внешних зависимостей
  • ❌ Очередь теряется при рестарте сервера
  • ❌ Не подходит для горизонтального масштабирования
Trade-off

Для текущего объёма (один бот) in-memory достаточно. При масштабировании — миграция на Redis.

Почему Lazy Initialization?

Проблема: Steam логин занимает время и может блокировать старт сервера.

Решение: TradeManager создаётся не при старте, а по событию webSession от SteamBotService. Сервер стартует мгновенно, бот авторизуется в фоне.

Последствия:

  • API /api/withdraw доступен сразу, но возвращает BOT_UNAVAILABLE пока бот не готов
  • isBotReady() check в каждом методе

4. Architecture

Trade Flow

Key Components

КомпонентПутьОписание
SteamBotServicebackend/src/domains/steam-trade-bot/services/steam-bot.service.tsСессия Steam, логин, 2FA, reconnect
TradeManagerServicebackend/src/domains/steam-trade-bot/services/trade-manager.service.tsTrade offers, 3-layer кеширование инвентаря
TradeQueueServicebackend/src/domains/steam-trade-bot/services/trade-queue.service.tsПоследовательная обработка jobs
ItemReservationServicebackend/src/domains/steam-trade-bot/services/item-reservation.service.tsЖизненный цикл предмета при выводе
TradeStatusTrackerServicebackend/src/domains/steam-trade-bot/services/trade-status-tracker.service.tsСинхронизация статусов со Steam
BotInventoryCacheServicebackend/src/domains/steam-trade-bot/services/bot-inventory-cache.service.tsКеширование инвентаря бота
IncomingTradeServicebackend/src/domains/steam-trade-bot/services/incoming-trade.service.tsАвто-принятие входящих gift-трейдов от admin whitelist
WithdrawReadinessServicebackend/src/domains/steam-trade-bot/services/withdraw-readiness.service.ts7-точечная проверка готовности
TradeControllerbackend/src/domains/steam-trade-bot/controllers/trade.controller.tsAPI эндпоинты
Routesbackend/src/domains/steam-trade-bot/routes/trade.routes.tsРоутинг

Circuit Breaker (Reconnection)

При потере сессии Steam бот использует exponential backoff:

Attempt 1: wait 1 sec → retry
Attempt 2: wait 2 sec → retry
Attempt 3: wait 4 sec → retry
Attempt 4: wait 8 sec → retry
Attempt 5: wait 16 sec → retry
After 5 fails: emit 'reconnectFailed', stop trying

5. Database Schema

Models

МодельОписаниеКлючевые поля
WithdrawalЗапись о выводе предметаuserId, itemId, status, tradeUrl, tradeOfferId
UserСвязь с пользователемsteamTradeUrl, steamId, steamVerified
ItemСвязь с предметомmarketHashName, itemType

Withdrawal Fields

ПолеТипОписание
idStringCUID
userIdStringFK → User
itemIdStringFK → Item
statusWithdrawalStatusPENDING, PROCESSING, SENT, COMPLETED, FAILED, CANCELLED
tradeUrlStringSnapshot Trade URL на момент запроса
tradeOfferIdString?ID трейд-оффера Steam
failureReasonString?Причина ошибки (показывается пользователю)
adminNoteString?Внутренняя заметка (только админка)
requestedAtDateTimeКогда запросили
completedAtDateTime?Когда завершили

Relationships


6. API Endpoints

МетодЭндпоинтОписаниеDocs
POST/api/withdrawСоздать запрос на вывод
GET/api/withdraw/readinessПроверить готовность к выводу
GET/api/withdraw/historyИстория выводов пользователя

7. Operations

Переменные окружения

ПеременнаяОбязательнаОписание
STEAM_USERNAMEДаЛогин бот-аккаунта Steam
STEAM_PASSWORDДаПароль бот-аккаунта
STEAM_SHARED_SECRETДа2FA shared secret (из SDA .maFile) — для генерации одноразовых кодов
STEAM_IDENTITY_SECRETДа2FA identity secret (из SDA .maFile) — для подтверждения трейдов
STEAM_ADMIN_IDSНетSteamID64 через запятую — whitelist для авто-принятия входящих трейдов
STEAM_DEVICE_IDНетDevice ID (генерируется автоматически если не задан)
STEAM_API_KEYНетSteam Web API Key
STEAM_ENABLEDНетfalse для явного отключения бота
STEAM_REQUIREDНетtrue если бот обязателен (на prod — обязателен по умолчанию)

Генерация 2FA кода вручную

Для ручного входа в бот-аккаунт Steam (например, чтобы скопировать Trade URL или проверить инвентарь) нужен одноразовый 2FA код. Сгенерировать его из STEAM_SHARED_SECRET:

cd ~/goloot/backend && node -e "require('dotenv').config(); console.log(require('steam-totp').generateAuthCode(process.env.STEAM_SHARED_SECRET))"

Как это работает:

  1. dotenv загружает STEAM_SHARED_SECRET из backend/.env
  2. steam-totp генерирует TOTP код (аналог Steam Guard) из этого секрета
  3. Код действует ~30 секунд — вводить сразу после генерации

Когда нужно:

  • Ручной вход в бот-аккаунт через браузер/клиент Steam
  • Копирование Trade URL бота
  • Проверка инвентаря бота вручную
  • Изменение настроек аккаунта
SDA (Steam Desktop Authenticator)

shared_secret и identity_secret берутся из .maFile — файла, который создаёт SDA при привязке 2FA. Этот файл содержит всё необходимое для генерации кодов и подтверждения трейдов без мобильного приложения Steam.


  • Inventory — источник виртуальных предметов для вывода
  • Profile — настройка Trade URL и Steam верификация
  • Live Feed — события ITEM_WITHDRAWN в ленте
  • Withdraw Readiness — детали 7-точечной проверки