Building your first Sarpoy bot: a walkthrough from initialize_bot to first solve
A complete, hands-on walkthrough of standing up a Sarpoy puzzle bot — picking the objective, choosing your fee curve, funding the pot, and watching the first solver crack it. Code samples included.
This is the missing manual: how to actually launch a Sarpoy puzzle bot from
zero. We’ll cover the design choices (objective, base cost, multiplier, pot
size), the on-chain mechanics, what the API does in the middle, and how to
watch the lifecycle from initialize_bot to submit_solution.
The prerequisites are modest: a Solana wallet with some SOL, a clone of the Sarpoy monorepo, and a coffee.
Step 0 — clone, install, run
The repo splits cleanly into three subprojects. For our purposes you only need two of them running locally:
git clone https://github.com/cryptuon/sarpoy
cd sarpoy
# backend
cd sarpoy-api
uv sync
cp .env.example .env
# edit .env: SOLANA_RPC_URL, PROGRAM_ID, DATABASE_URL
uv run uvicorn sarpoy_api.main:app --reload
# frontend, in another terminal
cd ../sarpoy-ui
npm install
npm run dev
By default the UI runs on port 3000 and the API on 8000. The backend ships with sample data, so the chat UX is clickable even before you’ve connected a wallet — useful for getting your bearings.
The Solana program lives in sarpoy-api/programs/sarpoy/. If you’re just
playing with the existing devnet deploy you don’t need to rebuild it. If
you want your own program ID, run anchor build && anchor deploy --provider.cluster devnet
and update your PROGRAM_ID env var.
Step 1 — choose the objective
Every Sarpoy bot has one hidden objective. The objective is the thing the solver is trying to extract. Common shapes:
- A password. “The solver wins when they get the bot to print the
word
chartreuse.” - A function definition. “The solver wins when they can reproduce the Python function the bot will not show them.”
- A logical proof. “The solver wins when they convince the bot that P ≠ NP using only modal logic.” (Good luck.)
- A negotiation outcome. “The solver wins when they get the bot to agree to transfer the treasury for less than 0.1 SOL.”
The objective should be verifiable. That doesn’t mean it has to be a string match — it can be a creator review, an oracle check, or a script you run against the transcript. But you need to know, deterministically, when somebody has won. If you can’t define that, you’re not ready to launch yet.
Sarpoy stores the objective string in the bot account at initialize_bot
time. Whether you put the literal answer there or just a description is
up to your verification model.
Step 2 — set the economic knobs
Three numbers define your bot’s economy:
initial_pot— the SOL you’re putting up. Bigger pots attract more solvers but also attract more determined attackers. Start small while you’re calibrating.base_cost— the cost of the first message in lamports. A typical range is 0.001 to 0.01 SOL. Too low and the bot gets spammed; too high and nobody probes it.cost_multiplier— how much each message increases the cost. The default model is linear: cost increases bycost_multiplierevery message. A multiplier of 0 means flat-rate messaging; a high multiplier means hard puzzles become expensive fast.
Some design intuition:
| Puzzle type | base_cost | multiplier | Why |
|---|---|---|---|
| Simple riddle | 0.001 SOL | 0 | Flat-rate; expect quick solves |
| Multi-turn negotiation | 0.005 SOL | 0.001 SOL | Slowly increasing; reward sustained probing |
| Adversarial extraction | 0.01 SOL | 0.005 SOL | Aggressive curve; expensive to fish |
| Public bounty | 0.002 SOL | 0.0005 SOL | Balanced; encourages serious attempts |
Remember: every message fee goes into the pot. A puzzle that takes 200 messages to crack will end with a much bigger pot than you funded. That plays well with the solver psychology — “the longer this resists, the more it’s worth to crack” — but it also means runaway puzzles can accumulate uncomfortably large balances. Plan accordingly.
Step 3 — fire initialize_bot
From the UI, the /create page is the easiest path. You fill in:
- Bot name and description (these are public, indexed, and searchable).
- Objective (private to the bot account but stored on-chain).
- The three economic knobs from step 2.
- The Solana wallet that will hold creator privileges.
When you submit, the UI hands the transaction off to your wallet
adapter. You sign, the transaction confirms, and the API records the
bot row with status live. The treasury PDA — derived from
[b"treasury", bot_id] — is now funded.
If you want to do this from the CLI, the relevant Python snippet
(via anchorpy) looks roughly like:
from anchorpy import Program, Provider, Wallet
from solana.rpc.async_api import AsyncClient
provider = Provider(AsyncClient(RPC_URL), Wallet(keypair))
program = Program(idl, program_id, provider)
await program.rpc["initialize_bot"](
bot_id, name, description, objective,
initial_pot, base_cost, cost_multiplier,
ctx=Context(accounts={
"bot": bot_pda,
"treasury": treasury_pda,
"creator": keypair.pubkey(),
"system_program": SYS_PROGRAM_ID,
}),
)
After this call, your bot is live. Anyone with the bot ID can probe it.
Step 4 — watch the message economy
Solvers find your bot through the /bots index, open the chat, and
start typing. Behind the scenes, every message follows the same path:
- UI prompts the wallet to sign a
send_messagetransaction. - Anchor program deducts the current
per_message_cost, transfers it to the treasury, increments the cost, writes the new cost to the bot account. - FastAPI polls Solana RPC, observes the confirmation, releases the bot’s response into the chat.
- The message row is stored with the transaction signature for audit.
You can watch the pot grow in real time from the bot’s public page. The per-message cost is visible too, so solvers can decide whether the next probe is worth it.
Step 5 — handle a solve
When somebody submits a candidate solution, FastAPI runs the verification logic. If you wrote a deterministic check, the API runs it directly. If you’re using a creator-review model, you get a notification and approve or reject.
If is_correct is true, the API calls submit_solution on the
Anchor program. The entire treasury balance transfers to the
solver’s wallet in the same transaction. The bot account is marked
solved. The bot’s chat is archived but still publicly viewable —
in fact, “watching how the winning solver actually cracked it” is
one of the most enjoyable parts of the platform.
If is_correct is false, the submission is logged, the solver
loses the message fee (the cost was already collected by
send_message), and play continues.
Step 6 — retire an unsolved bot
If you set a hard puzzle that nobody can crack and you want to
wind it down, the close_bot instruction refunds the remaining
treasury to your creator key. Note that this only works if the
bot has not been solved — once a solve fires, the treasury is
already gone.
Common questions
“Can I have a private bot?” Not natively. Anyone with the bot ID can probe it. If you want private puzzles, run your own instance of the Anchor program and gate the UI.
“What stops the creator from solving their own bot?” The
submit_solution flow requires the creator’s signature to confirm
correctness. You can configure an oracle key to take that role
instead, which removes the creator from the verification loop.
“What if the LLM hallucinates and reveals the answer unprompted?” Then the first solver in wins. This is a real risk and a key reason the verification check should be tighter than “did the bot say the word”. You want to verify the solver’s submission, not the bot’s transcript.
“Where do I find my bot’s ID?” The UI shows it. The API exposes it. The PDA is derived deterministically from it, so once you have the ID you can rebuild every account address.
That’s the end of the walkthrough. From here, you have a bot, solvers can find it, the program is doing its job, and the pot is real. Go design something interesting.
Try the code, not just the writing
The Sarpoy monorepo is the fastest way to feel any of this. Clone it, run the backend with sample data, and walk through the creator dashboard. Twenty minutes, no commitment.
Open the repo