<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Denshin Team</title>
    <description>The latest articles on DEV Community by Denshin Team (@denshin).</description>
    <link>https://dev.to/denshin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136368%2F60ba9ad4-9a65-4aa7-8a56-a1a72d063e75.png</url>
      <title>DEV Community: Denshin Team</title>
      <link>https://dev.to/denshin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denshin"/>
    <language>en</language>
    <item>
      <title>We built a Web3 quest game where the progress bar is a city</title>
      <dc:creator>Denshin Team</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:49:48 +0000</pubDate>
      <link>https://dev.to/denshin/i-built-a-web3-quest-game-where-the-progress-bar-is-a-city-8ka</link>
      <guid>https://dev.to/denshin/i-built-a-web3-quest-game-where-the-progress-bar-is-a-city-8ka</guid>
      <description>&lt;p&gt;Most Web3 quest platforms look the same: a list of tasks, a checkbox per task, a points counter. You swap, you tick, you forget. We wanted progress you can actually &lt;em&gt;see&lt;/em&gt;, so in Denshin the progress bar is a city.&lt;/p&gt;

&lt;p&gt;Denshin went live on September 10 with its first season, Genesis, and launches on Product Hunt on September 22. Here is how it's built, and a few decisions that turned out to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one paragraph
&lt;/h2&gt;

&lt;p&gt;The map is one district of 100 quarters, and four of them are open at the start. Each quest belongs to a quarter: complete it and the quarter opens. The Genesis season has 33 quests across Base, Soneium and Sonic, and 23 of them are verified straight from the chain: holding gas, holding a stablecoin, swapping on a named DEX, supplying to a lending market, providing liquidity. There is no token and there are no NFTs. The prize pool is $100 USDT for the top 50 of the season, small on purpose: the game has to be worth playing on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing the city with PixiJS
&lt;/h2&gt;

&lt;p&gt;The UI is Vue 3 and the map is PixiJS. The map is a stack of containers, bottom to top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseFill → roadWear → grid → roadMarkings → roadEffects (traffic)
→ neonGlow → buildings → players → aircraft → clouds → fog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two kinds of renderers draw into it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateless renderers&lt;/strong&gt; (road markings, wear, neon glow, buildings) create their display objects once and are done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animated systems&lt;/strong&gt; (traffic, pedestrians, aircraft, clouds, fog) register a ticker callback and hold state. Every one of them has an &lt;code&gt;init(layer, ticker)&lt;/code&gt; / &lt;code&gt;destroy(ticker)&lt;/code&gt; pair, and forgetting the second half is the fastest way to leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every procedural detail, from the stains on the asphalt to which cars drive which lanes, comes from a seeded PRNG (mulberry32) with one seed per renderer. The city looks the same for every player and on every reload, which also makes visual bugs reproducible.&lt;/p&gt;

&lt;p&gt;Two performance lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bake on desktop, not on phones.&lt;/strong&gt; On desktop the quarter art is baked into one world-sized &lt;code&gt;RenderTexture&lt;/code&gt;, which makes panning cheap. On phones that one texture runs the GPU out of memory, so mobile keeps live sprites and a lighter 512px WebP set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlas the small stuff.&lt;/strong&gt; Road markings and vehicles ship as committed spritesheets instead of hundreds of separate PNGs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Verifying quests from the chain
&lt;/h2&gt;

&lt;p&gt;No screenshots, no "paste your tx hash". Every on-chain quest check asks the chain one of three questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Native balance&lt;/td&gt;
&lt;td&gt;Do you hold at least X of the gas token on chain N?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token balance&lt;/td&gt;
&lt;td&gt;Do you hold at least X of a specific ERC-20?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swap&lt;/td&gt;
&lt;td&gt;Did you swap at least X on an approved venue since you started the quest?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three rules make this hard to game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accepted-at block.&lt;/strong&gt; Action checks only look at blocks after the moment you pressed &lt;em&gt;Start mission&lt;/em&gt;. Otherwise "did you swap on Base" would pay for any swap in the wallet's history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One transaction, one reward, ever.&lt;/strong&gt; An accepted tx hash is consumed: stored once, behind a uniqueness constraint on the hash itself. It can't close two tasks, the same task twice, or a task on another account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The server picks the hash.&lt;/strong&gt; The hash comes from the verifier's own read of the chain, never from the browser, so a client can't nominate one it likes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Swaps are matched against a registry of known pools and routers per network, so "a swap on Kyo" means Kyo, not any contract that happens to emit a &lt;code&gt;Swap&lt;/code&gt; event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the daily check-in on-chain
&lt;/h2&gt;

&lt;p&gt;Until this week the Daily Check was an off-chain button. Now it is a transaction: &lt;code&gt;sync()&lt;/code&gt; on a tiny contract called DenshinSync, deployed at the same address on Soneium, Base and Sonic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function sync() external returns (uint32 streak) {
    uint32 day = today(); // block.timestamp / 1 days
    Record memory record = _records[msg.sender];
    if (record.lastDay &amp;gt;= day) revert AlreadySynced(msg.sender, day);

    streak = record.lastDay + 1 == day ? record.streak + 1 : 1;
    // ...store lastDay, streak, longest, total
    emit Synced(msg.sender, day, streak, total);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It holds no funds, has no owner and no admin functions, and cannot be upgraded. The day is the block's UTC day, not the player's clock. The streak that pays points is computed off-chain from &lt;code&gt;Synced&lt;/code&gt; events, so a player can sync on Base one day and on Sonic the next without breaking it.&lt;/p&gt;

&lt;p&gt;The identical address on three chains comes from a deterministic CREATE2 deployment. A side effect we didn't expect to care about: changing even a comment in the source changes the bytecode metadata, and with it the address.&lt;/p&gt;

&lt;p&gt;For the player it costs gas only, a fraction of a cent. The contract is &lt;code&gt;0xCCFFb22048d9E1fDd04F5F3B9681cC0C95a0f654&lt;/code&gt;, source verified on each chain's explorer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; Vue 3, PixiJS, viem/wagmi, Reown AppKit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; NestJS, PostgreSQL, Redis, BullMQ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract:&lt;/strong&gt; Solidity, Hardhat&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The map and the quests are visible without a wallet: &lt;a href="https://app.denshin.io" rel="noopener noreferrer"&gt;https://app.denshin.io&lt;/a&gt;. Docs: &lt;a href="https://docs.denshin.io" rel="noopener noreferrer"&gt;https://docs.denshin.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We launch on Product Hunt on September 22. If you have thoughts on the map, the verification rules or the on-chain check-in, that is the best place to leave them: &lt;a href="https://www.producthunt.com/products/denshin" rel="noopener noreferrer"&gt;https://www.producthunt.com/products/denshin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're a small team, so honest feedback is the most useful thing you can give us.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>web3</category>
      <category>gamedev</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
