Skip to content

Epic 21 — Bounty Hunter

Producers post referral bounties for hard-to-find talent. Any user can claim by referring a match. CasTars released if the referred talent is hired. Referenced in spec "AFTER THOUGHTS #7". Distinct from the milestone-based bounty in Epic 13 (CasTars).


BE-BOUNTY-001 — Bounty Hunter model and API

  • [x] Done (built 2026-06-14 — src/services/bountyHunter, migrations 20260614175547_bounty_hunter + 20260614235340_bounty_project_and_job; escrow via ESCROW_HELD ledger source + guarded-flip idempotency)

Files:

  • Edit: prisma/schema.prisma — add TalentBounty, BountyClaim models
  • Run: pnpm db:migrate
  • Edit: src/graphql/schema/index.ts
  • Create: src/graphql/resolvers/bounty.ts
  • Create: src/services/bounty/index.ts

Schema:

prisma
model TalentBounty {
  id            String   @id @default(cuid())
  producerId    String
  producer      ProducerProfile @relation(...)
  description   String   // "Need a bilingual skateboarder, 18-22, based in NYC"
  rewardStars   Int      // CasTars attached as reward
  status        BountyStatus @default(OPEN)  // OPEN | CLAIMED | EXPIRED | CANCELLED
  expiresAt     DateTime?
  claims        BountyClaim[]
  createdAt     DateTime @default(now())
  @@map("talent_bounties")
}

model BountyClaim {
  id           String        @id @default(cuid())
  bountyId     String
  bounty       TalentBounty  @relation(...)
  claimantId   String        // user who referred
  talentId     String?       // TalentProfile referred (if on platform)
  externalLink String?       // external artist link (if not on platform)
  status       ClaimStatus   @default(PENDING)  // PENDING | SHORTLISTED | HIRED | REJECTED
  createdAt    DateTime      @default(now())
  @@map("bounty_claims")
}

enum BountyStatus { OPEN CLAIMED EXPIRED CANCELLED }
enum ClaimStatus  { PENDING SHORTLISTED HIRED REJECTED }

Key logic:

  • Producer posts bounty with rewardStars — CasTars are escrowed (deducted from producer balance immediately, held in a ESCROW ledger entry).
  • Any user claims with claimBounty(bountyId, talentId?, externalLink?) — generates a unique "Bounty Link."
  • When producer marks the referred talent as HIRED (via updateApplicationStatus), the service auto-releases escrowed CasTars to the claimant's balance.
  • If bounty expires with no hire, escrowed CasTars are refunded to producer.

Mutations: createBounty(description, rewardStars, expiresAt?), claimBounty(bountyId, talentId?, externalLink?), cancelBounty(bountyId)
Queries: openBounties(first, after): BountyConnection!, myBounties: [TalentBounty!]!, myClaims: [BountyClaim!]!


FE-BOUNTY-001 — Bounty Hunter UI

  • [x] Done (built 2026-06-14 — apps/app/src/pages/bounty, BOUNTY_HUNTER flag + notifications; E2E bounty-hunter.spec.ts)

Files:

  • Create: apps/app/src/pages/bounty/BountiesPage.tsx — browse open bounties
  • Create: apps/app/src/pages/bounty/CreateBountyPage.tsx — producer posts a bounty
  • Create: apps/app/src/hooks/useBounties.ts
  • Edit: apps/app/src/App.tsx — add /bounties route (visible in bottom nav as "Bounty Hunters" tab, already shown in Figma designs)

Description:

  • Browse page: list of open bounties with description, reward (CasTars), time remaining, claim count. "Claim" button → opens modal to tag a talent from platform or paste an external link. On claim: shows unique shareable "Bounty Link."
  • Create page (producer): description field, reward slider (min 500 CasTars), optional expiry date picker. Shows current CasTars balance and escrow warning. "Post Bounty" confirms and escrows the reward.
  • My Claims tab: claimant sees their submitted claims with status chips (Pending / Shortlisted / Hired / Rejected). Shows CasTars earned when status = Hired.