Crypto Payment Gateway Development
The main mistake when designing a crypto payment gateway is trying to stretch the traditional payment system model onto blockchain. In fiat payments there are authorizations, captures, refunds, and chargebacks. On blockchain — only confirmed transactions and no possibility of forced reversal. Everything else needs to be built around this fundamental feature.
Architectural Solutions
Address Model for Payment Reception
Two approaches with fundamentally different trade-offs:
One address per order (HD wallet derivation)
For each new payment we derive a unique address from the master xpub key via path m/44'/60'/0'/0/{orderId}. The customer sees an address unique to their order — no confusion with amounts, no conflicts between simultaneous payments. The private key for collecting funds is derived offline, only at the time of withdrawal.
import { HDNodeWallet, Mnemonic } from "ethers";
function derivePaymentAddress(xpub: string, index: number): string {
const node = HDNodeWallet.fromExtendedKey(xpub);
return node.deriveChild(index).address;
}
Problem: you need to monitor thousands of addresses. Solution — webhook subscriptions via Alchemy/Moralis/QuickNode for Activity on specific addresses, or your own node with eth_getLogs over block ranges.
Shared deposit address with memo/tag
One address, customer specifies a unique payment ID in the transaction data field. Simpler infrastructure, but creates UX problem: the user must remember to enter the memo. Works in B2B, often doesn't in B2C.
Multi-Currency Support
Minimum production set for 2024: ETH, USDT (ERC-20), USDC (ERC-20), BNB, USDT (BEP-20), BTC, TRC-20 USDT. Each network requires a separate monitoring worker.
For ERC-20 tokens, monitoring via Transfer(address indexed from, address indexed to, uint256 value) event:
const transferTopic = ethers.id("Transfer(address,address,uint256)");
const logs = await provider.getLogs({
address: USDT_CONTRACT,
topics: [transferTopic, null, ethers.zeroPadValue(depositAddress, 32)],
fromBlock: lastCheckedBlock,
toBlock: "latest",
});
Confirmation Worker
Critical component — a service that tracks transaction status. Logic:
PENDING → CONFIRMING (1 confirmation) → CONFIRMED (N confirmations) → CREDITED
Number of confirmations depends on amount and network:
| Network | Small amounts (<$100) | Medium ($100–$10k) | Large (>$10k) |
|---|---|---|---|
| Ethereum | 2 blocks | 6 blocks | 12 blocks |
| BSC | 15 blocks | 30 blocks | 60 blocks |
| Bitcoin | 1 confirmation | 3 confirmations | 6 confirmations |
| Tron | 20 blocks | 40 blocks | 60 blocks |
Reorgs are a real issue on BSC and EVM networks with fast blocks. The worker must be able to detect reorgs (transaction blockhash changed) and revert payment status.
Hot Wallet and Fund Collection
After payment confirmation, money at the deposit address needs to be collected into hot wallet. For EVM networks this is a separate transaction with gas that you need to finance:
async function sweepDeposit(depositIndex: number, amount: BigInt) {
const depositKey = derivePrivateKey(masterKey, depositIndex);
const depositWallet = new ethers.Wallet(depositKey, provider);
// First send ETH for gas
await hotWallet.sendTransaction({
to: depositWallet.address,
value: GAS_BUDGET, // ~0.001 ETH
});
// Then collect tokens
const token = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, depositWallet);
await token.transfer(hotWalletAddress, amount);
}
For ERC-20 there's a pattern via permit (EIP-2612) — if the token supports it, you can collect without pre-sending ETH for gas via transferFrom with signature.
Security
Segregation of keys: master xpub (for address derivation) is stored in the application. Private keys are derived only for sweep operations, and only in an isolated signing service. Hot wallet — separate HSM or KMS (AWS KMS, GCP Cloud HSM).
Double-spend protection: don't credit until reaching the confirmation threshold. Don't trust pending transactions — the mempool can be replaced via RBF (Replace-by-Fee) in Bitcoin.
Rate limiting on deposit addresses: one address should accept one payment. After receiving the first transaction — mark the address as "in use", handle new transactions to it separately with an alert.
Webhook signatures: all outgoing payment notifications are signed with HMAC-SHA256 with a secret. The recipient verifies the signature — protection against webhook spoofing.
Production Stack
- Backend: Node.js/TypeScript or Go for workers (high concurrency)
- Queue: Redis + BullMQ or RabbitMQ for event processing
- DB: PostgreSQL for payments, separate audit table (append-only)
- Node monitoring: Alchemy/QuickNode with failover to backup provider
- Alerts: Grafana + PagerDuty on worker hangs, anomalous amounts, confirmation errors
MVP development timeline supporting 3–4 networks and basic dashboard — 1–2 weeks. This is the minimum time with existing blockchain infrastructure experience.







