diff --git a/common/comment.ts b/common/comment.ts
index cdb62fd3..71c04af4 100644
--- a/common/comment.ts
+++ b/common/comment.ts
@@ -18,6 +18,7 @@ export type Comment<T extends AnyCommentType = AnyCommentType> = {
   userName: string
   userUsername: string
   userAvatarUrl?: string
+  bountiesAwarded?: number
 } & T
 
 export type OnContract = {
diff --git a/common/contract.ts b/common/contract.ts
index 248c9745..2e9d94c4 100644
--- a/common/contract.ts
+++ b/common/contract.ts
@@ -62,6 +62,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
   featuredOnHomeRank?: number
   likedByUserIds?: string[]
   likedByUserCount?: number
+  openCommentBounties?: number
 } & T
 
 export type BinaryContract = Contract & Binary
diff --git a/common/economy.ts b/common/economy.ts
index 7ec52b30..d25a0c71 100644
--- a/common/economy.ts
+++ b/common/economy.ts
@@ -15,3 +15,4 @@ export const BETTING_STREAK_BONUS_AMOUNT =
 export const BETTING_STREAK_BONUS_MAX = econ?.BETTING_STREAK_BONUS_MAX ?? 50
 export const BETTING_STREAK_RESET_HOUR = econ?.BETTING_STREAK_RESET_HOUR ?? 7
 export const FREE_MARKETS_PER_USER_MAX = econ?.FREE_MARKETS_PER_USER_MAX ?? 5
+export const COMMENT_BOUNTY_AMOUNT = econ?.COMMENT_BOUNTY_AMOUNT ?? 250
diff --git a/common/envs/prod.ts b/common/envs/prod.ts
index d0469d84..38dd4feb 100644
--- a/common/envs/prod.ts
+++ b/common/envs/prod.ts
@@ -41,6 +41,7 @@ export type Economy = {
   BETTING_STREAK_BONUS_MAX?: number
   BETTING_STREAK_RESET_HOUR?: number
   FREE_MARKETS_PER_USER_MAX?: number
+  COMMENT_BOUNTY_AMOUNT?: number
 }
 
 type FirebaseConfig = {
diff --git a/common/txn.ts b/common/txn.ts
index 2b7a32e8..c404059d 100644
--- a/common/txn.ts
+++ b/common/txn.ts
@@ -8,6 +8,7 @@ type AnyTxnType =
   | UniqueBettorBonus
   | BettingStreakBonus
   | CancelUniqueBettorBonus
+  | CommentBountyRefund
 type SourceType = 'USER' | 'CONTRACT' | 'CHARITY' | 'BANK'
 
 export type Txn<T extends AnyTxnType = AnyTxnType> = {
@@ -31,6 +32,8 @@ export type Txn<T extends AnyTxnType = AnyTxnType> = {
     | 'UNIQUE_BETTOR_BONUS'
     | 'BETTING_STREAK_BONUS'
     | 'CANCEL_UNIQUE_BETTOR_BONUS'
+    | 'COMMENT_BOUNTY'
+    | 'REFUND_COMMENT_BOUNTY'
 
   // Any extra data
   data?: { [key: string]: any }
@@ -98,6 +101,34 @@ type CancelUniqueBettorBonus = {
   }
 }
 
+type CommentBountyDeposit = {
+  fromType: 'USER'
+  toType: 'BANK'
+  category: 'COMMENT_BOUNTY'
+  data: {
+    contractId: string
+  }
+}
+
+type CommentBountyWithdrawal = {
+  fromType: 'BANK'
+  toType: 'USER'
+  category: 'COMMENT_BOUNTY'
+  data: {
+    contractId: string
+    commentId: string
+  }
+}
+
+type CommentBountyRefund = {
+  fromType: 'BANK'
+  toType: 'USER'
+  category: 'REFUND_COMMENT_BOUNTY'
+  data: {
+    contractId: string
+  }
+}
+
 export type DonationTxn = Txn & Donation
 export type TipTxn = Txn & Tip
 export type ManalinkTxn = Txn & Manalink
@@ -105,3 +136,5 @@ export type ReferralTxn = Txn & Referral
 export type BettingStreakBonusTxn = Txn & BettingStreakBonus
 export type UniqueBettorBonusTxn = Txn & UniqueBettorBonus
 export type CancelUniqueBettorBonusTxn = Txn & CancelUniqueBettorBonus
+export type CommentBountyDepositTxn = Txn & CommentBountyDeposit
+export type CommentBountyWithdrawalTxn = Txn & CommentBountyWithdrawal
diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts
index 038e0142..9bd73d05 100644
--- a/functions/src/create-notification.ts
+++ b/functions/src/create-notification.ts
@@ -1046,3 +1046,47 @@ export const createContractResolvedNotifications = async (
     )
   )
 }
+
+export const createBountyNotification = async (
+  fromUser: User,
+  toUserId: string,
+  amount: number,
+  idempotencyKey: string,
+  contract: Contract,
+  commentId?: string
+) => {
+  const privateUser = await getPrivateUser(toUserId)
+  if (!privateUser) return
+  const { sendToBrowser } = getNotificationDestinationsForUser(
+    privateUser,
+    'tip_received'
+  )
+  if (!sendToBrowser) return
+
+  const slug = commentId
+  const notificationRef = firestore
+    .collection(`/users/${toUserId}/notifications`)
+    .doc(idempotencyKey)
+  const notification: Notification = {
+    id: idempotencyKey,
+    userId: toUserId,
+    reason: 'tip_received',
+    createdTime: Date.now(),
+    isSeen: false,
+    sourceId: commentId ? commentId : contract.id,
+    sourceType: 'tip',
+    sourceUpdateType: 'created',
+    sourceUserName: fromUser.name,
+    sourceUserUsername: fromUser.username,
+    sourceUserAvatarUrl: fromUser.avatarUrl,
+    sourceText: amount.toString(),
+    sourceContractCreatorUsername: contract.creatorUsername,
+    sourceContractTitle: contract.question,
+    sourceContractSlug: contract.slug,
+    sourceSlug: slug,
+    sourceTitle: contract.question,
+  }
+  return await notificationRef.set(removeUndefinedProps(notification))
+
+  // maybe TODO: send email notification to comment creator
+}
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 9a8ec232..f5c45004 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -52,6 +52,7 @@ export * from './unsubscribe'
 export * from './stripe'
 export * from './mana-bonus-email'
 export * from './close-market'
+export * from './update-comment-bounty'
 
 import { health } from './health'
 import { transact } from './transact'
@@ -65,6 +66,7 @@ import { sellshares } from './sell-shares'
 import { claimmanalink } from './claim-manalink'
 import { createmarket } from './create-market'
 import { addliquidity } from './add-liquidity'
+import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
 import { withdrawliquidity } from './withdraw-liquidity'
 import { creategroup } from './create-group'
 import { resolvemarket } from './resolve-market'
@@ -91,6 +93,8 @@ const sellSharesFunction = toCloudFunction(sellshares)
 const claimManalinkFunction = toCloudFunction(claimmanalink)
 const createMarketFunction = toCloudFunction(createmarket)
 const addLiquidityFunction = toCloudFunction(addliquidity)
+const addCommentBounty = toCloudFunction(addcommentbounty)
+const awardCommentBounty = toCloudFunction(awardcommentbounty)
 const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity)
 const createGroupFunction = toCloudFunction(creategroup)
 const resolveMarketFunction = toCloudFunction(resolvemarket)
@@ -127,4 +131,6 @@ export {
   acceptChallenge as acceptchallenge,
   createPostFunction as createpost,
   saveTwitchCredentials as savetwitchcredentials,
+  addCommentBounty as addcommentbounty,
+  awardCommentBounty as awardcommentbounty,
 }
diff --git a/functions/src/on-update-contract.ts b/functions/src/on-update-contract.ts
index 5e2a94c0..d667f0d2 100644
--- a/functions/src/on-update-contract.ts
+++ b/functions/src/on-update-contract.ts
@@ -1,44 +1,118 @@
 import * as functions from 'firebase-functions'
-import { getUser } from './utils'
+import { getUser, getValues, log } from './utils'
 import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
 import { Contract } from '../../common/contract'
+import { Txn } from '../../common/txn'
+import { partition, sortBy } from 'lodash'
+import { runTxn, TxnData } from './transact'
+import * as admin from 'firebase-admin'
 
 export const onUpdateContract = functions.firestore
   .document('contracts/{contractId}')
   .onUpdate(async (change, context) => {
     const contract = change.after.data() as Contract
+    const previousContract = change.before.data() as Contract
     const { eventId } = context
-
-    const contractUpdater = await getUser(contract.creatorId)
-    if (!contractUpdater) throw new Error('Could not find contract updater')
-
-    const previousValue = change.before.data() as Contract
-
-    // Resolution is handled in resolve-market.ts
-    if (!previousValue.isResolved && contract.isResolved) return
+    const { openCommentBounties, closeTime, question } = contract
 
     if (
-      previousValue.closeTime !== contract.closeTime ||
-      previousValue.question !== contract.question
+      !previousContract.isResolved &&
+      contract.isResolved &&
+      (openCommentBounties ?? 0) > 0
     ) {
-      let sourceText = ''
-      if (
-        previousValue.closeTime !== contract.closeTime &&
-        contract.closeTime
-      ) {
-        sourceText = contract.closeTime.toString()
-      } else if (previousValue.question !== contract.question) {
-        sourceText = contract.question
-      }
-
-      await createCommentOrAnswerOrUpdatedContractNotification(
-        contract.id,
-        'contract',
-        'updated',
-        contractUpdater,
-        eventId,
-        sourceText,
-        contract
-      )
+      await handleUnusedCommentBountyRefunds(contract)
+      // No need to notify users of resolution, that's handled in resolve-market
+      return
+    }
+    if (
+      previousContract.closeTime !== closeTime ||
+      previousContract.question !== question
+    ) {
+      await handleUpdatedCloseTime(previousContract, contract, eventId)
     }
   })
+
+async function handleUpdatedCloseTime(
+  previousContract: Contract,
+  contract: Contract,
+  eventId: string
+) {
+  const contractUpdater = await getUser(contract.creatorId)
+  if (!contractUpdater) throw new Error('Could not find contract updater')
+  let sourceText = ''
+  if (previousContract.closeTime !== contract.closeTime && contract.closeTime) {
+    sourceText = contract.closeTime.toString()
+  } else if (previousContract.question !== contract.question) {
+    sourceText = contract.question
+  }
+
+  await createCommentOrAnswerOrUpdatedContractNotification(
+    contract.id,
+    'contract',
+    'updated',
+    contractUpdater,
+    eventId,
+    sourceText,
+    contract
+  )
+}
+
+async function handleUnusedCommentBountyRefunds(contract: Contract) {
+  const outstandingCommentBounties = await getValues<Txn>(
+    firestore.collection('txns').where('category', '==', 'COMMENT_BOUNTY')
+  )
+
+  const commentBountiesOnThisContract = sortBy(
+    outstandingCommentBounties.filter(
+      (bounty) => bounty.data?.contractId === contract.id
+    ),
+    (bounty) => bounty.createdTime
+  )
+
+  const [toBank, fromBank] = partition(
+    commentBountiesOnThisContract,
+    (bounty) => bounty.toType === 'BANK'
+  )
+  if (toBank.length <= fromBank.length) return
+
+  await firestore
+    .collection('contracts')
+    .doc(contract.id)
+    .update({ openCommentBounties: 0 })
+
+  const refunds = toBank.slice(fromBank.length)
+  await Promise.all(
+    refunds.map(async (extraBountyTxn) => {
+      const result = await firestore.runTransaction(async (trans) => {
+        const bonusTxn: TxnData = {
+          fromId: extraBountyTxn.toId,
+          fromType: 'BANK',
+          toId: extraBountyTxn.fromId,
+          toType: 'USER',
+          amount: extraBountyTxn.amount,
+          token: 'M$',
+          category: 'REFUND_COMMENT_BOUNTY',
+          data: {
+            contractId: contract.id,
+          },
+        }
+        return await runTxn(trans, bonusTxn)
+      })
+
+      if (result.status != 'success' || !result.txn) {
+        log(
+          `Couldn't refund bonus for user: ${extraBountyTxn.fromId} - status:`,
+          result.status
+        )
+        log('message:', result.message)
+      } else {
+        log(
+          `Refund bonus txn for user: ${extraBountyTxn.fromId} completed:`,
+          result.txn?.id
+        )
+      }
+    })
+  )
+}
+
+const firestore = admin.firestore()
diff --git a/functions/src/serve.ts b/functions/src/serve.ts
index 99ac6281..d861dcbc 100644
--- a/functions/src/serve.ts
+++ b/functions/src/serve.ts
@@ -29,6 +29,7 @@ import { getcurrentuser } from './get-current-user'
 import { createpost } from './create-post'
 import { savetwitchcredentials } from './save-twitch-credentials'
 import { testscheduledfunction } from './test-scheduled-function'
+import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
 
 type Middleware = (req: Request, res: Response, next: NextFunction) => void
 const app = express()
@@ -61,6 +62,8 @@ addJsonEndpointRoute('/sellshares', sellshares)
 addJsonEndpointRoute('/claimmanalink', claimmanalink)
 addJsonEndpointRoute('/createmarket', createmarket)
 addJsonEndpointRoute('/addliquidity', addliquidity)
+addJsonEndpointRoute('/addCommentBounty', addcommentbounty)
+addJsonEndpointRoute('/awardCommentBounty', awardcommentbounty)
 addJsonEndpointRoute('/withdrawliquidity', withdrawliquidity)
 addJsonEndpointRoute('/creategroup', creategroup)
 addJsonEndpointRoute('/resolvemarket', resolvemarket)
diff --git a/functions/src/update-comment-bounty.ts b/functions/src/update-comment-bounty.ts
new file mode 100644
index 00000000..af1d6c0a
--- /dev/null
+++ b/functions/src/update-comment-bounty.ts
@@ -0,0 +1,162 @@
+import * as admin from 'firebase-admin'
+import { z } from 'zod'
+
+import { Contract } from '../../common/contract'
+import { User } from '../../common/user'
+import { removeUndefinedProps } from '../../common/util/object'
+import { APIError, newEndpoint, validate } from './api'
+import {
+  DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+  HOUSE_LIQUIDITY_PROVIDER_ID,
+} from '../../common/antes'
+import { isProd } from './utils'
+import {
+  CommentBountyDepositTxn,
+  CommentBountyWithdrawalTxn,
+} from '../../common/txn'
+import { runTxn } from './transact'
+import { Comment } from '../../common/comment'
+import { createBountyNotification } from './create-notification'
+
+const bodySchema = z.object({
+  contractId: z.string(),
+  amount: z.number().gt(0),
+})
+const awardBodySchema = z.object({
+  contractId: z.string(),
+  commentId: z.string(),
+  amount: z.number().gt(0),
+})
+
+export const addcommentbounty = newEndpoint({}, async (req, auth) => {
+  const { amount, contractId } = validate(bodySchema, req.body)
+
+  if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
+
+  // run as transaction to prevent race conditions
+  return await firestore.runTransaction(async (transaction) => {
+    const userDoc = firestore.doc(`users/${auth.uid}`)
+    const userSnap = await transaction.get(userDoc)
+    if (!userSnap.exists) throw new APIError(400, 'User not found')
+    const user = userSnap.data() as User
+
+    const contractDoc = firestore.doc(`contracts/${contractId}`)
+    const contractSnap = await transaction.get(contractDoc)
+    if (!contractSnap.exists) throw new APIError(400, 'Invalid contract')
+    const contract = contractSnap.data() as Contract
+
+    if (user.balance < amount)
+      throw new APIError(400, 'Insufficient user balance')
+
+    const newCommentBountyTxn = {
+      fromId: user.id,
+      fromType: 'USER',
+      toId: isProd()
+        ? HOUSE_LIQUIDITY_PROVIDER_ID
+        : DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+      toType: 'BANK',
+      amount,
+      token: 'M$',
+      category: 'COMMENT_BOUNTY',
+      data: {
+        contractId,
+      },
+      description: `Deposit M$${amount} from ${user.id} for comment bounty for contract ${contractId}`,
+    } as CommentBountyDepositTxn
+
+    const result = await runTxn(transaction, newCommentBountyTxn)
+
+    transaction.update(
+      contractDoc,
+      removeUndefinedProps({
+        openCommentBounties: (contract.openCommentBounties ?? 0) + amount,
+      })
+    )
+
+    return result
+  })
+})
+export const awardcommentbounty = newEndpoint({}, async (req, auth) => {
+  const { amount, commentId, contractId } = validate(awardBodySchema, req.body)
+
+  if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
+
+  // run as transaction to prevent race conditions
+  const res = await firestore.runTransaction(async (transaction) => {
+    const userDoc = firestore.doc(`users/${auth.uid}`)
+    const userSnap = await transaction.get(userDoc)
+    if (!userSnap.exists) throw new APIError(400, 'User not found')
+    const user = userSnap.data() as User
+
+    const contractDoc = firestore.doc(`contracts/${contractId}`)
+    const contractSnap = await transaction.get(contractDoc)
+    if (!contractSnap.exists) throw new APIError(400, 'Invalid contract')
+    const contract = contractSnap.data() as Contract
+
+    if (user.id !== contract.creatorId)
+      throw new APIError(
+        400,
+        'Only contract creator can award comment bounties'
+      )
+
+    const commentDoc = firestore.doc(
+      `contracts/${contractId}/comments/${commentId}`
+    )
+    const commentSnap = await transaction.get(commentDoc)
+    if (!commentSnap.exists) throw new APIError(400, 'Invalid comment')
+
+    const comment = commentSnap.data() as Comment
+    const amountAvailable = contract.openCommentBounties ?? 0
+    if (amountAvailable < amount)
+      throw new APIError(400, 'Insufficient open bounty balance')
+
+    const newCommentBountyTxn = {
+      fromId: isProd()
+        ? HOUSE_LIQUIDITY_PROVIDER_ID
+        : DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+      fromType: 'BANK',
+      toId: comment.userId,
+      toType: 'USER',
+      amount,
+      token: 'M$',
+      category: 'COMMENT_BOUNTY',
+      data: {
+        contractId,
+        commentId,
+      },
+      description: `Withdrawal M$${amount} from BANK for comment ${comment.id} bounty for contract ${contractId}`,
+    } as CommentBountyWithdrawalTxn
+
+    const result = await runTxn(transaction, newCommentBountyTxn)
+
+    await transaction.update(
+      contractDoc,
+      removeUndefinedProps({
+        openCommentBounties: amountAvailable - amount,
+      })
+    )
+    await transaction.update(
+      commentDoc,
+      removeUndefinedProps({
+        bountiesAwarded: (comment.bountiesAwarded ?? 0) + amount,
+      })
+    )
+
+    return { ...result, comment, contract, user }
+  })
+  if (res.txn?.id) {
+    const { comment, contract, user } = res
+    await createBountyNotification(
+      user,
+      comment.userId,
+      amount,
+      res.txn.id,
+      contract,
+      comment.id
+    )
+  }
+
+  return res
+})
+
+const firestore = admin.firestore()
diff --git a/web/components/award-bounty-button.tsx b/web/components/award-bounty-button.tsx
new file mode 100644
index 00000000..7a69cf15
--- /dev/null
+++ b/web/components/award-bounty-button.tsx
@@ -0,0 +1,46 @@
+import clsx from 'clsx'
+import { ContractComment } from 'common/comment'
+import { useUser } from 'web/hooks/use-user'
+import { awardCommentBounty } from 'web/lib/firebase/api'
+import { track } from 'web/lib/service/analytics'
+import { Row } from './layout/row'
+import { Contract } from 'common/contract'
+import { TextButton } from 'web/components/text-button'
+import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
+import { formatMoney } from 'common/util/format'
+
+export function AwardBountyButton(prop: {
+  comment: ContractComment
+  contract: Contract
+}) {
+  const { comment, contract } = prop
+
+  const me = useUser()
+
+  const submit = () => {
+    const data = {
+      amount: COMMENT_BOUNTY_AMOUNT,
+      commentId: comment.id,
+      contractId: contract.id,
+    }
+
+    awardCommentBounty(data)
+      .then((_) => {
+        console.log('success')
+        track('award comment bounty', data)
+      })
+      .catch((reason) => console.log('Server error:', reason))
+
+    track('award comment bounty', data)
+  }
+
+  const canUp = me && me.id !== comment.userId && contract.creatorId === me.id
+  if (!canUp) return <div />
+  return (
+    <Row className={clsx('-ml-2 items-center gap-0.5', !canUp ? '-ml-6' : '')}>
+      <TextButton className={'font-bold'} onClick={submit}>
+        Award {formatMoney(COMMENT_BOUNTY_AMOUNT)}
+      </TextButton>
+    </Row>
+  )
+}
diff --git a/web/components/contract/add-comment-bounty.tsx b/web/components/contract/add-comment-bounty.tsx
new file mode 100644
index 00000000..8b716e71
--- /dev/null
+++ b/web/components/contract/add-comment-bounty.tsx
@@ -0,0 +1,74 @@
+import { Contract } from 'common/contract'
+import { useUser } from 'web/hooks/use-user'
+import { useState } from 'react'
+import { addCommentBounty } from 'web/lib/firebase/api'
+import { track } from 'web/lib/service/analytics'
+import { Row } from 'web/components/layout/row'
+import clsx from 'clsx'
+import { formatMoney } from 'common/util/format'
+import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
+import { Button } from 'web/components/button'
+
+export function AddCommentBountyPanel(props: { contract: Contract }) {
+  const { contract } = props
+  const { id: contractId, slug } = contract
+
+  const user = useUser()
+  const amount = COMMENT_BOUNTY_AMOUNT
+  const totalAdded = contract.openCommentBounties ?? 0
+  const [error, setError] = useState<string | undefined>(undefined)
+  const [isSuccess, setIsSuccess] = useState(false)
+  const [isLoading, setIsLoading] = useState(false)
+
+  const submit = () => {
+    if ((user?.balance ?? 0) < amount) {
+      setError('Insufficient balance')
+      return
+    }
+
+    setIsLoading(true)
+    setIsSuccess(false)
+
+    addCommentBounty({ amount, contractId })
+      .then((_) => {
+        track('offer comment bounty', {
+          amount,
+          contractId,
+        })
+        setIsSuccess(true)
+        setError(undefined)
+        setIsLoading(false)
+      })
+      .catch((_) => setError('Server error'))
+
+    track('add comment bounty', { amount, contractId, slug })
+  }
+
+  return (
+    <>
+      <div className="mb-4 text-gray-500">
+        Add a {formatMoney(amount)} bounty for good comments that the creator
+        can award.{' '}
+        {totalAdded > 0 && `(${formatMoney(totalAdded)} currently added)`}
+      </div>
+
+      <Row className={'items-center gap-2'}>
+        <Button
+          className={clsx('ml-2', isLoading && 'btn-disabled')}
+          onClick={submit}
+          disabled={isLoading}
+          color={'blue'}
+        >
+          Add {formatMoney(amount)} bounty
+        </Button>
+        <span className={'text-error'}>{error}</span>
+      </Row>
+
+      {isSuccess && amount && (
+        <div>Success! Added {formatMoney(amount)} in bounties.</div>
+      )}
+
+      {isLoading && <div>Processing...</div>}
+    </>
+  )
+}
diff --git a/web/components/contract/bountied-contract-badge.tsx b/web/components/contract/bountied-contract-badge.tsx
new file mode 100644
index 00000000..8e3e8c5b
--- /dev/null
+++ b/web/components/contract/bountied-contract-badge.tsx
@@ -0,0 +1,9 @@
+import { CurrencyDollarIcon } from '@heroicons/react/outline'
+
+export function BountiedContractBadge() {
+  return (
+    <span className="inline-flex items-center gap-1 rounded-full bg-blue-100 px-3  py-0.5 text-sm font-medium text-blue-800">
+      <CurrencyDollarIcon className={'h4 w-4'} /> Bounty
+    </span>
+  )
+}
diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx
index fc4bcfcf..22167a9c 100644
--- a/web/components/contract/contract-details.tsx
+++ b/web/components/contract/contract-details.tsx
@@ -32,6 +32,7 @@ import { PlusCircleIcon } from '@heroicons/react/solid'
 import { GroupLink } from 'common/group'
 import { Subtitle } from '../subtitle'
 import { useIsMobile } from 'web/hooks/use-is-mobile'
+import { BountiedContractBadge } from 'web/components/contract/bountied-contract-badge'
 
 export type ShowTime = 'resolve-date' | 'close-date'
 
@@ -63,6 +64,8 @@ export function MiscDetails(props: {
         </Row>
       ) : (contract?.featuredOnHomeRank ?? 0) > 0 ? (
         <FeaturedContractBadge />
+      ) : (contract.openCommentBounties ?? 0) > 0 ? (
+        <BountiedContractBadge />
       ) : volume > 0 || !isNew ? (
         <Row className={'shrink-0'}>{formatMoney(volume)} bet</Row>
       ) : (
diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx
index 5187030d..df6695ed 100644
--- a/web/components/contract/contract-info-dialog.tsx
+++ b/web/components/contract/contract-info-dialog.tsx
@@ -7,7 +7,7 @@ import { capitalize } from 'lodash'
 import { Contract } from 'common/contract'
 import { formatMoney } from 'common/util/format'
 import { contractPool, updateContract } from 'web/lib/firebase/contracts'
-import { LiquidityPanel } from '../liquidity-panel'
+import { LiquidityBountyPanel } from 'web/components/contract/liquidity-bounty-panel'
 import { Col } from '../layout/col'
 import { Modal } from '../layout/modal'
 import { Title } from '../title'
@@ -196,9 +196,7 @@ export function ContractInfoDialog(props: {
           <Row className="flex-wrap">
             <DuplicateContractButton contract={contract} />
           </Row>
-          {contract.mechanism === 'cpmm-1' && !contract.resolution && (
-            <LiquidityPanel contract={contract} />
-          )}
+          {!contract.resolution && <LiquidityBountyPanel contract={contract} />}
         </Col>
       </Modal>
     </>
diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx
index 33a3c05a..e53881d3 100644
--- a/web/components/contract/contract-tabs.tsx
+++ b/web/components/contract/contract-tabs.tsx
@@ -5,7 +5,7 @@ import { FeedBet } from '../feed/feed-bets'
 import { FeedLiquidity } from '../feed/feed-liquidity'
 import { FeedAnswerCommentGroup } from '../feed/feed-answer-comment-group'
 import { FeedCommentThread, ContractCommentInput } from '../feed/feed-comments'
-import { groupBy, sortBy } from 'lodash'
+import { groupBy, sortBy, sum } from 'lodash'
 import { Bet } from 'common/bet'
 import { Contract } from 'common/contract'
 import { PAST_BETS } from 'common/user'
@@ -25,6 +25,13 @@ import {
 import { buildArray } from 'common/util/array'
 import { ContractComment } from 'common/comment'
 
+import { formatMoney } from 'common/util/format'
+import { Button } from 'web/components/button'
+import { MINUTE_MS } from 'common/util/time'
+import { useUser } from 'web/hooks/use-user'
+import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
+import { Tooltip } from 'web/components/tooltip'
+
 export function ContractTabs(props: {
   contract: Contract
   bets: Bet[]
@@ -32,6 +39,7 @@ export function ContractTabs(props: {
   comments: ContractComment[]
 }) {
   const { contract, bets, userBets, comments } = props
+  const { openCommentBounties } = contract
 
   const yourTrades = (
     <div>
@@ -43,8 +51,16 @@ export function ContractTabs(props: {
 
   const tabs = buildArray(
     {
-      title: 'Comments',
+      title: `Comments`,
+      tooltip: openCommentBounties
+        ? `The creator of this market may award ${formatMoney(
+            COMMENT_BOUNTY_AMOUNT
+          )} for good comments. ${formatMoney(
+            openCommentBounties
+          )} currently available.`
+        : undefined,
       content: <CommentsTabContent contract={contract} comments={comments} />,
+      inlineTabIcon: <span>({formatMoney(COMMENT_BOUNTY_AMOUNT)})</span>,
     },
     {
       title: capitalize(PAST_BETS),
@@ -68,6 +84,8 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
   const { contract } = props
   const tips = useTipTxns({ contractId: contract.id })
   const comments = useComments(contract.id) ?? props.comments
+  const [sort, setSort] = useState<'Newest' | 'Best'>('Best')
+  const me = useUser()
   if (comments == null) {
     return <LoadingIndicator />
   }
@@ -119,12 +137,44 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
       </>
     )
   } else {
-    const commentsByParent = groupBy(comments, (c) => c.replyToCommentId ?? '_')
+    const tipsOrBountiesAwarded =
+      Object.keys(tips).length > 0 || comments.some((c) => c.bountiesAwarded)
+
+    const commentsByParent = groupBy(
+      sortBy(comments, (c) =>
+        sort === 'Newest'
+          ? -c.createdTime
+          : // Is this too magic? If there are tips/bounties, 'Best' shows your own comments made within the last 10 minutes first, then sorts by score
+          tipsOrBountiesAwarded &&
+            c.createdTime > Date.now() - 10 * MINUTE_MS &&
+            c.userId === me?.id
+          ? -Infinity
+          : -((c.bountiesAwarded ?? 0) + sum(Object.values(tips[c.id] ?? [])))
+      ),
+      (c) => c.replyToCommentId ?? '_'
+    )
+
     const topLevelComments = commentsByParent['_'] ?? []
     return (
       <>
+        <Button
+          size={'xs'}
+          color={'gray-white'}
+          className="mb-4"
+          onClick={() => setSort(sort === 'Newest' ? 'Best' : 'Newest')}
+        >
+          <Tooltip
+            text={
+              sort === 'Best'
+                ? 'Comments with tips or bounties will be shown first. Your comments made within the last 10 minutes will temporarily appear (to you) first.'
+                : ''
+            }
+          >
+            Sorted by: {sort}
+          </Tooltip>
+        </Button>
         <ContractCommentInput className="mb-5" contract={contract} />
-        {sortBy(topLevelComments, (c) => -c.createdTime).map((parent) => (
+        {topLevelComments.map((parent) => (
           <FeedCommentThread
             key={parent.id}
             contract={contract}
diff --git a/web/components/liquidity-panel.tsx b/web/components/contract/liquidity-bounty-panel.tsx
similarity index 77%
rename from web/components/liquidity-panel.tsx
rename to web/components/contract/liquidity-bounty-panel.tsx
index 7e216be5..4cc7fd70 100644
--- a/web/components/liquidity-panel.tsx
+++ b/web/components/contract/liquidity-bounty-panel.tsx
@@ -1,27 +1,30 @@
 import clsx from 'clsx'
 import { useEffect, useState } from 'react'
 
-import { CPMMContract } from 'common/contract'
+import { Contract, CPMMContract } from 'common/contract'
 import { formatMoney } from 'common/util/format'
 import { useUser } from 'web/hooks/use-user'
 import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/api'
-import { AmountInput } from './amount-input'
-import { Row } from './layout/row'
+import { AmountInput } from 'web/components/amount-input'
+import { Row } from 'web/components/layout/row'
 import { useUserLiquidity } from 'web/hooks/use-liquidity'
-import { Tabs } from './layout/tabs'
-import { NoLabel, YesLabel } from './outcome-label'
-import { Col } from './layout/col'
+import { Tabs } from 'web/components/layout/tabs'
+import { NoLabel, YesLabel } from 'web/components/outcome-label'
+import { Col } from 'web/components/layout/col'
 import { track } from 'web/lib/service/analytics'
-import { InfoTooltip } from './info-tooltip'
+import { InfoTooltip } from 'web/components/info-tooltip'
 import { BETTORS, PRESENT_BET } from 'common/user'
 import { buildArray } from 'common/util/array'
 import { useAdmin } from 'web/hooks/use-admin'
+import { AddCommentBountyPanel } from 'web/components/contract/add-comment-bounty'
 
-export function LiquidityPanel(props: { contract: CPMMContract }) {
+export function LiquidityBountyPanel(props: { contract: Contract }) {
   const { contract } = props
 
+  const isCPMM = contract.mechanism === 'cpmm-1'
   const user = useUser()
-  const lpShares = useUserLiquidity(contract, user?.id ?? '')
+  // eslint-disable-next-line react-hooks/rules-of-hooks
+  const lpShares = isCPMM && useUserLiquidity(contract, user?.id ?? '')
 
   const [showWithdrawal, setShowWithdrawal] = useState(false)
 
@@ -33,28 +36,34 @@ export function LiquidityPanel(props: { contract: CPMMContract }) {
   const isCreator = user?.id === contract.creatorId
   const isAdmin = useAdmin()
 
-  if (!showWithdrawal && !isAdmin && !isCreator) return <></>
-
   return (
     <Tabs
       tabs={buildArray(
-        (isCreator || isAdmin) && {
-          title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
-          content: <AddLiquidityPanel contract={contract} />,
-        },
-        showWithdrawal && {
-          title: 'Withdraw',
-          content: (
-            <WithdrawLiquidityPanel
-              contract={contract}
-              lpShares={lpShares as { YES: number; NO: number }}
-            />
-          ),
-        },
         {
-          title: 'Pool',
-          content: <ViewLiquidityPanel contract={contract} />,
-        }
+          title: 'Bounty Comments',
+          content: <AddCommentBountyPanel contract={contract} />,
+        },
+        (isCreator || isAdmin) &&
+          isCPMM && {
+            title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
+            content: <AddLiquidityPanel contract={contract} />,
+          },
+        showWithdrawal &&
+          isCPMM && {
+            title: 'Withdraw',
+            content: (
+              <WithdrawLiquidityPanel
+                contract={contract}
+                lpShares={lpShares as { YES: number; NO: number }}
+              />
+            ),
+          },
+
+        (isCreator || isAdmin) &&
+          isCPMM && {
+            title: 'Pool',
+            content: <ViewLiquidityPanel contract={contract} />,
+          }
       )}
     />
   )
diff --git a/web/components/feed/feed-comments.tsx b/web/components/feed/feed-comments.tsx
index 1b62690b..20d124f8 100644
--- a/web/components/feed/feed-comments.tsx
+++ b/web/components/feed/feed-comments.tsx
@@ -19,6 +19,7 @@ import { Content } from '../editor'
 import { Editor } from '@tiptap/react'
 import { UserLink } from 'web/components/user-link'
 import { CommentInput } from '../comment-input'
+import { AwardBountyButton } from 'web/components/award-bounty-button'
 
 export type ReplyTo = { id: string; username: string }
 
@@ -85,6 +86,7 @@ export function FeedComment(props: {
     commenterPositionShares,
     commenterPositionOutcome,
     createdTime,
+    bountiesAwarded,
   } = comment
   const betOutcome = comment.betOutcome
   let bought: string | undefined
@@ -93,6 +95,7 @@ export function FeedComment(props: {
     bought = comment.betAmount >= 0 ? 'bought' : 'sold'
     money = formatMoney(Math.abs(comment.betAmount))
   }
+  const totalAwarded = bountiesAwarded ?? 0
 
   const router = useRouter()
   const highlighted = router.asPath.endsWith(`#${comment.id}`)
@@ -162,6 +165,11 @@ export function FeedComment(props: {
             createdTime={createdTime}
             elementId={comment.id}
           />
+          {totalAwarded > 0 && (
+            <span className=" text-primary ml-2 text-sm">
+              +{formatMoney(totalAwarded)}
+            </span>
+          )}
         </div>
         <Content
           className="mt-2 text-[15px] text-gray-700"
@@ -170,6 +178,9 @@ export function FeedComment(props: {
         />
         <Row className="mt-2 items-center gap-6 text-xs text-gray-500">
           {tips && <Tipper comment={comment} tips={tips} />}
+          {(contract.openCommentBounties ?? 0) > 0 && (
+            <AwardBountyButton comment={comment} contract={contract} />
+          )}
           {onReplyClick && (
             <button
               className="font-bold hover:underline"
@@ -208,28 +219,32 @@ export function ContractCommentInput(props: {
   onSubmitComment?: () => void
 }) {
   const user = useUser()
+  const { contract, parentAnswerOutcome, parentCommentId, replyTo, className } =
+    props
+  const { openCommentBounties } = contract
   async function onSubmitComment(editor: Editor) {
     if (!user) {
       track('sign in to comment')
       return await firebaseLogin()
     }
     await createCommentOnContract(
-      props.contract.id,
+      contract.id,
       editor.getJSON(),
       user,
-      props.parentAnswerOutcome,
-      props.parentCommentId
+      !!openCommentBounties,
+      parentAnswerOutcome,
+      parentCommentId
     )
     props.onSubmitComment?.()
   }
 
   return (
     <CommentInput
-      replyTo={props.replyTo}
-      parentAnswerOutcome={props.parentAnswerOutcome}
-      parentCommentId={props.parentCommentId}
+      replyTo={replyTo}
+      parentAnswerOutcome={parentAnswerOutcome}
+      parentCommentId={parentCommentId}
       onSubmitComment={onSubmitComment}
-      className={props.className}
+      className={className}
     />
   )
 }
diff --git a/web/components/layout/tabs.tsx b/web/components/layout/tabs.tsx
index b82131ec..deff2203 100644
--- a/web/components/layout/tabs.tsx
+++ b/web/components/layout/tabs.tsx
@@ -3,13 +3,15 @@ import { useRouter, NextRouter } from 'next/router'
 import { ReactNode, useState } from 'react'
 import { track } from '@amplitude/analytics-browser'
 import { Col } from './col'
+import { Tooltip } from 'web/components/tooltip'
+import { Row } from 'web/components/layout/row'
 
 type Tab = {
   title: string
-  tabIcon?: ReactNode
   content: ReactNode
-  // If set, show a badge with this content
-  badge?: string
+  stackedTabIcon?: ReactNode
+  inlineTabIcon?: ReactNode
+  tooltip?: string
 }
 
 type TabProps = {
@@ -56,12 +58,16 @@ export function ControlledTabs(props: TabProps & { activeIndex: number }) {
             )}
             aria-current={activeIndex === i ? 'page' : undefined}
           >
-            {tab.badge ? (
-              <span className="px-0.5 font-bold">{tab.badge}</span>
-            ) : null}
             <Col>
-              {tab.tabIcon && <div className="mx-auto">{tab.tabIcon}</div>}
-              {tab.title}
+              <Tooltip text={tab.tooltip}>
+                {tab.stackedTabIcon && (
+                  <Row className="justify-center">{tab.stackedTabIcon}</Row>
+                )}
+                <Row className={'gap-1 '}>
+                  {tab.title}
+                  {tab.inlineTabIcon}
+                </Row>
+              </Tooltip>
             </Col>
           </a>
         ))}
diff --git a/web/components/tipper.tsx b/web/components/tipper.tsx
index ccb8361f..1dcb0f05 100644
--- a/web/components/tipper.tsx
+++ b/web/components/tipper.tsx
@@ -116,7 +116,7 @@ function DownTip(props: { onClick?: () => void }) {
       noTap
     >
       <button
-        className="hover:text-red-600 disabled:text-gray-300"
+        className="hover:text-red-600 disabled:text-gray-100"
         disabled={!onClick}
         onClick={onClick}
       >
@@ -137,7 +137,7 @@ function UpTip(props: { onClick?: () => void; value: number }) {
       noTap
     >
       <button
-        className="hover:text-primary disabled:text-gray-300"
+        className="hover:text-primary disabled:text-gray-100"
         disabled={!onClick}
         onClick={onClick}
       >
diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx
index 623b4d35..f9f77cf6 100644
--- a/web/components/user-page.tsx
+++ b/web/components/user-page.tsx
@@ -192,7 +192,7 @@ export function UserPage(props: { user: User }) {
             tabs={[
               {
                 title: 'Markets',
-                tabIcon: <ScaleIcon className="h-5" />,
+                stackedTabIcon: <ScaleIcon className="h-5" />,
                 content: (
                   <>
                     <Spacer h={4} />
@@ -202,7 +202,7 @@ export function UserPage(props: { user: User }) {
               },
               {
                 title: 'Portfolio',
-                tabIcon: <FolderIcon className="h-5" />,
+                stackedTabIcon: <FolderIcon className="h-5" />,
                 content: (
                   <>
                     <Spacer h={4} />
@@ -214,7 +214,7 @@ export function UserPage(props: { user: User }) {
               },
               {
                 title: 'Comments',
-                tabIcon: <ChatIcon className="h-5" />,
+                stackedTabIcon: <ChatIcon className="h-5" />,
                 content: (
                   <>
                     <Spacer h={4} />
diff --git a/web/lib/firebase/api.ts b/web/lib/firebase/api.ts
index 8aa7a067..3e803bc6 100644
--- a/web/lib/firebase/api.ts
+++ b/web/lib/firebase/api.ts
@@ -46,6 +46,14 @@ export function addLiquidity(params: any) {
   return call(getFunctionUrl('addliquidity'), 'POST', params)
 }
 
+export function addCommentBounty(params: any) {
+  return call(getFunctionUrl('addcommentbounty'), 'POST', params)
+}
+
+export function awardCommentBounty(params: any) {
+  return call(getFunctionUrl('awardcommentbounty'), 'POST', params)
+}
+
 export function withdrawLiquidity(params: any) {
   return call(getFunctionUrl('withdrawliquidity'), 'POST', params)
 }
diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts
index 733a1e06..e1b4ccef 100644
--- a/web/lib/firebase/comments.ts
+++ b/web/lib/firebase/comments.ts
@@ -35,6 +35,7 @@ export async function createCommentOnContract(
   contractId: string,
   content: JSONContent,
   user: User,
+  onContractWithBounty: boolean,
   answerOutcome?: string,
   replyToCommentId?: string
 ) {
@@ -50,7 +51,8 @@ export async function createCommentOnContract(
     content,
     user,
     ref,
-    replyToCommentId
+    replyToCommentId,
+    onContractWithBounty
   )
 }
 export async function createCommentOnGroup(
@@ -95,7 +97,8 @@ async function createComment(
   content: JSONContent,
   user: User,
   ref: DocumentReference<DocumentData>,
-  replyToCommentId?: string
+  replyToCommentId?: string,
+  onContractWithBounty?: boolean
 ) {
   const comment = removeUndefinedProps({
     id: ref.id,
@@ -108,13 +111,19 @@ async function createComment(
     replyToCommentId: replyToCommentId,
     ...extraFields,
   })
-
-  track(`${extraFields.commentType} message`, {
-    user,
-    commentId: ref.id,
-    surfaceId,
-    replyToCommentId: replyToCommentId,
-  })
+  track(
+    `${extraFields.commentType} message`,
+    removeUndefinedProps({
+      user,
+      commentId: ref.id,
+      surfaceId,
+      replyToCommentId: replyToCommentId,
+      onContractWithBounty:
+        extraFields.commentType === 'contract'
+          ? onContractWithBounty
+          : undefined,
+    })
+  )
   return await setDoc(ref, comment)
 }
 
diff --git a/web/package.json b/web/package.json
index a3ec9aaa..a5fa8ced 100644
--- a/web/package.json
+++ b/web/package.json
@@ -22,7 +22,7 @@
     "@amplitude/analytics-browser": "0.4.1",
     "@floating-ui/react-dom-interactions": "0.9.2",
     "@headlessui/react": "1.6.1",
-    "@heroicons/react": "1.0.5",
+    "@heroicons/react": "1.0.6",
     "@nivo/core": "0.80.0",
     "@nivo/line": "0.80.0",
     "@nivo/tooltip": "0.80.0",