Solidity on-chain Reactivity Tutorial

Create a contract-owned on-chain subscription that reacts to ERC-20 Transfer events

This tutorial builds a Solidity handler contract that subscribes to ERC-20 Transfer events and counts them on-chain. The subscription is owned by the handler contract itself, so the contract pays gas for each reactive transaction.

Use on-chain reactivity when the reaction should be part of chain execution. For client-side subscriptions over WebSocket, use off-chain reactivity.

How This Example Works

The contract:

  1. Inherits from SomniaEventHandler.

  2. Builds a non-wildcard SubscriptionFilter for one token's Transfer logs.

  3. Calls SomniaExtensions.subscribe(address(this), filter, options) from the constructor.

  4. Receives matching callbacks through _onEvent.

  5. Can unsubscribe later through stop().

On-chain wildcard subscriptions are not allowed. At least one of eventTopics, origin, or emitter must be set.

Prerequisites

You'll need:

  • Node.js 20+

  • A Foundry project

  • A Somnia RPC endpoint (see Network info)

  • A deployer account with enough SOMI or STT to fund the handler contract with at least 32 native tokens, plus gas for deployment and callback execution

  • An ERC-20 token address to watch (see Smart contracts)

Install the Solidity package:

Step 1: Write the Handler

Create src/TransferCounter.sol:

SomniaEventHandler verifies that only the reactivity precompile can call the external onEvent entry point. Your contract only implements _onEvent.

Step 2: Deploy and Fund the Handler

The subscription owner must hold at least 32 SOMI when the subscription is created. Because this constructor creates the subscription, send the funding with the deploy transaction.

2000000 is the handler gas limit. Increase it if your _onEvent logic does more work. Somnia storage operations can require more gas than the same pattern on Ethereum, so leave margin. See the on-chain Reactivity development advice for more gas-limit guidance.

Step 3: Inspect the Subscription

Set the deployed address:

Read the subscription ID:

Fetch subscription details from the node:

You should see the Transfer event topic and the token address in the stored filter.

Step 4: Trigger a Matching Transfer

Send or otherwise trigger a transfer on the watched token:

After the transfer lands, the chain checks the log against active subscriptions. If it matches, validators include a synthetic transaction that calls the handler.

Read the counter:

Read the tracked amount for a recipient:

Step 5: Stop the Subscription

When you no longer want the contract to react, unsubscribe:

After stop() succeeds, the old subscription ID should return an empty result from somnia_reactivityGetSubscriptionInfo.

Notes

  • The handler runs in a separate reactive transaction after the triggering transaction has executed.

  • msg.sender inside onEvent is the reactivity precompile at 0x0100.

  • tx.origin inside the handler is the subscription owner.

  • The owner pays for every handler invocation.

  • caller, isGuaranteed, and isCoalesced are reserved fields on the raw precompile struct. SomniaExtensions sets them to their required defaults.

  • Avoid feedback loops where the handler emits an event that also matches its own subscription. See the on-chain Reactivity development advice before subscribing to events your handler might emit.

For the full API reference and failure modes, see On-chain Reactivity.

Last updated