Los programas de Solana pueden emitir eventos de manera similar a cómo Ethereum emite eventos, aunque hay algunas diferencias que discutiremos.
Específicamente, los eventos en Solana tienen como objetivo pasar información al frontend en lugar de documentar transacciones pasadas. Para obtener el historial pasado, las transacciones de Solana pueden ser consultadas por dirección.
Logs y eventos de Solana
El programa a continuación tiene dos eventos: MyEvent y MySecondEvent. De manera similar a cómo los eventos de Ethereum tienen “argumentos”, los eventos de Solana tienen campos en el struct:
use anchor_lang::prelude::*;
declare_id!("FmyZrMmPvRzmJCG3p5R1AnbkPqSmzdJrcYzgnQiGKuBq");
#[program]
pub mod emit {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent { value: 42 });
emit!(MySecondEvent { value: 3, message: "hello world".to_string() });
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
#[event]
pub struct MyEvent {
pub value: u64,
}
#[event]
pub struct MySecondEvent {
pub value: u64,
pub message: String,
}
Los eventos se convierten en parte del IDL del programa de Solana, de manera similar a cómo los eventos son parte del ABI de un smart contract de Solidity. A continuación, mostramos una captura de pantalla del IDL del programa anterior, resaltando la parte relevante:

En Solana no existe la información “indexada” o “no indexada” como en Ethereum (aunque hay un campo “index” en la captura de pantalla anterior, no tiene ningún uso).
A diferencia de Ethereum, no podemos consultar directamente los eventos pasados en un rango de números de bloque. Solo podemos escuchar los eventos a medida que ocurren. (Más adelante veremos el método de Solana para auditar transacciones pasadas). El código a continuación muestra cómo escuchar eventos en Solana:
import * as anchor from "@coral-xyz/anchor";
import { BorshCoder, EventParser, Program } from "@coral-xyz/anchor";
import { Emit } from "../target/types/emit";
describe("emit", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Emit as Program<Emit>;
it("Is initialized!", async () => {
const listenerMyEvent = program.addEventListener('MyEvent', (event, slot) => {
console.log(`slot ${slot} event value ${event.value}`);
});
const listenerMySecondEvent = program.addEventListener('MySecondEvent', (event, slot) => {
console.log(`slot ${slot} event value ${event.value} event message ${event.message}`);
});
await program.methods.initialize().rpc();
// This line is only for test purposes to ensure the event
// listener has time to listen to event.
await new Promise((resolve) => setTimeout(resolve, 5000));
program.removeEventListener(listenerMyEvent);
program.removeEventListener(listenerMySecondEvent);
});
});
No es posible escanear logs pasados como en Ethereum, se deben observar mientras la transacción está ocurriendo.

Cómo funcionan los logs bajo el capó
En la EVM, los logs se emiten ejecutando el opcode log0, log1, log2, etc. En Solana, los logs se ejecutan llamando a la llamada al sistema sol_log_data. Como argumento, es simplemente una secuencia de bytes:
https://docs.rs/solana-program/latest/src/solana_program/log.rs.html#116-124
A continuación se muestra la función de la llamada al sistema en el cliente de Solana:
/// Print some slices as base64.
pub fn sol_log_data(data: &[&[u8]]) {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64)
};
#[cfg(not(target_os = "solana"))]
crate::program_stubs::sol_log_data(data);
}
La estructura “struct” que estamos usando para crear un evento es una abstracción sobre la secuencia de bytes. Detrás de escena, Anchor convierte el struct en una secuencia de bytes para pasarla a esta función. La llamada al sistema de Solana solo acepta una secuencia de bytes, no un struct.
Los logs de Solana no son para consultas históricas
En Ethereum, los logs se utilizan con fines de auditoría, pero en Solana, los logs no pueden usarse de esa manera ya que solo pueden consultarse a medida que ocurren. Por lo tanto, son más adecuados para pasar información a la aplicación frontend. Las funciones de Solana no pueden devolver datos al frontend de la forma en que lo hacen las funciones view de Solidity, por lo que los logs de Solana son una forma ligera de lograr esto.
Sin embargo, los eventos se conservan en el explorador de bloques. Vea la parte inferior de esta transacción como ejemplo:
https://explorer.solana.com/tx/JgyHQPxL3cPLFtV4cx5i842ZgBx57R2fkNn2TZn1wsQZqVXKfijd43CEHo88C3ridK27Kw8KkMzfvDdqaS398SX
A diferencia de Ethereum, las transacciones de Solana se pueden consultar por dirección
En Ethereum, no hay una forma directa de consultar las transacciones enviadas a un smart contract o desde una wallet en particular.
Podemos contar el número de transacciones enviadas desde una dirección utilizando eth_getTransactionCount. Podemos obtener una transacción específica utilizando el hash de la transacción con eth_getTransactionByHash. Podemos obtener las transacciones de un bloque específico utilizando eth_getBlockByNumber o eth_getBlockByHash.
Sin embargo, no es posible obtener todas las transacciones por dirección. Esto debe hacerse indirectamente analizando cada bloque desde que la wallet se volvió activa o el smart contract fue desplegado.
Para auditar las transacciones en un smart contract, los desarrolladores agregan eventos de smart contract para consultar las transacciones de interés.
Obtener el historial de transacciones en Solana
Por otro lado, Solana tiene una función RPC getSignaturesForAddress que enumera todas las transacciones que ha realizado una dirección. La dirección puede ser un programa o una wallet.
El siguiente es un script para enumerar las transacciones de una dirección:
let web3 = require('@solana/web3.js');
const solanaConnection = new web3.Connection(web3.clusterApiUrl("mainnet-beta"));
const getTransactions = async(address,limit) => {
const pubKey = new web3.PublicKey(address);
let transactionList = await solanaConnection.getSignaturesForAddress(pubKey, {limit: limit});
let signatureList = transactionList.map(transaction => transaction.signature);
console.log(signatureList);
for await (const sig of signatureList) {
console.log(await solanaConnection.getParsedTransaction(sig, {maxSupportedTransactionVersion: 0}));
}
}
let myAddress = "enter and address here";
getTransactions(myAddress, 3);
Tenga en cuenta que el contenido real de la transacción se recupera utilizando el método RPC getParsedTransaction, el cual solo es compatible con el SDK @solana/web3.js. Para otras bibliotecas, consulte getTransaction en su lugar.
Publicado originalmente el 20 de febrero de 2024