Set up the event listener

To automate real-time blockchain data, your oracle service must first establish a persistent connection to a node and subscribe to specific contract events. This setup creates the foundational pipeline where the oracle acts as a consumer, reacting immediately to state changes on-chain. You will configure a WebSocket connection to maintain a live stream of logs, filtering for the exact event signatures your smart contracts emit.

1. Initialize the WebSocket Connection

Start by connecting to a high-performance node provider (such as Alchemy, Infura, or a private Geth node) via WebSocket. Unlike HTTP polling, WebSockets provide a bidirectional channel that pushes new blocks and logs to your service in real time, minimizing latency. Ensure your connection includes retry logic and heartbeat checks to handle transient network drops without losing the subscription state.

2. Filter for Specific Event Logs

Once connected, subscribe to the logs topic with a filter object that targets your oracle’s listening contracts. Specify the address of the contract and the topics array containing the event signature hash (e.g., DataUpdated(uint256,string)). This narrows the data stream to only the events your oracle needs to process, reducing bandwidth and computational overhead. You can also filter by fromBlock to catch any missed events if the service restarts.

3. Parse and Validate Incoming Events

As events arrive, parse the raw log data to extract the indexed parameters and the event name. Validate the signature against your known oracle interface to ensure the data originates from a trusted contract. If the event matches your expected schema, serialize the payload into a structured format (like JSON) and pass it to your internal queue or processing function. This step ensures that only valid, actionable data moves forward in the automation pipeline.

JavaScript
import { ethers } from 'ethers';

// Connect to a WebSocket provider
const provider = new ethers.WebSocketProvider('wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY');

// Define the oracle contract interface
const oracleInterface = new ethers.Interface([
  'event DataUpdated(uint256 indexed id, string value)'
]);

// Set up the filter for the specific event
const filter = {
  address: '0xYourOracleContractAddress',
  topics: [oracleInterface.getEventTopic('DataUpdated')]
};

// Subscribe to logs
provider.on(filter, (log) => {
  try {
    // Parse the log data
    const parsedLog = oracleInterface.parseLog(log);
    console.log('Event received:', parsedLog.args.id, parsedLog.args.value);
    
    // Trigger oracle processing logic here
    processOracleData(parsedLog.args);
  } catch (error) {
    console.error('Failed to parse log:', error);
  }
});

console.log('Event listener active. Waiting for DataUpdated events...');

This code snippet demonstrates the core mechanism: a persistent WebSocket connection that filters for a specific event signature (DataUpdated) and parses the incoming logs. By isolating this listener, you ensure that your oracle service remains decoupled from the blockchain’s noise, reacting only to the precise data triggers required for automation.

Filter and validate incoming data

Raw blockchain events are noisy. Before sending data on-chain, you must parse the logs, filter for relevant metrics, and validate the payload to prevent oracle latency and excessive gas costs.

event-driven oracles
1
Parse the event log

Extract the event signature and parameters from the raw transaction receipt. Most event-driven oracles use libraries like ethers.js or web3.js to decode the topics and data fields into human-readable structs. Ensure your decoder matches the exact ABI of the smart contract emitting the event.

JavaScript
JavaScript
const parsedLog = contract.interface.parseLog({
  topics: log.topics,
  data: log.data
});

This step transforms binary data into a structured object, allowing you to access specific values like price, timestamp, or volume directly.

event-driven oracles
2
Filter for relevant data

Apply strict filters to discard irrelevant events. Check the source address to ensure the event comes from a trusted oracle feed or verified contract. Filter by timestamp to ignore stale data that might have been reorganized or delayed. If your oracle aggregates multiple sources, use a source_id filter to isolate the specific data stream you need for this validation round.

JavaScript
JavaScript
if (parsedLog.args.source !== TRUSTED_SOURCE) {
  return; // Ignore untrusted sources
}

Filtering prevents your oracle from processing spam or malicious events that do not contribute to the final on-chain state.

event-driven oracles
3
Validate the payload

Perform sanity checks on the parsed values. Ensure numeric fields are within expected bounds (e.g., price > 0 and < max_limit). Verify that the timestamp is not in the future and is within a reasonable window of the current block time. If the data fails validation, log the error and discard the event without submitting a transaction. This step protects your oracle from propagating corrupt or manipulated data.

JavaScript
JavaScript
if (parsedLog.args.price <= 0 || parsedLog.args.timestamp > currentBlockTime + 60) {
  console.error("Invalid payload");
  return;
}

Validation ensures data integrity before it triggers any on-chain logic.

event-driven oracles
4
Submit to oracle contract

Once the data passes parsing, filtering, and validation, package it into a transaction. Sign the transaction with your oracle node's private key and submit it to the network. Use ethers or web3 to call the oracle contract's updateData function with the validated payload. Monitor the transaction receipt to confirm successful inclusion.

JavaScript
JavaScript
const tx = await oracleContract.updateData(
  parsedLog.args.price,
  parsedLog.args.timestamp
);
await tx.wait();

This final step commits the validated off-chain data to the blockchain, making it available for downstream smart contracts.

Sign and submit the transaction

Once the off-chain oracle node has verified the data and prepared the payload, the next phase is cryptographic signing. The oracle must sign the transaction data using its private key to prove authenticity. This signature ensures that the smart contract can verify the data came from a trusted source and hasn't been tampered with during transmission.

The signing process involves hashing the payload data and applying the oracle's private key. The resulting signature is appended to the transaction object. This step is critical for maintaining data integrity, as any modification to the payload after signing will invalidate the signature, causing the transaction to be rejected by the target contract.

After signing, the transaction is submitted to the blockchain network. The oracle node broadcasts the signed transaction to the target smart contract. The contract's updatePrice function (or similar) receives the payload, verifies the signature against the oracle's public key, and updates the internal state if the signature is valid.

event-driven oracles
1
Hash the payload

Compute the Keccak-256 hash of the JSON-serialized payload data. This creates a fixed-size digest that represents the data uniquely.

2
Sign with private key

Use the oracle's private key to sign the hash. This generates a cryptographic proof that only the oracle could have created this payload.

event-driven oracles
3
Encode transaction data

Encode the payload, timestamp, and signature into the smart contract's expected function arguments using encodeFunctionData.

event-driven oracles
4
Broadcast to network

Send the signed transaction to the blockchain via an RPC provider. Wait for the transaction to be mined and included in a block.

Once the transaction is mined, the data is immutable and available on-chain. The smart contract can now trigger downstream actions based on the verified oracle data, completing the event-driven loop.

Handle retries and failed submissions

Blockchain transactions are not guaranteed. Network congestion, high gas prices, or temporary node outages can cause your event-driven oracle to drop packets or reject submissions. To ensure reliable data delivery, you must implement a robust retry mechanism that handles these transient failures without spamming the network.

Implement Exponential Backoff

When a transaction submission fails, do not retry immediately. Use exponential backoff to space out retries. This prevents overwhelming the mempool and reduces the risk of your node being rate-limited or banned by RPC providers.

JavaScript
async function submitWithRetry(txData, maxRetries = 5) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const receipt = await sendTransaction(txData);
      return receipt; // Success
    } catch (error) {
      if (error.code === 'NETWORK_ERROR' || error.code === 'REPLACEMENT_UNDERPRICED') {
        attempt++;
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s...
        console.log(`Retry ${attempt} after ${delay}ms`);
        await new Promise(r => setTimeout(r, delay));
      } else {
        throw error; // Non-retryable error
      }
    }
  }
  throw new Error('Max retries exceeded');
}

Monitor Transaction Status

Listening for the submission receipt is not enough. You must monitor the transaction status on-chain. If a transaction is stuck in the mempool or fails due to a revert, your oracle needs to know so it can adjust gas prices or abort the operation.

Use a transaction tracker service or a dedicated node endpoint to poll the status. If the transaction is confirmed, update your local state. If it fails, log the reason and trigger a fallback strategy, such as switching to a different data provider or alerting the operator.

Manage Gas Prices

During high network activity, standard gas prices may be insufficient. Implement dynamic gas pricing based on current network conditions. Fetch the latest gas prices from a reliable source and adjust your submission parameters accordingly. This ensures your transactions are included in blocks without overpaying during low-activity periods.

By combining exponential backoff with active transaction monitoring and dynamic gas pricing, your oracle can maintain high availability even during volatile market conditions.

Verify the on-chain update

Validate Data Integrity

Finally, verify that the data hash stored on-chain matches the hash of the payload sent by the oracle. This prevents replay attacks and ensures the data hasn't been tampered with during transmission. Compare the on-chain hash against the off-chain source hash.

JavaScript
const onChainHash = await oracleContract.getDataHash(feedId);
const offChainHash = ethers.keccak256(ethers.toUtf8Bytes(expectedValue));

if (onChainHash !== offChainHash) {
  throw new Error("Data integrity check failed");
}

Common questions about event-driven oracles

Event-driven oracles reduce latency by listening to on-chain logs rather than polling blocks. This approach cuts network overhead and ensures immediate reaction to state changes.

How does latency compare to polling?

Polling checks every block interval, creating a delay between the event and the oracle’s response. Event-driven oracles subscribe to specific logs, triggering transactions within seconds. This difference is critical for high-frequency trading or liquidation bots where seconds matter.

Are event-driven oracles more expensive?

Gas costs depend on transaction frequency, not listening method. Since event-driven oracles only submit transactions when necessary, they often save gas compared to constant polling. However, complex event filtering on-chain can increase deployment costs.

What security risks exist?

Relying on external event feeds introduces dependency risks. If the oracle node misses an event due to network issues, the contract state may drift. Always implement fallback mechanisms, such as periodic reconciliation checks, to catch missed events.