Somnia On-Chain Reactivity
Source: Dev.to

1. Introduction
As Web3 developers, we often build systems that require automatic reactions.
Examples from real projects like on‑chain games, PotWar pools, prediction markets:
- Last player wins when timer ends
- Auto reward after chest open
- Pool auto payout
- NFT level‑up after XP
- DAO proposal auto‑execute
With traditional smart contracts, this requires:
- Backend server
- Event indexer
- Cron jobs
- Extra user transactions
This increases complexity, cost, and centralization.
Somnia On‑Chain Reactivity solves this by allowing smart contracts to automatically react to events fully on‑chain.
2. What is Somnia On‑Chain Reactivity?
Somnia lets smart contracts execute logic automatically when subscribed events occur.
Traditional Flow
User Action → Event → Backend Listener → New Tx → State Update
Somnia Reactive Flow
User Action → Event → Validator Detect → _onEvent() → State Update
- No backend.
- No extra user gas.
- Fully decentralized.
3. Why This Matters for Web3 Devs
For builders working on gaming, DeFi, or automation:
- No server infra
- Less latency
- Cleaner architecture
- Better UX
- Faster hackathon builds
For example, in Last Player Standing we don’t need a backend timer; contract logic can react automatically.
4. Example: Magic Chest Reactive Game
Let’s build a small game.
Game Rules
- Common chest → +10 coins
- Rare chest → +50 coins
- Legendary chest → NFT sword
Player opens chest → reward auto‑given.
5. Solidity Contract Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import { SomniaEventHandler }
from "@somnia-chain/reactivity-contracts/contracts/SomniaEventHandler.sol";
contract MagicChestReactiveGame is SomniaEventHandler {
event ChestOpened(address indexed player, uint256 chestType);
mapping(address => uint256) public coins;
mapping(address => bool) public hasSword;
bytes32 constant CHEST_SIG =
keccak256("ChestOpened(address,uint256)");
uint256 constant COMMON = 1;
uint256 constant RARE = 2;
uint256 constant LEGENDARY = 3;
function openChest(uint256 chestType) external {
emit ChestOpened(msg.sender, chestType);
}
function _onEvent(
address,
bytes32[] calldata topics,
bytes calldata data
) internal override {
require(topics[0] == CHEST_SIG, "Wrong event");
address player =
address(uint160(uint256(topics[1])));
uint256 chestType =
abi.decode(data, (uint256));
if (chestType == COMMON)
coins[player] += 10;
else if (chestType == RARE)
coins[player] += 50;
else if (chestType == LEGENDARY)
hasSword[player] = true;
}
}
6. How Reactivity Works Internally
- Player calls
openChest() - Event emitted
- Somnia validators detect subscription
_onEvent()executes automatically- State updated
- No extra transaction required
Reactive transaction is executed by validator address:
0x0000000000000000000000000000000000000100
7. Creating a Subscription for the Event
import { SDK } from "@somnia-chain/reactivity";
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, createWalletClient, http } from "viem";
import { somniaTestnet } from "viem/chains";
import { keccak256, toBytes } from "viem";
const CONTRACT = "0xYourContractAddress";
async function main() {
const account = privateKeyToAccount(
process.env.PRIVATE_KEY as `0x${string}`
);
const publicClient = createPublicClient({
chain: somniaTestnet,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: somniaTestnet,
transport: http(),
});
const sdk = new SDK({
public: publicClient,
wallet: walletClient,
});
const EVENT_SIG = keccak256(
toBytes("ChestOpened(address,uint256)")
);
const txHash = await sdk.createSoliditySubscription({
handlerContractAddress: CONTRACT,
emitter: CONTRACT,
eventTopics: [EVENT_SIG],
gasLimit: 3_000_000n,
});
const receipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
});
// 👉 Extract subscriptionId
const log = receipt.logs[0];
const subscriptionId = BigInt(log.topics[2]);
console.log("Subscription ID:", subscriptionId.toString());
}
main();
Important Notes
- Need 32 STT balance.
- Save subscription ID.
- Event signature must match exactly.
8. Testing Reactivity
import { ethers } from "hardhat";
const CONTRACT = "0xYourContractAddress";
async function main() {
const [user] = await ethers.getSigners();
const game = await ethers.getContractAt(
"MagicChestReactiveGame",
CONTRACT
);
const before = await game.coins(user.address);
console.log("Coins before:", before.toString());
const tx = await game.openChest(1); // 1 = COMMON
await tx.wait();
console.log("Waiting for Somnia reactivity...");
await new Promise(r => setTimeout(r, 5000)); // adjust as needed
const after = await game.coins(user.address);
console.log("Coins after:", after.toString());
if (after > before) {
console.log("✅ Reactivity Working");
} else {
console.log("❌ No Reactivity");
}
}
main();
9. Real Use Cases
Gaming
- Last Player Standing
- PotWar auto payout
- Loot rewards
- NFT upgrades
Prediction Markets
- Auto reward distribution
- Score updates
DeFi
- Liquidations
- Yield distribution
DAO
- Proposal auto‑execute
10. Conclusion
Somnia On‑Chain Reactivity removes backend dependency and enables fully autonomous smart contracts.
For developers building automated gaming logic, prediction markets, or DeFi automation, this unlocks a new level of scalability and simplicity.
The future of Web3 apps is reactive.
Neeraj Choubisa (Nikku.Dev) – Full‑Stack Blockchain Engineer specializing in smart contract development, Web3 integrations, and consumer‑focused decentralized applications.
