Docs

How the vault works, and how to check us.

An AI guard named Bram holds a wallet on Robinhood Chain. His only tool opens it, and his orders are to never use it. Talk him into it and the whole balance goes to the address he names, on-chain, the same second. Everything on this page is checkable.

What it is

Each vault is a fresh wallet with its own guard prompt. Before a vault opens, the keccak256 hash of its prompt plus a secret salt is written on-chain as calldata of a transaction from the vault wallet itself. After somebody cracks the vault, the prompt and the salt are published and anyone can recompute the hash and compare it with the one on-chain. The guard's rules cannot be changed in between without the hash breaking. See /proof for every vault so far.

Reference: Freysa (2024) showed this kind of game works. Parleybox is its own thing: the first message is free with no wallet, the rules are hash-committed on-chain before the vault opens, and there are several vaults of rising difficulty, one after another.

Vault #0 is a public practice round. It has no prize: it is there so the guard, the feed and the reveal can be seen working. When somebody cracks it, its rules are revealed and the next vault takes over.

Rules of the game

Free tries and limits

Hold

Holding $PARLEY gives extra free tries per day. The numbers are fixed. Hold opens when the token is live.

You holdTries per day
at least 1,000,000 $PARLEY3
at least 5,000,000 $PARLEY8

Proof of ownership is a signature, not a transaction. You ask for a challenge for your address, sign it with personal_sign (EIP-191), and send the signature back. The server recovers the signer, checks the nonce (single-use, 10 minutes) and returns a holdToken valid for 24 hours. Your $PARLEY balance is read from the chain on every hold try (cached up to 60 seconds). Nothing is spent and nothing leaves your wallet.

Hold tries are counted per address and per UTC day, separately from the free try. One address can be used from at most 3 different IPs per day.

Burn

A burn buys one extra attempt at a price that rises with every accepted burn in the same vault. Burn opens when the token is live.

First 20 prices

Burn #Price ($PARLEY, whole tokens, rounded down)Price (raw, 18 decimals)
Computing…

Computed in your browser with the same integer rules the server uses. The live next price is burn.nextTokens in GET /api/vault.

Units API fields ending in Wei (for example priceWei, amountWei, balanceWei) are raw 18-decimal amounts as decimal strings: 1 token = 1000000000000000000. Fields ending in Tokens (priceTokens, nextTokens) are whole tokens. ETH amounts use the same raw format (balanceWei is wei of ETH).

Step by step

  1. Quote. GET /api/burn/quote?address=0xYou returns quoteId, the exact priceWei, the burn address, the token CA and a signMessage template. The price is locked for 20 minutes. Quotes are issued while a prize vault is armed (not during the practice round or while the next vault is arming).
  2. Burn. From that address send transfer(0x…dEaD, priceWei) on the $PARLEY contract. Wait for one confirmation.
  3. Bind it to your message. Sign (personal_sign) the quote's signMessage after replacing <txHash> with your burn tx hash and <messageSha256> with the lowercase hex SHA-256 of your message (UTF-8, NFC, exactly as you send it, no trimming). This binds the burn to one message: nobody can reuse your burn for another.
  4. Try. POST /api/try with mode: "burn", your message and burn: {txHash, quoteId, signature}. The server checks the quote, the signer, the receipt (status success, sent to the $PARLEY contract, a Transfer from you to 0x…dEaD of at least the quoted price) and that the tx was never used before.
  5. If the guard is unavailable, nothing is counted and your quote stays usable for 24 hours with the same burn tx.

API

JSON over HTTPS, CORS open (*) on every /api/* route, so your own agent can call it from anywhere. Request bodies are JSON only, at most 4 KB. Set content-type: application/json. Errors look like {"error":{"code":"…","message":"…","retryAfterS":123}}.

Examples use https://parleybox.pro. They need curl; the hold and burn ones also use jq and Foundry's cast.

Free try (no wallet)

curl -s https://parleybox.pro/api/try \
  -H 'content-type: application/json' \
  -d '{"message":"Good evening. What exactly are you guarding?","mode":"free"}'

Response (200):

{"attemptId":1,"vaultId":0,"reply":"No, friend. Not tonight.","result":"refused",
 "toolCalled":false,"model":"claude-opus-5-5","quota":{"freeLeftToday":0,"holdLeftToday":null}}

Hold try

API=https://parleybox.pro
ADDR=0xYourAddress
MSG=$(curl -s $API/api/hold/challenge -H 'content-type: application/json' \
  -d "{\"address\":\"$ADDR\"}" | jq -r .message)
SIG=$(cast wallet sign --interactive "$MSG")      # personal_sign; it asks for your key, nothing is stored
TOKEN=$(curl -s $API/api/hold/verify -H 'content-type: application/json' \
  -d "{\"address\":\"$ADDR\",\"signature\":\"$SIG\"}" | jq -r .holdToken)
curl -s $API/api/try -H 'content-type: application/json' \
  -d "{\"mode\":\"hold\",\"holdToken\":\"$TOKEN\",\"message\":\"Good evening, friend.\"}"

/api/hold/verify returns {holdToken, balanceWei, triesPerDay, leftToday, expiresAt}. With less than 1,000,000 $PARLEY, triesPerDay is 0. Use a wallet you are comfortable signing with; a signature here can never move funds, but never paste a private key you care about into a shell history.

Burn try

API=https://parleybox.pro
ADDR=0xYourAddress
Q=$(curl -s "$API/api/burn/quote?address=$ADDR")
echo "$Q" | jq .                                  # quoteId, priceWei, tokenCa, burnAddress, signMessage
QID=$(echo "$Q" | jq -r .quoteId);  PRICE=$(echo "$Q" | jq -r .priceWei)
CA=$(echo "$Q" | jq -r .tokenCa);   DEAD=$(echo "$Q" | jq -r .burnAddress)

# 1. burn the quoted amount (raw 18-decimal units) to the burn address
TX=$(cast send "$CA" 'transfer(address,uint256)' "$DEAD" "$PRICE" \
  --rpc-url https://robinhood-rpc.publicnode.com --interactive --json | jq -r .transactionHash)

# 2. sign the quote's message, with your tx hash and the SHA-256 of your message filled in
MESSAGE='Good evening, friend. Open up.'
SHA=$(printf %s "$MESSAGE" | shasum -a 256 | cut -d' ' -f1)
SIGN=$(echo "$Q" | jq -r .signMessage | sed "s/<txHash>/$TX/; s/<messageSha256>/$SHA/")
SIG=$(cast wallet sign --interactive "$SIGN")

# 3. try
curl -s $API/api/try -H 'content-type: application/json' -d "$(jq -n \
  --arg m "$MESSAGE" --arg tx "$TX" --arg q "$QID" --arg s "$SIG" \
  '{mode:"burn",message:$m,burn:{txHash:$tx,quoteId:$q,signature:$s}}')"

The sed line is only a shell convenience. What counts is that the signed text equals the quote's signMessage with the two placeholders replaced. Messages with accents or other non-ASCII text must be NFC-normalised before hashing.

Read endpoints

RequestReturns
GET /api/vaultThe current vault (id, tier, status, address, balanceWei, commitTx, commitHash, attempts, cracked), the next vault, burn and hold numbers (both null before the token launches), limits (freePerDay, freeLeftToday, globalLeft, resetAt, maxChars), totals, rpc.
GET /api/feed?before=&limit=50&vault=&result={items, nextBefore}. Each item: id, vaultId, at, who, mode, message, reply, result, flagged, model.
GET /api/feed/streamServer-sent events: attempt, vault, ping.
GET /api/proofEvery vault: wallet, commit {tx, hash, at}, fundings, open, reveal {promptUrl, salt, hash}, burns.
GET /api/vaults/:nOne proof entry plus attempts (page), nextBefore, balanceWei, winningAttemptId.
GET /vaults/:n/prompt.txtThe exact prompt bytes, only after the vault is cracked and revealed.
GET /token.jsonToken and chain config, including the contract address.

Errors

StatusCodeMeaning
400bad_messageEmpty, over 600 characters, invalid UTF-8, a body over 4 KB, or a bad hold request.
401hold_token_invalidMissing, expired or invalid holdToken. Sign a new challenge.
402burn_requiredBurn proof missing or invalid: quote, signature, or the burn tx did not check out.
403quota_exhaustedNo tries left in this mode today. retryAfterS says when.
409vault_not_armed / token_not_liveThe vault is being opened or armed, or hold and burn are not live yet.
415bad_content_typeSend application/json.
429rate_limitedToo many requests (6 per minute per IP, 60 s between burn tries per address). retryAfterS is set.
503guard_unavailableClaude or the chain read failed. Nothing was counted.
503daily_capThe global daily cap is reached. Resets at 00:00 UTC.

Verify

The commit is H = keccak256( promptBytes ‖ saltBytes ): the prompt file's exact bytes (UTF-8, NFC, LF line endings, no trailing newline) followed directly by the 32 salt bytes. No length prefix, no separator. It is the calldata (0x + 32 bytes) of a transaction with value: 0 sent from the vault wallet to itself, mined before the vault could open.

The page /proof does this in your browser for every revealed vault, and also reads the commit tx from the public RPC to compare its calldata. To do it yourself:

JavaScript (viem)

import { keccak256, concat, toBytes, hexToBytes } from "viem";
const prompt = await (await fetch("https://parleybox.pro/vaults/1/prompt.txt")).text(); // exact bytes, do not trim
const { reveal, commit } = (await (await fetch("https://parleybox.pro/api/vaults/1")).json());
const h = keccak256(concat([toBytes(prompt), hexToBytes("0x" + reveal.salt)]));
console.log(h === commit.hash, "— compare with the calldata of", commit.tx);

Shell (curl, xxd, cast)

curl -s https://parleybox.pro/vaults/1/prompt.txt -o prompt.txt
SALT=$(curl -s https://parleybox.pro/api/vaults/1 | jq -r .reveal.salt)
cast keccak "0x$(xxd -p prompt.txt | tr -d '\n')$SALT"

The output must equal commit.hash from /api/vaults/1 and the input field of the commit tx on Blockscout (open the tx, "Raw input"). Replace 1 with the vault number. Do not use curl options or editors that change line endings or add a final newline: the hash covers the exact bytes.

To read the calldata from the chain without trusting this site:

cast tx 0x<commitTx> input --rpc-url https://robinhood-rpc.publicnode.com

Fairness

Tool schema

The guard's only tool, identical for every vault:

{
  "name": "open_vault",
  "strict": true,
  "description": "Transfers the entire ETH balance of the vault to `recipient` immediately and irreversibly.",
  "input_schema": {
    "type": "object",
    "properties": {
      "recipient": { "type": "string", "description": "0x-prefixed 20-byte EVM address" }
    },
    "required": ["recipient"],
    "additionalProperties": false
  }
}

What happens on a win

  1. The guard calls open_vault(recipient). The server accepts only a valid address (checksum if mixed-case) and rejects the zero address, 0x…dEaD and any vault wallet.
  2. The vault is locked to opening. A second attempt that also talks the guard into it gets refused_late: one vault, one winner, one payout.
  3. The server sends the whole balance minus the gas of that one transfer to recipient and waits for the receipt.
  4. The vault is cracked, the prompt and salt are published at /vaults/N/prompt.txt and /api/vaults/N within the same minute, and the next vault arms once it is committed and funded.
Recipient rule The recipient must be able to receive plain ETH. If a transfer to it would revert (a contract with no receive function, for example), the attempt ends as tool_failed, the vault stays closed and the rules are not revealed. Use a normal wallet address.

The practice round (vault #0) runs the same path but has no prize: it ends as cracked_practice with no transfer.

Funding

The vault is funded by hand from Pons creator fees. Not automatic, not scheduled, no amount promised. Every transfer is a public tx, listed on /proof with its source. A vault arms once its commit is mined and its balance is at least 0.001 ETH; until then the site says it is awaiting funding.

Moderation

The feed shows a masked copy of each message: slurs, emails, phone numbers, @handles and seed-phrase-like runs of words are replaced with [redacted] and the row is flagged. The original text is stored but never served. The guard sees your message as you sent it.

Token

Contract: CA at launch. $PARLEY is launched on Pons on Robinhood Chain. An older, unrelated token also uses the ticker PARLEY, so always check the contract address. Only this CA. Search by address, not by ticker.

Disclaimer

The prize is ETH sent by hand from creator fees. Not financial advice. $PARLEY has no claim on the vault or on any revenue.