<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://effectstream.github.io/docs/blog</id>
    <title>EffectStream Blog</title>
    <updated>2026-04-28T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://effectstream.github.io/docs/blog"/>
    <subtitle>EffectStream Blog</subtitle>
    <icon>https://effectstream.github.io/docs/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[An Open Achievement Standard for On-Chain Games]]></title>
        <id>https://effectstream.github.io/docs/blog/achievement-system</id>
        <link href="https://effectstream.github.io/docs/blog/achievement-system"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Steam has achievements. Xbox has Gamerscore. PlayStation has Trophies. But on-chain games? No standard way to track player accomplishments across games. We built PRC-1, an open standard that any EffectStream game can implement, plus a portal where players see all their achievements in one place.]]></summary>
        <content type="html"><![CDATA[<p>Steam has achievements. Xbox has Gamerscore. PlayStation has Trophies. But on-chain games? No standard way to track player accomplishments across games. We built PRC-1, an open standard that any EffectStream game can implement, plus a portal where players see all their achievements in one place.</p>
<p><img decoding="async" loading="lazy" alt="Open achievement standard application overview" src="https://effectstream.github.io/docs/assets/images/standards-8f461631c4a8fea2c2cde92a70a56a9e.png" width="3118" height="1770" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prc-1-the-achievement-standard">PRC-1: the achievement standard<a href="https://effectstream.github.io/docs/blog/achievement-system#prc-1-the-achievement-standard" class="hash-link" aria-label="Direct link to PRC-1: the achievement standard" title="Direct link to PRC-1: the achievement standard" translate="no">​</a></h2>
<p>We created PRC-1 (Paima Request for Comment), a community standard similar to <a href="https://cips.cardano.org/" target="_blank" rel="noopener noreferrer" class="">CIPs</a> for Cardano or <a href="https://eips.ethereum.org/erc" target="_blank" rel="noopener noreferrer" class="">ERCs</a> for Ethereum, but for the EffectStream game ecosystem. PRC-1 defines how games register, track, and expose achievements through a standard HTTP API.</p>
<p>The spec is published at <a href="https://github.com/effectstream/midnight-game-api-spec" target="_blank" rel="noopener noreferrer" class="">effectstream/midnight-game-api-spec</a>.</p>
<p>A game implementing PRC-1 exposes two endpoints:</p>
<ul>
<li class=""><code>GET /achievements/public/list</code> returns all achievements defined by the game (name, description, display metadata)</li>
<li class=""><code>GET /achievements/wallet/:wallet</code> returns which achievements a specific player has earned, with timestamps and completion data</li>
</ul>
<p>The achievement list response is a flat TypeScript shape — no chain-specific fields, no platform-specific fields:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">AchievementPublicList</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  achievements</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)">// Unique achievement ID, e.g. "speed_demon"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    displayName</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)">// Player-facing title</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    description</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)">// How to unlock</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    isActive</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">         </span><span class="token comment" style="color:rgb(98, 114, 164)">// Currently unlockable?</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    score</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// Optional point value</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    category</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">         </span><span class="token comment" style="color:rgb(98, 114, 164)">// Optional grouping ("Gold", "Diamond", ...)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    percentCompleted</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// Percentage of players who have it</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    iconURI</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)">// Badge image URL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    iconGreyURI</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// Badge image URL when not yet earned</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    spoiler</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'all'</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'description'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// Hide details until unlocked</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    startDate</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// ISO8601, optional time-limited start</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    endDate</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)">// ISO8601, optional time-limited end</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Per-player completion follows a matching shape, keyed by achievement <code>name</code> with completion timestamps and optional incremental progress (<code>progress</code> / <code>total</code>). The full schema is in <a href="https://github.com/effectstream/midnight-game-api-spec/blob/main/prc-1.md" target="_blank" rel="noopener noreferrer" class=""><code>prc-1.md</code></a>.</p>
<p>Any game that implements these endpoints is automatically discoverable by achievement portals, leaderboard aggregators, and third-party tools. The standard is intentionally minimal: it defines the interface, not the implementation. Games track achievements however they want internally; they just expose results through these endpoints.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prc-6-midnight-dapp-integration-extension">PRC-6: Midnight dApp integration extension<a href="https://effectstream.github.io/docs/blog/achievement-system#prc-6-midnight-dapp-integration-extension" class="hash-link" aria-label="Direct link to PRC-6: Midnight dApp integration extension" title="Direct link to PRC-6: Midnight dApp integration extension" translate="no">​</a></h2>
<p>For games running on the <a href="https://midnight.fun/games" target="_blank" rel="noopener noreferrer" class="">midnight.fun</a> portal, we extended PRC-1 with PRC-6 (the Midnight dApp Integration Interface). PRC-6 adds:</p>
<ul>
<li class=""><strong>Channels</strong> for categorizing content within a game (modes, seasons, maps)</li>
<li class=""><strong>Leaderboards</strong> with ranked player standings per channel</li>
<li class=""><strong>Metrics</strong> for game-level statistics (total players, active sessions, transaction volume)</li>
<li class=""><strong>Identity resolution</strong> to map session wallets to main wallets via delegation, so achievements earned through auto-sign sessions get correctly attributed to the player's primary identity</li>
</ul>
<p>PRC-6 is what powers the rich game cards on midnight.fun. Each game tile shows live player counts, leaderboard positions, and achievement progress, all pulled from the standard API.</p>
<p>A PRC-6 application advertises its metadata, achievements, and channels at a single root endpoint:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">BASE_URL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">/metrics</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Cyber Drifter"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"High-octane neon racing."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"achievements"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"speed_demon"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"displayName"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Speed Demon"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Finish a lap under 60 seconds."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"isActive"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"percentCompleted"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">14.2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"channels"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"leaderboard"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Lap Time"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"scoreUnit"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Lap Time (s)"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"sortOrder"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ASC"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"kos"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Knock Outs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"scoreUnit"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"KOs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"sortOrder"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"DESC"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"volume"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"USD Volume"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"scoreUnit"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"USD Volume"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"sortOrder"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"DESC"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"auth"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The platform reads this once per game and uses it to render the game card, then queries each channel and per-user profile as needed.</p>
<p>The interesting trick is identity resolution. A query on a session wallet returns the resolved main wallet's stats, with the delegation chain made explicit:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">BASE_URL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">/metrics/users/mn_session_abc123?channel</span><span class="token operator">=</span><span class="token plain">leaderboard</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"identity"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"address"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"mn_main_driftking"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"delegatedFrom"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"mn_session_abc123"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"displayName"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"DriftKing"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"achievements"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"speed_demon"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"first_blood"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"channels"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"leaderboard"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"stats"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"score"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">45.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"rank"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>A player using <a class="" href="https://effectstream.github.io/docs/blog/auto-sign">auto-sign</a> may hold dozens of session wallets across a game's lifetime; the platform collapses them all back to one main wallet for reputation and leaderboard purposes.</p>
<p>The full PRC-6 spec, including all channel definitions, authentication rules, and snapshot vs. cumulative semantics, lives at <a href="https://github.com/effectstream/midnight-game-api-spec/blob/main/prc-6.md" target="_blank" rel="noopener noreferrer" class=""><code>prc-6.md</code></a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementing-it-in-a-game">Implementing it in a game<a href="https://effectstream.github.io/docs/blog/achievement-system#implementing-it-in-a-game" class="hash-link" aria-label="Direct link to Implementing it in a game" title="Direct link to Implementing it in a game" translate="no">​</a></h2>
<p>The spec repository ships with a runnable <a href="https://github.com/effectstream/midnight-game-api-spec/tree/main/demo" target="_blank" rel="noopener noreferrer" class="">reference server</a> that implements every endpoint against in-memory fixtures. The <code>/metrics</code> route is a few lines of Fastify:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Fastify </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fastify"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">APP_NAME</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">APP_DESCRIPTION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">ACHIEVEMENTS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">CHANNEL_DEFS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"./data/store.js"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> app </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Fastify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/metrics"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">APP_NAME</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  description</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">APP_DESCRIPTION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  achievements</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">ACHIEVEMENTS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// your achievement definitions</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  channels</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">CHANNEL_DEFS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// your metric channels</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">listen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> port</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3000</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>There is no SDK to install. PRC-6 is just HTTP. A new game spending an afternoon on this gets discoverable on midnight.fun and queryable by every third-party tool that speaks the standard.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="live-implementation-three-games-on-midnightfun">Live implementation: three games on midnight.fun<a href="https://effectstream.github.io/docs/blog/achievement-system#live-implementation-three-games-on-midnightfun" class="hash-link" aria-label="Direct link to Live implementation: three games on midnight.fun" title="Direct link to Live implementation: three games on midnight.fun" translate="no">​</a></h2>
<p>PRC-1 and PRC-6 are implemented in three live games on the midnight.fun portal:</p>
<ul>
<li class=""><a href="https://blockkart.paimastudios.com/" target="_blank" rel="noopener noreferrer" class=""><strong>Block Kart Legends</strong></a> - racing achievements (lap times, win streaks, distance milestones)</li>
<li class=""><a href="https://kachina.midnight.fun/" target="_blank" rel="noopener noreferrer" class=""><strong>Kachina Kolosseum</strong></a> - PvP combat achievements (victories, combos, rank progression)</li>
<li class=""><a href="https://safesolver.midnight.fun/" target="_blank" rel="noopener noreferrer" class=""><strong>Safe Solver</strong></a> - puzzle achievements (completion speed, streak bonuses, difficulty tiers)</li>
</ul>
<p><img decoding="async" loading="lazy" alt="midnight.fun games portal showing Block Kart, Kachina, and Safe Solver with achievements and leaderboards" src="https://effectstream.github.io/docs/assets/images/midnightfun-cad04b67e8b36607db375ecb6b04f988.png" width="1551" height="1669" class="img_ev3q"></p>
<p>The screenshot shows the midnight.fun portal pulling data from all three games. Each game card displays live achievement counts and leaderboard positions, sourced from the PRC-1 and PRC-6 APIs. There's no centralized database here; the data lives in each game's node, and the portal queries it on the fly.</p>
<iframe src="https://drive.google.com/file/d/1SSpY_nFAIm95b7v4LSEDhSywYgAkx0nK/preview" width="100%" height="480" allow="autoplay"></iframe>
<p>The walkthrough below shows the full earn-to-display loop: a player plays a game, unlocks an achievement, and the unlock surfaces immediately on the live leaderboard via the PRC-6 endpoints.</p>
<iframe src="https://drive.google.com/file/d/1j9KKy3Z2Jw5LAxK1a-PWNzUIhJj_-6H7/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works-under-the-hood">How it works under the hood<a href="https://effectstream.github.io/docs/blog/achievement-system#how-it-works-under-the-hood" class="hash-link" aria-label="Direct link to How it works under the hood" title="Direct link to How it works under the hood" translate="no">​</a></h2>
<p>When a player connects their wallet to midnight.fun, the portal queries each game's PRC-1 endpoint to fetch that player's achievements. Because the standard uses wallet addresses as the primary identifier, achievements are cross-game and cross-platform by default: any portal or app can query any PRC-1 game.</p>
<p>For games using auto-sign (session key delegation), PRC-6's identity resolution makes sure achievements don't get "lost" on temporary session wallets. The delegation chain maps session keys back to the player's main wallet, so everything gets attributed correctly no matter which signing method was used.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="an-open-ecosystem">An open ecosystem<a href="https://effectstream.github.io/docs/blog/achievement-system#an-open-ecosystem" class="hash-link" aria-label="Direct link to An open ecosystem" title="Direct link to An open ecosystem" translate="no">​</a></h2>
<p>Unlike platform-locked achievement systems (Steam, Xbox, PlayStation), PRC-1 achievements follow your wallet, not your platform account. Any app can aggregate achievements from any PRC-1 game. There's no central authority controlling the registry. And PRC-6 shows how you can extend the base standard for specific platforms without breaking compatibility.</p>
<p>The <a href="https://github.com/effectstream/effectstream" target="_blank" rel="noopener noreferrer" class="">achievement system code is built into EffectStream</a>. Any new game built with the framework gets PRC-1 support out of the box: define your achievements as data, and the framework handles tracking and the API. Every new game that adopts the standard makes the whole ecosystem richer.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="achievements" term="achievements"/>
        <category label="standards" term="standards"/>
        <category label="cross-game" term="cross-game"/>
        <category label="midnight" term="midnight"/>
        <category label="prc" term="prc"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Auto-Sign: Eliminating Wallet Pop-ups for Real-Time Applications]]></title>
        <id>https://effectstream.github.io/docs/blog/auto-sign</id>
        <link href="https://effectstream.github.io/docs/blog/auto-sign"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Every blockchain interaction that requires a wallet signature creates friction. In a turn-based game, you might tolerate one pop-up per move. But in a real-time game, you need dozens of signatures per minute, and each pop-up breaks the flow completely. Auto-sign fixes this by delegating non-financial signing to a session key: your wallet approves once, then the app signs on your behalf for the rest of the session.]]></summary>
        <content type="html"><![CDATA[<p>Every blockchain interaction that requires a wallet signature creates friction. In a turn-based game, you might tolerate one pop-up per move. But in a real-time game, you need dozens of signatures per minute, and each pop-up breaks the flow completely. Auto-sign fixes this by delegating non-financial signing to a session key: your wallet approves once, then the app signs on your behalf for the rest of the session.</p>
<p><img decoding="async" loading="lazy" alt="On-chain chess game requiring wallet signature to play" src="https://effectstream.github.io/docs/assets/images/signature-af970b47c9402e98bda0d200130de705.png" width="2018" height="1758" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-with-wallet-pop-ups">The problem with wallet pop-ups<a href="https://effectstream.github.io/docs/blog/auto-sign#the-problem-with-wallet-pop-ups" class="hash-link" aria-label="Direct link to The problem with wallet pop-ups" title="Direct link to The problem with wallet pop-ups" translate="no">​</a></h2>
<p>Blockchain games need cryptographic signatures to prove that a player authorized each action. Without auto-sign, every game move triggers a wallet confirmation dialog ("Sign this transaction?"). The result is an experience that feels nothing like a game and everything like filling out forms. Real-time gameplay is basically impossible under these conditions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-auto-sign-works">How auto-sign works<a href="https://effectstream.github.io/docs/blog/auto-sign#how-auto-sign-works" class="hash-link" aria-label="Direct link to How auto-sign works" title="Direct link to How auto-sign works" translate="no">​</a></h2>
<p>The core insight is separating two types of blockchain interactions:</p>
<ul>
<li class=""><strong>Financial transactions</strong> (asset transfers, token minting, contract calls with value) always require explicit wallet approval</li>
<li class=""><strong>Non-financial data submissions</strong> (game moves, messages, state updates) can be safely delegated to a session key</li>
</ul>
<p>When a player starts a session, their wallet generates a temporary key pair and signs a delegation certificate. This certificate authorizes the session key to submit non-financial data on the player's behalf. The session key is scoped (can only submit game data, never move funds), time-bounded (expires when the session ends), and revocable (the player can cancel delegation at any time).</p>
<p>This preserves the security guarantees players expect: nobody can move their assets without explicit approval. It just removes the friction that makes real-time apps unusable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-in-effectstreamwallets">Implementation in @effectstream/wallets<a href="https://effectstream.github.io/docs/blog/auto-sign#implementation-in-effectstreamwallets" class="hash-link" aria-label="Direct link to Implementation in @effectstream/wallets" title="Direct link to Implementation in @effectstream/wallets" translate="no">​</a></h2>
<p>Auto-sign lives in the <a href="https://www.npmjs.com/package/@effectstream/wallets" target="_blank" rel="noopener noreferrer" class=""><code>@effectstream/wallets</code></a> package, which provides multi-chain wallet support for EffectStream applications. The package handles the full delegation lifecycle: key generation, certificate signing, session management, and automatic signing of non-financial transactions.</p>
<p>The wallet layer supports multiple chains (EVM via injected providers and Ethers, Cardano, Midnight, Polkadot, Algorand, and Mina), and auto-sign works across all of them through a unified API. Developers don't need chain-specific signing logic; the framework handles the differences. Full reference is in the <a class="" href="https://effectstream.github.io/docs/home/components/wallets">wallets documentation</a>.</p>
<p>The three-step flow in Safe Solver is representative of every game on midnight.fun:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  WalletMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  walletLogin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  allInjectedWallets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/wallets"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. Initialize a local session wallet, silent and persisted in</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">//    encrypted browser storage. preferBatchedMode enables auto-sign.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> localWalletResult </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">walletLogin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  mode</span><span class="token operator">:</span><span class="token plain"> WalletMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">EvmEthers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  preferBatchedMode</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  connection</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    metadata</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"session"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> displayName</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Session Wallet"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    api</span><span class="token operator">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getLocalSignerFromStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. Let the user connect their real wallet (Midnight Lace in this</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">//    example) — this is the only step that shows a pop-up.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> injected </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">allInjectedWallets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  signatureSupport</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  transactionSupport</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> midnightWallet </span><span class="token operator">=</span><span class="token plain"> injected</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">WalletMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Midnight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> realWalletResult </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">walletLogin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  mode</span><span class="token operator">:</span><span class="token plain"> WalletMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Midnight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  preference</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> name</span><span class="token operator">:</span><span class="token plain"> midnightWallet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. One-time delegation: the real wallet signs a message</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">//    authorising the session wallet to submit non-financial inputs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">//    on its behalf. After this point, gameplay is silent.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> effectStreamService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">connectWallets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  localWalletResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  realWalletResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 4. Every subsequent game input is signed by the session wallet —</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">//    no pop-ups for the rest of the session.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sendGameMove</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> move</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x10y20"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The signing model is uniform regardless of chain: <code>WalletMode.Midnight</code> could be swapped for <code>WalletMode.EvmInjected</code>, <code>WalletMode.Cardano</code>, or any other supported chain and the rest of the flow stays the same.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="live-on-midnightfun">Live on midnight.fun<a href="https://effectstream.github.io/docs/blog/auto-sign#live-on-midnightfun" class="hash-link" aria-label="Direct link to Live on midnight.fun" title="Direct link to Live on midnight.fun" translate="no">​</a></h2>
<p>Auto-sign is live and in production on <a href="https://midnight.fun/games" target="_blank" rel="noopener noreferrer" class="">midnight.fun</a>, powering three games:</p>
<ul>
<li class=""><a href="https://safesolver.midnight.fun/" target="_blank" rel="noopener noreferrer" class=""><strong>Safe Solver</strong></a> - a puzzle game where auto-sign lets you submit moves rapidly</li>
<li class=""><a href="https://kachina.midnight.fun/" target="_blank" rel="noopener noreferrer" class=""><strong>Kachina Kolosseum</strong></a> - PvP combat where timing matters and pop-ups would ruin the experience</li>
<li class=""><a href="https://blockkart.paimastudios.com/" target="_blank" rel="noopener noreferrer" class=""><strong>Block Kart Legends</strong></a> - racing where dozens of state updates happen per session</li>
</ul>
<p><img decoding="async" loading="lazy" alt="midnight.fun games portal showing Safe Solver, Kachina, and Block Kart with achievements and leaderboards" src="https://effectstream.github.io/docs/assets/images/midnightfun-cad04b67e8b36607db375ecb6b04f988.png" width="1551" height="1669" class="img_ev3q"></p>
<p>The difference is immediately noticeable. Players approve wallet access once when connecting, then play freely. A typical Safe Solver session generates 20-30 signatures, all handled by the session key in the background.</p>
<iframe src="https://drive.google.com/file/d/1KhkfE4dM5dI3Wo0P3Ij8V3rOTLdHaGX1/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="before-and-after">Before and after<a href="https://effectstream.github.io/docs/blog/auto-sign#before-and-after" class="hash-link" aria-label="Direct link to Before and after" title="Direct link to Before and after" translate="no">​</a></h2>
<p><strong>Without auto-sign</strong>: Connect wallet → Start game → Pop-up → Approve → Make move → Pop-up → Approve → Make move → Pop-up → Approve → ... (player gives up after 3 moves)</p>
<p><strong>With auto-sign</strong>: Connect wallet → Approve delegation → Play freely for the entire session → Done</p>
<p>The gameplay goes from "blockchain app with constant interruptions" to "normal game that happens to be on-chain." And it's not just games: chat apps, collaborative tools, IoT dashboards, anything that needs rapid interaction benefits from session key delegation. The security model (limited scope, time-bounded, revocable) applies to all of them.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="wallets" term="wallets"/>
        <category label="ux" term="ux"/>
        <category label="auto-sign" term="auto-sign"/>
        <category label="midnight" term="midnight"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Data Availability with Celestia: High-Throughput On-Chain Applications]]></title>
        <id>https://effectstream.github.io/docs/blog/celestia-data-availability</id>
        <link href="https://effectstream.github.io/docs/blog/celestia-data-availability"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[On-chain apps that handle lots of data (games with frequent moves, social feeds, IoT streams) hit a wall: putting everything on the settlement layer is slow and expensive. Data Availability (DA) layers solve this by providing cheap, high-throughput data posting while settlement stays on Cardano or EVM. EffectStream now supports Celestia as a first-class data availability layer.]]></summary>
        <content type="html"><![CDATA[<p>On-chain apps that handle lots of data (games with frequent moves, social feeds, IoT streams) hit a wall: putting everything on the settlement layer is slow and expensive. Data Availability (DA) layers solve this by providing cheap, high-throughput data posting while settlement stays on Cardano or EVM. EffectStream now supports Celestia as a first-class data availability layer.</p>
<p><img decoding="async" loading="lazy" alt="Celestia DA application overview" src="https://effectstream.github.io/docs/assets/images/da-main-a8cc774df6bb793152d711a7684d195d.png" width="1172" height="880" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-a-data-availability-layer">Why a data availability layer?<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#why-a-data-availability-layer" class="hash-link" aria-label="Direct link to Why a data availability layer?" title="Direct link to Why a data availability layer?" translate="no">​</a></h2>
<p>Every EffectStream application needs to post user actions somewhere persistent and verifiable. For low-frequency applications, the settlement chain (Cardano, EVM) works fine. But when your application generates hundreds or thousands of actions per minute (game moves, chat messages, sensor readings), posting each one as a settlement-layer transaction gets expensive fast.</p>
<p>A DA layer like <a href="https://celestia.org/" target="_blank" rel="noopener noreferrer" class="">Celestia</a> provides an alternative: post high-throughput data cheaply to the DA layer, while settlement (asset transfers, contract state changes) stays on the main chain. EffectStream's state machine processes events from both layers without the developer having to think about it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="celestia-integration-architecture">Celestia integration architecture<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#celestia-integration-architecture" class="hash-link" aria-label="Direct link to Celestia integration architecture" title="Direct link to Celestia integration architecture" translate="no">​</a></h2>
<p>The Celestia integration adds a new data path to EffectStream's batcher system. The batcher aggregates individual user submissions into batches and posts them to Celestia for data availability, while settlement transactions go to the configured settlement chain.</p>
<p>The integration lives in the <a href="https://github.com/effectstream/effectstream/tree/v-next/bun-zswap-da" target="_blank" rel="noopener noreferrer" class=""><code>bun-zswap-da</code> template</a>, which includes:</p>
<ul>
<li class=""><strong>Batcher</strong> that aggregates and posts batches to Celestia</li>
<li class=""><strong>Database</strong> tracking submitted batches, token states, and offer lifecycle</li>
<li class=""><strong>Frontend</strong> demo UI for interacting with the DA-backed application</li>
<li class=""><strong>E2E tests</strong> verifying the full pipeline from user action to Celestia submission to state machine processing</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>The batch processor submits data to Celestia and tracks each submission:</p>
<p><img decoding="async" loading="lazy" alt="Celestia batch processor logs showing successful submissions" src="https://effectstream.github.io/docs/assets/images/celestia-1-902f1ceb6b17b5f8182e0ea0899f0767.png" width="1618" height="626" class="img_ev3q"></p>
<p>The backend database tracks the complete state (tokens, offers, batch references):</p>
<p><img decoding="async" loading="lazy" alt="Database tables tracking tokens and offers posted via Celestia DA" src="https://effectstream.github.io/docs/assets/images/celestia-2-b378249965e491b4c5ab620168880208.png" width="1678" height="564" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Celestia integration: batch submission tracking with block heights" src="https://effectstream.github.io/docs/assets/images/celestia-3-c4c179ea3b970588ccd325aeb450277b.png" width="2170" height="636" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Celestia integration: offer lifecycle and settlement state" src="https://effectstream.github.io/docs/assets/images/celestia-4-41b8087a8ec9ff215624a52ce31b5261.png" width="1742" height="550" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Celestia integration: complete DA pipeline monitoring" src="https://effectstream.github.io/docs/assets/images/celestia-5-bf30ff80ab70947234fc6babf9b9d2a2.png" width="2042" height="644" class="img_ev3q"></p>
<iframe src="https://drive.google.com/file/d/1vqybh83YG5hR7eeDNlQVNpxSWQrWXqtG/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="parallel-funnel-architecture">Parallel funnel architecture<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#parallel-funnel-architecture" class="hash-link" aria-label="Direct link to Parallel funnel architecture" title="Direct link to Parallel funnel architecture" translate="no">​</a></h2>
<p>EffectStream uses a "funnel" abstraction to read data from multiple blockchains through a unified interface. The Celestia funnel runs in parallel with other chain funnels: Celestia blocks arrive independently from the settlement chain, so the funnel ingests them concurrently. No blocking, no waiting for one chain to catch up to another.</p>
<p>This matters for multi-chain apps. In a game that settles on Cardano but posts moves to Celestia, you don't want Cardano's block time to bottleneck your data layer. Both chains process at their native speed, and the state machine receives events from each as they arrive.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-use-a-da-layer">When to use a DA layer<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#when-to-use-a-da-layer" class="hash-link" aria-label="Direct link to When to use a DA layer" title="Direct link to When to use a DA layer" translate="no">​</a></h2>
<p>A DA layer makes sense when:</p>
<ul>
<li class=""><strong>High throughput</strong>: your app generates more data than the settlement layer can handle economically</li>
<li class=""><strong>Low-value actions</strong>: individual actions don't need settlement-layer security (a game move isn't a financial transaction)</li>
<li class=""><strong>Real-time requirements</strong>: you need sub-second data posting, not the settlement chain's block time</li>
</ul>
<p>We learned this the hard way building Tarochi (an earlier EffectStream game): using EVM as the DA layer caused sync time issues as the chain of events grew. Moving high-frequency data to a purpose-built DA layer keeps the settlement chain lean, handling only the transactions that actually need its security.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hybrid-dapp-pattern">Hybrid dApp pattern<a href="https://effectstream.github.io/docs/blog/celestia-data-availability#hybrid-dapp-pattern" class="hash-link" aria-label="Direct link to Hybrid dApp pattern" title="Direct link to Hybrid dApp pattern" translate="no">​</a></h2>
<p>With Celestia support, developers can build hybrid applications:</p>
<table><thead><tr><th>Data type</th><th>Layer</th><th>Why</th></tr></thead><tbody><tr><td>Game moves, chat messages, state updates</td><td>Celestia DA</td><td>Cheap, fast, high-throughput</td></tr><tr><td>Asset transfers, minting, contract calls</td><td>Settlement chain (Cardano/EVM)</td><td>Full security guarantees</td></tr></tbody></table>
<p>The developer configures which data goes where, and the framework handles routing, batching, and event delivery.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/bun-zswap-da" target="_blank" rel="noopener noreferrer" class="">Celestia integration code (<code>bun-zswap-da</code> template)</a></li>
<li class=""><a href="https://celestia.org/developer-portal/" target="_blank" rel="noopener noreferrer" class="">Celestia documentation</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="celestia" term="celestia"/>
        <category label="data-availability" term="data-availability"/>
        <category label="cross-chain" term="cross-chain"/>
        <category label="scalability" term="scalability"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Cardano Primitives: From Indexer Tasks to First-Class Chain Events]]></title>
        <id>https://effectstream.github.io/docs/blog/cardano-primitives</id>
        <link href="https://effectstream.github.io/docs/blog/cardano-primitives"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[When we set out to connect Cardano to game state, we started from the obvious place a shared architecture that turns Dolos/UTxORPC streams into typed, queryable state machine events, with five concrete primitives delivered out of the box.]]></summary>
        <content type="html"><![CDATA[<p>When we set out to connect Cardano to game state, we started from the obvious place: write an indexer task. Each new use-case got its own custom Carp module, its own SQL schema, its own glue code. After the third one — stake-pool delegation, projected NFTs, native-asset transfers — it was clear we'd been writing the same scaffold three times. EffectStream now ships those scaffolds as <strong>Cardano Primitives</strong>: a shared architecture that turns Dolos/UTxORPC streams into typed, queryable state machine events, with five concrete primitives delivered out of the box.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-primitive-is">What a primitive is<a href="https://effectstream.github.io/docs/blog/cardano-primitives#what-a-primitive-is" class="hash-link" aria-label="Direct link to What a primitive is" title="Direct link to What a primitive is" translate="no">​</a></h2>
<p>A primitive is a small contract between a chain and an application state machine:</p>
<ol>
<li class=""><strong>A predicate</strong> that the indexer (Dolos via UTxORPC) evaluates server-side to filter transactions before they ever leave the gRPC stream.</li>
<li class=""><strong>A typed grammar</strong> — a TypeBox schema declaring the shape of the event the primitive emits.</li>
<li class=""><strong>An IVM (Indexed View Materializer)</strong> — a PostgreSQL materialised view definition that maintains a queryable snapshot of state as events arrive.</li>
<li class=""><strong>A state-machine prefix</strong> — every event the primitive emits routes to the application's <code>addStateTransition(prefix, ...)</code> handler under this name.</li>
</ol>
<p>Five primitives ship with EffectStream today, all sharing the same architecture:</p>
<table><thead><tr><th>Primitive</th><th>What it tracks</th><th>UTxORPC predicate</th><th>IVM view</th></tr></thead><tbody><tr><td><code>Cardano:Transfer</code></td><td>ADA + native-asset transfers</td><td><code>has_address</code></td><td>none (event-only)</td></tr><tr><td><code>Cardano:MintBurn</code></td><td>Token mints and burns</td><td><code>mints_asset</code></td><td>none (event-only)</td></tr><tr><td><code>Cardano:DelayedAsset</code></td><td>UTxO creation/spending for a policy</td><td><code>moves_asset</code></td><td><code>cardano_asset_utxos_view_&lt;name&gt;</code></td></tr><tr><td><code>Cardano:PoolDelegation</code></td><td>Stake pool delegations (pre-Conway + Conway)</td><td><code>has_certificate</code></td><td><code>cardano_pool_delegation_view_&lt;name&gt;</code></td></tr><tr><td><code>Cardano:ProjectedNFT</code></td><td>Lock/Unlock/Claim on a Hololocker script</td><td><code>has_address</code> (by <code>payment_part</code>)</td><td><code>cardano_projected_nft_view_&lt;name&gt;</code></td></tr></tbody></table>
<p>Source for all five lives in <a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src" target="_blank" rel="noopener noreferrer" class=""><code>packages/node-sdk/sm/primitives/src</code></a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-isnt-just-an-indexer">Why this isn't just an indexer<a href="https://effectstream.github.io/docs/blog/cardano-primitives#why-this-isnt-just-an-indexer" class="hash-link" aria-label="Direct link to Why this isn't just an indexer" title="Direct link to Why this isn't just an indexer" translate="no">​</a></h2>
<p>The temptation when wiring a new chain integration is to start with a generic block listener and write parsing logic in the application. That works once. By the second integration you're re-parsing UTXOs in two places; by the third you're trying to remember which event normalisation lives in which template.</p>
<p>The primitive pattern pushes three concerns down the stack:</p>
<ul>
<li class=""><strong>Filtering</strong> moves to the gRPC layer. Dolos evaluates the predicate before serialising — your application never sees a transaction it doesn't care about.</li>
<li class=""><strong>Parsing</strong> moves into the primitive. The Hololocker datum, Conway delegation certificates, CIP-14 asset fingerprints — each gets parsed once, in the primitive, into a stable typed payload.</li>
<li class=""><strong>State materialisation</strong> moves into Postgres. The IVM definition declares the table shape and the triggers; EffectStream keeps it consistent with the stream.</li>
</ul>
<p>What's left in the application is the part you actually wanted to write: the state transition handler that decides what <em>your game</em> does when a delegation changes or an NFT gets locked.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="worked-example-the-projected-nft-primitive">Worked example: the Projected NFT primitive<a href="https://effectstream.github.io/docs/blog/cardano-primitives#worked-example-the-projected-nft-primitive" class="hash-link" aria-label="Direct link to Worked example: the Projected NFT primitive" title="Direct link to Worked example: the Projected NFT primitive" translate="no">​</a></h2>
<p>The <code>CardanoProjectedNFT</code> primitive is the densest of the five because the Hololocker contract has a non-trivial state machine — Lock, Unlocking (with a time-lock expiry), and Claim — and each of those transitions has to be reconstructed from raw inputs and outputs.</p>
<p>The grammar declares the typed payload:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> projectedNftGrammar </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"ownerAddress"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"previousTxId"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"previousOutputIndex"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"currentTxId"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"currentOutputIndex"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"policyId"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"assetName"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"status"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">     </span><span class="token comment" style="color:rgb(98, 114, 164)">// "Lock" | "Unlocking" | "Claim"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"forHowLong"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// time-lock expiry for Unlocking</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>Wiring it into a config is a few lines:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildPrimitives</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addPrimitive</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> sp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parallelUtxoRpc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"GameNFTLocker"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> PrimitiveTypeCardanoProjectedNFT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      startBlockHeight</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      stateMachinePrefix</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-projected-nft"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      scriptHash</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"abc123..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// hololocker script hash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      network</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"yaci"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>And the state machine subscribes by registering a handler against the prefix:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-projected-nft"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> ownerAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> policyId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> assetName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> status</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> forHowLong </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">status </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Lock"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">grantPower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token operator">:</span><span class="token plain"> ownerAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> policyId </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">status </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Claim"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">revokePower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token operator">:</span><span class="token plain"> ownerAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> policyId </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The primitive itself does the heavy lifting: walking <code>tx.inputs</code> and <code>tx.outputs</code> at the script address, parsing the Hololocker <code>State</code> datum, matching consumed inputs to produced outputs to detect state transitions, and emitting one event per transition. None of that logic appears in the game.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-projected-nfts-are-the-same-architecture-as-pool-delegation">Why projected NFTs are the same architecture as pool delegation<a href="https://effectstream.github.io/docs/blog/cardano-primitives#why-projected-nfts-are-the-same-architecture-as-pool-delegation" class="hash-link" aria-label="Direct link to Why projected NFTs are the same architecture as pool delegation" title="Direct link to Why projected NFTs are the same architecture as pool delegation" translate="no">​</a></h2>
<p>The <code>CardanoPoolDelegation</code> primitive was the first to ship under this pattern (as part of the <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">stake pool delegation work</a>). When we built <code>CardanoProjectedNFT</code>, almost everything carried over:</p>
<ul>
<li class="">Same UTxORPC parallel sync protocol (<code>CARDANO_UTXORPC_PARALLEL</code>).</li>
<li class="">Same grammar + state-machine prefix mechanic.</li>
<li class="">Same IVM scaffolding for the materialised view (UPSERT on transitions, DELETE on terminal events).</li>
<li class="">Same handler signature on the state-machine side.</li>
</ul>
<p>What differs is local: the predicate (<code>has_certificate</code> for delegation, <code>has_address</code> for the script), the payload (delegation triple vs. NFT lock state), the IVM trigger logic (replace-on-change vs. lock/unlocking/claim lifecycle), and the datum parser (no datum for delegation; a CBOR Plutus datum walk for Projected NFTs). The shared scaffold is what lets a new primitive be a few hundred lines of focused code rather than a multi-week integration.</p>
<p>The five primitives that ship today are the ones we needed for the templates we're building. Adding a sixth — Cardano voting, drep delegation, governance actions — would follow the same shape.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-the-template">Running the template<a href="https://effectstream.github.io/docs/blog/cardano-primitives#running-the-template" class="hash-link" aria-label="Direct link to Running the template" title="Direct link to Running the template" translate="no">​</a></h2>
<p>Each primitive is paired with a runnable template that exercises it end-to-end:</p>
<ul>
<li class=""><code>templates/cardano-delegation</code> — <code>CardanoPoolDelegation</code> + Stake Pool Delegation Explorer dApp</li>
<li class=""><code>templates/projected-nft-preorder</code> — <code>CardanoProjectedNFT</code> + Hololocker lock/unlock/claim UI plus a campaign + marketplace state machine on top</li>
</ul>
<p>Both templates start the same way:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/effectstream/effectstream.git</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> effectstream/templates/projected-nft-preorder   </span><span class="token comment" style="color:rgb(98, 114, 164)"># or cardano-delegation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun run dev</span><br></div></code></pre></div></div>
<p>The orchestrator brings up PGLite, YACI DevKit, Dolos, the sync node, and the frontend together. Open the URL printed in the terminal and the primitive is live — every state transition emitted by the primitive is observable in the materialised view and surfaced on the frontend.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-unlocks">What this unlocks<a href="https://effectstream.github.io/docs/blog/cardano-primitives#what-this-unlocks" class="hash-link" aria-label="Direct link to What this unlocks" title="Direct link to What this unlocks" translate="no">​</a></h2>
<p>Once a chain has a primitive layer, the cost of adding a new on-chain integration drops to "implement the predicate + parser + IVM, then write the game logic." The five primitives that ship today cover the most common Cardano integration patterns — fungible transfers, native-asset lifecycle, mint/burn, stake-pool delegation, and projected-NFT custody. A new primitive can plug into the same machinery without touching the framework.</p>
<hr>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src" target="_blank" rel="noopener noreferrer" class="">All five Cardano primitives source</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives reference docs</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Stake Pool Delegation post</a> — the first primitive built on this architecture</li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/projected-nfts">Projected NFTs post</a> — the Hololocker dApp + ProjectedNFT primitive end-to-end</li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/cardano-delegation" target="_blank" rel="noopener noreferrer" class=""><code>cardano-delegation</code> template</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/projected-nft-preorder" target="_blank" rel="noopener noreferrer" class=""><code>projected-nft-preorder</code> template</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="primitives" term="primitives"/>
        <category label="dolos" term="dolos"/>
        <category label="utxorpc" term="utxorpc"/>
        <category label="technical" term="technical"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Data Availability Part 2: When to Use a DA Layer, and How to Index What You Posted]]></title>
        <id>https://effectstream.github.io/docs/blog/data-availability-part-2</id>
        <link href="https://effectstream.github.io/docs/blog/data-availability-part-2"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Part 1 walked through the Celestia integration as an architecture — the batcher, the funnel, the database, the demo UI. This second post is the document-the-subtleties counterpart: when should you reach for a DA layer at all? Both Avail and Celestia are first-class options in EffectStream — same primitive shape, different chains under the hood. Choosing between them, or choosing to skip the DA layer entirely, is the first design decision a team needs to make. After that, the second decision is how to index the data you posted, because a blob on a DA layer is useless to your application until something reads it back into PostgreSQL.]]></summary>
        <content type="html"><![CDATA[<p><a class="" href="https://effectstream.github.io/docs/blog/celestia-data-availability">Part 1</a> walked through the Celestia integration as an architecture — the batcher, the funnel, the database, the demo UI. This second post is the document-the-subtleties counterpart: <strong>when should you reach for a DA layer at all?</strong> Both Avail and Celestia are first-class options in EffectStream — same primitive shape, different chains under the hood. Choosing between them, or choosing to skip the DA layer entirely, is the first design decision a team needs to make. After that, the second decision is how to <strong>index</strong> the data you posted, because a blob on a DA layer is useless to your application until something reads it back into PostgreSQL.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-da-options-avail-and-celestia">Two DA options: Avail and Celestia<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#two-da-options-avail-and-celestia" class="hash-link" aria-label="Direct link to Two DA options: Avail and Celestia" title="Direct link to Two DA options: Avail and Celestia" translate="no">​</a></h2>
<p>EffectStream ships generic DA primitives for both networks. They follow the same shape:</p>
<table><thead><tr><th>Primitive type</th><th>Network</th><th>Posts to</th><th>Read primitive</th></tr></thead><tbody><tr><td><code>AVAIL:Generic</code></td><td><a href="https://www.availproject.org/" target="_blank" rel="noopener noreferrer" class="">Avail</a></td><td>An Avail app ID (Substrate-based)</td><td><code>availGenericGrammar</code></td></tr><tr><td><code>CELESTIA:Generic</code></td><td><a href="https://celestia.org/" target="_blank" rel="noopener noreferrer" class="">Celestia</a></td><td>A Celestia namespace</td><td><code>celestiaGenericGrammar</code></td></tr></tbody></table>
<p>Both grammars are intentionally minimal:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// celestia-generic-grammar.ts (avail-generic-grammar.ts is identical in shape)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> celestiaGenericGrammar </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"payload"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">Object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> suppliedValue</span><span class="token operator">:</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The primitive's job is to watch its DA layer and deliver each posted blob to the state machine as a <code>payload</code> event. What the blob <em>means</em> is up to your application — that's the part you index.</p>
<p>In practice, when do you pick which?</p>
<ul>
<li class=""><strong>Avail</strong> has Substrate ergonomics — useful if your team already runs Substrate infra or wants account-model semantics on the DA side.</li>
<li class=""><strong>Celestia</strong> has the most mature blob-namespace model and a wide light-node ecosystem — useful if you want anyone to verify your data without trusting a full node.</li>
</ul>
<p>Both are interchangeable at the EffectStream layer: same grammar, same state-machine handler shape, only the config changes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-a-da-layer-makes-sense">When a DA layer makes sense<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#when-a-da-layer-makes-sense" class="hash-link" aria-label="Direct link to When a DA layer makes sense" title="Direct link to When a DA layer makes sense" translate="no">​</a></h2>
<p>Settlement chains (Cardano, Arbitrum, etc.) are expensive per byte and slow per block. They are the right home for: asset transfers, contract state, anything where censorship-resistance and finality are load-bearing for the <em>value</em> being moved. They are the <em>wrong</em> home for: high-volume, low-value data where speed isn't critical but you still need verifiability.</p>
<p>Three patterns where a DA layer is the right choice:</p>
<p><strong>1. Many small messages.</strong> Game moves, chat lines, IoT sensor readings, application logs. Each individual record isn't worth a settlement-chain transaction, but together they're the bulk of your application's data. Post them as DA blobs at hundreds-to-thousands per minute, settle only the things that move value.</p>
<p><strong>2. Large blobs.</strong> Generated assets, ZK proof artefacts, encoded media, sealed game replays. Anything over a few kilobytes that you don't want sitting in settlement-chain calldata. DA layers handle these cheaply — settlement chains do not.</p>
<p><strong>3. Cold storage with main-chain hashes.</strong> When the data itself doesn't need to live on the settlement chain, but its <em>existence</em> does. Post the blob to Avail/Celestia, post only the blob's hash (and any commitment) to Cardano/EVM. Anyone can verify the hash matches and pull the body from DA on demand. This is the right pattern for "we promised to keep this immutable" data that nobody reads often.</p>
<p>The shared property: <strong>speed is not the critical dimension</strong>. If you need a record to be visible to the application within one block, DA is wrong — use the settlement chain (or your L2's mempool). DA is for "eventually-visible, cheaply, in volume."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="you-must-index-what-you-posted">You must index what you posted<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#you-must-index-what-you-posted" class="hash-link" aria-label="Direct link to You must index what you posted" title="Direct link to You must index what you posted" translate="no">​</a></h2>
<p>A blob on Celestia or Avail is just bytes. Your application doesn't see those bytes until the state machine reads the primitive's event and writes a row to PostgreSQL. <strong>The indexer is the part of your application that turns blobs back into queryable state.</strong></p>
<p>The state-machine handler does three things on every DA event:</p>
<ol>
<li class="">Decode the blob into a typed record.</li>
<li class="">Validate it (signature, sequence, schema).</li>
<li class="">Write it to one or more tables keyed by something useful to the application.</li>
</ol>
<p>Here's the minimal shape, wired against the Celestia generic primitive:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> Stm </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/sm"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> World </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/coroutine"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> grammar </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"./grammar.ts"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> insertDaRecord </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@my-app/database"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> stm </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Stm</span><span class="token class-name operator">&lt;</span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">typeof</span><span class="token class-name"> grammar</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token class-name"> </span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">grammar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// "celestia-blobs" is the stateMachinePrefix declared in the primitive config.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"celestia-blobs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> suppliedValue </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. Decode — the blob is whatever your app posted (JSON, CBOR, protobuf...).</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> record</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> kind</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> key</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> body</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    record </span><span class="token operator">=</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">suppliedValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// malformed blob, skip</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. Validate — schema check, signature, idempotency, etc.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">kind </span><span class="token operator">||</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. Index — one INSERT per blob keyed by something queryable.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertDaRecord</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    da_block_height</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    da_namespace</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"celestia:my-app"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    record_kind</span><span class="token operator">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    record_key</span><span class="token operator">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    record_body</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    posted_at</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockTimestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>Swap <code>"celestia-blobs"</code> for <code>"avail-blobs"</code> and the handler is identical against the Avail primitive — that's the whole point of the matched grammar.</p>
<p>The table this writes to is the application's index:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> da_records </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SERIAL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  da_block_height </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  da_namespace </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  record_kind </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  record_key </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  record_body </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  posted_at TIMESTAMPTZ </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_da_records_kind_key</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> da_records</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">record_kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> record_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>Now the application can <code>SELECT … WHERE record_kind = 'zswap' AND record_key = '0xabc...'</code> and get the blob body back, sorted by DA block height, without re-reading the DA layer.</p>
<p>The two things to get right at this layer are <strong>idempotency</strong> (each DA blob should produce the same row on replay — keep <code>(da_namespace, da_block_height, record_key)</code> unique) and <strong>decode-failure tolerance</strong> (a malformed blob shouldn't halt the state machine — log it, skip it, move on).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="worked-example-indexing-zswap-offers">Worked example: indexing ZSwap offers<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#worked-example-indexing-zswap-offers" class="hash-link" aria-label="Direct link to Worked example: indexing ZSwap offers" title="Direct link to Worked example: indexing ZSwap offers" translate="no">​</a></h2>
<p><a href="https://github.com/effectstream/effectstream/tree/v-next/bun-zswap-da" target="_blank" rel="noopener noreferrer" class="">ZSwap</a> is the concrete case the M2 template demonstrates. ZSwap offers are short JSON records — wallet, token in, token out, amounts, nonce, signature — typically a few hundred bytes each. A busy ZSwap market generates thousands of offers per hour. Three properties matter:</p>
<ul>
<li class="">Each offer is small (a few hundred bytes).</li>
<li class="">The volume is high (orders of magnitude more than settlement-chain capacity).</li>
<li class="">The data must be decentralised — offers are public, fillable by any participant, with no privileged operator.</li>
</ul>
<p>A traditional settlement chain is the wrong home: per-byte cost is prohibitive at that volume. A centralised database is wrong for a different reason: any operator can censor offers. A DA layer is the right answer — every offer is publicly retrievable, cheap, and anyone with the namespace can verify the full offer book by reading directly from Celestia or Avail.</p>
<p>The <code>bun-zswap-da</code> template applies the indexing pattern above against a richer schema. Offers, batches, and settlement state each get their own table; the DA handler decodes blobs into offer records and writes them; settlement-chain events on the EVM side mark offers as filled or expired. The on-chain footprint is one batch reference per N offers; the rest is on the DA layer.</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// State-machine slice from bun-zswap-da, simplified</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"zswap-offer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> offer </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">parseZswapOffer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">suppliedValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">offer </span><span class="token operator">||</span><span class="token plain"> </span><span class="token operator">!</span><span class="token function" style="color:rgb(80, 250, 123)">isSignatureValid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertOffer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    offer_id</span><span class="token operator">:</span><span class="token plain">        offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    wallet</span><span class="token operator">:</span><span class="token plain">          offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">wallet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    token_in</span><span class="token operator">:</span><span class="token plain">        offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tokenIn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    token_out</span><span class="token operator">:</span><span class="token plain">       offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tokenOut</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    amount_in</span><span class="token operator">:</span><span class="token plain">       offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amountIn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    amount_out</span><span class="token operator">:</span><span class="token plain">      offer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amountOut</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    da_block_height</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    da_namespace</span><span class="token operator">:</span><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"celestia:zswap"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    status</span><span class="token operator">:</span><span class="token plain">          </span><span class="token string" style="color:rgb(255, 121, 198)">"open"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The query surface on top of this is straightforward — "show me all open ZSwap offers for this token pair" becomes a single indexed <code>SELECT</code>, even though the underlying data lives on Celestia (or Avail) rather than in the application's own write path.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-not-to-use-a-da-layer">When NOT to use a DA layer<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#when-not-to-use-a-da-layer" class="hash-link" aria-label="Direct link to When NOT to use a DA layer" title="Direct link to When NOT to use a DA layer" translate="no">​</a></h2>
<p>The mirror-image of the three patterns above:</p>
<ul>
<li class=""><strong>One transaction is enough.</strong> If your application generates handful of records per hour and each one moves value, the settlement chain is simpler — no DA layer to keep healthy, no indexer to maintain, no extra namespace to fund.</li>
<li class=""><strong>Real-time visibility is required.</strong> DA layers have their own block time and the indexer adds further delay. If a user needs to see a record reflected in application state within one settlement-chain block, DA is the wrong tool.</li>
<li class=""><strong>The data is private.</strong> DA layers are <em>public</em>. A private payload still needs a settlement-side mechanism (encryption, ZK commitments) — DA alone gives you availability, not confidentiality.</li>
</ul>
<p>The decision tree is short:</p>
<blockquote>
<p><em>Is the data high-volume, low-individual-value, and verifiable rather than hidden?</em> If yes, DA. If no, settle on the main chain or look at L2/ZK.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://effectstream.github.io/docs/blog/data-availability-part-2#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>DA layers are a sharp tool, not a free upgrade. They unlock workloads that were previously stuck between "too expensive for settlement" and "too important for a private database" — but only if the data has a clear indexing strategy and a use-case where availability matters more than instant visibility. The same primitive abstraction in EffectStream covers both Avail and Celestia, so the architectural decision is whether to use DA at all; the network choice is a tactical one you can change later.</p>
<hr>
<ul>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/celestia-data-availability">Part 1: Celestia Data Availability — the integration architecture</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/bun-zswap-da" target="_blank" rel="noopener noreferrer" class=""><code>bun-zswap-da</code> template (Celestia DA worked example)</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src/avail-generic" target="_blank" rel="noopener noreferrer" class="">Avail Generic primitive source</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src/celestia-generic" target="_blank" rel="noopener noreferrer" class="">Celestia Generic primitive source</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="celestia" term="celestia"/>
        <category label="avail" term="avail"/>
        <category label="data-availability" term="data-availability"/>
        <category label="indexing" term="indexing"/>
        <category label="zswap" term="zswap"/>
        <category label="technical" term="technical"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Engine Integrations: From Local Development to Mobile]]></title>
        <id>https://effectstream.github.io/docs/blog/engine-integrations</id>
        <link href="https://effectstream.github.io/docs/blog/engine-integrations"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[EffectStream is designed to integrate with any game engine or platform. This article covers four distinct integrations, each showing a different technology combination: modern multi-chain local development tooling, a traditional game engine, social platform frames, and a mobile app.]]></summary>
        <content type="html"><![CDATA[<p>EffectStream is designed to integrate with any game engine or platform. This article covers four distinct integrations, each showing a different technology combination: modern multi-chain local development tooling, a traditional game engine, social platform frames, and a mobile app.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="evm-cardano-local-development-environment">EVM-Cardano local development environment<a href="https://effectstream.github.io/docs/blog/engine-integrations#evm-cardano-local-development-environment" class="hash-link" aria-label="Direct link to EVM-Cardano local development environment" title="Direct link to EVM-Cardano local development environment" translate="no">​</a></h2>
<p>One of the biggest friction points for multi-chain developers is setting up a local dev environment. Syncing a Cardano indexer like Carp can take days on testnet and even longer on mainnet, which makes rapid iteration pretty much impossible. We built a template that replaces this entire workflow with an instant localhost setup.</p>
<p>The EVM-Cardano template, <a href="https://github.com/effectstream/effectstream/tree/v-next/templates/evm-cardano" target="_blank" rel="noopener noreferrer" class="">available in the monorepo</a>, combines:</p>
<ul>
<li class=""><a href="https://hardhat.org/" target="_blank" rel="noopener noreferrer" class=""><strong>Hardhat v3</strong></a> for EVM smart contract development and a local EVM chain</li>
<li class=""><a href="https://github.com/bloxbean/yaci-devkit" target="_blank" rel="noopener noreferrer" class=""><strong>YACI DevKit</strong></a> for a local Cardano devnet</li>
<li class=""><a href="https://github.com/txpipe/dolos" target="_blank" rel="noopener noreferrer" class=""><strong>Dolos</strong></a> with UTxORPC for Cardano indexing via gRPC</li>
</ul>
<p>The result: spin up a full multi-chain dev environment (EVM + Cardano) in minutes, not days.</p>
<p><img decoding="async" loading="lazy" alt="Local development orchestrator showing Hardhat, YACI DevKit, and Dolos running together" src="https://effectstream.github.io/docs/assets/images/evm-cardano-1-d5f432abcc5a588336a2998055ea121e.png" width="930" height="465" class="img_ev3q"></p>
<p>The template includes an explorer UI for interacting with the local environment. You can mint NFTs on EVM, send ADA on Cardano, and see cross-chain events in a unified feed:</p>
<p><img decoding="async" loading="lazy" alt="EVM-Cardano Explorer: mint NFTs, send ADA, cross-chain event feed" src="https://effectstream.github.io/docs/assets/images/evm-cardano-2-913c1a1618878d4fd8d5855c975e257a.png" width="883" height="645" class="img_ev3q"></p>
<p>This is a complete local dev environment for building cross-chain applications. Write smart contracts on both chains, test interactions between them, iterate fast. No testnet, no waiting for chain sync.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="controlling-the-stack-the-orchestrator">Controlling the stack: the orchestrator<a href="https://effectstream.github.io/docs/blog/engine-integrations#controlling-the-stack-the-orchestrator" class="hash-link" aria-label="Direct link to Controlling the stack: the orchestrator" title="Direct link to Controlling the stack: the orchestrator" translate="no">​</a></h3>
<p>A multi-chain dev environment is not one process — it's six or seven, with dependencies between them: PGLite has to come up before the sync node, Dolos needs cardano-node, the contracts have to be deployed before the frontend can talk to them. We built <code>bunx orchestrator</code> as the master controller for this. It launches every process in dependency order, restarts individual ones, follows their logs, and silences the noisy ones, all from a single CLI.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">$ bunx orchestrator --help</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">orchestrator — Bun-based process orchestration CLI</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Usage:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  orchestrator &lt;command&gt; [options]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Commands:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  start   [config|name...]    Start all processes, or specific ones by name</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  status                      Show status of running processes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  restart &lt;name&gt;              Restart a specific process by name</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  stop    [name]              Stop a specific process (or all if no name given)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  list    [config]            List processes in config without starting them</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  silence [name...]           Suppress terminal output for processes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  unsilence &lt;name...&gt;         Resume terminal output for processes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  logs    [name...]           Follow background daemon log files (like tail -f)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Global options:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -c, --config  &lt;path&gt;        Config file (default: auto-detected)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -h, --help                  Print help</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Options for `start`:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -b, --background            Run as a detached background daemon</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -p, --port    &lt;n&gt;           Orchestrator API port (default: 4747)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -o, --only    &lt;p1,p2,...&gt;   Run only these processes (+ dependencies)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -e, --except  &lt;p1,p2,...&gt;   Skip these processes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -s, --serial                Launch one at a time instead of in parallel waves</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --no-deps                   Launch only named processes, skip dependency resolution</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --log-dir     &lt;path&gt;        Log directory when using -b (default: .orchestrator-logs)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --no-api                    Disable the HTTP API server</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --silence     &lt;p1,p2,...&gt;   Suppress terminal output for these processes</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Options for `status`:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  -f, --follow                Continuously refresh the status table (1s interval)</span><br></div></code></pre></div></div>
<p>In practice, the developer workflow is just:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">bunx orchestrator start              </span><span class="token comment" style="color:rgb(98, 114, 164)"># bring up the whole stack</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bunx orchestrator status             </span><span class="token comment" style="color:rgb(98, 114, 164)"># see what's running and what failed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bunx orchestrator restart midnight-node   </span><span class="token comment" style="color:rgb(98, 114, 164)"># bounce a single process</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bunx orchestrator logs sync-node     </span><span class="token comment" style="color:rgb(98, 114, 164)"># tail the logs of one process</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bunx orchestrator stop               </span><span class="token comment" style="color:rgb(98, 114, 164)"># tear everything down</span><br></div></code></pre></div></div>
<p>The <code>--only</code> and <code>--except</code> flags let you run partial stacks — useful when you're iterating on the sync node and don't need the frontend rebuilding, or when you're testing the batcher in isolation. <code>--background</code> runs the whole orchestrator as a detached daemon with logs going to files, which is how the e2e test suite runs the same stack on CI.</p>
<p>The point is that the same <code>bun run dev</code> command that boots the EVM-Cardano template also boots the cardano-delegation template, the zk-cardano template, and every other template in the monorepo — they all share the orchestrator and just declare a different process graph in their config.</p>
<iframe src="https://drive.google.com/file/d/1dmXshwpqeXi9sez5ekfqfrlXBdjVVABW/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="game-maker-integration">Game Maker integration<a href="https://effectstream.github.io/docs/blog/engine-integrations#game-maker-integration" class="hash-link" aria-label="Direct link to Game Maker integration" title="Direct link to Game Maker integration" translate="no">​</a></h2>
<p>Not every on-chain game needs to be built from scratch. We created a template that integrates EffectStream with <a href="https://gamemaker.io/" target="_blank" rel="noopener noreferrer" class="">Game Maker</a>, one of the most popular 2D game engines. Game designers use familiar visual tools for gameplay while EffectStream handles the blockchain backend: on-chain state, wallet interactions, and multi-chain settlement.</p>
<p>The point here is that EffectStream isn't limited to web-based games. Any engine that can make HTTP calls can talk to an EffectStream backend, and Game Maker's event-driven architecture maps naturally to EffectStream's state machine model.</p>
<iframe src="https://drive.google.com/file/d/1NhXGPajMx79w-odUkY8vzbhfGt2BZPg9/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="farcaster-frame-integration">Farcaster Frame integration<a href="https://effectstream.github.io/docs/blog/engine-integrations#farcaster-frame-integration" class="hash-link" aria-label="Direct link to Farcaster Frame integration" title="Direct link to Farcaster Frame integration" translate="no">​</a></h2>
<p><a href="https://docs.farcaster.xyz/developers/frames/" target="_blank" rel="noopener noreferrer" class="">Farcaster Frames</a> are mini-applications that run directly inside Farcaster's social feed. We built a template that turns an EffectStream game into a Frame, playable without leaving the feed. No app install required.</p>
<p>This is a completely different distribution model. Instead of "download our app and connect your wallet," it's "play this game right here in your feed." Every share is a playable instance. The social feed becomes the game platform.</p>
<ul>
<li class=""><a href="https://github.com/PaimaStudios/paima-game-templates/tree/main/farcaster-frame" target="_blank" rel="noopener noreferrer" class="">Template code</a></li>
</ul>
<p>The integration handles the constraints of the Frame environment (limited UI real estate, server-side rendering requirements, Farcaster's auth model) while keeping full EffectStream backend functionality.</p>
<iframe src="https://drive.google.com/file/d/1XFpNegn8fcYIKAgp999I8i4FwGkB7JdJ/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mobile-with-gps">Mobile with GPS<a href="https://effectstream.github.io/docs/blog/engine-integrations#mobile-with-gps" class="hash-link" aria-label="Direct link to Mobile with GPS" title="Direct link to Mobile with GPS" translate="no">​</a></h2>
<p>The most ambitious integration: a game template combining GPS location data for iOS and Android. Location-based gameplay where the assets you find, collect, and trade are real on-chain tokens.</p>
<p>The mobile app communicates with an EffectStream backend that manages on-chain state, while the mobile layer renders game objects at GPS-determined positions. It shows that EffectStream works well beyond the browser.</p>
<ul>
<li class=""><a href="https://github.com/PaimaStudios/paima-game-templates" target="_blank" rel="noopener noreferrer" class="">Template code (<code>paima-game-templates</code> repository)</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/16IH6do2cyxigKaxL996TtGqMFwHZR3Xs/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-common-thread">The common thread<a href="https://effectstream.github.io/docs/blog/engine-integrations#the-common-thread" class="hash-link" aria-label="Direct link to The common thread" title="Direct link to The common thread" translate="no">​</a></h2>
<table><thead><tr><th>Integration</th><th>Platform</th><th>What's new</th></tr></thead><tbody><tr><td>EVM-Cardano</td><td>Local dev</td><td>Instant multi-chain dev environment, replacing days-long Carp sync</td></tr><tr><td>Game Maker</td><td>Desktop engine</td><td>Traditional game engine + blockchain backend</td></tr><tr><td>Farcaster Frame</td><td>Social feed</td><td>Play on-chain games without leaving the social platform</td></tr><tr><td>Mobile GPS</td><td>iOS/Android</td><td>Location-based on-chain gameplay</td></tr></tbody></table>
<p>All templates are open-source in the <a href="https://github.com/PaimaStudios/paima-game-templates" target="_blank" rel="noopener noreferrer" class="">game templates repository</a>. The EVM-Cardano template with its Dolos-based local development stack is probably the most impactful: it removes the biggest barrier to Cardano development by getting rid of the sync time that's historically made local testing impractical.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/evm-cardano" target="_blank" rel="noopener noreferrer" class="">EVM-Cardano template code (Dolos + YACI DevKit)</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="game-engines" term="game-engines"/>
        <category label="developer-tools" term="developer-tools"/>
        <category label="mobile" term="mobile"/>
        <category label="farcaster" term="farcaster"/>
        <category label="evm" term="evm"/>
        <category label="cardano" term="cardano"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Game Template Library: On-Chain Game Patterns for Every Chain]]></title>
        <id>https://effectstream.github.io/docs/blog/game-templates</id>
        <link href="https://effectstream.github.io/docs/blog/game-templates"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The best way to learn a framework is to see it in action. We've built five game templates, each one showing a different chain integration, cryptographic technique, or architectural pattern. They're all open-source, playable live, and ready to fork.]]></summary>
        <content type="html"><![CDATA[<p>The best way to learn a framework is to see it in action. We've built five game templates, each one showing a different chain integration, cryptographic technique, or architectural pattern. They're all open-source, playable live, and ready to fork.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="safe-solver-multichain-l2-on-arbitrum--midnight">Safe Solver: multichain L2 on Arbitrum + Midnight<a href="https://effectstream.github.io/docs/blog/game-templates#safe-solver-multichain-l2-on-arbitrum--midnight" class="hash-link" aria-label="Direct link to Safe Solver: multichain L2 on Arbitrum + Midnight" title="Direct link to Safe Solver: multichain L2 on Arbitrum + Midnight" translate="no">​</a></h2>
<p>Safe Solver is a fast, interactive puzzle game that runs multichain on Arbitrum and <a href="https://midnight.network/" target="_blank" rel="noopener noreferrer" class="">Midnight</a>. It demonstrates EffectStream working as an L2 across both chains, with deterministic randomness derived per block and L2 interaction for game execution.</p>
<p>The game itself is a puzzle where players solve safe combinations on-chain. The random puzzle generation is deterministic per block, so all nodes reach the same result. EffectStream handles game logic and state transitions as an L2, while Arbitrum and Midnight provide the settlement layers. This is a good example of how a fast, interactive game can work on-chain without sacrificing responsiveness.</p>
<p>Most on-chain games either commit to a single chain or accept inconsistent state across chains. Safe Solver runs the <strong>same</strong> game logic over Arbitrum and Midnight blocks simultaneously, with EffectStream computing the deterministic result both chains independently agree on — and this multichain L2 pattern is the foundation the other four templates build on.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/safe-solver" target="_blank" rel="noopener noreferrer" class="">Source code</a></li>
<li class=""><a href="https://safesolver.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">Play live</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1KhkfE4dM5dI3Wo0P3Ij8V3rOTLdHaGX1/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="kachina-kolosseum-multiplayer-midnight-zk-game">Kachina Kolosseum: multiplayer Midnight ZK game<a href="https://effectstream.github.io/docs/blog/game-templates#kachina-kolosseum-multiplayer-midnight-zk-game" class="hash-link" aria-label="Direct link to Kachina Kolosseum: multiplayer Midnight ZK game" title="Direct link to Kachina Kolosseum: multiplayer Midnight ZK game" translate="no">​</a></h2>
<p>Kachina Kolosseum is a multiplayer PvP game built entirely on Midnight, demonstrating commit-reveal schemes written in Compact (Midnight's ZK language). All proofs are generated in the browser, so players don't need any backend infrastructure to play.</p>
<p>In PvP on a public blockchain, the second player has an inherent advantage: they can see the first player's move and counter it. The commit-reveal scheme in Compact fixes this. Both players commit their moves privately, then reveal simultaneously. ZK proofs verify the revealed move matches the original commitment without exposing it early. If someone refuses to reveal (griefing), a timeout kicks in and they forfeit. Proof generation happens client-side and completes in under a second.</p>
<p>The protocol is named after the academic work it builds on — <a href="https://eprint.iacr.org/2020/543.pdf" target="_blank" rel="noopener noreferrer" class="">Kachina: Foundations of Private Smart Contracts (Kerber, Kiayias, Kohlweiss; University of Edinburgh, 2020)</a> and the <a href="https://www.pure.ed.ac.uk/ws/portalfiles/portal/217971659/Kachina_KERBER_DOA19042021_AFV.pdf" target="_blank" rel="noopener noreferrer" class="">extended 2021 manuscript</a>, which formalises the model for private smart contracts that underlies Midnight's design. Kachina Kolosseum is the gameplay-shaped instantiation of those ideas: a trustless commit-reveal arena where the cryptography is the rulebook.</p>
<ul>
<li class=""><a href="https://kachina.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">Play live</a></li>
<li class=""><a href="https://github.com/PaimaStudios/pvp-arena" target="_blank" rel="noopener noreferrer" class="">Source code</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1nWB8zxtxPLHfFZIw3trh0bAylb6DE8JT/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="block-kart-legends-on-chain-typescript-simulation-on-arbitrum--midnight">Block Kart Legends: on-chain TypeScript simulation on Arbitrum + Midnight<a href="https://effectstream.github.io/docs/blog/game-templates#block-kart-legends-on-chain-typescript-simulation-on-arbitrum--midnight" class="hash-link" aria-label="Direct link to Block Kart Legends: on-chain TypeScript simulation on Arbitrum + Midnight" title="Direct link to Block Kart Legends: on-chain TypeScript simulation on Arbitrum + Midnight" translate="no">​</a></h2>
<p>Block Kart Legends is a racing game that runs <strong>complex TypeScript on-chain code</strong> cross-chain on Arbitrum and Midnight. It generates a deterministic L2 based on EVM + Midnight blocks, where it executes the race simulations. The entire race physics run deterministically in the L2, so every node computes the same result.</p>
<p>"On-chain TypeScript code" is the unusual part. Most chains run a custom VM (EVM, Move, Cairo) or expose a constrained DSL. Block Kart's game loop is plain TypeScript — collisions, friction, momentum, lap detection — running as the L2's state transition function and replayed identically by every full node. The language doesn't change between local development, testing, and on-chain execution, which is what makes it practical to ship a real physics simulation as on-chain logic rather than as a thin coordination layer over an off-chain server.</p>
<p>Arbitrum handles the transaction layer (race entries, rewards, leaderboard updates), while Midnight provides ZK verification of race results. This means a player's race time is provably legitimate without needing a centralized game server to validate every result.</p>
<ul>
<li class=""><a href="https://blockkart.paimastudios.com/" target="_blank" rel="noopener noreferrer" class="">Play live</a></li>
<li class=""><a href="https://github.com/effectstream/block-kart-legends" target="_blank" rel="noopener noreferrer" class="">Source code</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1ccQkU2bNeJLPzqsURORuzctcg0kZqpSe/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dust-2-dust-large-scale-zk-game-in-compact">Dust-2-Dust: large-scale ZK game in Compact<a href="https://effectstream.github.io/docs/blog/game-templates#dust-2-dust-large-scale-zk-game-in-compact" class="hash-link" aria-label="Direct link to Dust-2-Dust: large-scale ZK game in Compact" title="Direct link to Dust-2-Dust: large-scale ZK game in Compact" translate="no">​</a></h2>
<p>The most technically complex template, by a clear margin. Dust-2-Dust is a roguelike deck builder written entirely in Compact (Midnight's ZK language), proving that ZK languages can encode and execute complex game behaviors. All proofs are calculated with WASM in the browser.</p>
<p>This isn't a simple proof-of-concept. It's a full game with hidden inventory, private resource management, and strategic decisions that stay secret until their effects show up in gameplay. Multiple interacting ZK circuits compose together for complex game logic while preserving privacy throughout.</p>
<p>Where most ZK games stop at a single circuit — one private state, one reveal — Dust-2-Dust threads many circuits together: drawing cards, using items, combat resolution, level progression, each with their own private state, all composed into one game session that lasts dozens of turns. The template is deliberately pushing the limits of state-of-the-art ZK circuit complexity: how much logic can you fit in a single Compact program, how many circuits can compose without proof generation becoming impractical, how much hidden state can persist between rounds without leaking through the public commitment? Dust-2-Dust's answers to those questions are the upper bound for what's currently shippable as a browser-side ZK game.</p>
<ul>
<li class=""><a href="https://dust2dust.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">Play live</a></li>
<li class=""><a href="https://github.com/PaimaStudios/midnight-game-2/" target="_blank" rel="noopener noreferrer" class="">Source code</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1FjfRFUPtxoIVSWhiFV0I7CtdS43xuM-L/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="go-fish-zk-mental-poker">Go Fish: ZK Mental Poker<a href="https://effectstream.github.io/docs/blog/game-templates#go-fish-zk-mental-poker" class="hash-link" aria-label="Direct link to Go Fish: ZK Mental Poker" title="Direct link to Go Fish: ZK Mental Poker" translate="no">​</a></h2>
<p>Go Fish is a ZK implementation of "Mental Poker," the classic cryptographic problem of playing a fair card game without a trusted dealer. It's built entirely on a ZK contract, which means any card-like game can be implemented on top of the same protocol. Trustless card games, no server needed.</p>
<p>The protocol uses a commit-reveal scheme combined with shared secret derivation. When cards are "dealt," the deck is encrypted so that neither player can read it alone. Each card reveal requires cooperative decryption: both players contribute their share to reveal a card, which means neither side can peek ahead or manipulate the draw order. A fair deal is enforced by the math, not by trusting a server.</p>
<p>Once you have a trustless deck, the protocol generalises. Poker, blackjack, and trick-taking games all fall out of the same foundation. So do trading-card games with hidden hands — a Magic: The Gathering-style game, where each player has a private hand drawn from a shuffled deck and cards are revealed when played, is exactly the shape of the problem Go Fish solves. The same circuit that makes "Go Fish" trustless makes a full Magic-style card game trustless too: the only thing that changes is the card definitions and the turn rules, both of which run on top of the shared deck-and-hand primitive.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/go-fish" target="_blank" rel="noopener noreferrer" class="">Template code</a></li>
<li class=""><a href="https://docs.google.com/document/d/1FTXbkeUkDAVDI45KWkmQGFHYqPuSQxiX6wW6mYv0FOY/edit" target="_blank" rel="noopener noreferrer" class="">Cryptography design document</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1gpwcnFksFkytiP6G9OORXyd5aWLv51bB/preview" width="100%" height="480" allow="autoplay"></iframe>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="all-templates-at-a-glance">All templates at a glance<a href="https://effectstream.github.io/docs/blog/game-templates#all-templates-at-a-glance" class="hash-link" aria-label="Direct link to All templates at a glance" title="Direct link to All templates at a glance" translate="no">​</a></h2>
<table><thead><tr><th>Template</th><th>Chain</th><th>What's novel</th><th>Live URL</th></tr></thead><tbody><tr><td><strong>Safe Solver</strong></td><td>Arbitrum + Midnight</td><td>Multichain L2, deterministic random per block</td><td><a href="https://safesolver.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">safesolver.midnight.fun</a></td></tr><tr><td><strong>Kachina Kolosseum</strong></td><td>Midnight</td><td>Multiplayer ZK game, browser proofs, Compact commit-reveal (based on the Kachina academic work)</td><td><a href="https://kachina.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">kachina.midnight.fun</a></td></tr><tr><td><strong>Block Kart Legends</strong></td><td>Arbitrum + Midnight</td><td>On-chain TypeScript code, deterministic L2 physics</td><td><a href="https://blockkart.paimastudios.com/" target="_blank" rel="noopener noreferrer" class="">blockkart.paimastudios.com</a></td></tr><tr><td><strong>Dust-2-Dust</strong></td><td>Midnight</td><td>Full roguelike deck builder in Compact, multi-circuit ZK at the limit of the current state of the art</td><td><a href="https://dust2dust.midnight.fun/" target="_blank" rel="noopener noreferrer" class="">dust2dust.midnight.fun</a></td></tr><tr><td><strong>Go Fish</strong></td><td>Midnight</td><td>ZK Mental Poker, foundation for any trustless trading-card game (Magic-style hidden hands, poker, blackjack)</td><td>Local only</td></tr></tbody></table>
<p>Every template is a complete, deployable game you can fork, modify, and ship. The <a href="https://github.com/PaimaStudios/paima-game-templates" target="_blank" rel="noopener noreferrer" class="">game templates repository</a> is the fastest way to start building with EffectStream.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="templates" term="templates"/>
        <category label="games" term="games"/>
        <category label="open-source" term="open-source"/>
        <category label="midnight" term="midnight"/>
        <category label="zk" term="zk"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Cross-Chain Privacy: Midnight + Cardano Zero-Knowledge Integration]]></title>
        <id>https://effectstream.github.io/docs/blog/mina-zk-integration</id>
        <link href="https://effectstream.github.io/docs/blog/mina-zk-integration"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Cardano provides strong settlement guarantees, but all state is public. For applications that need private computation (secret ballots, hidden hands, confidential business logic), that's a real limitation. By integrating Midnight's ZK-native design with Cardano, EffectStream applications can have public settlement on Cardano with private state via Midnight's zero-knowledge proofs.]]></summary>
        <content type="html"><![CDATA[<p>Cardano provides strong settlement guarantees, but all state is public. For applications that need private computation (secret ballots, hidden hands, confidential business logic), that's a real limitation. By integrating Midnight's ZK-native design with Cardano, EffectStream applications can have public settlement on Cardano with private state via Midnight's zero-knowledge proofs.</p>
<div>Loading diagram...</div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-privacy-gap-on-cardano">The privacy gap on Cardano<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#the-privacy-gap-on-cardano" class="hash-link" aria-label="Direct link to The privacy gap on Cardano" title="Direct link to The privacy gap on Cardano" translate="no">​</a></h2>
<p>Cardano's UTxO model is transparent by design: every transaction, every datum, every output is visible on the public ledger. That's great for financial transparency, but it's a problem for applications that need private state:</p>
<ul>
<li class=""><strong>Voting</strong>: ballots should stay private until the vote closes</li>
<li class=""><strong>Games</strong>: hidden hands, fog-of-war, and secret strategies need private state</li>
<li class=""><strong>Business logic</strong>: competitive information shouldn't be visible to rivals</li>
</ul>
<p><a href="https://midnight.network/" target="_blank" rel="noopener noreferrer" class="">Midnight</a> is a privacy-focused blockchain with native zero-knowledge proof support. Combining Cardano's settlement with Midnight's private computation gives applications the best of both worlds.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="private-delegation-voting-template">Private Delegation Voting template<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#private-delegation-voting-template" class="hash-link" aria-label="Direct link to Private Delegation Voting template" title="Direct link to Private Delegation Voting template" translate="no">​</a></h2>
<p>To show this cross-chain privacy pattern in practice, we built a <strong>Private Delegation Voting</strong> template that combines Cardano stake pool delegation data with Midnight's ZK proofs for private ballot casting.</p>
<p>Here's the concept: a governance vote where voting power comes from your Cardano stake pool delegation, but your actual vote stays private. The system proves via zero-knowledge that:</p>
<ol>
<li class="">The voter has a valid Cardano delegation (verified on-chain)</li>
<li class="">The vote was cast correctly (the ballot is well-formed)</li>
<li class="">The voter hasn't voted twice (nullifier prevents double-voting)</li>
</ol>
<p>...without revealing which pool the voter delegates to or how they voted. The delegation data comes from Cardano via EffectStream's PoolDelegation primitive, and the privacy layer runs on Midnight.</p>
<p><img decoding="async" loading="lazy" alt="Private Delegation Voting: Cardano + Midnight cross-chain ZK voting with stake pool delegation" src="https://effectstream.github.io/docs/assets/images/zk-cardano-84dbfd70f1ab3446128a1e04164c991b.png" width="1600" height="1352" class="img_ev3q"></p>
<p>The full <a href="https://github.com/effectstream/effectstream/tree/v-next/templates/zk-cardano" target="_blank" rel="noopener noreferrer" class="">ZK-Cardano template</a> is available in the monorepo alongside five new <a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano primitives</a>.</p>
<iframe src="https://drive.google.com/file/d/1qeFPXN3cjsd66aFn-kgu2q8fqTr8SA-A/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>EffectStream uses a "funnel" abstraction to read data from multiple blockchains through a unified interface. Adding a new chain means implementing a new funnel; the developer's state machine receives events from all configured chains without needing to know each chain's data format.</p>
<p>For the Midnight + Cardano integration:</p>
<ol>
<li class=""><strong>Cardano funnel</strong> reads delegation state via Dolos/UTxORPC (who delegates to which pool, how much stake)</li>
<li class=""><strong>Midnight funnel</strong> reads ZK proof submissions and private state transitions</li>
<li class=""><strong>EffectStream state machine</strong> combines both data sources, verifying delegation eligibility from Cardano while preserving vote privacy from Midnight</li>
</ol>
<p>The batcher system supports Midnight wallets via <a href="https://www.npmjs.com/package/@effectstream/wallets" target="_blank" rel="noopener noreferrer" class=""><code>@effectstream/wallets</code></a>, which provides unified wallet support across EVM, Cardano, Midnight, Polkadot, Algorand, and Mina. Users can sign transactions with Midnight wallets, making it a first-class chain in the framework.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="beyond-voting-the-privacy-pattern">Beyond voting: the privacy pattern<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#beyond-voting-the-privacy-pattern" class="hash-link" aria-label="Direct link to Beyond voting: the privacy pattern" title="Direct link to Beyond voting: the privacy pattern" translate="no">​</a></h2>
<p>Private Delegation Voting is one application of the Cardano + ZK pattern, but the architecture generalizes. Any scenario where public chain data needs private processing fits:</p>
<ul>
<li class=""><strong>Private governance</strong> - vote with your Cardano stake without revealing your position</li>
<li class=""><strong>Hidden-hand games</strong> - use Cardano NFTs as game pieces with hidden state on Midnight</li>
<li class=""><strong>Confidential analytics</strong> - prove facts about on-chain data without revealing the underlying data</li>
<li class=""><strong>Anonymous credentials</strong> - prove you meet criteria (delegation amount, token holdings) without identifying yourself</li>
</ul>
<p>Each follows the same template: read public state from Cardano, process it privately on Midnight, publish only the ZK proof.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-zk-proof-generation">Local ZK proof generation<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#local-zk-proof-generation" class="hash-link" aria-label="Direct link to Local ZK proof generation" title="Direct link to Local ZK proof generation" translate="no">​</a></h2>
<p>Beyond chain-integrated ZK, EffectStream also supports computing zero-knowledge proofs locally, without settling to any ZK chain. This removes the network dependency while keeping the privacy guarantees:</p>
<ul>
<li class="">Lower latency (no network round-trip)</li>
<li class="">Lower cost (no on-chain fees for proof verification)</li>
<li class="">Offline capability (proofs can be generated without network access)</li>
</ul>
<p>The trade-off is in verification: chain-verified proofs inherit the chain's security guarantees, while locally-verified proofs need the application to validate them. For many use cases (anti-cheat in games, client-side validation), local proofs are sufficient and quite a bit faster.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="template-werewolf-multiplayer-zk-with-browser-proofs">Template: Werewolf (multiplayer ZK with browser proofs)<a href="https://effectstream.github.io/docs/blog/mina-zk-integration#template-werewolf-multiplayer-zk-with-browser-proofs" class="hash-link" aria-label="Direct link to Template: Werewolf (multiplayer ZK with browser proofs)" title="Direct link to Template: Werewolf (multiplayer ZK with browser proofs)" translate="no">​</a></h2>
<p>The clearest worked example of local proofs is <strong>Werewolf</strong> — a multiplayer Mafia-style template where every player's role, night action, and vote stays private behind a ZK proof. The Compact contract tracks alive players in a Merkle tree and uses nullifiers to prevent double-voting; each player-side proof is generated <strong>in the browser</strong> before the input reaches the game server.</p>
<p><img decoding="async" loading="lazy" alt="Werewolf game — 3D table with multiple players, ZK roles hidden, Compact contract enforcing inclusion proofs and nullifiers" src="https://effectstream.github.io/docs/assets/images/werewolf-game-655f87f3d7fdcb84162a4e6b9dfb4e1e.png" width="1207" height="819" class="img_ev3q"></p>
<p>The night/day loop is structured around two ZK actions:</p>
<ul>
<li class=""><strong>Night action</strong> — werewolves submit an encrypted target with a ZK proof that they hold the werewolf role. The role itself never appears in any log.</li>
<li class=""><strong>Vote</strong> — any alive player votes to eliminate, attaching a ZK inclusion proof against the alive-player Merkle root plus a nullifier so the same player can't vote twice.</li>
</ul>
<p>The full stack runs locally with no external dependency: Midnight node, indexer, proof server, game server, and frontend all spin up from the template's README. Template source: <a href="https://github.com/effectstream/werewolf-game" target="_blank" rel="noopener noreferrer" class=""><code>effectstream/werewolf-game</code></a>.</p>
<p>A second template, <a href="https://github.com/effectstream/go-fish" target="_blank" rel="noopener noreferrer" class="">Go Fish</a>, uses the same local-proof pattern for ZK Mental Poker — trustless card games without a dealer, with all card commitments and reveals proved client-side.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/werewolf-game" target="_blank" rel="noopener noreferrer" class="">Werewolf template code</a></li>
<li class=""><a href="https://github.com/effectstream/werewolf-game/tree/main/packages/shared/contracts/midnight/contract-werewolf/src" target="_blank" rel="noopener noreferrer" class="">Werewolf Compact contract source</a></li>
<li class=""><a href="https://github.com/effectstream/go-fish" target="_blank" rel="noopener noreferrer" class="">Go Fish template (ZK Mental Poker)</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/zk-cardano" target="_blank" rel="noopener noreferrer" class="">ZK-Cardano template code</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a></li>
<li class=""><a href="https://midnight.network/" target="_blank" rel="noopener noreferrer" class="">Midnight Network</a></li>
<li class=""><a href="https://utxorpc.org/watch/intro/" target="_blank" rel="noopener noreferrer" class="">UTxORPC Watch Module</a> - streaming protocol for Cardano data</li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="midnight" term="midnight"/>
        <category label="zk-proofs" term="zk-proofs"/>
        <category label="privacy" term="privacy"/>
        <category label="cross-chain" term="cross-chain"/>
        <category label="cardano" term="cardano"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building a Cross-Chain NFT Launchpad]]></title>
        <id>https://effectstream.github.io/docs/blog/nft-launchpad</id>
        <link href="https://effectstream.github.io/docs/blog/nft-launchpad"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[NFT launches are complicated. Creators need smart contracts for the sale, a UI for buyers, multi-chain payment support, proof of participation, and fulfillment tracking. We built a full launchpad platform that handles all of this, with EffectStream powering the backend state management and cross-chain payment processing.]]></summary>
        <content type="html"><![CDATA[<p>NFT launches are complicated. Creators need smart contracts for the sale, a UI for buyers, multi-chain payment support, proof of participation, and fulfillment tracking. We built a full launchpad platform that handles all of this, with EffectStream powering the backend state management and cross-chain payment processing.</p>
<p><img decoding="async" loading="lazy" alt="Launchpad UI, Cardano view with ADA prices, packages, and cart checkout" src="https://effectstream.github.io/docs/assets/images/preorder-2-6eda4c6b4c77172b6084fd25c186f314.png" width="1014" height="1209" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pre-order-smart-contract">The pre-order smart contract<a href="https://effectstream.github.io/docs/blog/nft-launchpad#the-pre-order-smart-contract" class="hash-link" aria-label="Direct link to The pre-order smart contract" title="Direct link to The pre-order smart contract" translate="no">​</a></h2>
<p>The foundation is a smart contract that manages the NFT pre-order lifecycle. When a campaign launches, the contract accepts deposits from participants. Each deposit is recorded with the contributor's wallet address, amount, and selected items. The contract enforces campaign rules: deposit limits, time windows, and refund conditions. If a campaign doesn't reach its goal, contributors can reclaim their funds. If it succeeds, the creator fulfills orders and distributes NFTs.</p>
<p>The contract uses Aiken for Cardano-side validation and Solidity for EVM. Transaction metadata is validated on-chain to ensure payment integrity, and merkle trees efficiently prove participation for later NFT claims.</p>
<p>The EVM contract exposes two payment paths — native (ETH) and ERC-20 — and emits a <code>BuyItems</code> event that the EffectStream state machine indexes:</p>
<div class="language-solidity codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-solidity codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">contract</span><span class="token plain"> </span><span class="token class-name">PaimaLaunchpad</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> OwnableUpgradeable</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> UUPSUpgradeable </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">event</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">BuyItems</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">indexed</span><span class="token plain"> receiver</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">indexed</span><span class="token plain"> buyer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">indexed</span><span class="token plain"> paymentToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> referrer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> itemsIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> itemsQuantities</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buyItemsNative</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> receiver</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">payable</span><span class="token plain"> referrer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">calldata</span><span class="token plain"> itemsIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">calldata</span><span class="token plain"> itemsQuantities</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">external</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">payable</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buyItemsErc20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> paymentToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token plain"> paymentAmount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> receiver</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">address</span><span class="token plain"> referrer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">calldata</span><span class="token plain"> itemsIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">uint256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">calldata</span><span class="token plain"> itemsQuantities</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">external</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>A factory contract (<code>PaimaLaunchpadFactory</code>) lets each campaign deploy its own launchpad instance with its own accepted-token list and referrer-reward configuration.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/preorder" target="_blank" rel="noopener noreferrer" class="">Preorder template</a> — full multi-chain stack, runs locally with one command</li>
<li class=""><a href="https://github.com/effectstream/effectstream/blob/v-next/templates/preorder/packages/contracts-evm/src/contracts/PaimaLaunchpad.sol" target="_blank" rel="noopener noreferrer" class="">EVM contract source (<code>PaimaLaunchpad.sol</code>)</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/preorder/packages/contracts-cardano" target="_blank" rel="noopener noreferrer" class="">Cardano contract integration</a></li>
<li class="">(Original repository: <a href="https://github.com/PaimaStudios/paima-preorder" target="_blank" rel="noopener noreferrer" class="">https://github.com/PaimaStudios/paima-preorder</a> — preserved for reference)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-separate-deployments">Why separate deployments<a href="https://effectstream.github.io/docs/blog/nft-launchpad#why-separate-deployments" class="hash-link" aria-label="Direct link to Why separate deployments" title="Direct link to Why separate deployments" translate="no">​</a></h2>
<p>The backend is powered by EffectStream, with each presale campaign running as its own deployment. This isn't just organizational; it's a performance decision.</p>
<p>If all campaigns shared one EffectStream instance, you'd need to resync one very long chain of events for every new campaign. That's especially painful when campaigns monitor multiple chains for payments, since each chain adds sync overhead. Separate deployments keep sync times fast and campaigns independent of each other.</p>
<p>EffectStream's NTP-based sync (instead of block-based) makes this practical. The "main clock" is an NTP server, which is very cheap to poll, rather than a blockchain. The system only fetches blockchain data when relevant events occur, so a lightweight campaign deployment costs almost nothing when idle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-launchpad-ui">The launchpad UI<a href="https://effectstream.github.io/docs/blog/nft-launchpad#the-launchpad-ui" class="hash-link" aria-label="Direct link to The launchpad UI" title="Direct link to The launchpad UI" translate="no">​</a></h2>
<p>The launchpad UI supports both EVM and Cardano wallets. Creators set up sales with configurable parameters (countdown timers, reward tiers, package bundles, item catalogs). Buyers browse campaigns, add items to a cart, and contribute funds from whichever chain they prefer.</p>
<ul>
<li class=""><a href="https://github.com/PaimaStudios/paima-portal/tree/main/src/components/launchpad" target="_blank" rel="noopener noreferrer" class="">Launchpad portal components</a></li>
<li class=""><a href="https://github.com/PaimaStudios/paima-portal" target="_blank" rel="noopener noreferrer" class="">Portal repository</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1MiTyu_Et36zyE1qP7vWYG-2DEaUFyI-s/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-chain-payments">Cross-chain payments<a href="https://effectstream.github.io/docs/blog/nft-launchpad#cross-chain-payments" class="hash-link" aria-label="Direct link to Cross-chain payments" title="Direct link to Cross-chain payments" translate="no">​</a></h2>
<p>The launchpad accepts payments natively on each chain: Cardano users pay in ADA, EVM users pay in ETH or ERC-20 tokens. We originally planned to use bridge-based payments (Milkomeda for Cardano↔EVM), but pivoted to native chain primitives when the bridge infrastructure proved unreliable. Turned out to be simpler and more reliable anyway, with no dependency on third-party bridge availability.</p>
<p>All payments are tracked in the backend database with full transaction details:</p>
<p><img decoding="async" loading="lazy" alt="Database: cardano_payments table showing tx hashes, amounts, and block heights" src="https://effectstream.github.io/docs/assets/images/preorder-3-74b1df3f1247f273080d87ca8e22c44f.png" width="1461" height="481" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Database: launchpad_user_items table mapping wallets to purchased items" src="https://effectstream.github.io/docs/assets/images/preorder-4-f7b2049b607b8506ae5870e36a573eca.png" width="1458" height="624" class="img_ev3q"></p>
<p>The state machine handles two distinct payment paths — EVM events and Cardano UTXO outputs — under a unified schema:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> Stm </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/sm"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> grammar </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"./grammar.ts"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> World </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/coroutine"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> stm </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Stm</span><span class="token class-name operator">&lt;</span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">typeof</span><span class="token class-name"> grammar</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token class-name"> </span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">grammar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// EVM purchase event (from the BuyItems event above)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"buy-items"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> receiver</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> paymentToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> itemsIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> itemsQuantities </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Validate items, supply limits, payment amount...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> validationResult </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">validateItems</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertParticipation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    launchpad</span><span class="token operator">:</span><span class="token plain"> launchpadData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    wallet</span><span class="token operator">:</span><span class="token plain"> receiver</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    payment_token</span><span class="token operator">:</span><span class="token plain"> paymentToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    payment_amount</span><span class="token operator">:</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    item_ids</span><span class="token operator">:</span><span class="token plain"> itemsIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">","</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    item_quantities</span><span class="token operator">:</span><span class="token plain"> itemsQuantities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">","</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    chain</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"evm"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    participation_valid</span><span class="token operator">:</span><span class="token plain"> validationResult </span><span class="token operator">!==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Cardano payment — read UTXO outputs + transaction metadata (label 42)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-payment"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> txId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> outputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> metadata </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Metadata format: { "42": [{ k: "p", v: "preorder" },</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">//                          { k: "w", v: walletBech32 },</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">//                          { k: "i", v: [[itemId, qty], ...] }] }</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> metaItems </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">parseMetadataItems</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> output </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">of</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">outputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> matchingLaunchpad </span><span class="token operator">=</span><span class="token plain"> launchpadsData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">find</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cardanoPaymentAddressHex </span><span class="token operator">===</span><span class="token plain"> output</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">matchingLaunchpad</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertCardanoPayment</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      tx_hash</span><span class="token operator">:</span><span class="token plain"> txId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      amount</span><span class="token operator">:</span><span class="token plain"> output</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">coin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      payment_address</span><span class="token operator">:</span><span class="token plain"> matchingLaunchpad</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cardanoPaymentAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      block_height</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// ... item validation and participation upsert</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The two handlers write to the same <code>participations</code> and <code>launchpad_user_items</code> tables, so a downstream consumer (the portal UI, an off-chain fulfillment service) sees a uniform view regardless of which chain the buyer paid on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-the-template-locally">Running the template locally<a href="https://effectstream.github.io/docs/blog/nft-launchpad#running-the-template-locally" class="hash-link" aria-label="Direct link to Running the template locally" title="Direct link to Running the template locally" translate="no">​</a></h2>
<p>The Preorder template ships as a one-command stack: PGLite, Hardhat, YACI DevKit, Dolos, the sync node, and the frontend all come up together under the EffectStream orchestrator.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/effectstream/effectstream.git</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> effectstream/templates/preorder</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun run dev</span><br></div></code></pre></div></div>
<p>The orchestrator starts each component on a dedicated port:</p>
<table><thead><tr><th>Component</th><th>Tool</th><th>Port</th></tr></thead><tbody><tr><td>Database</td><td>PGLite</td><td>5432</td></tr><tr><td>EVM Chain</td><td>Hardhat</td><td>8545</td></tr><tr><td>Cardano Node</td><td>YACI DevKit</td><td>10000 (admin), 3001 (node)</td></tr><tr><td>Cardano Indexer</td><td>Dolos</td><td>50051 (gRPC), 3000 (Blockfrost)</td></tr><tr><td>Sync Node API</td><td>EffectStream</td><td>9999</td></tr><tr><td>Frontend</td><td>Vite + Fastify</td><td>10599</td></tr></tbody></table>
<p>Open <a href="http://localhost:10599/" target="_blank" rel="noopener noreferrer" class="">http://localhost:10599</a> and the full launchpad is live against the local devnets — you can mint test ERC-20s and ADA, buy items with each, and watch the participations land in PostgreSQL in real time.</p>
<p>The same <code>start.dev.ts</code> config also drives the E2E test suite (<code>bun run test</code>), which covers infrastructure boot, native ETH purchase, ERC-20 purchase, validation (supply limits, underpayment), Cardano ADA payment, the REST API, and the frontend build.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="offline-by-design">Offline by design<a href="https://effectstream.github.io/docs/blog/nft-launchpad#offline-by-design" class="hash-link" aria-label="Direct link to Offline by design" title="Direct link to Offline by design" translate="no">​</a></h2>
<p>The launchpad isn't deployed as a single shared site, and that's a deliberate decision. Each presale campaign is its own EffectStream deployment: separate database, separate sync node, separate frontend. Three reasons:</p>
<ol>
<li class=""><strong>Sync cost</strong> — if every campaign shared one deployment, each new launch would pay the sync cost of every chain ever monitored. Independent deployments keep sync times bounded per campaign.</li>
<li class=""><strong>Operator independence</strong> — a creator can take their campaign down without affecting anyone else's; an operator can hard-fork the template to add custom validation, accept exotic payment tokens, or wire in their own KYC.</li>
<li class=""><strong>Devnet-first development</strong> — the YACI DevKit + Hardhat stack runs entirely on the developer's machine. You can iterate on contract logic, test edge cases (underpayment, supply exhaustion, refund flows), and validate the full multi-chain pipeline before touching mainnet. The same template config and tests carry over to testnet and mainnet deployments unchanged.</li>
</ol>
<p>The trade-off is that there's no single hosted demo to click through. Instead, anyone can clone the template and have a full multi-chain launchpad running on their machine in under five minutes — and that local stack is the same stack a real campaign would deploy to production.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-gets-you">What this gets you<a href="https://effectstream.github.io/docs/blog/nft-launchpad#what-this-gets-you" class="hash-link" aria-label="Direct link to What this gets you" title="Direct link to What this gets you" translate="no">​</a></h2>
<p>Creators get a turnkey platform for multi-chain NFT sales: deploy a campaign, configure tiers and pricing, accept payments from both Cardano and EVM wallets through a single interface. Buyers get a familiar e-commerce experience no matter which chain they're on.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="nfts" term="nfts"/>
        <category label="launchpad" term="launchpad"/>
        <category label="presale" term="presale"/>
        <category label="smart-contracts" term="smart-contracts"/>
        <category label="cardano" term="cardano"/>
        <category label="evm" term="evm"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Projected NFTs: Using Cardano NFTs in Smart Contract Applications]]></title>
        <id>https://effectstream.github.io/docs/blog/projected-nfts</id>
        <link href="https://effectstream.github.io/docs/blog/projected-nfts"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Users want to use their NFTs in applications (games, DeFi, social platforms) without giving up ownership. Projected NFTs solve this: lock an NFT on Cardano L1 and use a representation in your application. The NFT never leaves your wallet in any meaningful sense; it's locked in a smart contract you control, and you can unlock it at any time.]]></summary>
        <content type="html"><![CDATA[<p>Users want to use their NFTs in applications (games, DeFi, social platforms) without giving up ownership. Projected NFTs solve this: lock an NFT on Cardano L1 and use a representation in your application. The NFT never leaves your wallet in any meaningful sense; it's locked in a smart contract you control, and you can unlock it at any time.</p>
<p><img decoding="async" loading="lazy" alt="Hololocker dApp showing Mint, Lock, Unlock, and Withdrawal Request lifecycle" src="https://effectstream.github.io/docs/assets/images/holodeck-0da788076f043028f3dc64672f5f7533.png" width="927" height="694" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-aiken-smart-contract">The Aiken smart contract<a href="https://effectstream.github.io/docs/blog/projected-nfts#the-aiken-smart-contract" class="hash-link" aria-label="Direct link to The Aiken smart contract" title="Direct link to The Aiken smart contract" translate="no">​</a></h2>
<p>The Projected NFT smart contract is written in <a href="https://aiken-lang.org/" target="_blank" rel="noopener noreferrer" class="">Aiken</a>, Cardano's Rust-based smart contract language. It manages the full projection lifecycle:</p>
<ol>
<li class=""><strong>Lock</strong> - the user sends their NFT to the contract, which records the owner and NFT details in the datum</li>
<li class=""><strong>Use</strong> - the application reads the locked state and grants in-app utility (powers, access, cosmetics)</li>
<li class=""><strong>Unlock</strong> - the user initiates a withdrawal request</li>
<li class=""><strong>Claim</strong> - after a cooldown period, the user claims the NFT back to their wallet</li>
</ol>
<p>The cooldown on withdrawal prevents flash-loan-style exploits where a user could project, gain benefits, and immediately unproject in the same transaction. A CI pipeline validates that the contract compiles on every change.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-hololocker-dapp">The Hololocker dApp<a href="https://effectstream.github.io/docs/blog/projected-nfts#the-hololocker-dapp" class="hash-link" aria-label="Direct link to The Hololocker dApp" title="Direct link to The Hololocker dApp" translate="no">​</a></h2>
<p>We built a full dApp called Hololocker for projecting and managing NFTs. It supports the complete lifecycle: mint new NFTs, lock them into the projection contract, view locked state, initiate unlock, and complete withdrawal.</p>
<ul>
<li class=""><a href="https://github.com/dcSpark/projected-nft-whirlpool" target="_blank" rel="noopener noreferrer" class="">Source code</a></li>
</ul>
<iframe src="https://drive.google.com/file/d/1TbBngP-OKCX38dDaVQGoXnd_HncEW-jw/preview" width="100%" height="480" allow="autoplay"></iframe>
<iframe src="https://drive.google.com/file/d/12HzmHV7HI8msoc1yvI6zRqsZL69TSBeA/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="indexing-with-utxorpc-and-dolos">Indexing with UTxORPC and Dolos<a href="https://effectstream.github.io/docs/blog/projected-nfts#indexing-with-utxorpc-and-dolos" class="hash-link" aria-label="Direct link to Indexing with UTxORPC and Dolos" title="Direct link to Indexing with UTxORPC and Dolos" translate="no">​</a></h2>
<p>To track NFT lock/unlock events in real-time, we originally built a <a href="https://dcspark.github.io/carp/" target="_blank" rel="noopener noreferrer" class="">Carp</a> indexer task. But Carp requires heavy synchronization (days on testnet, even longer on mainnet), which made rapid development impractical.</p>
<p>We've since moved to <a href="https://github.com/txpipe/dolos" target="_blank" rel="noopener noreferrer" class="">Dolos</a> via the UTxORPC protocol. The new <strong>ProjectedNFT primitive</strong> in EffectStream reads projection events directly from Dolos's gRPC stream, parsing the datum to extract lock status, owner, policy ID, and asset name. It is one of five new <a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano primitives</a> backed by an IVM (Indexed View Materializer) with PostgreSQL materialized views.</p>
<p>The ProjectedNFT primitive tracks:</p>
<ul>
<li class="">Lock events (NFT enters the projection contract)</li>
<li class="">Unlock requests (user initiates withdrawal)</li>
<li class="">Claim completions (NFT returns to user's wallet)</li>
<li class="">Current projection status per NFT (datum parsing for owner, policy, asset)</li>
</ul>
<p>All state changes are streamed in real-time through UTxORPC's <a href="https://utxorpc.org/watch/intro/" target="_blank" rel="noopener noreferrer" class="">Watch module</a>, which supports filtering by address, delegation part, and asset policy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="framework-integration">Framework integration<a href="https://effectstream.github.io/docs/blog/projected-nfts#framework-integration" class="hash-link" aria-label="Direct link to Framework integration" title="Direct link to Framework integration" translate="no">​</a></h2>
<p>The ProjectedNFT primitive connects to EffectStream's funnel system, the abstraction layer that reads data from multiple blockchains. When an NFT is projected or unprojected, the event flows through the funnel into the application's state machine. Developers react to it in their game logic like any other event.</p>
<p>A game could grant in-game powers when a player projects a specific NFT, remove them when it's unprojected, or display a player's projected collection as their in-game inventory.</p>
<p>The integration is end-to-end tested with 21 E2E tests covering the full lifecycle: stake registration, delegation, lock, unlock, and claim, all verified against a local Dolos instance.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src" target="_blank" rel="noopener noreferrer" class="">ProjectedNFT primitive source code</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-stack">The full stack<a href="https://effectstream.github.io/docs/blog/projected-nfts#the-full-stack" class="hash-link" aria-label="Direct link to The full stack" title="Direct link to The full stack" translate="no">​</a></h2>
<p>Five layers work together to make Projected NFTs possible:</p>
<table><thead><tr><th>Layer</th><th>Technology</th><th>Role</th></tr></thead><tbody><tr><td>Smart Contract</td><td>Aiken</td><td>Lock/unlock logic on Cardano L1</td></tr><tr><td>Indexer</td><td>Dolos + UTxORPC</td><td>Real-time event streaming via gRPC</td></tr><tr><td>Primitive</td><td>EffectStream IVM</td><td>State materialization in PostgreSQL</td></tr><tr><td>Framework</td><td>EffectStream funnel</td><td>Event delivery to application state machine</td></tr><tr><td>dApp</td><td>Hololocker</td><td>User interface for projection lifecycle</td></tr></tbody></table>
<p>This lets games use Cardano NFTs for in-game utility without transferring ownership, DeFi protocols collateralize without moving assets, and social platforms gate features based on NFT holdings. The NFT stays under the user's control the whole time.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="nfts" term="nfts"/>
        <category label="aiken" term="aiken"/>
        <category label="smart-contracts" term="smart-contracts"/>
        <category label="dolos" term="dolos"/>
        <category label="utxorpc" term="utxorpc"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Stake Pool Delegation Part 3: Building a Pool-Aware Batcher]]></title>
        <id>https://effectstream.github.io/docs/blog/stakepool-delegation-batcher</id>
        <link href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In Part 1 we covered how EffectStream indexes delegation changes. In Part 2 we walked through the state machine that writes delegation events to PostgreSQL. Now we'll build on that foundation: a custom Batcher that reads delegation state from the database and decides whether to accept or reject user transactions based on which pool they're delegating to.]]></summary>
        <content type="html"><![CDATA[<p>In <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Part 1</a> we covered how EffectStream indexes delegation changes. In <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical">Part 2</a> we walked through the state machine that writes delegation events to PostgreSQL. Now we'll build on that foundation: a custom Batcher that reads delegation state from the database and decides whether to accept or reject user transactions based on which pool they're delegating to.</p>
<p>This is the key use case for stake pool operators: <strong>free transactions for your delegators</strong>. The SPO runs a batcher that covers gas fees, but only for users delegating to their pool.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-batcher-does">What the Batcher does<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#what-the-batcher-does" class="hash-link" aria-label="Direct link to What the Batcher does" title="Direct link to What the Batcher does" translate="no">​</a></h2>
<p>The EffectStream Batcher is a standalone service that:</p>
<ol>
<li class="">Receives user inputs via HTTP (<code>POST /send-input</code>)</li>
<li class="">Validates each input using a chain-specific adapter</li>
<li class="">Queues valid inputs in persistent storage</li>
<li class="">Batches them into a single on-chain transaction when criteria are met</li>
<li class="">Submits the batch and waits for confirmation</li>
</ol>
<p>The adapter is the extension point. By implementing a custom <code>BlockchainAdapter</code>, you control validation, serialization, and submission. For the delegation use case, we add a <code>validateInput()</code> method that queries the <code>delegations</code> table before accepting the input.</p>
<p>See the full <a class="" href="https://effectstream.github.io/docs/home/components/batcher/overview">Batcher documentation</a> for the complete API.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<div>Loading diagram...</div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-the-delegation-aware-adapter">1. The delegation-aware adapter<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#1-the-delegation-aware-adapter" class="hash-link" aria-label="Direct link to 1. The delegation-aware adapter" title="Direct link to 1. The delegation-aware adapter" translate="no">​</a></h2>
<p>The core idea: extend the default EVM adapter with a <code>validateInput()</code> method that checks the user's delegation status before accepting their input. The batcher reads from the same <code>delegations</code> table that the sync node writes to (from <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical">Part 2</a>).</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">BlockchainAdapter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">DefaultBatcherInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">ValidationResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  EffectStreamL2DefaultAdapter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/batcher"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> getDelegationsByAddress </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@cardano-delegation/database"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> Pool </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pg"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">DelegationAwareBatcherAdapter</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">extends</span><span class="token plain"> </span><span class="token class-name">EffectStreamL2DefaultAdapter</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">readonly</span><span class="token plain"> dbConn</span><span class="token operator">:</span><span class="token plain"> Pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">readonly</span><span class="token plain"> enabledPools</span><span class="token operator">:</span><span class="token plain"> Set</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">constructor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    contractAddress</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    privateKey</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    fee</span><span class="token operator">:</span><span class="token plain"> bigint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    syncProtocolName</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    dbConn</span><span class="token operator">:</span><span class="token plain"> Pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    enabledPools</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">super</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">contractAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> privateKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fee</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> syncProtocolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dbConn </span><span class="token operator">=</span><span class="token plain"> dbConn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">enabledPools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      enabledPools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toLowerCase</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">validateInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    input</span><span class="token operator">:</span><span class="token plain"> DefaultBatcherInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">ValidationResult</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// Look up the user's current delegation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> delegations </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> getDelegationsByAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token operator">:</span><span class="token plain"> input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">address </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dbConn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">length </span><span class="token operator">===</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        valid</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        error</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"No delegation found for this address"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// Check if the most recent delegation is to an enabled pool</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> latestDelegation </span><span class="token operator">=</span><span class="token plain"> delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">enabledPools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">has</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">latestDelegation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toLowerCase</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        valid</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        error</span><span class="token operator">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Not delegating to an enabled pool. </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Current pool: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">latestDelegation</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">pool</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> valid</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The <code>getDelegationsByAddress</code> query is the same pgtyped query from Part 2 — it returns delegations ordered by <code>id DESC</code>, so <code>delegations[0]</code> is the most recent one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-configuring-the-batcher">2. Configuring the batcher<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#2-configuring-the-batcher" class="hash-link" aria-label="Direct link to 2. Configuring the batcher" title="Direct link to 2. Configuring the batcher" translate="no">​</a></h2>
<p>Wire the adapter into a batcher instance with the pool hashes you want to subsidize:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> main</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> suspend </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"effection"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  createNewBatcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  FileStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">BatcherConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/batcher"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> getConnection </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/db"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> DelegationAwareBatcherAdapter </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"./delegation-adapter.ts"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">ENABLED_POOLS</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"7301761068762f5900bde9eb7c1c15b09840285130f5b0f53606cc57"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"82ec502f8c0a51e7c0db410e6722dd42df3b8e11f48e833f9fdf2941"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> dbConn </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getConnection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> adapter </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">DelegationAwareBatcherAdapter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">L2_CONTRACT_ADDRESS</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">BATCHER_PRIVATE_KEY</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token number">0n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">// Free for delegators</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"mainEvmRPC"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  dbConn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token constant" style="color:rgb(189, 147, 249)">ENABLED_POOLS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> config</span><span class="token operator">:</span><span class="token plain"> BatcherConfig </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  pollingIntervalMs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  adapters</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> evm</span><span class="token operator">:</span><span class="token plain"> adapter </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  defaultTarget</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"evm"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  batchingCriteria</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    evm</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hybrid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      timeWindowMs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// Batch every 5 seconds</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      maxBatchSize</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">50</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// Or when 50 inputs queue up</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  confirmationLevel</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"wait-receipt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  enableHttpServer</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  port</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3334</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  enableEventSystem</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> batcher </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">createNewBatcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">FileStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"./batcher-data"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">main</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"startup"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> publicConfig </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Pool-aware batcher started on port </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">publicConfig</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">port</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Enabled pools: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation constant" style="color:rgb(189, 147, 249)">ENABLED_POOLS</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">length</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"batch:submit"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> txHash </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Batch submitted: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">txHash</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">runBatcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">suspend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The <code>fee: 0n</code> means the SPO covers the cost. Users delegating to the enabled pools get free transactions. Users delegating elsewhere get rejected at the <code>validateInput()</code> step before their input even enters the queue.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-the-validation-flow">3. The validation flow<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#3-the-validation-flow" class="hash-link" aria-label="Direct link to 3. The validation flow" title="Direct link to 3. The validation flow" translate="no">​</a></h2>
<p>When a user submits an input, the batcher pipeline processes it in order:</p>
<ol>
<li class=""><strong>Targeting</strong> — route to the correct adapter based on <code>input.target</code></li>
<li class=""><strong>Signature verification</strong> — verify the user's wallet signature (inherited from <code>EffectStreamL2DefaultAdapter</code>)</li>
<li class=""><strong>Input validation</strong> — our custom <code>validateInput()</code> runs:<!-- -->
<ul>
<li class="">Query <code>delegations</code> table by <code>input.address</code></li>
<li class="">Check if the latest delegation pool is in <code>enabledPools</code></li>
<li class="">Return <code>{ valid: true }</code> or <code>{ valid: false, error: "..." }</code></li>
</ul>
</li>
<li class=""><strong>Storage</strong> — if valid, the input is persisted to disk</li>
<li class=""><strong>Batching</strong> — when criteria are met, inputs are serialized and submitted</li>
</ol>
<p>A rejected input returns immediately with an error:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-X</span><span class="token plain"> POST http://localhost:3334/send-input </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "data": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      "address": "0x742d35Cc...",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      "addressType": 0,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      "input": "gameMove|x10y20",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      "signature": "0x...",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      "timestamp": "1234567890"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># If not delegating to an enabled pool:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># { "success": false, "error": "Not delegating to an enabled pool. Current pool: abc123..." }</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># If delegating to an enabled pool:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># { "success": true, "hash": "0x..." }</span><br></div></code></pre></div></div>
<p>The walkthrough below shows the full flow end-to-end: the batcher is started with two enabled pools, a delegator of one of those pools submits a transaction and is accepted, and a non-delegator submits the same transaction and is rejected — both responses visible at the HTTP layer.</p>
<iframe src="https://drive.google.com/file/d/1EI3hvbpCP8QzSTyyzcr2pHFQHeH9D2A_/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-batching-criteria-options">4. Batching criteria options<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#4-batching-criteria-options" class="hash-link" aria-label="Direct link to 4. Batching criteria options" title="Direct link to 4. Batching criteria options" translate="no">​</a></h2>
<p>The batcher supports multiple strategies for when to submit batches. Choose based on your game's requirements:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// Time-based: submit every N milliseconds</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"time"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeWindowMs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5000</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Size-based: submit when N inputs are queued</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"size"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> maxBatchSize</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">50</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Hybrid: whichever comes first</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hybrid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeWindowMs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> maxBatchSize</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">50</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Value-based: submit when accumulated value reaches threshold</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"value"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">valueAccumulatorFn</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">parseFloat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"|"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  targetValue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Custom: any logic you want</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  criteriaType</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"custom"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">isBatchReadyFn</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">inputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> lastProcessTime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// Process immediately if any high-priority input exists</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">some</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">startsWith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"urgent|"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-adding-event-monitoring">5. Adding event monitoring<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#5-adding-event-monitoring" class="hash-link" aria-label="Direct link to 5. Adding event monitoring" title="Direct link to 5. Adding event monitoring" translate="no">​</a></h2>
<p>The batcher emits lifecycle events you can use for monitoring and observability:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"startup"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> publicConfig </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Batcher ready on port </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">publicConfig</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">port</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"batch:submit"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> txHash </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Batch submitted: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">txHash</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"batch:confirmed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> receipt </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Confirmed in block </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">receipt</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">blockNumber</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">batcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"input:rejected"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> error </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">warn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">Input rejected: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">error</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-spos-can-build-with-this">What SPOs can build with this<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#what-spos-can-build-with-this" class="hash-link" aria-label="Direct link to What SPOs can build with this" title="Direct link to What SPOs can build with this" translate="no">​</a></h2>
<p>With the delegation-aware batcher, stake pool operators can offer tangible benefits to their delegators:</p>
<ul>
<li class=""><strong>Free game transactions</strong> — delegators play without paying gas fees, the SPO covers the cost</li>
<li class=""><strong>Priority batching</strong> — delegators of partner pools get their inputs processed first</li>
<li class=""><strong>Tiered access</strong> — different pools unlock different game features or areas</li>
<li class=""><strong>Dynamic pricing</strong> — charge non-delegators, subsidize delegators</li>
</ul>
<p>The configuration is straightforward: add your pool hash to <code>ENABLED_POOLS</code>, fund the batcher wallet, and point your game's frontend at the batcher endpoint. The delegation check happens automatically on every input.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-the-full-stack">Running the full stack<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher#running-the-full-stack" class="hash-link" aria-label="Direct link to Running the full stack" title="Direct link to Running the full stack" translate="no">​</a></h2>
<p>The complete delegation + batcher stack runs as two pieces: the cardano-delegation template (sync node + indexing) and the pool-aware batcher adapter from <a href="https://github.com/effectstream/effectstream/pull/691" target="_blank" rel="noopener noreferrer" class="">PR #691</a>. Both share the same PostgreSQL database — the sync node writes delegation events, the batcher reads them during validation.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Terminal 1: Start the delegation sync node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/effectstream/effectstream.git</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> effectstream/templates/cardano-delegation</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun run dev</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Sync node indexes delegations → http://localhost:10599</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Terminal 2: Start the pool-aware batcher</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Code lives in PR #691: https://github.com/effectstream/effectstream/pull/691</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Check out that PR branch and run its batcher entrypoint.</span><br></div></code></pre></div></div>
<hr>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/pull/691" target="_blank" rel="noopener noreferrer" class="">Pool-aware batcher code example (PR #691)</a> — the <code>DelegationAwareBatcherAdapter</code> source</li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/components/batcher/overview">Batcher documentation</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/components/batcher/adapter">Custom Adapters guide</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Part 1: Connecting Cardano SPOs</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical">Part 2: Building the State Machine</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/cardano-delegation" target="_blank" rel="noopener noreferrer" class="">Cardano delegation template source code</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="stakepools" term="stakepools"/>
        <category label="delegation" term="delegation"/>
        <category label="batcher" term="batcher"/>
        <category label="technical" term="technical"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Stake Pool Delegation Part 4: Stake Pools as Chain Validators]]></title>
        <id>https://effectstream.github.io/docs/blog/stakepool-delegation-midnight</id>
        <link href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In Part 1 through Part 3 we built a stack that observes Cardano delegation — index the certificates, persist them, gate user inputs on pool membership. The application reads delegation; delegation does not read the application. There is a more ambitious place to put stake pools, though: at the consensus layer of a chain itself. That is where Midnight lives. Once you go there, "react to delegation" stops being a feature flag inside a game and starts being block production for an entire ecosystem of games.]]></summary>
        <content type="html"><![CDATA[<p>In <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Part 1</a> through <a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher">Part 3</a> we built a stack that <strong>observes</strong> Cardano delegation — index the certificates, persist them, gate user inputs on pool membership. The application reads delegation; delegation does not read the application. There is a more ambitious place to put stake pools, though: at the consensus layer of a chain itself. That is where Midnight lives. Once you go there, "react to delegation" stops being a feature flag inside a game and starts being block production for an entire ecosystem of games.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-event-consumer-to-consensus-input">From event consumer to consensus input<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#from-event-consumer-to-consensus-input" class="hash-link" aria-label="Direct link to From event consumer to consensus input" title="Direct link to From event consumer to consensus input" translate="no">​</a></h2>
<p>The progression across this series goes like this:</p>
<table><thead><tr><th>Part</th><th>Role of delegation</th><th>Surface area</th></tr></thead><tbody><tr><td>1</td><td>Indexed event</td><td>A PostgreSQL row</td></tr><tr><td>2</td><td>State machine input</td><td>An <code>addStateTransition</code> handler</td></tr><tr><td>3</td><td>Batcher validation</td><td>An accept/reject decision per input</td></tr><tr><td>4</td><td><strong>Consensus weight</strong></td><td><strong>Block production share on Midnight</strong></td></tr></tbody></table>
<p>By Part 3 a stake pool delegation could already decide whether your transaction gets free batching. Part 4 raises the stakes: delegating to a registered SPO contributes to that pool's share of blocks produced on Midnight. The same delegation certificate now does two things at once — it routes ADA rewards on Cardano, and it weights consensus participation on a partner chain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-partner-chain-is">What a Partner Chain is<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#what-a-partner-chain-is" class="hash-link" aria-label="Direct link to What a Partner Chain is" title="Direct link to What a Partner Chain is" translate="no">​</a></h2>
<p>Midnight is a <a href="https://github.com/input-output-hk/partner-chains" target="_blank" rel="noopener noreferrer" class="">Partner Chain</a>. Partner Chains are blockchains that run in parallel to Cardano and inherit Cardano's security model by reusing its set of Stake Pool Operators as block producers. The Partner Chains Toolkit — built by IOG, the same engineering team behind Cardano itself — provides the off-chain CLI, on-chain Plutus scripts, and Substrate runtime pallets that turn a generic Substrate chain into a Cardano-anchored partner chain.</p>
<p>The toolkit pulls three things from Cardano:</p>
<ol>
<li class=""><strong>SPO registrations</strong> — a Cardano transaction in which an SPO signs a message declaring they want to produce blocks on the partner chain.</li>
<li class=""><strong>Delegation stake</strong> — read off Cardano's ledger via <code>db-sync</code>, used to weight how many blocks a registered SPO produces.</li>
<li class=""><strong>Governance parameters</strong> — the <em>D-Parameter</em> and the permissioned validator whitelist, written to a Cardano script address and observed by the partner chain runtime.</li>
</ol>
<div>Loading diagram...</div>
<p>The arrows are not metaphorical. Every Midnight epoch, the runtime re-reads Cardano state — registrations from the <code>CommitteeCandidateValidator</code> address, the D-Parameter from <code>DParameterValidator</code>, the permissioned candidates list — and feeds them into <a href="https://github.com/input-output-hk/partner-chains" target="_blank" rel="noopener noreferrer" class="">Ariadne</a>, the committee selection algorithm. Ariadne outputs the validator set for the next epoch. That set is what Aura (Substrate's slot-based consensus) uses to produce Midnight blocks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-cardano-spos-become-midnight-validators">How Cardano SPOs become Midnight validators<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#how-cardano-spos-become-midnight-validators" class="hash-link" aria-label="Direct link to How Cardano SPOs become Midnight validators" title="Direct link to How Cardano SPOs become Midnight validators" translate="no">​</a></h2>
<p>The registration flow is intentionally permissionless. An SPO who wants to produce Midnight blocks runs three CLI commands from the Partner Chains Toolkit. The exact commands live in the toolkit documentation; the shape of the flow is:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 1. Generate the registration signatures on an offline machine</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#    (cold keys never touch the internet)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pc-node registration-signatures </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --genesis-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GENESIS_UTXO</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --mainchain-signing-key cold.skey </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --sidechain-signing-key partner-chain.skey </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --registration-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$REGISTRATION_UTXO</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 2. Submit the registration as a Cardano transaction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pc-node smart-contracts register </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --genesis-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GENESIS_UTXO</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --registration-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$REGISTRATION_UTXO</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --payment-key-file payment.skey </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --partner-chain-public-keys </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$PC_PUB</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$AURA_PUB</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GRANDPA_PUB</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --partner-chain-signature </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$PC_SIG</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --spo-public-key </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$SPO_PUB</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --spo-signature </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$SPO_SIG</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 3. Confirm activation — registrations made in epoch N</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#    become active in epoch N+2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pc-node registration-status </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --stake-pool-pub-key </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$SPO_PUB</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --mc-epoch-number </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$EPOCH</span><br></div></code></pre></div></div>
<p>What lands on Cardano after step 2 is a UTXO at the <code>CommitteeCandidateValidator</code> script address, with a datum that carries the SPO's main-chain public key, their partner-chain public key, their Aura and Grandpa session keys, and the cross-signature proving the same operator controls both sides. Midnight's runtime reads that datum every epoch and treats it as a vote: "I, this SPO, with my Cardano delegators behind me, want to produce blocks on this chain."</p>
<p>The crucial property is that <strong>the stake weight comes from Cardano's ledger, not from a token on Midnight</strong>. There is no separate "Midnight validator stake" to bond. The delegators who already delegated their ADA to that SPO on Cardano are — without any extra action — backing the SPO's block production on Midnight.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-d-parameter-governance-over-the-registeredpermissioned-ratio">The D-Parameter: governance over the registered/permissioned ratio<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#the-d-parameter-governance-over-the-registeredpermissioned-ratio" class="hash-link" aria-label="Direct link to The D-Parameter: governance over the registered/permissioned ratio" title="Direct link to The D-Parameter: governance over the registered/permissioned ratio" translate="no">​</a></h2>
<p>Block producers on a partner chain come from two pools:</p>
<ul>
<li class=""><strong>Registered validators</strong> — Cardano SPOs who completed the registration flow above.</li>
<li class=""><strong>Permissioned validators</strong> — a whitelist maintained by the chain's governance authority, intended to bootstrap the network when SPO registrations are still rolling in.</li>
</ul>
<p>The split between them is governed by a single on-chain value, the <strong>D-Parameter</strong>, written to Cardano via:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pc-node smart-contracts upsert-d-parameter </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --permissioned-candidates-count </span><span class="token number">3</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --registered-candidates-count </span><span class="token number">7</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --payment-key-file governance.skey </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --genesis-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GENESIS_UTXO</span><br></div></code></pre></div></div>
<p>This particular setting reserves 3 of the 10 committee seats per epoch for permissioned validators and lets the remaining 7 be filled by registered SPOs proportional to delegation stake. The two-epoch delay before changes take effect gives the network a stable window to observe new parameters before they apply.</p>
<p>A chain typically launches near <code>permissioned=N, registered=0</code> and ramps toward <code>permissioned=0, registered=N</code> as SPO registrations stabilise. This is the same decentralisation curve Cardano itself walked from Byron through Shelley, but governed transparently on-chain instead of by a hard fork.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="querying-partner-chain-state-from-effectstream">Querying partner chain state from EffectStream<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#querying-partner-chain-state-from-effectstream" class="hash-link" aria-label="Direct link to Querying partner chain state from EffectStream" title="Direct link to Querying partner chain state from EffectStream" translate="no">​</a></h2>
<p>Everything above is observable, which means application code can read it. The Partner Chains Toolkit exposes the relevant state via CLI commands and JSON-RPC. For an EffectStream app running on top of (or alongside) a partner chain, the two queries that matter for delegation-aware logic are:</p>
<ul>
<li class=""><strong><code>ariadne-parameters --mc-epoch-number N</code></strong> — returns the registered candidates, the permissioned candidates, the D-Parameter, and the delegation stakes effective at epoch <em>N</em>. Use this to find out which SPOs are actually producing blocks right now.</li>
<li class=""><strong><code>registration-status --stake-pool-pub-key K --mc-epoch-number N</code></strong> — returns whether SPO <code>K</code> is registered and active at epoch <em>N</em>. Use this to gate features on whether a specific pool has joined the partner chain.</li>
</ul>
<p>Wired into a state machine transition, these become first-class inputs alongside the <code>delegations</code> table from Part 2:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> World </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/runtime"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  getDelegationsByAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  getActivePartnerChainSPOs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@cardano-delegation/database"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  fetchAriadneParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">AriadneParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/partner-chains"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"delegation-bonus"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. Cardano side: where is this address currently delegating?</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> delegations </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    getDelegationsByAddress</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">length </span><span class="token operator">===</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> userPool </span><span class="token operator">=</span><span class="token plain"> delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toLowerCase</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. Partner-chain side: which SPOs are in the active committee</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">//    for the current Cardano epoch?</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> epoch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">slotToEpoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> params</span><span class="token operator">:</span><span class="token plain"> AriadneParameters </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fetchAriadneParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    mcEpochNumber</span><span class="token operator">:</span><span class="token plain"> epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> activeProducers </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    params</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">registeredCandidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stakePoolPubKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toLowerCase</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. Game-side: if the user's delegated SPO is actively producing</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">//    blocks on the partner chain this epoch, grant a bonus.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">activeProducers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">has</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">userPool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">grantBonus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      pool</span><span class="token operator">:</span><span class="token plain"> userPool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      multiplier</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      reason</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"active-validator-delegator"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>What this handler captures is the full loop:</p>
<ol>
<li class="">The user delegated ADA to an SPO on Cardano (Part 1 indexed it).</li>
<li class="">The state machine wrote that delegation to PostgreSQL (Part 2).</li>
<li class="">The SPO registered on the partner chain and is now in the block-production committee.</li>
<li class="">The application reads both sides and rewards the user proportional to consensus participation, not just delegation existence.</li>
</ol>
<p>A user who switches to a non-registered pool keeps earning Cardano staking rewards but loses the application-side bonus. A user who switches to a pool that just got into the committee for next epoch picks it up automatically two epochs later — the same cadence Cardano itself uses for stake-distribution snapshots.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-chain-identity-associating-cardano-and-partner-chain-addresses">Cross-chain identity: associating Cardano and partner-chain addresses<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#cross-chain-identity-associating-cardano-and-partner-chain-addresses" class="hash-link" aria-label="Direct link to Cross-chain identity: associating Cardano and partner-chain addresses" title="Direct link to Cross-chain identity: associating Cardano and partner-chain addresses" translate="no">​</a></h2>
<p>One more piece of the Partner Chains Toolkit is worth pointing at because it makes everything above clean to use: address association. A user with a Cardano stake address and a partner-chain address can sign a message linking the two, then submit the signature to the partner-chain ledger:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pc-node sign-address-association </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --genesis-utxo </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GENESIS_UTXO</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --partnerchain-address </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$PC_ADDRESS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --signing-key cardano-stake.skey</span><br></div></code></pre></div></div>
<p>Once associated, application code on the partner chain can resolve <code>partner-chain address → Cardano stake address → current delegation</code> without the user re-signing anything per session. For games this matters more than it sounds: it removes a per-transaction wallet prompt from the loop, and combined with the auto-sign work from <a class="" href="https://effectstream.github.io/docs/blog/auto-sign">the wallets release</a>, it gives delegators a friction-free path from "I stake to this pool on Cardano" to "I have the right state inside this game on Midnight."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-runs-on-this-today">What runs on this today<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#what-runs-on-this-today" class="hash-link" aria-label="Direct link to What runs on this today" title="Direct link to What runs on this today" translate="no">​</a></h2>
<p>The applications already deployed on Midnight are the proof that this is not a thought experiment. Four games — Safe Solver, Kachina Kolosseum, Block Kart Legends, and Dust-2-Dust — run on a chain whose validator set is selected from registered Cardano SPOs by Ariadne, and whose epoch boundaries are tied to Cardano's. We covered the games themselves in the <a class="" href="https://effectstream.github.io/docs/blog/game-templates">game templates post</a>; the point here is that the consensus underneath them is <em>the same delegation system we spent Parts 1–3 indexing from inside an application</em>. The difference in Part 4 is that the chain itself does the reading.</p>
<p>For SPOs the practical consequence is a second product line:</p>
<ul>
<li class="">Each new partner chain is another network where their existing delegators carry over.</li>
<li class="">Each application on those chains is another place where their delegators can collect benefits.</li>
<li class="">Registration is permissionless and observable; deregistration is symmetric.</li>
<li class="">Block participation rewards (covered in the Partner Chains Toolkit's <code>block-participation-rewards</code> module) flow back to delegators in the partner chain's native token.</li>
</ul>
<p>The outreach mechanic is the registration mechanic. SPOs don't need a marketing pitch from us to participate — they need a Cardano transaction and two epochs of patience.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-midnight#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Four parts in, the story has gone from "watch a UTXO change" to "weight a consensus committee." The same <code>pool</code> field that started as a string in a <code>delegations</code> row in Part 1 ends up, in Part 4, as a vote that decides who produces the next block on a chain that hosts production games. The indexing layer, the state machine, and the batcher we built in Parts 1–3 still apply on top — they just now sit on a chain that already shares their assumption that stake pool delegation is a first-class signal.</p>
<hr>
<ul>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Part 1: Connecting Cardano SPOs</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical">Part 2: Building the State Machine</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation-batcher">Part 3: Building a Pool-Aware Batcher</a></li>
<li class=""><a href="https://github.com/input-output-hk/partner-chains" target="_blank" rel="noopener noreferrer" class="">Partner Chains Toolkit</a></li>
<li class=""><a href="https://input-output-hk.github.io/partner-chains/" target="_blank" rel="noopener noreferrer" class="">Partner Chains Rust docs</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/game-templates">Game templates running on Midnight</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="stakepools" term="stakepools"/>
        <category label="delegation" term="delegation"/>
        <category label="midnight" term="midnight"/>
        <category label="partner-chains" term="partner-chains"/>
        <category label="technical" term="technical"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Stake Pool Delegation Part 2: Building a Delegation State Machine]]></title>
        <id>https://effectstream.github.io/docs/blog/stakepool-delegation-technical</id>
        <link href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A step-by-step walkthrough of how the EffectStream cardano-delegation template works internally. We'll trace the full path from raw Cardano blocks to application state, covering the config, primitive, grammar, state machine, database, and API layers.]]></summary>
        <content type="html"><![CDATA[<p>A step-by-step walkthrough of how the EffectStream <a href="https://github.com/effectstream/effectstream/tree/v-next/templates/cardano-delegation" target="_blank" rel="noopener noreferrer" class="">cardano-delegation template</a> works internally. We'll trace the full path from raw Cardano blocks to application state, covering the config, primitive, grammar, state machine, database, and API layers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-overview">Architecture overview<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#architecture-overview" class="hash-link" aria-label="Direct link to Architecture overview" title="Direct link to Architecture overview" translate="no">​</a></h2>
<p>The delegation template is a complete EffectStream application. It connects to a local Cardano devnet (YACI DevKit + Dolos), watches for delegation certificates, and stores them in PostgreSQL. The orchestrator spins up all infrastructure with a single <code>bun run dev</code>.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Cardano (YACI DevKit)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    → Dolos (UTxORPC gRPC)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        → EffectStream Sync Node</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            → PoolDelegation Primitive (extracts certificates)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                → State Machine (writes to DB)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    → REST API (reads from DB)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        → Frontend (Delegation Explorer)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-configuration-connecting-the-chains">1. Configuration: connecting the chains<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#1-configuration-connecting-the-chains" class="hash-link" aria-label="Direct link to 1. Configuration: connecting the chains" title="Direct link to 1. Configuration: connecting the chains" translate="no">​</a></h2>
<p>The config defines two networks and two sync protocols. The NTP network provides the main clock, while the Cardano network connects to Dolos via UTxORPC:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/config.dev.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> config </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ConfigBuilder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">setNamespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">setSecurityNamespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-delegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildNetworks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    builder</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addNetwork</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ntp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        type</span><span class="token operator">:</span><span class="token plain"> ConfigNetworkType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">NTP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        startTime</span><span class="token operator">:</span><span class="token plain"> launchStartTime </span><span class="token operator">??</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getTime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        blockTimeMS</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addNetwork</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"yaci"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        type</span><span class="token operator">:</span><span class="token plain"> ConfigNetworkType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">CARDANO</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        nodeUrl</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http://127.0.0.1:10000"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        network</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"yaci"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildSyncProtocols</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    builder</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addMain</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ntp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          name</span><span class="token operator">:</span><span class="token plain"> mainSyncProtocolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          type</span><span class="token operator">:</span><span class="token plain"> ConfigSyncProtocolType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">NTP_MAIN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          chainUri</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          startBlockHeight</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          pollingInterval</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addParallel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">networks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">networks </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">yaci</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"parallelUtxoRpc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          type</span><span class="token operator">:</span><span class="token plain"> ConfigSyncProtocolType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">CARDANO_UTXORPC_PARALLEL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          rpcUrl</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http://127.0.0.1:50051"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          startChainPoint</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"origin"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          confirmationDepth</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          delayMs</span><span class="token operator">:</span><span class="token plain"> yaciDevKitStartTime </span><span class="token operator">??</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          pollingInterval</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          headers</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token string-property property">"x-rpc-key"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dev"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>The key detail is <code>CARDANO_UTXORPC_PARALLEL</code> — this tells EffectStream to use a parallel sync protocol that streams blocks from Dolos via gRPC, independently of the main NTP clock. The <code>confirmationDepth: 0</code> means we process blocks immediately (appropriate for a local devnet).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-the-primitive-filtering-and-extracting-delegation-certificates">2. The primitive: filtering and extracting delegation certificates<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#2-the-primitive-filtering-and-extracting-delegation-certificates" class="hash-link" aria-label="Direct link to 2. The primitive: filtering and extracting delegation certificates" title="Direct link to 2. The primitive: filtering and extracting delegation certificates" translate="no">​</a></h2>
<p>The primitive is registered in the config with a pool filter:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/config.dev.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildPrimitives</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addPrimitive</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">syncProtocols</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">syncProtocols </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parallelUtxoRpc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CardanoPoolDelegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> PrimitiveTypeCardanoPoolDelegation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      startBlockHeight</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      stateMachinePrefix</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-pool-delegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      pools</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"7301761068762f5900bde9eb7c1c15b09840285130f5b0f53606cc57"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"82ec502f8c0a51e7c0db410e6722dd42df3b8e11f48e833f9fdf2941"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      network</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"yaci"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>The <code>pools</code> array filters which delegation events to track. Only delegations to these specific pool operator keyhashes will reach the state machine. Remove the filter to track all pools.</p>
<p>Under the hood, the primitive uses a UTxORPC predicate to tell Dolos to only send transactions containing certificates — filtering out the vast majority of transfer-only transactions at the gRPC level:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// CardanoPoolDelegationPrimitive.getConfig()</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">predicate</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> match</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> cardano</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> has_certificate</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>The <code>getPayload()</code> method then iterates the certificates array, handling both pre-Conway (<code>stakeDelegation</code>) and Conway-era (<code>stakeRegDelegCert</code>, <code>stakeVoteDelegCert</code>) certificate types:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// CardanoPoolDelegationPrimitive.getPayload()</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> cert </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">of</span><span class="token plain"> tx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">certificates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> c </span><span class="token operator">=</span><span class="token plain"> cert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">certificate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">case </span><span class="token operator">!==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stakeDelegation"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&amp;&amp;</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">case </span><span class="token operator">!==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stakeRegDelegCert"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&amp;&amp;</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">case </span><span class="token operator">!==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stakeVoteDelegCert"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> cred </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">stakeCredentialToHex</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">deleg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stakeCredential</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> poolKeyhash </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">uint8ArrayToHexString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">deleg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">poolKeyhash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Filter by configured pools (skip if not in our watch list)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">length </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token operator">!</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">includes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">poolKeyhash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toLowerCase</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Generate state machine input from the grammar</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> stateMachinePayload </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">generateRawStmInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">grammar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stateMachinePrefix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      address</span><span class="token operator">:</span><span class="token plain"> cred</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      pool</span><span class="token operator">:</span><span class="token plain"> poolKeyhash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      epoch</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">slotToEpoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">absoluteSlot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">network</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-the-grammar-defining-the-input-shape">3. The grammar: defining the input shape<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#3-the-grammar-defining-the-input-shape" class="hash-link" aria-label="Direct link to 3. The grammar: defining the input shape" title="Direct link to 3. The grammar: defining the input shape" translate="no">​</a></h2>
<p>The grammar is a TypeBox schema that defines the shape of data flowing from the primitive into the state machine. It's intentionally minimal:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/grammar.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> builtinGrammars </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/sm/grammar"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> grammar </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string-property property">"cardano-pool-delegation"</span><span class="token operator">:</span><span class="token plain"> builtinGrammars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cardanoPoolDelegation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> satisfies GrammarDefinition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The built-in grammar resolves to:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// pool-delegation-grammar.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> poolDelegationGrammar </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"address"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// staking credential hash (hex)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"pool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">     </span><span class="token comment" style="color:rgb(98, 114, 164)">// pool operator keyhash (hex)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"epoch"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// computed from slot + network params</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The grammar key <code>"cardano-pool-delegation"</code> matches the <code>stateMachinePrefix</code> in the primitive config. This is how EffectStream routes primitive outputs to the correct state machine transition.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-the-state-machine-turning-events-into-application-state">4. The state machine: turning events into application state<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#4-the-state-machine-turning-events-into-application-state" class="hash-link" aria-label="Direct link to 4. The state machine: turning events into application state" title="Direct link to 4. The state machine: turning events into application state" translate="no">​</a></h2>
<p>This is where on-chain data becomes application state. The state machine receives parsed delegation events and writes them to PostgreSQL:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/state-machine.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> stm </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Stm</span><span class="token class-name operator">&lt;</span><span class="token class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">typeof</span><span class="token class-name"> grammar</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token class-name"> </span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">grammar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-pool-delegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> epoch </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    address</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    pool</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    epoch</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">address </span><span class="token operator">||</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Insert the delegation record</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertDelegation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    block_height</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_hash</span><span class="token operator">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Update aggregate pool statistics</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">updatePoolStats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    latest_epoch</span><span class="token operator">:</span><span class="token plain"> epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    latest_block</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><code>World.resolve()</code> executes a <a href="https://pgtyped.dev/" target="_blank" rel="noopener noreferrer" class="">pgtyped</a> prepared query inside the EffectStream coroutine context. The queries run in the same transaction as the block processing, so if anything fails, the entire block rolls back cleanly.</p>
<p>The <code>gameStateTransitions</code> export wires this into the EffectStream runtime:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/state-machine.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> gameStateTransitions</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">StartConfigGameStateTransitions</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    blockHeight</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    input</span><span class="token operator">:</span><span class="token plain"> BaseStfInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> SyncStateUpdateStream</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">processInput</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-database-schema">5. Database schema<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#5-database-schema" class="hash-link" aria-label="Direct link to 5. Database schema" title="Direct link to 5. Database schema" translate="no">​</a></h2>
<p>The database has two tables — raw delegation events and aggregate pool statistics:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- packages/database/migrations/000-init.sql</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> delegations </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SERIAL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  block_height </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  address </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  pool </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  epoch </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  tx_hash </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">NOW</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_delegations_pool </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_delegations_address </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> delegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> pool_stats </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  pool </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  total_delegators </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  latest_epoch </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'0'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  latest_block </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The <code>pool_stats</code> table is maintained by an upsert query that recalculates <code>total_delegators</code> on each new delegation:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> pool_stats </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> total_delegators</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> latest_epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> latest_block</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">:pool</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> :latest_epoch</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> :latest_block</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> CONFLICT </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DO</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  total_delegators </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> delegations </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> pool </span><span class="token operator">=</span><span class="token plain"> :pool</span><span class="token operator">!</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  latest_epoch </span><span class="token operator">=</span><span class="token plain"> GREATEST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool_stats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">latest_epoch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> :latest_epoch</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  latest_block </span><span class="token operator">=</span><span class="token plain"> GREATEST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool_stats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">latest_block</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> :latest_block</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-rest-api">6. REST API<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#6-rest-api" class="hash-link" aria-label="Direct link to 6. REST API" title="Direct link to 6. REST API" translate="no">​</a></h2>
<p>The API layer exposes delegation data over HTTP. It's a standard Fastify router registered with the EffectStream runtime:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/api.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> apiRouter</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">StartConfigApiRouter</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token operator">:</span><span class="token plain"> FastifyInstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  dbConn</span><span class="token operator">:</span><span class="token plain"> Pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// All delegations (paginated)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/delegations"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> reply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> limit </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"50"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> offset </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">query </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      limit</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> offset</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> getDelegations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> limit</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> offset</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">offset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> dbConn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    reply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">send</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Delegations filtered by pool</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/delegations/:pool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> reply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> pool </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">params </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> pool</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> getDelegationsByPool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> pool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> offset</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">offset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> dbConn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    reply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">send</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Delegations filtered by staking address</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/delegations/by-address/:address"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Aggregate pool statistics</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/pool-stats"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Current sync heights</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/api/block-heights"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-putting-it-all-together-the-entry-point">7. Putting it all together: the entry point<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#7-putting-it-all-together-the-entry-point" class="hash-link" aria-label="Direct link to 7. Putting it all together: the entry point" title="Direct link to 7. Putting it all together: the entry point" translate="no">​</a></h2>
<p>The entry point wires config, grammar, state machine, API, and migrations into the EffectStream runtime:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// packages/node/main.dev.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> init</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@effectstream/runtime"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> main</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> suspend </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"effection"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">main</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">init</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">withEffectstreamStaticConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">start</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      appName</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-delegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      appVersion</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      syncInfo</span><span class="token operator">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toSyncProtocolWithNetwork</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      gameStateTransitions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      migrations</span><span class="token operator">:</span><span class="token plain"> migrationTable</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      apiRouter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      grammar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">suspend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-the-orchestrator-one-command-to-run-everything">8. The orchestrator: one command to run everything<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#8-the-orchestrator-one-command-to-run-everything" class="hash-link" aria-label="Direct link to 8. The orchestrator: one command to run everything" title="Direct link to 8. The orchestrator: one command to run everything" translate="no">​</a></h2>
<p>The <code>start.dev.ts</code> orchestrator config launches the full stack in dependency order:</p>
<ol>
<li class=""><strong>PGLite</strong> — embedded PostgreSQL (no external DB needed)</li>
<li class=""><strong>YACI DevKit</strong> — local Cardano devnet with instant block production</li>
<li class=""><strong>Dolos</strong> — lightweight Cardano node exposing UTxORPC gRPC</li>
<li class=""><strong>Register test pool</strong> — registers a second stake pool on the devnet</li>
<li class=""><strong>Sync node</strong> — the EffectStream application (config + state machine + API)</li>
<li class=""><strong>Frontend</strong> — builds and serves the Delegation Explorer dApp</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone https://github.com/effectstream/effectstream.git</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> effectstream/templates/cardano-delegation</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun i</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bun run dev</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Open http://localhost:10599</span><br></div></code></pre></div></div>
<p>The orchestrator handles dependency ordering, port management, health checks, and graceful shutdown. Developers only need <a href="https://bun.sh/" target="_blank" rel="noopener noreferrer" class="">Bun</a> installed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extending-the-template">Extending the template<a href="https://effectstream.github.io/docs/blog/stakepool-delegation-technical#extending-the-template" class="hash-link" aria-label="Direct link to Extending the template" title="Direct link to Extending the template" translate="no">​</a></h2>
<p>To add your own delegation logic, modify the state machine transition in <code>packages/node/state-machine.ts</code>. The <code>data</code> object gives you:</p>
<ul>
<li class=""><code>data.parsedInput</code> — the delegation event (<code>address</code>, <code>pool</code>, <code>epoch</code>)</li>
<li class=""><code>data.blockHeight</code> — the block number</li>
<li class=""><code>data.blockTimestamp</code> — the block timestamp</li>
</ul>
<p>For example, to unlock content for delegators of a specific pool:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">stm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addStateTransition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cardano-pool-delegation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> pool </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parsedInput </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    address</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> pool</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> epoch</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// Your custom logic here</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pool </span><span class="token operator">===</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">PARTNER_POOL_HASH</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">unlockPremiumContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      block_height</span><span class="token operator">:</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">blockHeight</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token operator">*</span><span class="token plain"> World</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">insertDelegation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<hr>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/cardano-delegation" target="_blank" rel="noopener noreferrer" class="">Template source code</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/packages/node-sdk/sm/primitives/src/cardano-pool-delegation" target="_blank" rel="noopener noreferrer" class="">PoolDelegation primitive source</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/blog/stakepool-delegation">Overview blog post</a></li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a></li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="stakepools" term="stakepools"/>
        <category label="delegation" term="delegation"/>
        <category label="dolos" term="dolos"/>
        <category label="utxorpc" term="utxorpc"/>
        <category label="technical" term="technical"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Stake Pool Delegation Part 1: Connecting Cardano SPOs to On-Chain Applications]]></title>
        <id>https://effectstream.github.io/docs/blog/stakepool-delegation</id>
        <link href="https://effectstream.github.io/docs/blog/stakepool-delegation"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Cardano's stake pool ecosystem has millions of ADA delegated across hundreds of pools, but delegation has always been a one-way relationship: delegators earn rewards, and that's it. What if games and apps could react to where you delegate? What if stake pool operators could offer in-game benefits to their delegators? EffectStream now makes this possible with a new PoolDelegation primitive that streams delegation changes in real-time.]]></summary>
        <content type="html"><![CDATA[<p>Cardano's stake pool ecosystem has millions of ADA delegated across hundreds of pools, but delegation has always been a one-way relationship: delegators earn rewards, and that's it. What if games and apps could react to where you delegate? What if stake pool operators could offer in-game benefits to their delegators? EffectStream now makes this possible with a new PoolDelegation primitive that streams delegation changes in real-time.</p>
<div>Loading diagram...</div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-challenge-indexing-delegation-state">The challenge: indexing delegation state<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#the-challenge-indexing-delegation-state" class="hash-link" aria-label="Direct link to The challenge: indexing delegation state" title="Direct link to The challenge: indexing delegation state" translate="no">​</a></h2>
<p>Delegation changes on Cardano are ledger state transitions, not traditional transactions. When a user delegates to a new pool, there's no explicit "delegation event" in the transaction log; the change shows up in the ledger state at epoch boundaries. That makes delegation harder to index than token transfers or smart contract interactions.</p>
<p>The original approach used <a href="https://dcspark.github.io/carp/" target="_blank" rel="noopener noreferrer" class="">Carp</a> as the indexer, but Carp requires syncing the full Cardano chain history. That takes days on testnet and even longer on mainnet. For local development and rapid iteration, it was a non-starter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-dolos--utxorpc-watch">The solution: Dolos + UTxORPC Watch<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#the-solution-dolos--utxorpc-watch" class="hash-link" aria-label="Direct link to The solution: Dolos + UTxORPC Watch" title="Direct link to The solution: Dolos + UTxORPC Watch" translate="no">​</a></h2>
<p>We replaced Carp with <a href="https://github.com/txpipe/dolos" target="_blank" rel="noopener noreferrer" class="">Dolos</a>, a lightweight Cardano node that exposes chain data via the UTxORPC gRPC protocol. The key part is the <a href="https://utxorpc.org/watch/intro/" target="_blank" rel="noopener noreferrer" class="">UTxORPC Watch module</a>, which provides real-time transaction streaming with filtering:</p>
<ul>
<li class=""><strong>By address</strong> - watch all transactions involving a specific address</li>
<li class=""><strong>By delegation part</strong> - watch transactions by stake credential, finding all UTxOs delegated to a specific stake key</li>
<li class=""><strong>By asset policy</strong> - watch transactions involving tokens from a specific policy</li>
</ul>
<p>For stake pool delegation, the "by delegation part" filter is the important one. It lets EffectStream detect when any address changes its delegation to or from a monitored pool, without scanning the entire chain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pooldelegation-primitive">The PoolDelegation primitive<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#the-pooldelegation-primitive" class="hash-link" aria-label="Direct link to The PoolDelegation primitive" title="Direct link to The PoolDelegation primitive" translate="no">​</a></h2>
<p>The <strong>PoolDelegation</strong> primitive is one of <a href="https://github.com/effectstream/effectstream/tree/v-next-bun/packages/node-sdk/sm/primitives/src" target="_blank" rel="noopener noreferrer" class="">five Cardano primitives</a> in EffectStream. It reads delegation events from Dolos via UTxORPC and materializes them into PostgreSQL using EffectStream's IVM (Indexed View Materializer). See the <a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a> for the full reference.</p>
<p>The primitive tracks:</p>
<ul>
<li class=""><strong>Stake registrations</strong> - new stake addresses entering the system</li>
<li class=""><strong>Delegation changes</strong> - an address moving from one pool to another</li>
<li class=""><strong>Deregistrations</strong> - stake addresses leaving the system</li>
</ul>
<p>All state is kept in PostgreSQL materialized views, giving applications fast query access to current delegation state without re-scanning the chain. When a delegation change occurs, the event flows through EffectStream's funnel system into the application's state machine as a first-class event, just like a smart contract interaction or token transfer.</p>
<p>Here's the Cardano Stake Pool Delegation Explorer dApp, where you can look up delegation info, register stake keys, and trigger delegations:</p>
<p><img decoding="async" loading="lazy" alt="Cardano Stake Pool Delegation Explorer dApp" src="https://effectstream.github.io/docs/assets/images/delegate-pool1-354644d9722993f0dca5cc468756244d.png" width="1648" height="1236" class="img_ev3q"></p>
<p>And this is the terminal output showing EffectStream detecting delegation changes as they happen, with the state machine reacting to each one:</p>
<p><img decoding="async" loading="lazy" alt="Terminal showing delegation side effects being detected in real-time" src="https://effectstream.github.io/docs/assets/images/delegate-pool2-b4ec722bac53722ebdb8249517f0d5d8.png" width="1537" height="1015" class="img_ev3q"></p>
<iframe src="https://drive.google.com/file/d/1KrFfkgRWf_OKqH6agM6T44NyPS5aIqAc/preview" width="100%" height="480" allow="autoplay"></iframe>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-developers-can-build">What developers can build<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#what-developers-can-build" class="hash-link" aria-label="Direct link to What developers can build" title="Direct link to What developers can build" translate="no">​</a></h2>
<p>With delegation data flowing into the state machine, developers can build game logic that reacts to where players delegate:</p>
<ul>
<li class="">"This area is accessible only to delegators of Pool X" (unlock content)</li>
<li class="">"All delegators of Pool Y receive a bonus item at epoch boundary" (distribute rewards)</li>
<li class="">Adjust game parameters based on which pools your players support (dynamic difficulty)</li>
<li class="">Delegators of partner pools get free transaction batching via the <code>BATCHER_CARDANO_ENABLED_POOLS</code> config (the SPO covers the cost)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="end-to-end-testing">End-to-end testing<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#end-to-end-testing" class="hash-link" aria-label="Direct link to End-to-end testing" title="Direct link to End-to-end testing" translate="no">​</a></h2>
<p>The PoolDelegation primitive is covered by EffectStream's <a href="https://github.com/effectstream/effectstream/tree/v-next-bun/e2e" target="_blank" rel="noopener noreferrer" class="">E2E test suite</a>, which verifies the full Cardano primitive lifecycle. The delegation tests cover stake address registration, initial delegation to a pool, delegation change detection, and state verification in PostgreSQL materialized views. All tests run against a local Dolos instance with YACI DevKit, so CI runs fast with no mainnet or testnet dependencies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-bigger-picture">The bigger picture<a href="https://effectstream.github.io/docs/blog/stakepool-delegation#the-bigger-picture" class="hash-link" aria-label="Direct link to The bigger picture" title="Direct link to The bigger picture" translate="no">​</a></h2>
<p>Stake pool delegation becomes a game mechanic. SPOs offer in-game benefits, players choose pools based on gameplay advantages, and a new economic layer sits on top of Cardano's existing delegation system. Delegation goes from a passive yield-earning activity to an active strategic decision: you delegate to a pool not just for staking rewards, but for the games and apps that pool supports.</p>
<ul>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next-bun/packages/node-sdk/sm/primitives/src/cardano-pool-delegation" target="_blank" rel="noopener noreferrer" class="">PoolDelegation primitive source</a> - implementation</li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next-bun/templates/cardano-delegation" target="_blank" rel="noopener noreferrer" class="">Cardano delegation template</a> - starter project</li>
<li class=""><a class="" href="https://effectstream.github.io/docs/home/chains/cardano#primitives">Cardano Primitives documentation</a> - full reference for all five Cardano primitives</li>
<li class=""><a href="https://utxorpc.org/watch/intro/" target="_blank" rel="noopener noreferrer" class="">UTxORPC Watch Module</a> - streaming transaction protocol</li>
<li class=""><a href="https://github.com/txpipe/dolos" target="_blank" rel="noopener noreferrer" class="">Dolos</a> - lightweight Cardano node</li>
</ul>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="cardano" term="cardano"/>
        <category label="stakepools" term="stakepools"/>
        <category label="delegation" term="delegation"/>
        <category label="dolos" term="dolos"/>
        <category label="utxorpc" term="utxorpc"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI-Powered Gameplay: Integrating Deterministic LLMs with On-Chain Games]]></title>
        <id>https://effectstream.github.io/docs/blog/user-experience-integrations</id>
        <link href="https://effectstream.github.io/docs/blog/user-experience-integrations"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[What happens when you put an AI engine inside an on-chain game? Not a cloud API call that could return different results each time, but a deterministic LLM that runs locally and produces verifiable, reproducible outputs. We integrated Shinkai, a deterministic LLM engine, into an EffectStream game template to find out.]]></summary>
        <content type="html"><![CDATA[<p>What happens when you put an AI engine inside an on-chain game? Not a cloud API call that could return different results each time, but a deterministic LLM that runs locally and produces verifiable, reproducible outputs. We integrated <a href="https://shinkai.com/" target="_blank" rel="noopener noreferrer" class="">Shinkai</a>, a deterministic LLM engine, into an EffectStream game template to find out.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-determinism-problem">The determinism problem<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#the-determinism-problem" class="hash-link" aria-label="Direct link to The determinism problem" title="Direct link to The determinism problem" translate="no">​</a></h2>
<p>Standard LLM APIs (OpenAI, Anthropic, etc.) are non-deterministic: the same prompt can produce different outputs on each call. For off-chain apps that's fine, but on-chain games need reproducibility. If a game's state machine processes an AI response, every node running that state machine must produce the same result. Non-deterministic AI breaks consensus.</p>
<p>Shinkai solves this by running models locally with deterministic inference. Same input, same output, every time. That makes it compatible with on-chain state machines where every participant must agree on the result.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quest-for-tokens-ai-npc-gameplay">Quest for Tokens: AI NPC gameplay<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#quest-for-tokens-ai-npc-gameplay" class="hash-link" aria-label="Direct link to Quest for Tokens: AI NPC gameplay" title="Direct link to Quest for Tokens: AI NPC gameplay" translate="no">​</a></h2>
<p>To show what AI-powered on-chain gameplay looks like, we built <strong>Quest for Tokens</strong>, a game where an AI NPC judges player answers and awards tokens based on response quality.</p>
<p><img decoding="async" loading="lazy" alt="Quest for Tokens: title screen with floating castle and &amp;quot;Enter the Kingdom&amp;quot;" src="https://effectstream.github.io/docs/assets/images/taiko1-4248b9f3f3052c632c8f9bad589f1649.png" width="2020" height="1056" class="img_ev3q"></p>
<p>Here's the game flow:</p>
<ol>
<li class="">The player enters the game world and encounters an AI NPC (a tiger guardian)</li>
<li class="">The NPC asks a question, drawn from game state so it varies based on context</li>
<li class="">The player types their answer</li>
<li class="">The AI evaluates the response and decides how many tokens to award</li>
</ol>
<p><img decoding="async" loading="lazy" alt="AI gameplay: tiger NPC asking a question with wallet signature popup" src="https://effectstream.github.io/docs/assets/images/taiko2-acb8ac84433eb7f90545eb823843e021.png" width="2042" height="1131" class="img_ev3q"></p>
<p>The wallet signature popup in the screenshot shows the on-chain integration. The player's answer is signed and submitted as a blockchain transaction. The AI's evaluation is also processed on-chain, so the token award is verifiable and permanent.</p>
<p><img decoding="async" loading="lazy" alt="AI NPC response: evaluating the player&amp;#39;s answer and awarding tokens" src="https://effectstream.github.io/docs/assets/images/taiko3-f01182c843a4e8df7bf7060f3fbfd44c.png" width="2167" height="1294" class="img_ev3q"></p>
<p>The AI doesn't just give a score; it explains its reasoning. Players can see why their answer earned or lost tokens, which creates an engaging feedback loop that feels more like a conversation than a multiple-choice quiz.</p>
<ul>
<li class=""><a href="https://tokenquest.zkdojo.com/" target="_blank" rel="noopener noreferrer" class="">Play live</a></li>
<li class=""><a href="https://github.com/effectstream/effectstream/tree/v-next/templates/shinkai-v2" target="_blank" rel="noopener noreferrer" class="">Template code</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters-for-on-chain-games">Why this matters for on-chain games<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#why-this-matters-for-on-chain-games" class="hash-link" aria-label="Direct link to Why this matters for on-chain games" title="Direct link to Why this matters for on-chain games" translate="no">​</a></h2>
<p>AI NPCs open up game mechanics that weren't possible with deterministic-only logic:</p>
<ul>
<li class=""><strong>Dynamic content</strong> - AI generates questions, dialog, and scenarios that vary with game state; no two playthroughs are the same</li>
<li class=""><strong>Natural interaction</strong> - players type freeform responses instead of picking from a menu; the AI parses intent and evaluates quality</li>
<li class=""><strong>Emergent gameplay</strong> - the AI's judgment creates situations that the game designer didn't explicitly program</li>
</ul>
<p>And because Shinkai's inference is deterministic, all of this works in an on-chain context. Every node processing the game state gets the same AI output, maintaining consensus across the network.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-integration-architecture">The integration architecture<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#the-integration-architecture" class="hash-link" aria-label="Direct link to The integration architecture" title="Direct link to The integration architecture" translate="no">​</a></h2>
<p>Three systems connect together:</p>
<table><thead><tr><th>Component</th><th>Role</th></tr></thead><tbody><tr><td><strong>EffectStream</strong></td><td>Game state machine: processes moves, tracks tokens, manages the game world</td></tr><tr><td><strong>Shinkai</strong></td><td>Deterministic LLM engine: evaluates player inputs and generates NPC responses</td></tr><tr><td><strong>Blockchain</strong></td><td>Settlement layer: records player actions and token awards permanently</td></tr></tbody></table>
<p>The player's input flows from the browser → wallet signature → blockchain transaction → EffectStream state machine → Shinkai evaluation → state update → token award. The entire pipeline is on-chain and verifiable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wallet-and-session-management">Wallet and session management<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#wallet-and-session-management" class="hash-link" aria-label="Direct link to Wallet and session management" title="Direct link to Wallet and session management" translate="no">​</a></h2>
<p>The game uses EffectStream's <a href="https://www.npmjs.com/package/@effectstream/wallets" target="_blank" rel="noopener noreferrer" class=""><code>@effectstream/wallets</code></a> package for wallet connections. Players connect their wallet to sign game actions, with the option of auto-sign delegation for a smoother experience (no wallet popup on every move).</p>
<p>The wallet layer supports multiple chains, so the same game template could run on EVM, Cardano, or Midnight. The AI integration and game logic stay the same no matter which settlement chain you pick.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-ai-powered-games">Building AI-powered games<a href="https://effectstream.github.io/docs/blog/user-experience-integrations#building-ai-powered-games" class="hash-link" aria-label="Direct link to Building AI-powered games" title="Direct link to Building AI-powered games" translate="no">​</a></h2>
<p>Quest for Tokens is a starting point. The template has the full pipeline from AI evaluation to on-chain settlement. Fork it and customize the AI's evaluation criteria, the reward structure, the game context (what the NPC asks), and the NPC personality. The <a href="https://github.com/PaimaStudios/paima-game-templates" target="_blank" rel="noopener noreferrer" class="">game templates repository</a> has the full source code, ready to deploy.</p>]]></content>
        <author>
            <name>EffectStream</name>
            <uri>https://github.com/effectstream</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="shinkai" term="shinkai"/>
        <category label="wallets" term="wallets"/>
        <category label="game-templates" term="game-templates"/>
    </entry>
</feed>