QA Environment · Non-production · Devnet
Email Connector: Resend · Emails: Sending
Contests Rules Reserves
v0.25 devnet

The Contract

The exact smart contract that holds your prize pools, on chain. Every instruction. Every signer role. Every byte.

What this contract does

Every Turf Monster contest you enter is escrowed by a small program that lives on Solana — a public, immutable blockchain. The prize pool isn't sitting in a Turf Monster bank account. It's locked in an on-chain account that only the contract itself can move, following rules anyone can read.

You sign your own entry. Your USDC moves from your wallet to a contest account in one transaction. When the contest settles, the contract pays winners directly from that account to their wallets — we can't touch the money in between.

Below: the exact bytes that ship on chain, every instruction the contract exposes, and who is allowed to call each one.

Deploy binary
501,528bytes
~490 KB. The ELF that lands on chain.
Permanent rent
3.494SOL
Forever-locked rent on the ProgramData account. 0.25 SOL more than v0.15.1's 3.248.
Refundable buffer
3.492SOL
During-deploy buffer account. Refunded once close-buffer runs.
Total float at deploy
~6.99SOL
Both accounts exist simultaneously during deploy. Needed in the bot (Alex) before write-buffer.

Where the 490 KB goes

ELF section headers

Solana charges rent on every byte in the ProgramData account, regardless of what's in it. The ELF section layout decides what those bytes are.

.text
84.8%
.rel.dyn
7.9%
.rodata
4.4%
.data.rel.ro
2.6%
dyn meta
0.2%
ELF hdr+pad
0.2%
.text 425,304 · 84.8%
BPF bytecode — every Anchor handler, every Rust core/alloc/serde routine pulled in by it.
.rel.dyn 39,664 · 7.9%
Relocation entries the BPF loader applies at load time to fix up addresses.
.rodata 21,976 · 4.4%
Read-only strings: error names, msg!() log strings, account-field names embedded by Anchor, plus the embedded IDL JSON for `anchor idl fetch`.
.data.rel.ro 12,888 · 2.6%
Relocatable read-only constants — Anchor discriminators, instruction-method tables.
dyn meta 830 · 0.2%
.dynamic + .dynsym + .dynstr — minimal dynamic-linking metadata.
ELF hdr+pad 866 · 0.2%
ELF header, program headers, .shstrtab, section padding.

What's inside .text

code: 425,304 bytes

.text is the BPF bytecode that actually runs on chain. Most of it is Anchor's auto-generated account-validation code, not the business logic itself.

Handler logic (18 instructions)
180.3 KB
43.4%
why this is here

Each handler's handle_X function plus the Anchor-generated try_accounts validator. The validators dwarf the business logic. Measured per-instruction from a debug-info rebuild.

Anchor framework runtime
43.0 KB
10.3%
why this is here

anchor_lang account-info helpers, ErrorCode formatters, AccountSerialize / Deserialize machinery, Bumps-struct support.

Rust core + alloc
53.2 KB
12.8%
why this is here

core::* and alloc::* monomorphizations: slice indexing, format machinery, Vec growth. Pulled in by msg!() and Vec usage.

Anchor IDL embed
28.3 KB
6.8%
why this is here

The embedded IDL JSON plus the 6 Idl* instruction handlers (IdlCreateAccount, IdlResize, IdlClose, IdlWrite, IdlSetAuthority, IdlSetBuffer). Lets Solscan and `anchor idl fetch` decode the program without our source.

Solana + SPL Token (required for USDC/USDT)
35.2 KB
8.5%
why this is here

solana_program::* invoke helpers, system-program builders, classic SPL Token instruction builders, Pack/Unpack codecs, ATA helpers. Every USDC and USDT transfer routes through these — they're not optional, they're literally how SPL tokens move on Solana.

Token-2022 (unused, Anchor 0.32.1 quirk)
7.8 KB
1.9%
why this is here

spl_token_2022 + spl_transfer_hook + spl_token_group + spl_elgamal instruction builders. The contract doesn't use any Token-2022 mints, but Anchor 0.32.1 pulls these in unconditionally — known framework bug. ~0.06 SOL of permanent rent wasted; resolved automatically when we upgrade to Anchor 0.33+.

Turf state & errors
21.0 KB
5.1%
why this is here

VaultState/UserAccount/Contest/ContestEntry/EntryTokenAccount/Season/AcceptedCurrency serializers plus the 39 VaultError variants (codes 6000–6038).

Anchor program dispatch
15.4 KB
3.7%
why this is here

The 8-byte-discriminator switch generated by #[program] that routes each TX to one of the 18 handlers.

serde / borsh / panic / misc
31.1 KB
7.5%
why this is here

borsh deserializers, panic_fmt / begin_panic for require!() failures, compiler_builtins for soft-float multiplication, sha256 for mint_entry_token's source_ref hashing.

What you can call yourself

5 user-signed instructions

These are the instructions you (the contestant) sign directly. Your wallet's signature is the only thing that authorizes them — no operator can call them on your behalf.

enter_contest user signs
20,784 bytes

Pay your entry fee from your own wallet's USDC (or USDT) ATA, in one signed transaction. Your stats counter ticks up.

Replaces v0.15.1's `enter_contest_direct`. You sign an SPL transfer from your ATA to the contest's per-currency operator-revenue ATA. The instruction creates the ContestEntry PDA, awards seeds from the current Season, and increments your UserAccount.entries counter.

enter_contest_with_token user signs
16,664 bytes

Consume one of your unused free-entry tokens to enter a contest at no cash cost.

You sign as the token owner. The instruction marks the EntryTokenAccount as consumed and creates a ContestEntry with currency_idx = u8::MAX (token-funded). No SPL transfer occurs — the operator covers the prize-pool side.

set_username user signs
3,992 bytes

Change your on-chain display name. Validated against a reserved-prefix list (admin, system, turf, ...) and required to be printable ASCII, 3+ characters.

Phantom users sign in the browser. Custodial / managed-wallet users sign via the server-held encrypted keypair. Admin alone cannot rename someone — wallet consent is required.

create_user_account permissionless
6,664 bytes

First-touch onboarding. Creates your on-chain UserAccount PDA — the stats record that tracks your seeds, entries, wins, and total payouts.

Permissionless and idempotent: anyone can pay the rent to create another wallet's UserAccount. Turf Monster's server bot (Alex) does this for every new signup via an after_commit job.

create_contest 1-of-3 + creator
21,784 bytes

Open a new contest, funded with a USDC prize pool that you (the creator) transfer from your ATA at creation time.

Dual-signer: a vault signer pays the SOL rent for the new Contest PDA and per-contest prize-pool ATA; the creator signs the USDC transfer that funds the prizes. Stores the per-currency entry-fee schedule the contest will honor.

Who can call what

signer matrix

Four signer roles. Each instruction is bound to exactly one role — the contract refuses to execute when the wrong key signs.

INIT_AUTHORITY
Mr. McRitchie's Phantom key
  • initialize (once, ever)
1-of-3 vault signer
Alex · Mr. McRitchie · Mason
  • create_season · create_contest
  • set_contest_lock_time
  • set_contest_conclusion_time
  • mint_entry_token · close_contest
2-of-3 multisig
2 of {Alex (bot) · Mr. McRitchie · Mason}
  • register_currency · deactivate_currency
  • settle_contest · cancel_contest
  • sweep_operator_revenue
  • pause · unpause
User wallet signs
Phantom or managed keypair
  • enter_contest
  • enter_contest_with_token
  • set_username
  • create_user_account (permissionless)

Deploy-cost calculator

SOL → USD

Solana rent formula: (data_len + 128) × 6960 lamports — this is the rent-exempt minimum (2 years × lamports-per-byte-year × 2× exemption threshold).

Binary: 501,528 bytes SOL needed: -- --

PERMANENT (you spend forever)

ProgramData rent (501,528 + 128) × 6960
--
Program account (36 + 128) × 6960
1,141,440 ℓ
Deploy TX fees (signatures + compute)
~1,000,000 ℓ
Permanent SOL spent
--
≈ at current price
--

FLOAT (you need on hand, then refundable)

Buffer account (same size as binary)
--
— refunded when you close the buffer
Additional float during deploy
--
≈ at current price
--
Recommended balance before write-buffer
--

Subsequent upgrades through the Squad multisig need only the float portion — a new buffer is written, the program is upgraded in place, then the old buffer is closed. Net cost per upgrade: roughly zero, modulo TX fees and the brief float for the new buffer.