En SoftDev.Mx hemos construido infraestructura de pagos en criptomonedas para plataformas con miles de usuarios activos en múltiples países. Este artículo documenta la arquitectura que diseñamos para un gateway de pagos que soporta Bitcoin, USDT (TRC-20, ERC-20, BEP-20) y próximamente más redes.
El objetivo era claro: los usuarios debían poder depositar criptomonedas y ver su saldo reflejado al instante. Detrás de escena, esto implicaba monitorear múltiples blockchains, detectar transacciones entrantes, asignar wallets dedicados por pago y barrer fondos automáticamente hacia una wallet maestra.
Necesitábamos una librería que nos permitiera interactuar con Ethereum y sus derivados (BSC, Tron) de manera unificada. ethers.js v6 fue la elección natural: soporta múltiples redes, tiene un API limpia y está activamente mantenida. Para Bitcoin, usamos bitcoinjs-lib combinado con Electrum para monitoreo de transacciones.
Cada vez que un usuario inicia un depósito, el backend genera una wallet única dedicada exclusivamente a esa transacción. Esto se logra derivando una nueva dirección a partir de una semilla maestra usando BIP32/BIP44. Ventajas: si un usuario deposita en la dirección incorrecta, el impacto es limitado a una sola transacción; la trazabilidad es completa.
Las transacciones entrantes se detectan mediante polling a los nodos RPC o APIs de exploradores de bloques. Para Ethereum y BSC, usamos llamadas JSON-RPC a nodos propios. Para Tron, usamos la API de TronGrid. Para Bitcoin, Electrum. Cada red tiene su propia lógica de confirmaciones y reorgs, lo que añade complejidad al manejo de estados.
La arquitectura de sweeping es clave para la seguridad. Los fondos acumulados en wallets temporales se barren periódicamente hacia una wallet maestra fría. Implementamos un cron job que cada hora revisa los balances de todas las wallets activas y ejecuta transacciones de barrido. Para minimizar el costo en gas, las transacciones se agrupan cuando es posible.
Cada transacción entrante y saliente se registra en PostgreSQL con su estado (pendiente, confirmando, confirmada, fallida). Un worker escucha eventos `TransactionConfirmer` que actualiza los estados a medida que las confirmaciones en cadena aumentan. El dashboard en tiempo real consume estos datos para mostrar al usuario el estado de sus transacciones.
Con esta arquitectura hemos procesado miles de transacciones sin pérdida de fondos. Las lecciones clave: usar direcciones dedicadas por transacción, tener redundancia en nodos RPC, implementar límites de gas dinámicos, y sobre todo — probar exhaustivamente en testnet antes de tocar mainnet.