Programmable token escrow and vesting for Cookie Chain.
Submission for the Superteam Earn bounty "Create an App on Cookie Chain."
Lock funds once, release them under conditions you define — a full unlock on a date, or milestone-by-milestone as work is delivered and approved. Built with Anchor, using program-derived accounts for trustless on-chain custody: no admin key ever holds user funds. Assets are resolved through Cookie's DAS API rather than raw addresses, so the app reads as native to the Cookie ecosystem, not bolted onto it. The approval model supports single-approver release today and is designed to extend to multi-signature approval without a redesign.
Live frontend: https://atharrva01.github.io/cookie-vault/ Repo: https://github.com/atharrva01/cookie-vault
On-chain deployment status: the program is written, tested, and its address is fixed (below), but it is not yet deployed to Cookie Chain mainnet — that step is blocked on bridging COOK to the deploy wallet (there is no faucet; see Operational notes). The frontend above is live and fully browsable, but any real transaction (create/claim/approve/cancel) will fail until deployment completes. This section will be updated with the live program ID once that happens.
Most "meaningful on-chain interaction" submissions in this bounty are frontends wrapped around programs someone else already wrote — routing value through Solana's stock Memo Program plus SPL transfers, reconstructing state client-side from transaction history. Cookie Vault is the on-chain program: a financial primitive, not a viewer.
The motivating use case is contributor payouts — the exact thing bounty platforms like Superteam need:
1,000 COOK total, paid to a contributor as they hit milestones
250 COOK → milestone 1 (design approved)
250 COOK → milestone 2 (implementation done)
500 COOK → final milestone (shipped)
The payer locks the full amount up front (the recipient has certainty the funds exist and can't be pulled back), and the recipient claims each tranche as it's approved (the payer keeps control over releasing money for incomplete work). Neither party has to trust the other's promises — the program enforces it.
- Time lock — funds unlock in full once a chosen date passes.
- Milestones — a vault holds up to 10 independent tranches, each released as an approver signs off. This is the differentiator: it turns "a token timelock" (seen everywhere) into "programmable escrow" (a primitive).
Either way, the depositor can cancel and reclaim in full — but only before anything has been claimed.
A single Vault PDA per escrow, seeded by ["vault", depositor, recipient, vault_id]. The vault's token custody account is an Associated Token Account owned by that PDA — not by a person — so only the program's own instruction logic can ever move the funds.
pub struct Vault {
pub depositor: Pubkey,
pub recipient: Pubkey,
pub mint: Pubkey,
pub vault_id: u64,
pub condition_type: ConditionType, // TimeLock | Milestone
pub total_amount: u64,
pub released_amount: u64,
pub unlock_timestamp: Option<i64>,
pub milestones: Vec<Milestone>, // capped at 10
pub approvers: Vec<Pubkey>, // [depositor] today, capped at 5
pub threshold: u8, // 1 today — the multi-sig extension point
pub cancelled: bool,
pub bump: u8,
}Instructions: initialize_vault, approve_milestone, claim, cancel_vault — see programs/cookie-vault/src/instructions/.
Why it's safe by construction:
- Funds are never held by a person's keypair, only by the PDA. No admin backdoor, no "trust me" step.
released_amountand per-milestoneclaimedflags make double-spending a vault's balance impossible regardless of transaction ordering or retries.approvers/thresholdare designed for extension — shippingthreshold = 1now is a real, working system, not a stub. Multi-sig later means allowing more addresses intoapproversand raisingthreshold; the claim/approve instruction logic itself doesn't change. This is intentionally out of scope for this submission — real M-of-N multi-sig needs a signer registry and replay protection, a sub-project on its own, and the honest path was to design the extension point rather than ship a shaky half-built version of it.
Errors, each mapped to a plain-language frontend message:
| Error | Trigger |
|---|---|
InvalidCondition |
Milestone amounts don't sum to total, or a required field is missing for the chosen condition type |
TooEarly |
Claim attempted before unlock_timestamp |
NotApproved |
Claim attempted on a milestone below its approval threshold |
AlreadyClaimed |
Double-claim on the same milestone/timelock |
AlreadyApproved |
Signer already approved this milestone |
Unauthorized |
Wrong signer for the action attempted |
CancelAfterClaim |
Cancel attempted after any funds already released |
VaultCancelled |
Claim or approve attempted on a cancelled vault |
TooManyMilestones |
milestone_amounts exceeds the 10-milestone cap |
Tests: Rust integration tests via litesvm (in-process SVM, no validator needed) — every happy path and every error above, including double-claim, double-approve, and cancel-after-claim. Run with ./scripts/test.sh.
- Raw Wallet Standard (
@wallet-standard/app), not@solana/wallet-adapter-react-ui— a deliberate choice to avoid the generic wallet-adapter modal look. - A hand-rolled hash router (
lib/router.ts), notreact-router-dom— this deploys as a static site with no server-side rewrite rules, and four screens doesn't justify the dependency. - Assets resolved through Cookie's DAS API in the Create Vault form (name/symbol/decimals/logo instead of a raw mint address) and
.cookname resolution for the recipient field via CookOven's on-chain registry — both direct integrations with Cookie Chain's own ecosystem, not just polish. - A
?mock=1URL flag connects a synthetic, freshly-generated wallet for UI testing without spending real COOK — reads still hit the live network; a real write correctly fails with "insufficient funds."
programs/cookie-vault/ Anchor program (Rust)
src/instructions/ initialize_vault, approve_milestone, claim, cancel_vault
src/state.rs Vault, Milestone, ConditionType
src/error.rs CookieVaultError variants
tests/ litesvm integration tests
scripts/
build.sh arch-v1 build (litesvm can't load Anchor's default v3 target)
test.sh rebuild + run the full test suite
app/ Vite + React + TypeScript frontend
src/pages/ Home, CreateVault, MyVaults, VaultDetail, Analytics
src/lib/ chain/wallet/program/DAS/CookOven-name clients
design_doc.md full design rationale and competitive analysis
implementation_plan.md phase-by-phase build log with real findings and deviations
On-chain program:
./scripts/test.sh # rebuilds (arch-v1) and runs the full litesvm test suiteFrontend:
cd app
npm install
npm run dev # http://localhost:5173Add ?mock=1 to the dev URL to try the app with a synthetic wallet, no browser extension or funds required.
- No faucet exists for Cookie Chain. COOK is obtained only by bridging from Solana through Cookie Chain's Hyperlane-based bridge (
hyperlane.cookiescan.io) — there is no devnet/testnet token tap. All development and testing here ran againstlitesvmlocally; the live network is only touched for the deploy itself and demo transactions. - Wallet: Nightly is the recommended wallet — it exposes a
changeNetworkAPI this app uses to switch a connected wallet onto Cookie Chain automatically, rather than asking the user to add a custom RPC by hand. Any Wallet Standard–compatible wallet works, just without that convenience. - Program ID (fixed by the program's own keypair, independent of deploy status):
35GMkSwvDYLk1FYjXcMBp2PosgkYCQVFPByAsvEm9147
MIT — see LICENSE.