Discord бот для розподілу ролей за NFT
Token gating — стандартна практика для спільнот NFT. Маєш NFT з колекції — отримаєш доступ до приватних каналів, ранній доступ, виключні дропи. Технічно це верифікація: гаманець з потрібним NFT → Discord акаунт → роль. Завдання виглядає простим, але між «верифікацією» та «справжнім постійним доступом» лежить цілий шар інфраструктури, який передбачено ламається.
Архітектура системи
Зв'язок wallet ↔ Discord
Користувач натискає «Verify» → редирект на сторінку верифікації → підключає гаманець через WalletConnect v2 → підписує повідомлення (без газу) → сервер перевіряє підпис → зберігає {discordId: walletAddress}.
Підпис повідомлення — не транзакція, користувач нічого не платить. Стандартне повідомлення для верифікації:
Verify Discord: username#1234
Nonce: a3f8b2c1
Timestamp: 1711234567
Nonce — випадкова рядок, унікальна для кожної сесії, з TTL 5 хвилин. Без nonce можливі replay-атаки: скопійований підпис можна переозначити.
Верифікація підпису на бекенді:
import { verifyMessage } from 'viem';
const isValid = await verifyMessage({
address: claimedAddress,
message: expectedMessage,
signature: userSignature
});
Перевірка володіння NFT: три підходи
Прямий RPC виклик. balanceOf(wallet, tokenId) через ethers.js або viem. Простий, працює для малих колекцій. Проблема: при 10 000 користувачів — 10 000 RPC викликів при кожній перевірці.
Alchemy/Moralis NFT API. getNFTsForOwner(wallet, contractAddress) — один запит повертає всі токени. Швидко, але залежність від зовнішнього сервісу. При даунтаймі Alchemy — бот не працює.
The Graph subgraph. Індексуємо Transfer події колекції, будуємо {owner: [tokenIds]} маппінг. Швидкий GraphQL запит. Затримка індексації ~1-5 хвилин — при швидкій продажі токена користувач ще 5 хвилин зберігає роль.
Для продакшн ботів використовуємо Alchemy NFT API як основний з The Graph як резервна та прямим RPC як останній fallback.
Discord bot: slash команди та обробка подій
Бот реалізований на discord.js v14. Ключові slash команди:
-
/verify— початок верифікації, бот відправляє ephemeral повідомлення зі ссилкою -
/check— примусова перевірка володіння (для користувачів, які продали токен) -
/roles— показати всі ролі та вимоги до них
Ролі призначаються через guild.members.cache.get(userId)?.roles.add(roleId). Вимагає дозволу MANAGE_ROLES та роль бота має бути вище за призначувані ролі в ієрархії — часта помилка при налаштуванні.
async function syncUserRoles(userId: string, wallet: string): Promise<void> {
const member = await guild.members.fetch(userId);
const ownedTokens = await getNFTsForOwner(wallet, CONTRACT_ADDRESS);
for (const [roleId, requirement] of ROLE_REQUIREMENTS) {
const qualifies = checkQualification(ownedTokens, requirement);
if (qualifies && !member.roles.cache.has(roleId)) {
await member.roles.add(roleId);
} else if (!qualifies && member.roles.cache.has(roleId)) {
await member.roles.remove(roleId);
}
}
}
Періодична ресинхронізація
Критичний момент: користувач продав NFT, має втратити роль. Бот не отримує подію від Discord — він повинен періодично перевіряти сам.
Cron job кожні 10-30 хвилин: для кожного верифікованого користувача перевіряємо поточний баланс, оновлюємо ролі. При 1000 користувачів та 30-хвилинному інтервалі — ~33 API запиту на хвилину. Укладається в ліміти Alchemy.
Оптимізація: слухаємо Transfer події контракту через WebSocket (Alchemy WebSocket API). При будь-якому Transfer перевіряємо, задіяний ли верифікований гаманець, та негайно оновлюємо роль. Це знижує затримку до секунд.
const provider = new WebSocketProvider(ALCHEMY_WS_URL);
const contract = new Contract(NFT_ADDRESS, erc721Abi, provider);
contract.on('Transfer', async (from, to, tokenId) => {
const affectedWallets = [from, to].filter(w => w !== ethers.ZeroAddress);
for (const wallet of affectedWallets) {
await syncRolesForWallet(wallet);
}
});
Підтримка декількох колекцій та ролей на основі ознак
Справжні проекти вимагають складних умов:
- Маєш ≥3 токени з колекції A → VIP роль
- Маєш токен з ознакою «Legendary» → роль Legendary
- Маєш токен з колекції A та колекції B → роль Collab
Для ролей на основі ознак потрібен доступ до метаданих. Alchemy getNFTsForOwner повертає tokenMetadata включаючи attributes. Конфігурація ролей у JSON:
{
"LEGENDARY_ROLE_ID": {
"contract": "0x...",
"minBalance": 1,
"requiredTrait": {"trait_type": "Rarity", "value": "Legendary"}
}
}
Stack
| Компонент | Технологія |
|---|---|
| Бот | discord.js v14, TypeScript |
| Wallet connect | WalletConnect v2 (веб-додаток для верифікації) |
| NFT дані | Alchemy NFT API + WebSocket |
| База даних | PostgreSQL (userId ↔ wallet маппінг) |
| Хостинг | Railway або Render (постійний процес) |
| Верифікація підписів | viem verifyMessage |
Орієнтири за часом
Базовий бот з однією колекцією та однією роллю — 3-4 дні. Розширений з декількома колекціями, ролями на основі ознак та реал-тайм синхром через WebSocket — 4-5 днів.







