Withdrawal Flow
- User requests withdrawal via dashboard (amount + destination address).
- Balance check: ensure user has sufficient USDT after fees.
- Withdrawal record created with
status: pending. - Withdrawal job queued in BullMQ.
- Worker decrypts stored wallet, constructs USDT transfer, broadcasts to BSC.
- Webhook fires, record updated to
status: completedwith txHash.
Withdrawal Fee Config
// withdrawal_fee_configs table
{ userId, network, token, feeFlat, feePercent }
// Fee formula (same as deposit fee):
withdrawalFee = Math.max(feeFlat, amount × feePercent / 100)
Wallet Encryption
Withdraw wallets are stored encrypted:
// Encryption: AES-256-CBC
// Key: WITHDRAW_WALLET_ENCRYPTION_KEY (32-byte hex)
// The raw private key is never stored in plaintext in the database.
Address Validation
Destination addresses are validated as valid EVM addresses via ethers.utils.isAddress() before the withdrawal is accepted. An invalid address returns a 400 error immediately — before any DB write.
Minimum Withdrawal Amount
Configurable per-user in withdrawal_fee_configs. Defaults to 2× the flat fee to ensure the net amount is always positive.
Revoke History
The withdraw_wallet_revoke_history table tracks whenever a withdrawal wallet is revoked or replaced, providing an audit trail for compliance.