v0.25 mainnet-beta

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 runs on a small program that lives on Solana — a public, immutable blockchain. A contest's prize money isn't sitting in a Turf Monster bank account. It's held in an on-chain prize pool the program controls: the contract pays the winners when the contest settles, and returns the money to the creator who funded the pool if the contest is cancelled — following rules anyone can read.

You sign your own entry, and that one transaction moves exactly the entry fee and nothing else. The fee is Turf Monster's revenue, so it goes to a dedicated on-chain account for that currency, and the only place the program can send it from there is our treasury. Prizes come out of the contest's prize pool instead — a separate account — and the contract cannot pay out more than that pool already holds.

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

Deployed program
545,928bytes
~533 KB. The file the loader wrote, read through its last byte. 544,904 bytes of it are logical ELF content; the rest is zero padding.
Locked in ProgramData
3.801SOL
What the account holds today, funded at 6,960 lamports a byte. 0.55 SOL more than v0.15.1's 3.248, funded at that same rate.
Refundable buffer
2.774SOL
A buffer holding the same 545,928-byte file. Refunded once close-buffer runs.
Float to deploy today
~5.55SOL
Both accounts exist at once during a deploy. Needed in the deploying wallet before write-buffer.

Measured on turf-vault v0.25.0, as deployed on mainnet-beta, at slot 446,739,318 (2026-09-13). Program sha256 e71a3fce25f8b5f28a203b33705a0274ebf1b2200c9f092ac86339931f2dbee7 — check it with solana program dump and shasum -a 256. Rent is a snapshot, not a constant: this account's 545,973 bytes were funded at 6,960 lamports a byte, and at that slot the cluster's rent-exempt minimum priced them at 5,080 a byte (2.774 SOL), a rate it keeps lowering.

Where the 533 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. The account holds 545,973 bytes: a 45-byte loader header in front of the 545,928-byte file broken down below.

.text
85.0%
.rel.dyn
7.8%
.rodata
4.3%
.data.rel.ro
2.4%
dyn meta
0.1%
ELF hdr+pad
0.2%
zero padding
0.2%
.text 464,208 · 85.0%
BPF bytecode — every Anchor handler, every Rust core/alloc/serde routine pulled in by it.
.rel.dyn 42,384 · 7.8%
Relocation entries the BPF loader applies at load time to fix up addresses.
.rodata 23,272 · 4.3%
Read-only strings: error names and messages, msg!() log strings, account-field and PDA-seed names embedded by Anchor.
.data.rel.ro 13,344 · 2.4%
Relocatable read-only constants — Anchor discriminators, instruction-method tables.
dyn meta 758 · 0.1%
.dynamic + .dynsym + .dynstr — minimal dynamic-linking metadata.
ELF hdr+pad 938 · 0.2%
ELF header, program headers, .shstrtab, the section-header table, alignment padding.
zero padding 1,024 · 0.2%
Zeros after the ELF's last byte. The loader wrote the whole deployed file and the account rents every byte of it, ELF content or not.

What's inside .text

v0.19 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.

These buckets were attributed on the turf-vault v0.19 build, whose .text was 425,304 bytes. The deployed v0.25.0 .text is 464,208 bytes and has not been re-attributed yet.

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.

Byte counts per instruction were measured on the turf-vault v0.19 build and have not been re-measured for the deployed v0.25.0.

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 vault-wide, per-currency operator-revenue ATA (seeds [b"op_rev", mint]) — not to the contest's prize pool. 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 (Xan) 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
Xan (bot) · 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 {Xan (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

An account is rent-exempt once it holds the minimum for its size, and that minimum is not a fixed constant — the cluster sets the per-byte rate and has been lowering it. Ask the chain rather than multiply: solana rent 545973, or getMinimumBalanceForRentExemption. Every minimum below was queried at slot 446,739,318 (2026-09-13), where it worked out to 5,080 lamports a byte. This program's accounts were funded at 6,960, so what they hold is more than what they would cost to open today.

Deployed file: 545,928 bytes SOL needed: -- --

PERMANENT (you spend forever)

ProgramData minimum, 545,973 bytes
2,774,193,080 ℓ
Program account minimum, 36 bytes
833,120 ℓ
Deploy TX fees (signatures + compute)
1,000,000 ℓ
Permanent SOL at that slot's rate
--
≈ at current price
--
What this program's accounts hold today
3,802,004,400 ℓ
— funded at 6,960 lamports a byte, not today's 5,080

FLOAT (you need on hand, then refundable)

Buffer minimum, 545,965 bytes (37 + the 545,928-byte file)
2,774,152,440 ℓ
— 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.