← All Posts
How It Works

Withdrawal System Deep Dive: How Paychainly Handles User-Initiated USDT Withdrawals

May 21, 2026· 1 min read

Withdrawal Flow

  1. User requests withdrawal via dashboard (amount + destination address).
  2. Balance check: ensure user has sufficient USDT after fees.
  3. Withdrawal record created with status: pending.
  4. Withdrawal job queued in BullMQ.
  5. Worker decrypts stored wallet, constructs USDT transfer, broadcasts to BSC.
  6. Webhook fires, record updated to status: completed with 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.

← Back to Blog
withdrawalsUSDTfee configwalletsecurity