<?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: Gaurav Jain</title>
    <description>The latest articles on DEV Community by Gaurav Jain (@gaurav_designer).</description>
    <link>https://dev.to/gaurav_designer</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%2F4094414%2F93976367-2392-4f9b-a6e5-2020c9fa4636.jpg</url>
      <title>DEV Community: Gaurav Jain</title>
      <link>https://dev.to/gaurav_designer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaurav_designer"/>
    <language>en</language>
    <item>
      <title>I built a product leaderboard where rank is bought, not voted</title>
      <dc:creator>Gaurav Jain</dc:creator>
      <pubDate>Tue, 25 Aug 2026 15:22:40 +0000</pubDate>
      <link>https://dev.to/gaurav_designer/i-built-a-product-leaderboard-where-rank-is-bought-not-voted-1e7e</link>
      <guid>https://dev.to/gaurav_designer/i-built-a-product-leaderboard-where-rank-is-bought-not-voted-1e7e</guid>
      <description>&lt;p&gt;Most launch platforms decide your visibility with upvotes. That sounds democratic until you notice what actually wins: posting at the right hour, having the right followers, and a network that shows up on cue.&lt;/p&gt;

&lt;p&gt;So I built the opposite as an experiment. On &lt;a href="https://launchbid.lol" rel="noopener noreferrer"&gt;LaunchBid&lt;/a&gt;, your rank is exactly the number of tokens bid on your product. No votes, no algorithm, no launch window. Totals never reset and anyone can outbid anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The token economy
&lt;/h2&gt;

&lt;p&gt;Three ways to get tokens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Share quest&lt;/strong&gt;: post about it on X, paste your post link, get 50 tokens. Verified server-side against the post's real content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore quest&lt;/strong&gt;: 2 tokens for each listing you visit, capped at 10 a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy a pack&lt;/strong&gt;: card checkout, credited automatically by webhook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Listing costs 50 tokens and the share earns exactly 50, so a free listing costs one public post. That was deliberate: free-but-frictionless invites spam, free-but-costs-a-post invites distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Enforce money rules in the database, not the app.&lt;/strong&gt; Every token movement is an append-only ledger row, balances can't go negative, and spending happens inside a single atomic Postgres function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;token_balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token_balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
 &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v_user&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;token_balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="n"&gt;token_balance&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;v_balance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v_balance&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="s1"&gt;'NOT_ENOUGH_TOKENS'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the guard lives in the &lt;code&gt;where&lt;/code&gt; clause, two concurrent requests can never both succeed on the same last tokens. When I later raised the listing price, I changed one function and the entire app was correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anonymous identity removes the biggest drop-off.&lt;/strong&gt; There is no signup at all. Every visitor silently gets an anonymous session, so someone can land, earn tokens, and list a product without ever seeing a password field. Row-level security still isolates every balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic generation beats stored assets.&lt;/strong&gt; Every product gets a pixel character generated from its slug, so a listing keeps its face forever with zero storage and zero uploads. That one bit me: I seeded the palette indices with a 32-bit hash and used &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; to shift it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HAIRS&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;HAIRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;   &lt;span class="c1"&gt;// negative index when the high bit is set&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;HAIRS&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;HAIRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// fixed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signed shifts turned half the hashes negative, which produced undefined colors and crashed rendering on the server. The board still looked fine in the browser because it silently fell back to client rendering, so the only visible symptom was my HTML quietly emptying out. Use &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; on hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;Paying for rank sounds cynical. I would argue it is the most transparent thing on a launch site: everyone can see exactly what a position cost, and nobody pretends the ranking is organic.&lt;/p&gt;

&lt;p&gt;It is Minecraft-themed, with skins, sound effects, and an explosion when you list something. Take a look: &lt;a href="https://launchbid.lol" rel="noopener noreferrer"&gt;launchbid.lol&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>indiehackers</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
