Somnia Data Streams Reactivity

How Somnia Data Streams Reactivity through a publisher and subscriber lens

Reactivity background

Consider this scenario: Alice transfers 5 USDC to Bob on an arbitrary EVM chain. Alice interacts with the USDC ERC20 smart contract which does two things:

  1. Update state (of both Alice and Bob) and,

  2. Emit a Transfer(from, to, value) event

Now, enter the humble ERC indexer. The humble indexer tries to does its best to work with an EVM node to walk the chain and make note of the Transfer events it cares about. The humble indexer can have many observers that will pull the data for either display or to do something else with the data (arbitrary logic). Indexers come in many shapes and forms and additional tooling has to be built ontop of indexers to be notified about new data present in the indexer. More critically, if an observer wants to cross check the indexer against chain data directly from another node, additional work needs to be done increasing the number of round trips to the node not to mention other edge cases that need to be considered.

Reactivity allows one to express the following:

When X event is emitted by Y contract,
invoke my client with data from a view function on Z contract I specify

This collapses the number of round trips to the node and allows users to get faster confirmations about their actions. It does not completely remove the need for an indexer but simplifies the requirements.

As you can see, reactivity hooks into events as a core primitive for other subscribers to take advantage of Etherbase reactivity.

Writing data, events and reacting

The scenario mentioned above is a very standard scenario when it comes to generally writing data to chain i.e. publishing state and emitting an event (also known as a log).

When using the chain to write arbitrary data and emit an event, each time you do that Etherbase offers you the tooling to do this without the requirements of having to write your own custom Solidity contract. This also allows you to take advantage of existing schemas for publishing data allowing multiple applications to benefit from composability and data sharing for better interoperability. Example:

import { SDK } from "@somnia-chain/streams"
import { zeroAddress, erc721Abi } from "viem"

// Use WebSocket transport in the public client for subscription tasks
// For the SDK instance that executes transactions, stick with htttp
const sdk = new SDK({
    public: getPublicClient(),
    wallet: getWalletClient(),
})

// Encode view function calls to be executed when an event takes place
const ethCalls = [{
    to: "0x23B66B772AE29708a884cca2f9dec0e0c278bA2c",
    data: encodeFunctionData({
        abi: erc721Abi,
        functionName: "balanceOf",
        args: ["0x3dC360e0389683cA0341a11Fc3bC26252b5AF9bA"]
    })
}]

// Start a subsciption
const subscription = await sdk.streams.subscribe({
    somniaStreamsEventId: "Firework",
    ethCalls,
    onData: (data) => {
        const decodedLog = decodeEventLog({
            abi: fireworkABI,
            topics: data.result.topics,
            data: data.result.data,
        });

        const decodedFunctionResult = decodeFunctionResult({
            abi: erc721Abi,
            functionName: 'balanceOf',
            data: data.result.simulationResults[0],
        });

        console.log("Decoded event", decodedLog);
        console.log("Decoded function call result", decodedFunctionResult);
    }
})

// Write data and emit events that will trigger the above callback!
const dataStreams = [{
    id,
    schemaId: driverSchemaId,
    data: encodedData
}]

const eventStreams = [{
    id: somniaStreamsEventId,
    argumentTopics,
    data
}]

const setAndEmitEventsTxHash = await sdk.streams.setAndEmitEvents(
    dataStreams,
    eventStreams
)

Writing data and emitting events will trigger a call back to subscribers that care about a specified event emitted from the Somnia Data Streams protocol (or any contract for that matter) without having the need to poll the chain. It follows the observer pattern meaning push rather than pull which is always a more efficient paradigm.

Last updated