Properly configuring gas parameters is critical for on-chain reactivity subscriptions. If gas values are too low, validators will silently skip your subscription — no error, no warning, just nothing happens.
This is the #1 cause of "reactivity not working." Most developers set up their contracts and subscriptions correctly, but use gas values that are too low for validators to process.
Understanding the Parameters
On-chain reactivity subscriptions require three gas parameters:
Parameter
Description
Unit
priorityFeePerGas
Tip paid to validators to prioritize your handler execution
gwei (nanoSomi)
maxFeePerGas
Maximum total fee per gas (base fee + priority fee)
gwei (nanoSomi)
gasLimit
Maximum gas provisioned per handler invocation
gas units
How They Work Together
1
priorityFeePerGas
This is essentially the "tip" for validators. The default value is 0 nanoSomi. Increase it to make sure your handler is executed before others.
2
maxFeePerGas
The ceiling on what you'll pay per gas unit. The minimum is baseFee + priorityFeePerGas , where the base fee in Somnia is 6 nanoSomi. Setting this too low will cause your handler invocation to fail in peak times.
3
gasLimit
How much gas your _onEvent handler is allowed to consume. If your handler runs out of gas, it reverts.
Recommended Values
Standard Use Cases
import{parseGwei}from'viem';awaitsdk.createSoliditySubscription({handlerContractAddress:'0x...',emitter:'0x...',eventTopics: [eventSignature],priorityFeePerGas:parseGwei('0'),// 0 gwei — typically, no priority is required maxFeePerGas:parseGwei('10'),// 10 gwei — comfortable ceilinggasLimit:2_000_000n,// Sufficient for simple state updatesisGuaranteed:true,isCoalesced:false,});
By Handler Complexity
Handler Type
priorityFeePerGas
maxFeePerGas
gasLimit
Example
Simple (state updates, emit event)
parseGwei('0')
parseGwei('10')
2_000_000n
Counter, token reward
Medium (cross-contract calls)
parseGwei('0')
parseGwei('10')
3_000_000n
Game logic with external calls
Complex (multiple external calls, loops)
parseGwei('10')
parseGwei('20')
10_000_000n
Settlement, multi-step workflows
Quick Reference Table (Raw BigInt Values)
If you prefer raw values instead of parseGwei():
Level
priorityFeePerGas
maxFeePerGas
gasLimit
Minimum recommended
0n
10_000_000_000n
2_000_000n
Comfortable
0n
10_000_000_000n
3_000_000n
High priority
10_000_000_000n
20_000_000_000n
10_000_000n
Common Mistakes
Using wei instead of gwei
10n is 10 wei = 0.00000001 gwei. This is 200 million times less than the recommended 2 gwei. Always use parseGwei() to avoid unit confusion. See SOMI coin for details on how this is calculated.
Computing from gasPrice with too-small divisors
Setting gasLimit too low
Somnia operates on a different gas model to Ethereum. One of the key differences is that the 1,000,000 gas reserve is required for any storage operations, see Storage EVM operations. It is safe to up your gas limit to meet the reserve requirements. If your handler reverts due to out-of-gas, the subscription still charges you but the state change doesn't happen.
Forgetting to recreate subscription after redeploying
Subscriptions are tied to specific contract addresses. If you redeploy your contract, you get a new address. The old subscription won't trigger for the new contract.
Cost Estimation
The subscription owner pays for each handler invocation. The cost per invocation is:
Where effectiveGasPrice is at most maxFeePerGas and at least baseFee + priorityFeePerGas.
Example
For a simple handler using ~50,000 gas at 10 gwei max fee:
The subscription owner must maintain at least 32 SOMI balance. This is not spent — it's a minimum holding requirement. Actual costs are deducted per invocation.
Debugging Gas Issues
If your subscription was created successfully but the handler is never invoked:
1
Check subscription info
Verify that priorityFeePerGas is at least 2000000000 (2 gwei).
2
Test with a CLI script first
Before debugging frontend issues, confirm reactivity works via a Hardhat script:
3
Look for validator transactions
On-chain reactivity is executed by validators from the address 0x0000000000000000000000000000000000000100. Check the block explorer for transactions from this address to your handler contract after your event was emitted.
// WRONG — these are 10 wei and 20 wei, essentially zero
priorityFeePerGas: 10n,
maxFeePerGas: 20n,
// CORRECT — these are 2 gwei and 10 gwei
priorityFeePerGas: parseGwei('2'), // = 2_000_000_000n
maxFeePerGas: parseGwei('10'), // = 10_000_000_000n
// RISKY — gas price fluctuates and dividing by 10 may yield too-low values
const gasPrice = await publicClient.getGasPrice();
priorityFeePerGas: gasPrice / 10n,
// SAFER — use fixed proven values
priorityFeePerGas: parseGwei('2'),
// TOO LOW for any storage operations
gasLimit: 100_000n,
// SAFE for most handlers
gasLimit: 2_000_000n,
// SAFE for complex handlers with external calls
gasLimit: 10_000_000n,
Deploy contract → address A
Create subscription → emitter: A, handler: A ✅
Redeploy contract → address B
Old subscription still points to A → ❌ won't work
Must create NEW subscription → emitter: B, handler: B ✅
cost = gasUsed × effectiveGasPrice
cost ≈ 50,000 × 10 gwei = 500,000 gwei = 0.0005 SOM per invocation
const info = await sdk.getSubscriptionInfo(subscriptionId);
console.log(JSON.stringify(info, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));
// 1. Call your contract function that emits the event
const tx = await contract.myFunction();
await tx.wait();
// 2. Poll for state change
for (let i = 0; i < 15; i++) {
await new Promise(r => setTimeout(r, 2000));
const result = await contract.myStateVariable();
if (result !== previousValue) {
console.log('Reactivity worked!');
break;
}
}