La última vez completamos el sistema de control de riesgos de la bolsa, en esta ocasión conectamos la wallet de la bolsa a la cadena Solana. El modelo de cuentas, el almacenamiento de logs y el mecanismo de confirmación de Solana son muy diferentes de las cadenas basadas en Ethereum. Si se sigue usando el esquema de Ethereum, es muy fácil cometer errores. A continuación, repasamos la lógica general para entender Solana.
Comprendiendo la singularidad de Solana
Modelo de cuentas de Solana
Solana utiliza un modelo donde programa y datos están separados; los programas son compartidos, mientras que los datos del programa se almacenan en cuentas PDA (Program Derived Address) de forma independiente. Debido a que los programas son compartidos, se necesita Token Mint para distinguir diferentes tokens. La cuenta Token Mint almacena metadatos globales del token, como permiso de acuñación (mint_authority), suministro total (supply), decimales (decimals), etc.
Cada token tiene una dirección única de cuenta Mint que lo identifica, por ejemplo, USDC en la red principal de Solana tiene la dirección de Mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.
En Solana existen dos programas de Token: SPL Token y SPL Token-2022. Cada SPL Token tiene una ATA (Associated Token Account) independiente para guardar el saldo del usuario. Cuando se realiza una transferencia de tokens, en realidad se llama a su programa respectivo para transferir entre cuentas ATA.
Restricciones en los logs de Solana
En Ethereum, se obtiene información de transferencias analizando los logs históricos. En Solana, los logs de ejecución no se conservan permanentemente por defecto, no pertenecen al estado del libro mayor (no hay un filtro Bloom para logs), y pueden ser truncados durante la ejecución.
Por lo tanto, no podemos hacer conciliación de depósitos solo escaneando logs, sino que debemos usar getBlock o getSignaturesForAddress para analizar instrucciones.
Confirmación y reorganización en Solana
El tiempo de bloque en Solana es de aproximadamente 400ms. Con 32 confirmaciones (unos 12 segundos), se alcanza el estado finalizado (finalized). Si la precisión en tiempo real no es crítica, basta confiar en los bloques finalizados.
Para mayor precisión, hay que considerar posibles reorganizaciones de bloques, aunque son raras. La diferencia es que el consenso de Solana no depende del parentBlockHash para formar la cadena, por lo que no se puede detectar bifurcaciones comparando parentBlockHash y blockHash en la base de datos como en Ethereum. ¿Cómo detectar si un bloque ha sido reorganizado?
Al escanear localmente, debemos registrar el blockhash de cada slot. Si el blockhash de un mismo slot cambia, indica que hubo un rollback.
Entendiendo las diferencias de Solana, ahora podemos comenzar con las modificaciones en la base de datos:
Diseño de tablas en la base de datos
Dado que Solana tiene dos tipos de tokens, en la tabla tokens añadiremos un campo token_type para distinguir entre SPL Token y SPL Token-2022.
Aunque las direcciones de Solana difieren de Ethereum, ambas pueden derivarse usando BIP32 y BIP44, solo que con diferentes rutas de derivación. Por ello, podemos mantener la tabla wallets existente, pero para soportar el mapeo ATA y el seguimiento de bloques en Solana, añadiremos estas tres tablas:
Nombre de la tabla
Campos clave
Descripción
solana_slots
slot, block_hash, status, parent_slot
Información redundante de slots, para detectar bifurcaciones y gestionar reversiones
solana_transactions
tx_hash, slot, to_addr, token_mint, amount, type
Detalles de depósitos y retiros, tx_hash único para seguimiento de doble firma
Registro del mapeo ATA-usuario, para que el módulo de escaneo pueda consultar por ata_address
Detalles:
solana_slots registra estados como confirmed, finalized, skipped; el escáner decide si guardar o revertir según el estado.
solana_transactions almacena en lamports o unidades mínimas del token, con un campo type para distinguir depósitos, retiros, etc. La escritura sensible requiere firma de control de riesgos.
solana_token_accounts mantiene la relación con wallets/usuarios, garantizando la unicidad de ATA (wallet_address + token_mint), que es clave para la lógica de escaneo.
Para más detalles, consultar db_gateway/database.md
Procesamiento de depósitos de usuarios
Para gestionar depósitos, se deben escanear continuamente los datos en la cadena Solana, con dos métodos principales:
Escaneo de firmas: getSignaturesForAddress
Escaneo de bloques: getBlock
Método 1: Escanear firmas del address. Se llama a getSignaturesForAddress con la dirección (que puede ser la ATA generada para el usuario o el programID). Se pasa como parámetros before, until, limit para obtener firmas incrementales. Luego, se obtiene la transacción con getTransaction usando la firma.
Este método funciona bien con pocos usuarios o cuentas, pero si hay muchas, es mejor usar el método de escaneo de bloques, que es el que implementamos aquí.
Método 2: Escanear bloques. Se obtiene el slot más reciente, se llama a getBlock para obtener detalles completos, incluyendo transacciones y cuentas, filtrando por instrucciones y cuentas relevantes.
Nota: Debido al alto volumen de transacciones y TPS en Solana, en producción puede que el análisis y filtrado no puedan seguir el ritmo de los bloques. En ese caso, se recomienda usar colas de mensajes (Kafka, RabbitMQ) para filtrar las transferencias de tokens y detectar potenciales eventos de depósito, enviándolos a los consumidores para su procesamiento y almacenamiento. Para acelerar el filtrado, algunos datos calientes se almacenan en Redis. Si hay muchos usuarios, se puede dividir por ATA para mejorar la eficiencia con múltiples consumidores.
Otra opción es usar servicios de indexadores de terceros, que ofrecen Webhook, monitoreo de cuentas y filtrado avanzado, soportando cargas de datos elevadas.
Proceso de escaneo de bloques
Utilizamos el método 2, con código en los módulos scan/solana-scan (blockScanner.ts y txParser.ts). El flujo principal:
1. Sincronización inicial y recuperación histórica (performInitialSync)
Desde el último slot escaneado, se recorre hasta el más reciente.
Cada 100 slots, se verifica si hay nuevos slots.
Se obtiene el bloque con confirmación “confirmed” para balance entre rapidez y precisión.
2. Escaneo en tiempo real (scanNewSlots)
Se comprueba continuamente si hay nuevos slots.
Se revalida el slot confirmado más reciente para detectar reversiones.
3. Análisis de bloques (txParser.parseBlock)
Se llama a getBlock con commitment “confirmed” y encoding “jsonParsed”.
Se recorren las transacciones, instrucciones y metadatos internos.
Solo se procesan transacciones exitosas (tx.meta.err === null).
4. Análisis de instrucciones (txParser.parseInstruction)
Transferencias SOL: coinciden con System Program y tipo ‘transfer’, verificando si la dirección destino está en la lista monitoreada.
Transferencias SPL Token: coinciden con Token Program o Token-2022, verificando si la dirección destino es una ATA, y mapeando a la wallet y token mint en la base de datos.
Gestión de reversiones:
El programa obtiene continuamente el slot finalizado (finalizedSlot). Cuando un slot es menor o igual, se marca como finalizado. Para los que aún están en confirmed, se comprueba si el blockhash ha cambiado para detectar reversiones.
Ejemplo de código clave:
// blockScanner.ts - Escaneo de un slot
async scanSingleSlot(slot: number) {
const block = await solanaClient.getBlock(slot);
if (!block) {
await insertSlot({ slot, status: 'skipped' });
return;
}
const finalizedSlot = await getCachedFinalizedSlot();
const status = slot <= finalizedSlot ? 'finalized' : 'confirmed';
await processBlock(slot, block, status);
}
// txParser.ts - Parseo de instrucciones
for (const tx of block.transactions) {
if (tx.meta?.err) continue; // Saltar transacciones fallidas
const instructions = [
...tx.transaction.message.instructions,
...(tx.meta.innerInstructions ?? []).flatMap(i => i.instructions)
];
for (const ix of instructions) {
// Transferencia SOL
if (ix.programId === SYSTEM_PROGRAM_ID && ix.parsed?.type === 'transfer') {
if (monitoredAddresses.has(ix.parsed.info.destination)) {
// Procesar depósito
}
}
// Transferencia SPL Token
if (ix.programId === TOKEN_PROGRAM_ID || ix.programId === TOKEN_2022_PROGRAM_ID) {
if (ix.parsed?.type === 'transfer' || ix.parsed?.type === 'transferChecked') {
const ataAddress = ix.parsed.info.destination;
const walletAddress = ataToWalletMap.get(ataAddress);
if (walletAddress && monitoredAddresses.has(walletAddress)) {
// Procesar depósito
}
}
}
}
}
Al detectar depósitos, se mantiene la seguridad con DB Gateway y firma doble del control de riesgos, y se registra en la tabla de fondos.
Retiro
El proceso de retiro en Solana es similar al de EVM, pero con diferencias en la construcción de transacciones:
Hay dos tipos de tokens: SPL-Token y SPL-Token 2022, con diferentes programID. Al construir la transacción, se debe distinguir entre ellos.
La transacción consta de dos partes: firmas (signatures) y mensaje (message). El message incluye header, accountKeys, recentBlockhash, instructions. El hash del mensaje se firma con las firmas.
No hay nonce en Solana; en su lugar, se usa recentBlockhash, válido por unos 150 bloques (~1 minuto). Cada vez que se realiza un retiro, se obtiene un recentBlockhash actualizado en tiempo real. Si el retiro requiere revisión manual, se vuelve a obtener el recentBlockhash, se construye la transacción y se firma nuevamente.
Proceso de retiro
El flujo general:
![diagrama de flujo de retiro]
El código clave para firmar y enviar la transacción:
// Construcción de instrucciones
const instruction = getTransferSolInstruction({
source: hotWalletSigner,
destination: solanaAddressTo,
amount: BigInt(amount)
});
// Para token
const instruction = getTransferInstruction({
source: sourceAta,
destination: destAta,
authority: hotWalletSigner,
amount: BigInt(amount)
});
// Construcción y firma del mensaje
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(hotWalletSigner, tx),
tx => setTransactionMessageLifetime({ blockhash, lastValidBlockHeight }),
tx => appendTransactionMessageInstruction(instruction, tx)
);
// Firmar
const signedTx = await signTransactionMessageWithSigners(transactionMessage);
// Codificación para envío
const signedTransaction = getBase64EncodedWireTransaction(signedTx);
Verificación previa de ATA: asegurar que la cuenta ATA exista antes del retiro, o crearla si no.
Prioridad en tarifas: en congestión, ajustar computeUnitPrice para priorizar.
Resumen
Integrar Solana en la bolsa no requiere cambios en la arquitectura general, solo adaptar su modelo de cuentas, estructura de transacciones y mecanismo de confirmación.
Para depósitos, mantener un mapeo ATA-wallet, monitorizar cambios en blockhash para detectar reorganizaciones, y actualizar dinámicamente el estado del bloque (confirmed → finalized).
Para retiros, obtener el último recentBlockhash en tiempo real, distinguir entre tokens SPL y Token-2022, y construir transacciones específicas para cada caso.
Esta página puede contener contenido de terceros, que se proporciona únicamente con fines informativos (sin garantías ni declaraciones) y no debe considerarse como un respaldo por parte de Gate a las opiniones expresadas ni como asesoramiento financiero o profesional. Consulte el Descargo de responsabilidad para obtener más detalles.
Desarrollo del sistema de billetera de la plataforma de intercambio — Integración con la cadena Solana
La última vez completamos el sistema de control de riesgos de la bolsa, en esta ocasión conectamos la wallet de la bolsa a la cadena Solana. El modelo de cuentas, el almacenamiento de logs y el mecanismo de confirmación de Solana son muy diferentes de las cadenas basadas en Ethereum. Si se sigue usando el esquema de Ethereum, es muy fácil cometer errores. A continuación, repasamos la lógica general para entender Solana.
Comprendiendo la singularidad de Solana
Modelo de cuentas de Solana
Solana utiliza un modelo donde programa y datos están separados; los programas son compartidos, mientras que los datos del programa se almacenan en cuentas PDA (Program Derived Address) de forma independiente. Debido a que los programas son compartidos, se necesita Token Mint para distinguir diferentes tokens. La cuenta Token Mint almacena metadatos globales del token, como permiso de acuñación (mint_authority), suministro total (supply), decimales (decimals), etc.
Cada token tiene una dirección única de cuenta Mint que lo identifica, por ejemplo, USDC en la red principal de Solana tiene la dirección de Mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v.
En Solana existen dos programas de Token: SPL Token y SPL Token-2022. Cada SPL Token tiene una ATA (Associated Token Account) independiente para guardar el saldo del usuario. Cuando se realiza una transferencia de tokens, en realidad se llama a su programa respectivo para transferir entre cuentas ATA.
Restricciones en los logs de Solana
En Ethereum, se obtiene información de transferencias analizando los logs históricos. En Solana, los logs de ejecución no se conservan permanentemente por defecto, no pertenecen al estado del libro mayor (no hay un filtro Bloom para logs), y pueden ser truncados durante la ejecución.
Por lo tanto, no podemos hacer conciliación de depósitos solo escaneando logs, sino que debemos usar getBlock o getSignaturesForAddress para analizar instrucciones.
Confirmación y reorganización en Solana
El tiempo de bloque en Solana es de aproximadamente 400ms. Con 32 confirmaciones (unos 12 segundos), se alcanza el estado finalizado (finalized). Si la precisión en tiempo real no es crítica, basta confiar en los bloques finalizados.
Para mayor precisión, hay que considerar posibles reorganizaciones de bloques, aunque son raras. La diferencia es que el consenso de Solana no depende del parentBlockHash para formar la cadena, por lo que no se puede detectar bifurcaciones comparando parentBlockHash y blockHash en la base de datos como en Ethereum. ¿Cómo detectar si un bloque ha sido reorganizado?
Al escanear localmente, debemos registrar el blockhash de cada slot. Si el blockhash de un mismo slot cambia, indica que hubo un rollback.
Entendiendo las diferencias de Solana, ahora podemos comenzar con las modificaciones en la base de datos:
Diseño de tablas en la base de datos
Dado que Solana tiene dos tipos de tokens, en la tabla tokens añadiremos un campo token_type para distinguir entre SPL Token y SPL Token-2022.
Aunque las direcciones de Solana difieren de Ethereum, ambas pueden derivarse usando BIP32 y BIP44, solo que con diferentes rutas de derivación. Por ello, podemos mantener la tabla wallets existente, pero para soportar el mapeo ATA y el seguimiento de bloques en Solana, añadiremos estas tres tablas:
Detalles:
Para más detalles, consultar db_gateway/database.md
Procesamiento de depósitos de usuarios
Para gestionar depósitos, se deben escanear continuamente los datos en la cadena Solana, con dos métodos principales:
Método 1: Escanear firmas del address. Se llama a getSignaturesForAddress con la dirección (que puede ser la ATA generada para el usuario o el programID). Se pasa como parámetros before, until, limit para obtener firmas incrementales. Luego, se obtiene la transacción con getTransaction usando la firma.
Este método funciona bien con pocos usuarios o cuentas, pero si hay muchas, es mejor usar el método de escaneo de bloques, que es el que implementamos aquí.
Método 2: Escanear bloques. Se obtiene el slot más reciente, se llama a getBlock para obtener detalles completos, incluyendo transacciones y cuentas, filtrando por instrucciones y cuentas relevantes.
Otra opción es usar servicios de indexadores de terceros, que ofrecen Webhook, monitoreo de cuentas y filtrado avanzado, soportando cargas de datos elevadas.
Proceso de escaneo de bloques
Utilizamos el método 2, con código en los módulos scan/solana-scan (blockScanner.ts y txParser.ts). El flujo principal:
1. Sincronización inicial y recuperación histórica (performInitialSync)
2. Escaneo en tiempo real (scanNewSlots)
3. Análisis de bloques (txParser.parseBlock)
4. Análisis de instrucciones (txParser.parseInstruction)
Gestión de reversiones:
El programa obtiene continuamente el slot finalizado (finalizedSlot). Cuando un slot es menor o igual, se marca como finalizado. Para los que aún están en confirmed, se comprueba si el blockhash ha cambiado para detectar reversiones.
Ejemplo de código clave:
Al detectar depósitos, se mantiene la seguridad con DB Gateway y firma doble del control de riesgos, y se registra en la tabla de fondos.
Retiro
El proceso de retiro en Solana es similar al de EVM, pero con diferencias en la construcción de transacciones:
No hay nonce en Solana; en su lugar, se usa recentBlockhash, válido por unos 150 bloques (~1 minuto). Cada vez que se realiza un retiro, se obtiene un recentBlockhash actualizado en tiempo real. Si el retiro requiere revisión manual, se vuelve a obtener el recentBlockhash, se construye la transacción y se firma nuevamente.
Proceso de retiro
El flujo general:
![diagrama de flujo de retiro]
El código clave para firmar y enviar la transacción:
Luego, se envía usando @solana/web3.js:
Los archivos relevantes son:
Optimización pendiente:
Resumen
Integrar Solana en la bolsa no requiere cambios en la arquitectura general, solo adaptar su modelo de cuentas, estructura de transacciones y mecanismo de confirmación.
Para depósitos, mantener un mapeo ATA-wallet, monitorizar cambios en blockhash para detectar reorganizaciones, y actualizar dinámicamente el estado del bloque (confirmed → finalized).
Para retiros, obtener el último recentBlockhash en tiempo real, distinguir entre tokens SPL y Token-2022, y construir transacciones específicas para cada caso.