Setting up USDT/USDC betting intake for casino
Integrating stablecoins into a gambling platform differs technically from regular crypto processing: you need transaction confirmation speed comparable to game temporality, exact balance accounting without floating point errors, processing large number of micro-transactions, and — critically — verifying that accepted USDT/USDC is exactly the contract you expect, not a fake token with same name.
Network choice
USDT and USDC exist on dozens of networks. For casino what matters: finality speed, transaction cost, liquidity for conversion.
| Network | Finality | Tx cost | USDT | USDC | Suitable |
|---|---|---|---|---|---|
| Ethereum | ~12 blocks (~2.5 min) | $0.5-10 | ✓ | ✓ | Large deposits |
| Tron | 20 confirmations (~1 min) | ~$1 | ✓ (main) | ✓ | Asian audience |
| BNB Chain | 15 blocks (~45 sec) | $0.05-0.2 | ✓ | ✓ | Good balance |
| Polygon | 256 blocks (~5 min) | < $0.01 | ✓ | ✓ | Cheap option |
| Arbitrum | ~1 min (soft finality) | $0.01-0.1 | ✓ | ✓ | Optimal choice |
| Solana | ~2.5 sec | < $0.01 | ✓ | ✓ (native) | Maximum speed |
| Tron | — | — | Largest USDT volume | — | — |
Practical recommendation for casino: Tron (USDT TRC-20) + BNB Chain (USDT BEP-20) cover most audience. Add Ethereum for large players. USDC — Arbitrum or Base (Circle actively supports).
Important to verify contract address on deposit — accept only from official addresses:
const VERIFIED_CONTRACTS = {
USDT_ERC20: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
USDT_TRC20: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
USDT_BEP20: '0x55d398326f99059fF775485246999027B3197955',
USDC_ERC20: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
USDC_ARB: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
};
function verifyToken(contractAddress: string, network: string): boolean {
const key = `${tokenSymbol}_${network}`;
return VERIFIED_CONTRACTS[key]?.toLowerCase() === contractAddress.toLowerCase();
}
Deposit architecture
Address generation
Two approaches:
One address per user — HD wallet with BIP-32 derivation. Each user gets unique deposit address owned by platform. Any deposit to this address credits account. Simple to implement, but requires monitoring thousands of addresses.
import { HDNodeWallet } from 'ethers';
const masterWallet = HDNodeWallet.fromMnemonic(mnemonic);
function getDepositAddress(userId: number): string {
// Derivation path: m/44'/60'/0'/0/{userId}
const child = masterWallet.deriveChild(userId);
return child.address;
}
Common address + memo — one wallet for all, identifier in memo/tag. Simpler for EVM (just monitor one address), standard for Tron, XRP, Stellar. Risk: user may not specify memo.
Monitoring incoming transfers
For EVM chains — subscribe to Transfer events of needed contracts:
const usdtContract = getContract({
address: USDT_ERC20,
abi: erc20Abi,
client: publicClient,
});
// Subscribe to Transfer events to our addresses
publicClient.watchContractEvent({
address: USDT_ERC20,
abi: erc20Abi,
eventName: 'Transfer',
args: { to: ourDepositAddresses }, // filter by recipient
onLogs: async (logs) => {
for (const log of logs) {
const { from, to, value } = log.args;
const amountUSDT = Number(value) / 1e6; // USDT 6 decimals
await creditUserBalance(getUserByAddress(to), amountUSDT);
}
},
});
Important about decimals. USDT and USDC use 6 decimals (not 18 like ETH). 1 USDT = 1_000_000 in raw units. All balance arithmetic — in integer units (avoid float).
Confirmations and finality
Bet can't be accepted until deposit is finalized. Recommended thresholds:
| Network | Recommended confirmations | Wait time |
|---|---|---|
| Ethereum | 12-20 | 3-5 minutes |
| BNB Chain | 15 | 45-60 sec |
| Polygon | 256 | 8-10 minutes |
| Arbitrum | 1 (soft) / L1 finality | 1 min / 15 min |
| Tron | 20 | ~1 minute |
For Arbitrum: soft finality (1 L2 confirmation) sufficient for deposits — reorg risk virtually zero, but 7-day challenge window for bridged funds.
Withdrawals (payouts)
Security
Withdrawal of USDT/USDC — real fund movement. Critical rules:
Hot/cold wallet separation. Hot wallet holds only daily payout volume. Remainder — cold storage (hardware wallet or MPC custody).
Two-step confirmation. Withdrawal request → backend validation → queue → payout. Never send transaction synchronously in HTTP request.
Withdrawal rate limiting. Max X USDT per withdrawal, Y USDT per user per day, Z USDT per hour platform-wide.
Anti-money laundering checks. Check address through Chainalysis or TRM Labs API before payout — standard for licensed operators.
async function processWithdrawal(
userId: string,
toAddress: string,
amount: bigint, // in smallest units
): Promise<string> {
// Check address for sanctions/mixer activity
const riskScore = await checkAddressRisk(toAddress);
if (riskScore > RISK_THRESHOLD) {
await flagForManualReview(userId, toAddress, amount);
throw new Error('Withdrawal flagged for review');
}
// Prepare transaction
const tx = await walletClient.writeContract({
address: USDT_CONTRACT,
abi: erc20Abi,
functionName: 'transfer',
args: [toAddress, amount],
});
await recordWithdrawal(userId, tx, amount);
return tx;
}
Gas management
For USDT/USDC payouts you need native network token for gas (ETH, BNB, MATIC). If user addresses don't have ETH — can't transfer USDT. Not problem for casino (platform pays gas), but need hot wallet native token balance monitoring.
Balance accounting
User balance stored in database, not on-chain. On-chain transactions only for deposits/withdrawals.
-- Transaction table (ledger)
CREATE TABLE balance_transactions (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
type VARCHAR(20) NOT NULL, -- 'deposit', 'bet', 'win', 'withdrawal'
amount BIGINT NOT NULL, -- in smallest units (6 decimals for USDT)
currency VARCHAR(10) NOT NULL,
token_network VARCHAR(20),
tx_hash VARCHAR(66), -- on-chain hash for deposit/withdrawal
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Current balance = sum of all transactions
CREATE VIEW user_balances AS
SELECT user_id, currency,
SUM(CASE WHEN type IN ('deposit','win') THEN amount
WHEN type IN ('bet','withdrawal') THEN -amount END) AS balance
FROM balance_transactions
GROUP BY user_id, currency;
All sums in integer (BIGINT), unit = smallest token unit. No float arithmetic.
What's included in integration
Setup takes 2-4 weeks: choosing and configuring networks, generating and securely storing deposit wallets, monitoring incoming Transfer events, confirmation logic, balance crediting, withdrawal queue with checks, gas management, basic monitoring. Separately — AML provider connection and limit setup.







