<?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: NovaPoints (NOVAP) — by Codiologism</title>
    <description>The latest articles on DEV Community by NovaPoints (NOVAP) — by Codiologism (@novapointsapp).</description>
    <link>https://dev.to/novapointsapp</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%2F4205007%2F0452de3d-d7db-499e-a51b-2b1791270503.jpg</url>
      <title>DEV Community: NovaPoints (NOVAP) — by Codiologism</title>
      <link>https://dev.to/novapointsapp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/novapointsapp"/>
    <language>en</language>
    <item>
      <title>An airdrop you can verify, not trust: building Merkle claims on TON</title>
      <dc:creator>NovaPoints (NOVAP) — by Codiologism</dc:creator>
      <pubDate>Sat, 24 Oct 2026 15:35:55 +0000</pubDate>
      <link>https://dev.to/novapointsapp/an-airdrop-you-can-verify-not-trust-building-merkle-claims-on-ton-1lgk</link>
      <guid>https://dev.to/novapointsapp/an-airdrop-you-can-verify-not-trust-building-merkle-claims-on-ton-1lgk</guid>
      <description>&lt;p&gt;Crypto airdrops have a trust problem. Users spend months farming points on&lt;br&gt;
the promise that a database they'll never see will eventually produce a fair&lt;br&gt;
number. When the token launches, the operator publishes a result — and you&lt;br&gt;
either believe it or you don't.&lt;/p&gt;

&lt;p&gt;When we started building NovaPoints — a points-economy Telegram Mini App on&lt;br&gt;
TON — we wanted the allocation math and the payout itself to be checkable&lt;br&gt;
end-to-end. Not "trust us, the spreadsheet is fair," but: the rules are&lt;br&gt;
published before the snapshot, every score is reproducible from disclosed&lt;br&gt;
inputs, and the contract that pays out can &lt;em&gt;only&lt;/em&gt; pay what the frozen snapshot&lt;br&gt;
says.&lt;/p&gt;

&lt;p&gt;This is a walkthrough of how it actually works — the FunC contract, the&lt;br&gt;
TON-native Merkle structure (which is not the keccak tree you know from EVM),&lt;br&gt;
and the parts we still can't make trustless.&lt;/p&gt;
&lt;h2&gt;
  
  
  The design contract
&lt;/h2&gt;

&lt;p&gt;Four commitments, all verifiable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rules before results.&lt;/strong&gt; Season 1's gates (activity ratio, verified TON
wallet, qualified referrals) and the per-user cap (50,000 NOVAP of a
25M pool) are published while the season runs — not decided at snapshot
time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible scores.&lt;/strong&gt; The score function is deterministic over
disclosed inputs: activity, retention, missions, referrals, reputation,
events, multiplied by a risk band (the anti-Sybil engine), a campaign
multiplier, and a badge bonus. Each score row stores a SHA-256 of its
input JSON — anyone can recompute it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A frozen snapshot.&lt;/strong&gt; Finalization writes an immutable allocation table;
the admin tooling refuses to touch a published snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A contract that can only pay the snapshot.&lt;/strong&gt; The claim contract holds a
single 256-bit Merkle root and pays whatever a valid proof says — it has
no allocation logic of its own.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  TON doesn't do Merkle trees like Ethereum
&lt;/h2&gt;

&lt;p&gt;On EVM, airdrop claims are a keccak binary Merkle tree: you send a list of&lt;br&gt;
sibling hashes, the contract re-hashes up to the root. TON's equivalent is&lt;br&gt;
more interesting — the VM itself does half the verification.&lt;/p&gt;

&lt;p&gt;Every data structure on TON is a &lt;strong&gt;cell&lt;/strong&gt; — up to 1023 bits plus 4 references,&lt;br&gt;
identified by the hash of its contents. A key-value map is a &lt;code&gt;Hashmap&lt;/code&gt;, a&lt;br&gt;
Merkleized radix tree of cells. And TON has &lt;strong&gt;exotic cells&lt;/strong&gt;: a&lt;br&gt;
&lt;code&gt;merkle_proof&lt;/code&gt; cell (type &lt;code&gt;0x03&lt;/code&gt;) is a normal subtree where branches not on&lt;br&gt;
the proof path are replaced by their hashes. When a message carrying an&lt;br&gt;
exotic cell is imported, the &lt;em&gt;virtual machine&lt;/em&gt; validates that the subtree's&lt;br&gt;
virtualized hash equals the hash stored in the proof header — the contract&lt;br&gt;
never re-computes the tree at all.&lt;/p&gt;

&lt;p&gt;Our claim tree is a &lt;code&gt;Hashmap 256&lt;/code&gt; keyed by each claimant's 256-bit account&lt;br&gt;
id; the leaf is just &lt;code&gt;Coins&lt;/code&gt; — the claimable amount:&lt;/p&gt;

&lt;p&gt;​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;claimKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c"&gt;// 256-bit account id, basechain only&lt;/span&gt;
    &lt;span class="n"&gt;leaf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginCell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustStoreBigCoins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndCell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToCell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                       &lt;span class="c"&gt;// its hash IS the merkle root&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s"&gt;```



Generating a proof is equally direct — prune every branch except the
claimant's:

​

```&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;
&lt;span class="n"&gt;pb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMerkleProofBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;slc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// mark the leaf path&lt;/span&gt;
&lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateProof&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                    &lt;span class="c"&gt;// merkle_proof#03 cell&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s"&gt;```



## The contract is a verifier, not an allocator

`&lt;/span&gt;&lt;span class="n"&gt;merkle_claim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="s"&gt;` stores five things: the root, an admin, its jetton wallet,
a deadline, and a `&lt;/span&gt;&lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="s"&gt;` dictionary. The entire claim path:

​

```&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;
&lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in_msg_body&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;load_ref&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;exotic&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proof&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin_parse_exotic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;XCTOS&lt;/span&gt;
&lt;span class="n"&gt;throw_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bad_proof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exotic&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;throw_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bad_proof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;load_uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;merkle_proof&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="m"&gt;03&lt;/span&gt;
&lt;span class="n"&gt;throw_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bad_proof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;load_uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);;;&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="n"&gt;equality&lt;/span&gt;

&lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;load_ref&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt; &lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udict_get&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ah&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="n"&gt;claimant&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
&lt;span class="n"&gt;throw_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;no_allocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;load_coins&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udict_get&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ah&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;throw_if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;already_claimed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;udict_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ah&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;send_jettons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s"&gt;```



Three properties fall out of this design:

- **It can't pay anyone else.** The sender's address hash *is* the dict key —
   a proof for your address only pays your address. There's no "claim to a
   different destination" parameter to exploit.
- **It can't pay more than the snapshot.** The amount is in the leaf, fixed
   at tree-build time. `&lt;/span&gt;&lt;span class="n"&gt;set_root&lt;/span&gt;&lt;span class="s"&gt;` exists for the monthly vesting tranches
   (25% at TGE, then three monthly roots) — rotating the root is the only
   way to change what's payable, and every rotation is a public on-chain
   event anyone can diff against the published dataset.
- **Proofs never expire because of other people's claims.** `&lt;/span&gt;&lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="s"&gt;` lives
   in a separate dictionary, so the tree — and every outstanding proof —
   stays valid until the deadline.

The deadline matters too: claims are first-come within a window, and the
admin `&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="s"&gt;` path exists explicitly for sweeping unclaimed tokens
*after* expiry — they roll into the next season's pool rather than sitting
in limbo.

## The backend half: from snapshot to proof

On-chain is the easy half. The flow that feeds it:

1. Season ends → allocations finalize → snapshot marked `&lt;/span&gt;&lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="s"&gt;`.
2. `&lt;/span&gt;&lt;span class="n"&gt;GenerateClaimTree&lt;/span&gt;&lt;span class="s"&gt;` pulls every allocation with a **verified wallet on
   the target network** — only verified, basechain (`&lt;/span&gt;&lt;span class="n"&gt;wc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="s"&gt;`) addresses become
   leaves — builds the dict, and stores its full BoC in `&lt;/span&gt;&lt;span class="n"&gt;claim_trees&lt;/span&gt;&lt;span class="s"&gt;`.
   Regeneration is hard-blocked once any claim is `&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="s"&gt;`/`&lt;/span&gt;&lt;span class="n"&gt;claimed&lt;/span&gt;&lt;span class="s"&gt;`.
3. **Wallets lock at tree time.** Each bound wallet gets `&lt;/span&gt;&lt;span class="n"&gt;locked_at&lt;/span&gt;&lt;span class="s"&gt;` set —
   you can't swap your linked address after the snapshot commits you.
4. Proofs are generated lazily: when a user opens the claim screen, the API
   prunes their branch out of the stored dict BoC, caches `&lt;/span&gt;&lt;span class="n"&gt;proof_boc&lt;/span&gt;&lt;span class="s"&gt;`, and
   hands it to the Mini App.
5. TON Connect sends the claim tx; the worker confirms it via the
   contract's `&lt;/span&gt;&lt;span class="n"&gt;is_claimed&lt;/span&gt;&lt;span class="s"&gt;` get-method rather than trusting the client.

## The risk engine is a multiplier, not a judge

One decision we're happy with: anti-Sybil output doesn't delete people — it
*scales* them. The score function multiplies the weighted activity score by
a risk band (`&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suspicious&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="s"&gt;`). A flagged
account still sees its real score in-app; the multiplier is just math in the
same published formula, not an invisible blacklist. Fraud signals (shared
IP clusters, device patterns, referral rings) feed the band, and the band
feeds the published score — so "why is my allocation X" always has a
recomputable answer.

## What this does *not* prove

Honesty section, because "verifiable" gets overclaimed:

- **The rules are still ours.** We picked the gates, the weights, the risk
  bands. Verification proves the result matches the published rules — it
  can't prove the rules were *good*.
- **The risk signals are off-chain.** Bands are inputs we produce from
  internal telemetry. You can verify a score used a `&lt;/span&gt;&lt;span class="n"&gt;suspicious&lt;/span&gt;&lt;span class="s"&gt;` multiplier;
  you can't verify why that flag fired. Publishing aggregate signal stats is
  on our list.
- **Verification needs the dataset.** Root + claims are on-chain, but
  recomputing your leaf needs the full allocation table — we export it as a
  public dataset (IPFS at TGE) for exactly this reason.

## The boring trust signals that matter more than code

- Minter admin is `&lt;/span&gt;&lt;span class="n"&gt;addr_none&lt;/span&gt;&lt;span class="s"&gt;` — revoked at deploy. Supply is 100M, forever.
  (`&lt;/span&gt;&lt;span class="n"&gt;EQDvO&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h0g6m5SN_EN&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ikseFdtjtvET_ytKRCm2A26qXaIYGV&lt;/span&gt;&lt;span class="s"&gt;` on Tonviewer.)
- 79M NOVAP — airdrop pool + liquidity reserve — sits in a standard
  audited multisig-v2 2-of-3: `&lt;/span&gt;&lt;span class="n"&gt;EQAcU52wnFWIh500eDs2RUEurOc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pIj409IAJZMWX_Inig29&lt;/span&gt;&lt;span class="s"&gt;`.
- Founder allocation vests 10 months behind a second 2-of-3 escrow; LP
  timelocks 12 months at TGE.

Season 1 is live now — closes at 1,000 eligible users or Dec 31, 2026.
If you want to check any of this yourself: `&lt;/span&gt;&lt;span class="n"&gt;novapoints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ton&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="s"&gt;` has every
address, and the claim contract source goes up on verifier.ton.org when the
tree ships at TGE.

*Built by Codiologism — questions and audit-brain welcome in the comments.*
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>crypto</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
