Security and custody
Pounty holds your money while a dare is open. This page says exactly how, and exactly what that costs you in trust.
The custody model, plainly
When you create a dare, the server generates a fresh wallet for it and keeps that wallet's private key. The key is encrypted before it is written to the database, but the server can decrypt it whenever it needs to, because that is how the payout works. You fund an address whose key belongs to Pounty.
Pounty is custodial. It is not trustless, it is not non-custodial, and there is no escrow contract holding anything: the escrow is an ordinary externally owned account controlled by the server. An admin, a person, decides which submission wins, and the server signs the transfer.
Between funding a dare and the payout or refund landing, your money is under the operator's control. Post bounties you are willing to have in someone else's custody for the length of the dare.
What is encrypted, and where
| Item | Where it lives | Protection |
|---|---|---|
| Escrow private key | The dares table, one row per dare | AES-256-GCM, encrypted before it is stored and decrypted only on the payout and refund paths. |
| The encryption key | The ESCROW_ENC_KEY environment variable | A 32-byte key given as 64 hex characters. It is never written to the database. |
| The gas sponsor key | The SPONSOR_PRIVATE_KEY environment variable | A hot wallet that only ever sends small ETH top-ups to escrows. |
| Session | An httpOnly cookie named pounty_session | Server-side session row, 30 days, not readable by page scripts. |
stored form: enc:<12-byte IV, hex>:<GCM auth tag, hex>:<ciphertext, hex>
The authentication tag means a tampered ciphertext fails to decrypt instead of yielding a wrong key. A decrypted value that is not a valid private key is rejected. If ESCROW_ENC_KEY is missing, the create endpoint refuses to make a funded dare at all rather than storing a key in the clear, and any older plaintext key found in the database is re-encrypted the first time it is read.
The escrow private key is never returned by any endpoint. Admin views expose only whether a key exists, not the key itself.
What an operator can do
Being honest about this is more useful than reassurance. Whoever runs the server, with the database and the environment variables in hand, can:
| Can | Cannot |
|---|---|
| Decrypt any escrow key and move any escrow balance at any time. | Touch anything already sent. A completed payout or refund is a settled transaction on Robinhood Chain. |
| Approve or reject any submission, and so decide who is paid. | Take money out of your own wallet. Only what you sent to a specific escrow is at stake. |
| Remove a dare, which rejects its pending submissions and refunds the escrow. | Redirect a refund silently. The destination follows the recorded refund address, linked wallet and payer, and every attempt is written down. |
| Record a refund by hand and mark a dare settled. | Change history in the explorer. Anything claimed in the interface is checkable against the chain. |
Who counts as an admin
Admin rights come from the deployment's environment, never from anything a user can set. An account is an admin if its server-assigned user id is listed in ADMIN_USER_IDS, its username is in ADMIN_USERNAMES, or its linked wallet is in ADMIN_PUBLICKEYS. Every admin endpoint checks that on every request.
Because a username could otherwise be claimed by whoever registers first, any username listed in ADMIN_USERNAMES is reserved: registering it requires the admin setup secret. The safest configuration is to grant admin by user id, which is random and assigned by the server.
The scheduler endpoints are separate. They accept a bearer token equal to ADMIN_CRON_KEY or CRON_SECRET, and if neither is configured in production they stay closed rather than falling back to a development default.
What you should verify yourself
Nothing here needs the interface's cooperation. Everything below is public on https://robinhoodchain.blockscout.com.
| Check | How |
|---|---|
| The escrow got your money | Open the escrow address on the explorer, shown on the dare from the moment it is created, and look at its balance and incoming transfers. |
| The reward is the token you think it is | Compare the dare's token address with the registry. Only addresses in that list can be used, but check anyway. |
| You were actually paid | Open the payment transaction hash from the submission and confirm the recipient and the amount. |
| The fee was what was advertised | The fee transfer is its own transaction from the same escrow, immediately before the payout. |
| A refund really happened | Open the refund transaction hash on the dare and confirm the destination address is yours. |
Review and arbitration
Proof is judged by an admin. The software gives them two buttons and records the outcome with a reason; it does not give anyone else a vote, and there is no built-in appeal, escalation or dispute window. An approval immediately rejects every other pending submission on that dare, so the decision is not reversible from inside the app once the payout has gone out.
What the reviewer is judging is the description you wrote. A vague dare is decided by one person's reading of it. The published rules govern what content is allowed and state that fraudulent proof forfeits the reward.
Limits of the system
These are known and unhidden, not oversights waiting to be discovered.
| Limit | Consequence |
|---|---|
| Custodial keys | Server compromise means escrow compromise. Funds in flight are only as safe as the deployment. |
| Admin discretion | There is no on-chain arbitration and no appeal path in the software. |
| Gas dust | Token payouts leave a small ETH remainder in the escrow wallet. It is neither swept nor returned. |
| Manual refunds | A refund the scheduler cannot complete waits for a person. The money stays in the escrow until then. |
| Wrong-chain and wrong-token deposits | Recoverable only if they landed in an escrow whose key the server holds, and never automatically. Anything sent on another network is gone. |
| Third-party prices | Dollar values come from CoinGecko and Dexscreener. They can be stale or unavailable, and the site says so rather than inventing a number. |
| Wallet linking | A wallet linked through /api/auth/link-phantom is proved by a signature. A wallet supplied while registering is not signature-checked, so treat a linked wallet as a convenience and set an explicit refund address when it matters. |
| Account recovery | An account is a username, a device fingerprint and a recovery code. Lose the code and the device, and the account, along with the dares tied to it, is not recoverable. |
If any of this is unacceptable for the size of a bounty you had in mind, the right response is a smaller bounty, not a longer one.