Этот документ описывает наше понимание механики Telegram Gift Auctions, принятые допущения и архитектурные решения.
- Исследование продукта
- Многораундовая система
- Модель ставок
- Anti-Sniping механизм
- Финансовая модель
- Edge Cases
- Наши допущения
- Отличия от конкурентов
Для понимания механики мы изучили:
- Официальные анонсы Telegram о Gift Auctions
- Статьи и обзоры (membertel.com/blog/telegram-gift-auctions/)
- Паттерны поведения реальных аукционов
- Сравнение с классическими аукционами (eBay, Sotheby's и др.)
| Аспект | Классический аукцион | Telegram Gift Auction |
|---|---|---|
| Структура | Один дедлайн | Множество раундов |
| Победители | Один победитель | Частичные победители в каждом раунде |
| Проигравшие | Ставки отменяются | Ставки переносятся в следующий раунд |
| Anti-sniping | Редко | Встроенный механизм |
| Участие | Можно выйти в любой момент | Средства заблокированы до победы/окончания |
Telegram Gift Auction — это не классический аукцион "победитель забирает всё", а система поэтапного распределения лимитированных товаров.
┌─────────────────────────────────────────────────────────────────┐
│ АУКЦИОН: 100 NFT │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Раунд 1 │ │ Раунд 2 │ │ Раунд 3 │ │
│ │ 30 NFT │───▶│ 40 NFT │───▶│ 30 NFT │ │
│ │ 2 минуты │ │ 2 минуты │ │ 2 минуты │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Топ-30 ставок Топ-40 ставок Топ-30 ставок │
│ = победители = победители = победители │
│ │
│ Остальные ─────▶ Остальные ─────▶ Возврат средств │
│ переносятся переносятся │
│ │
└─────────────────────────────────────────────────────────────────┘
- Раунд начинается в запланированное время
- Участники делают ставки или повышают существующие
- Раунд завершается (с возможными anti-sniping продлениями)
- Топ-N участников получают товары (N = количество товаров в раунде)
- Средства победителей списываются
- Проигравшие автоматически переходят в следующий раунд с текущей ставкой
- В последнем раунде оставшиеся ставки возвращаются
| Преимущество | Описание |
|---|---|
| Поддержание интереса | Участники остаются вовлечены на протяжении всего аукциона |
| Справедливость | Больше шансов получить товар при продолжении участия |
| Ликвидность | Средства остаются в системе до финала |
| Динамика | Каждый раунд — это новый шанс |
flowchart TD
U[👤 Пользователь] --> A{Есть активная ставка?}
A -->|Нет| B[Создать новую ставку]
A -->|Да| C{Новая сумма > текущей?}
C -->|Да| D[Повысить ставку]
C -->|Нет| E[❌ Отклонить]
B --> F[Заблокировать сумму]
D --> G[Заблокировать разницу]
F --> H[✅ Ставка активна]
G --> H
- Одна активная ставка на аукцион на пользователя
- Только повышение — нельзя понизить ставку
- Мгновенная блокировка — средства блокируются сразу
- Инкрементальная блокировка — при повышении блокируется только разница
Баланс пользователя: 1000 USDT
Действие 1: Ставка 100 USDT
├── Баланс: 1000 → 900
├── Заблокировано: 0 → 100
└── Активная ставка: 100
Действие 2: Повышение до 150 USDT
├── Баланс: 900 → 850
├── Заблокировано: 100 → 150
└── Активная ставка: 150
Действие 3: Попытка понизить до 120 USDT
└── ❌ ОТКЛОНЕНО: нельзя понижать ставку
Результат раунда: Проигрыш
├── Ставка переносится в следующий раунд
└── Средства остаются заблокированы (150)
Результат финала: Проигрыш
├── Заблокировано: 150 → 0
└── Баланс: 850 → 1000 (возврат)
Sniping — стратегия размещения ставки в последние секунды/миллисекунды аукциона, чтобы конкуренты не успели ответить.
Без anti-sniping:
┌────────────────────────────────────────────────┐
│ Раунд Конец │
│ ═══════════════════════════════════════│ │
│ ↑ │
│ Ставка бота │
│ (последняя мс) │
│ │
│ Другие участники не успевают ответить! ❌ │
└────────────────────────────────────────────────┘
С anti-sniping:
┌────────────────────────────────────────────────────────────┐
│ Раунд │ Anti-sniping │ Продление │
│ ═════════════════════════│══════════════│═════════════════│
│ ↑ ↑ │
│ 30 сек до Ставка = │
│ конца продление │
│ │
│ Другие участники получают шанс ответить! ✅ │
└────────────────────────────────────────────────────────────┘
| Параметр | Описание | По умолчанию |
|---|---|---|
antiSnipingWindowSeconds |
Окно детекции (последние N секунд) | 30 |
antiSnipingExtensionSeconds |
Продление при ставке в окне | 30 |
maxAntiSnipingExtensions |
Максимум продлений | 5 |
flowchart TD
BID[Новая ставка] --> CHECK{До конца < windowSeconds?}
CHECK -->|Нет| NORMAL[Обычная обработка]
CHECK -->|Да| EXT_CHECK{extensions < maxExtensions?}
EXT_CHECK -->|Нет| NORMAL
EXT_CHECK -->|Да| EXTEND[Продлить на extensionSeconds]
EXTEND --> INC[extensions++]
INC --> NOTIFY[WebSocket: anti-sniping-extended]
NOTIFY --> NORMAL
| Без anti-sniping | С anti-sniping |
|---|---|
| Боты доминируют | Честная конкуренция |
| Участники разочарованы | Вовлечённость растёт |
| Низкие финальные цены | Рыночные цены |
Все движения средств записываются в append-only журнал:
sequenceDiagram
participant U as Пользователь
participant L as Ledger
participant DB as MongoDB
Note over L: Каждая операция = новая запись
U->>L: Депозит 1000 USDT
L->>DB: INSERT { type: "deposit", amount: 1000 }
U->>L: Ставка 100 USDT
L->>DB: INSERT { type: "hold", amount: 100, ref: bidId }
U->>L: Повышение до 150 USDT
L->>DB: INSERT { type: "hold", amount: 50, ref: bidId }
Note over L: При победе
L->>DB: INSERT { type: "capture", amount: 150, ref: bidId }
Note over L: При проигрыше
L->>DB: INSERT { type: "release", amount: 150, ref: bidId }
| Тип | Описание | Влияние на баланс |
|---|---|---|
deposit |
Пополнение | balance ↑ |
hold |
Блокировка под ставку | balance ↓, frozen ↑ |
capture |
Списание при победе | frozen ↓ |
release |
Возврат при проигрыше | frozen ↓, balance ↑ |
withdrawal_request |
Запрос вывода | balance ↓, frozen ↑ |
withdrawal_complete |
Вывод завершён | frozen ↓ |
Для каждого пользователя в любой момент времени:
1. balance >= 0
2. frozen >= 0
3. sum(active_holds) == frozen
4. sum(deposits) - sum(captures) - sum(withdrawals) == balance + frozen
Каждая операция имеет idempotencyKey:
// Первый вызов
await ledger.hold({ userId, amount: 100, idempotencyKey: "bid-123-v1" });
// → Создаёт запись, возвращает holdId
// Повторный вызов с тем же ключом
await ledger.hold({ userId, amount: 100, idempotencyKey: "bid-123-v1" });
// → Возвращает тот же holdId, без создания новой записи
// Вызов с тем же ключом, но другой суммой
await ledger.hold({ userId, amount: 200, idempotencyKey: "bid-123-v1" });
// → 409 ConflictПроблема: Два запроса на ставку от одного пользователя одновременно.
Решение: Распределённая блокировка в Redis.
Request 1: SETNX lock:user:auction → SUCCESS → обработка
Request 2: SETNX lock:user:auction → FAIL → 409 Conflict
Проблема: Две ставки с одинаковой суммой.
Решение: Детерминированное ранжирование:
- Сумма (DESC)
- Время создания (ASC) — кто раньше, тот выше
- ID ставки (ASC) — для полной детерминированности
Проблема: В раунде меньше ставок, чем товаров.
Решение: Все участники с валидными ставками побеждают.
Проблема: Сервер падает в процессе расчёта победителей.
Решение:
- Распределённая блокировка с TTL
- Идемпотентные операции
- Возможность перезапуска финализации
Проблема: Ставка отправлена за 1ms до закрытия.
Решение:
- Если успела до закрытия — принята
- Если раунд уже closed — отклонена с 400
Проблема: Боты бесконечно продлевают раунд.
Решение: maxAntiSnipingExtensions ограничивает продления.
| Допущение | Уверенность | Источник |
|---|---|---|
| Многораундовая система | ✅ Высокая | Официальные анонсы |
| Перенос ставок между раундами | ✅ Высокая | Статьи и обзоры |
| Anti-sniping существует | ✅ Высокая | Упоминания в документации |
| Допущение | Обоснование |
|---|---|
| Одна ставка на аукцион | Упрощает UX, предотвращает самоперебивание |
| Мгновенная блокировка | Гарантия платёжеспособности |
| Нельзя понижать ставку | Предотвращает манипуляции |
| Детерминированный tiebreaker | Прозрачность и проверяемость |
| Продление = фиксированное время | Проще понять для пользователей |
| Аспект | Наш выбор | Альтернатива |
|---|---|---|
| Минимальный шаг ставки | Нет ограничения | Фиксированный % |
| Минимальная ставка | На уровне аукциона | Глобальная |
| Уведомления о перебитии | Мгновенные | Батчами |
| Особенность | Описание |
|---|---|
| Криптографическая проверяемость | Merkle-корни ставок, подписи раундов, replay-эндпоинты |
| Ledger-first финансы | Append-only журнал операций, полный аудит |
| Микросервисная архитектура | Независимое масштабирование, изоляция сбоев |
| Прокси-ставки | Максимум в эскроу, авто-повышение |
| 3 стратегии кошельков | address_pool, memo_tag, address_per_user |
| KMS интеграция | Опциональное хранение ключей |
- Детерминированность — одинаковые входные данные дают одинаковые результаты
- Идемпотентность — безопасные повторные запросы
- Проверяемость — независимая верификация результатов
- Устойчивость — восстановление из журнала при сбоях
- Масштабируемость — горизонтальное масштабирование сервисов
Система спроектирована как production-ready решение с полным циклом: от депозитов до выдачи выигрышей.