The DApp Publisher Proxy Pattern

In the "Working with Multiple Publishersarrow-up-right" tutorial, you learned the standard pattern for building an aggregator:

  1. Maintain a list of all known publisher addresses.

  2. Loop through this list.

  3. Call sdk.streams.getAllPublisherDataForSchema() for each address.

  4. 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:

  1. Somehow find all 10,000 player addresses.

  2. 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:

  1. User (Publisher): Calls a function on your DApp's contract (e.g., myGame.submitScore(100)). The msg.sender is the user's address.

  2. DApp Contract (The Proxy): Internally, your submitScore function:

    • Adds the user's address (msg.sender) into the data payload to preserve provenance.

    • Calls somniaStreams.esstores(...) using its own contract address.

  3. 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

Let's build a conceptual example of this pattern.

What You'll Build

  1. A new Schema that includes the original publisher's address.

  2. A GameLeaderboard.sol smart contract that acts as the proxy.

  3. A Client Script that writes to the proxy contract instead of Streams.

  4. A new Aggregator that reads from the proxy contract's address.

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

The Proxy Smart Contract (Solidity)

This is a new smart contract you would write and deploy for your DApp. It acts as the gatekeeper.

circle-info

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

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

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