GuardiaNNN contests — rules

A contest is a buy-and-hold campaign a project runs for its community. The project puts up a prize, sets the goals, and funds an on-chain escrow that only pays out to verified winners. The rules below are the ones the Rules block on every contest links to — this file is rendered as the site's /contests/rules page.

Each contest's prize lives in its own immutable smart contract — a clone deployed just for that campaign. Money can leave it in only four ways: a winner's claim, a zero-winner refund, a post-deadline sweep, or an admin cancel refund. Nobody — not the project, not GuardiaNNN — can withdraw a funded prize by any other path.

Joining and qualifying

  • Join while the contest is live (after it starts, before it ends). You join explicitly; there is no automatic entry.
  • Each contest has one or more goals, and every goal is verified on-chain by GuardiaNNN — the site reads the chain and Codex, never your word for it. Goals can include:
    • Buy ≥ $X — an on-chain DEX swap that acquires the token, valued in USD at the time of the trade. Airdrops, plain transfers in, CEX withdrawals and LP removals are not buys. Once you meet a buy goal it stays met — a later price drop cannot un-complete it.
    • Hold for N days — still holding at the contest's finalize time. This is a single snapshot taken at the same moment for everyone, so a last-minute buyer can still finish.
    • Hold ≥ X tokens, N buys in the window, or refer N friends who join and qualify.
  • Only your linked wallets count, and only wallets linked before the contest ends. Buys are never summed across different wallets.
  • The project's own team can't win its own pool. The creator, project members and the default payer wallet are refused on join and dropped at finalize.

How the prize is split

When the contest finalizes, GuardiaNNN builds the list of everyone who qualified and posts it on chain as a Merkle root together with the winner count.

  • The prize is split equally: share = pool ÷ winner_count, where pool is the escrow's actual balance at finalize.
  • One winner takes the whole pool.
  • Zero winners → the prize is refunded to the creator. Nothing is stranded.
  • Any indivisible remainder (a few wei from integer division) stays in the contract and is swept to the creator after the claim window.

Claiming

  • After finalize there is a 24-hour challenge window before claims open. In that window a wrong or malicious winner list can still be revoked by the factory owner (a cold hardware wallet) — after it, the list is immutable forever.
  • Once claims open you have 180 days to claim. You call claim from your own wallet and pay your own gas; the prize is sent to the winning account, so it is safe even if someone else submits your claim for you.
  • After the 180-day window closes, any unclaimed remainder can be swept back to the creator.

Deposits are final

Once a project funds a contest, the deposit cannot be withdrawn — in any state, by anyone. The only ways money leaves the escrow are the four above (winner claim, zero-winner refund, post-deadline sweep, admin-cancel refund). A project sees this warning before the first deposit. There is no creator "cancel and take the money back".

The dates and prize can't change once set

The reward token, the amount and the dates are fixed when the contest is created and paid for — they are parameters of the escrow contract. Title, description, image and goals can be edited only until the contest is started (locked); after that everything is frozen.

When GuardiaNNN can step in (disable policy)

GuardiaNNN staff watch every contest and can disable one (for example, an abusive or fraudulent campaign). Disabling hides it and pauses the contract — joins and claims stop, but money does not move. From a disabled contest, staff may either cancel it and refund the creator, or finalize it early with the honest winner list. Staff can never redirect a prize to themselves; the on-chain challenge window and the cold-wallet revoke are the backstop.

There is no platform cut from the prize. The project pays a flat credit fee to create a contest; 100 % of the prize goes to winners (or back to the creator if there are none).

Verifying a winner list yourself

The winner list is public (GET /contests/{id}/winners) so anyone can rebuild the Merkle root and confirm it matches the one posted on chain. Each leaf is domain-separated by chain and by contract, so a proof from one contest can never be replayed on another:

leaf = keccak256(bytes.concat(keccak256(abi.encode(
           uint256 chainId,       // the chain the contest is on
           address contest,       // the contest clone's address
           uint256 index,         // the winner's index in the list
           address account        // the winning wallet
       ))))

This is the standard OpenZeppelin StandardMerkleTree encoding: padded (abi.encode) leaf preimage, double-hashed, with commutative (keccak256(sorted(a, b))) parents. Build the identical tree off-chain with @openzeppelin/merkle-tree:

StandardMerkleTree.of(
    winners.map((wallet, i) => [chainId, contestAddress, i, wallet]),
    ["uint256", "address", "uint256", "address"],
);

Verify a single proof with MerkleProof.verify(proof, root, leaf). The contest contract also exposes leafOf(index, account), which returns exactly these bytes, so the site, the contract and any third-party verifier all compute the same leaf.

For operators: the deploy runbook and the security model live in guardian-contracts/contests/ROLLOUT.md, README.md and SECURITY.md.