Advanced: The DApp Publisher Proxy Pattern
Advanced: The DApp Publisher Proxy Pattern
In the "Working with Multiple Publishers" tutorial, you learned the standard pattern for building an aggregator:
Maintain a list of all known publisher addresses.
Loop through this list.
Call
sdk.streams.getAllPublisherDataForSchema()for each address.Merge and sort the results on the client side.
This pattern is simple and effective for a known, manageable number of publishers (e.g., 50 IoT sensors from a single company).
But what happens at a massive scale?
The Problem: The 10,000-Publisher Scenario
Imagine you are building a popular on-chain game. You have a leaderboardSchema and 10,000 players actively publishing their scores.
If you use the standard aggregator pattern, your "global leaderboard" DApp would need to:
Somehow find all 10,000 player addresses.
Perform 10,000 separate read calls (
getAllPublisherDataForSchema) to the Somnia RPC node.
This is not scalable, fast, or efficient. It creates an enormous (and slow) data-fetching burden on your application.
The Solution: The DApp Publisher Proxy
This is an advanced architecture that inverts the model to solve the read-scalability problem.
Instead of having 10,000 publishers write to Streams directly, they all write to your DApp's smart contract, which then publishes to Streams on their behalf.
The Flow:
User (Publisher): Calls a function on your DApp's contract (e.g.,
myGame.submitScore(100)). Themsg.senderis the user's address.DApp Contract (The Proxy): Internally, your
submitScorefunction:Adds the user's address (
msg.sender) into the data payload to preserve provenance.Calls
somniaStreams.esstores(...)using its own contract address.
Somnia Data Streams: Records the data. To the Streams contract, the only publisher is your DApp Contract's address.
The Result:
Your global leaderboard aggregator now only needs to make one single read call to fetch all 10,000 players' data:
sdk.streams.getAllPublisherDataForSchema(schemaId, YOUR_DAPP_CONTRACT_ADDRESS)
This is massively scalable and efficient for read-heavy applications.
Tutorial: Building a GameLeaderboard Proxy
GameLeaderboard ProxyLet's build a conceptual example of this pattern.
What You'll Build
A new Schema that includes the original publisher's address.
A
GameLeaderboard.solsmart contract that acts as the proxy.A Client Script that writes to the proxy contract instead of Streams.
A new Aggregator that reads from the proxy contract's address.
Step 1: The Schema (Solving for Provenance)
Since the msg.sender to the Streams contract will always be our proxy contract, we lose the built-in provenance. We must re-create it by adding the original player's address to the schema itself.
src/lib/schema.ts
Step 2: The Proxy Smart Contract (Solidity)
This is a new smart contract you would write and deploy for your DApp. It acts as the gatekeeper.
SDK set() vs. Contract esstores() This example uses the low-level contract function esstores(). When you use sdk.streams.set() in your client-side code, the SDK is calling the esstores() function on the Somnia Streams contract "under the hood." This proxy contract is simply calling that same function directly.
src/contracts/GameLeaderboard.sol
Step 3: The Client Script (Publishing to the Proxy)
The client-side logic changes. The user no longer needs the Streams SDK to publish, but rather a way to call your DApp's submitScore function.
src/scripts/publishScore.ts
Step 4: The Aggregator Script (Simple, Scalable Reads)
This is the pay-off. The aggregator script is now dramatically simpler and more scalable. It only needs to know the single DApp contract address.
src/scripts/readLeaderboard.ts
Trade-Offs & Considerations
This pattern is powerful, but it's important to understand the trade-offs.
Feature
Standard Pattern (Multi-Publisher)
Proxy Pattern (Single Publisher)
Read Scalability
Low. Requires N read calls (N = # of publishers).
High. Requires 1 read call, regardless of publisher count.
Publisher Gas Cost
Low. 1 transaction (streams.set).
High. 1 transaction + 1 internal transaction. User pays more gas.
Provenance
Automatic & Implicit. msg.sender is the user.
Manual. Must be built into the schema (address player).
Complexity
Simple. Requires only the SDK.
Complex. Requires writing, deploying, and maintaining a custom smart contract.
Conclusion
The DApp Publisher Proxy is an advanced but essential pattern for any Somnia Data Streams application that needs to scale to thousands or millions of publishers (e.t., games, social media, large IoT networks).
It simplifies the data aggregation logic from N+1 read calls down to 1, at the cost of higher gas fees for publishers and increased development complexity.
For most DApps, we recommend starting with the simpler "Multi-Publisher Aggregator" pattern. When your application's read performance becomes a bottleneck due to a high number of publishers, you can evolve to this proxy pattern to achieve massive read scalability.
Last updated