Реалізація виводу токенів (Withdraw) з мобільної GameFi-гри
Вивід токенів — найчутливіша операція у GameFi. Гравець накопив 500 GOLD-токенів у грі та хочет вивести їх на зовнішній гаманець. За цією простою кнопкою: верифікація ігрового балансу, підпис транзакції, захист від чит-клієнтів та ботів, газ на мобільному пристрої.
Чому вивід ломається найчастіше
Головний ризик — двійне списання. Гравець ініціював вивід, транзакція зависла у mempool через низький газ, гравець повторив запит. Серверний баланс вже зменшений при першому запиті, але токени на гаманець не прийшли ніколи. Або навпаки — прийшли вдвічі, тому що idempotency не реалізована.
Розв'язання: nonce-система на рівні додатка. Кожен запит на вивід отримує унікальний withdrawalId (UUID). Бекенд приймає вивід тільки один раз для кожного withdrawalId. Статус: pending → submitted → confirmed / failed. Поки статус pending — повторний запит з тим же ID повертає поточний статус, не створює новий вивід.
// Android: стани виводу через sealed class
sealed class WithdrawalState {
object Idle : WithdrawalState()
data class Pending(val withdrawalId: String) : WithdrawalState()
data class Submitted(val txHash: String) : WithdrawalState()
data class Confirmed(val txHash: String, val amount: BigDecimal) : WithdrawalState()
data class Failed(val reason: String) : WithdrawalState()
}
class WithdrawViewModel(private val repository: WithdrawRepository) : ViewModel() {
private val _state = MutableStateFlow<WithdrawalState>(WithdrawalState.Idle)
val state: StateFlow<WithdrawalState> = _state
fun initiateWithdraw(amount: BigDecimal, toAddress: String) {
viewModelScope.launch {
val withdrawalId = UUID.randomUUID().toString()
_state.emit(WithdrawalState.Pending(withdrawalId))
try {
val result = repository.createWithdrawal(withdrawalId, amount, toAddress)
_state.emit(WithdrawalState.Submitted(result.txHash))
pollConfirmation(result.txHash)
} catch (e: Exception) {
_state.emit(WithdrawalState.Failed(e.message ?: "Unknown error"))
}
}
}
}
Серверна верифікація ігрового балансу
Мобільний клієнт ніколи не є джерелом правди про баланс. Баланс зберігається на сервері, вся ігрова логіка — серверна. Запит на вивід містить amount, сервер верифікує: достатньо токенів, немає активного cooldown (наприклад, 24 години між виводами), аккаунт не забанений.
Після успішної верифікації — сервер або мінтить токени на гаманець гравця (якщо це централізований мінт), або підписує withdrawal voucher, який гравець предявляє смарт-контракту.
Паттерн Withdrawal Voucher
// Гравець предявляє підписаний сервером ваучер
contract GameTokenBridge {
address public signer; // backend сервер
function withdraw(
uint256 amount,
uint256 nonce,
bytes memory signature
) external {
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount, nonce));
bytes32 ethHash = hash.toEthSignedMessageHash();
require(ethHash.recover(signature) == signer, "Invalid signature");
require(!usedNonces[nonce], "Nonce already used");
usedNonces[nonce] = true;
_mint(msg.sender, amount);
}
}
Сервер підписує ваучер своїм приватним ключем (signer). Контракт перевіряє підпис. Це означає: без підпису сервера ніхто не може вивести токени — захист від експлойтів на смарт-контракті.
Гаманець та підпис транзакції на мобільному
Для GameFi з масовою аудиторією — Account Abstraction (ERC-4337). Гравець не управляє seed-фразою; додаток створює smart account через Biconomy SDK або ZeroDev. Підпис транзакції — через Face ID / Touch ID, а не seed-фразу. Газ спонсирується Paymaster'ом.
Для продвинутих користувачів — підтримка зовнішніх гаманців через WalletConnect v2: Deep Link відкриває MetaMask/Trust Wallet на телефоні, користувач підтверджує транзакцію там.
Комісії та газ
Показуємо користувачу:
- Скільки токенів отримає (amount - fee)
- Поточну вартість газу у USD
- Очікуване час підтвердження
Мінімальний поріг виводу — обов'язковий параметр. Вивід 0.01 GOLD при газі $0.50 беззмістовний. Показуємо попередження, якщо комісія > 10% від суми виводу.
Захист від ботів
Cooldown між виводами, ліміти на суму в добу/тиждень, перевірка аккаунта на підозрілу активність (занадто багато токенів за короткий час — ознака чита). Fingerprinting пристрою через DeviceCheck (iOS) або Play Integrity API (Android) — верифікуємо що запит йде з реального пристрою, а не з емулятора/скрипта.
Сроки
2–3 тижні для реалізації виводу з voucher-паттерном, idempotency та UI. З Account Abstraction та Paymaster — плюс один тиждень. Вартість розраховується індивідуально після аналізу вимог.







