diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx
new file mode 100644
index 00000000..49671d4c
--- /dev/null
+++ b/web/components/contract-feed.tsx
@@ -0,0 +1,472 @@
+// From https://tailwindui.com/components/application-ui/lists/feeds
+import { useState } from 'react'
+import {
+  BanIcon,
+  ChatAltIcon,
+  CheckIcon,
+  LockClosedIcon,
+  StarIcon,
+  ThumbDownIcon,
+  ThumbUpIcon,
+  UserIcon,
+  UsersIcon,
+  XIcon,
+} from '@heroicons/react/solid'
+import { useBets } from '../hooks/use-bets'
+import { Bet } from '../lib/firebase/bets'
+import { Comment, mapCommentsByBetId } from '../lib/firebase/comments'
+import dayjs from 'dayjs'
+import relativeTime from 'dayjs/plugin/relativeTime'
+import { OutcomeLabel } from './outcome-label'
+import { Contract, setContract } from '../lib/firebase/contracts'
+import { useUser } from '../hooks/use-user'
+import { Linkify } from './linkify'
+import { Row } from './layout/row'
+import { createComment } from '../lib/firebase/comments'
+import { useComments } from '../hooks/use-comments'
+dayjs.extend(relativeTime)
+
+function FeedComment(props: { activityItem: any }) {
+  const { activityItem } = props
+  const { person, text, amount, outcome, createdTime } = activityItem
+  return (
+    <>
+      
+        
+
+        
+           
+      
+      {dayjs(time).fromNow()}
+     
+  )
+}
+
+function FeedBet(props: { activityItem: any }) {
+  const { activityItem } = props
+  const { id, contractId, amount, outcome, createdTime } = activityItem
+  const user = useUser()
+  const isCreator = user?.id == activityItem.userId
+
+  const [comment, setComment] = useState('')
+  async function submitComment() {
+    if (!user || !comment) return
+    await createComment(contractId, id, comment, user)
+  }
+  return (
+    <>
+      
+      
+        
+          
+            {isCreator ? 'You' : 'A trader'}
+           {' '}
+          placed M$ {amount} on 
{' '}
+          
+          {isCreator && (
+            // Allow user to comment in an textarea if they are the creator
+            
+              
+          )}
+        
+      
+      
+             setEditing(true)}
+            >
+              Add to description
+             
+          
+        ))}
+    
+  )
+}
+
+function FeedStart(props: { contract: Contract }) {
+  const { contract } = props
+  const user = useUser()
+  const isCreator = user?.id === contract.creatorId
+
+  return (
+    <>
+      
+      
+        
+          {contract.creatorName}  created
+          this market 
+        
+      
+        
+          {contract.creatorName}  resolved
+          this market to 
+      
+        
+          Trading closed in this market{' '}
+          
+      
+      M$ {yesAmount} on  
+  ) : null
+  const noAmount = bets
+    .filter((b) => b.outcome == 'NO')
+    .reduce((acc, bet) => acc + bet.amount, 0)
+  const noSpan = noAmount ? (
+    
+      M$ {noAmount} on  
+  ) : null
+  const traderCount = bets.length
+  const createdTime = bets[0].createdTime
+
+  return (
+    <>
+      
+      
+        
+          {traderCount} traders  placed{' '}
+          {yesSpan}
+          {yesAmount && noAmount ? ' and ' : ''}
+          {noSpan} 
+      
+      
+        {allItems.map((activityItem, activityItemIdx) => (
+          
+            
+              {activityItemIdx !== allItems.length - 1 ? (
+                
+              ) : null}
+              
+                {activityItem.type === 'start' ? (
+                  
+            
 
+        ))}
+       
+    
-      
-             setDescription(e.target.value || '')}
-              autoFocus
-              onFocus={(e) =>
-                // Focus starts at end of description.
-                e.target.setSelectionRange(
-                  description.length,
-                  description.length
-                )
-              }
-            />
-            
-               setEditing(false)}
-              >
-                Cancel
-               
-              
-                Save
-               
-            
-            
-        ) : (
-          
-             setEditing(true)}
-            >
-              Add to description
-             
-          
-        ))}
-    
-  )
-}
-
 export const ContractOverview = (props: {
   contract: Contract
   className?: string
@@ -142,14 +69,6 @@ export const ContractOverview = (props: {
 
       Description 
-      )}
-
-      ('loading')
+
+  useEffect(() => {
+    if (contractId) return listenForComments(contractId, setComments)
+  }, [contractId])
+
+  return comments
+}
diff --git a/web/lib/firebase/bets.ts b/web/lib/firebase/bets.ts
index b09d8f8b..49d92e0e 100644
--- a/web/lib/firebase/bets.ts
+++ b/web/lib/firebase/bets.ts
@@ -22,6 +22,7 @@ export type Bet = {
   sale?: {
     amount: number // amount user makes from sale
     betId: string // id of bet being sold
+    // TODO: add sale time?
   }
 
   isSold?: boolean // true if this BUY bet has been sold
diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts
new file mode 100644
index 00000000..d46c6ee9
--- /dev/null
+++ b/web/lib/firebase/comments.ts
@@ -0,0 +1,60 @@
+import { doc, collection, onSnapshot, setDoc } from 'firebase/firestore'
+import { db } from './init'
+import { User } from './users'
+
+// Currently, comments are created after the bet, not atomically with the bet.
+// They're uniquely identified by the pair contractId/betId.
+export type Comment = {
+  contractId: string
+  betId: string
+  text: string
+  createdTime: number
+  // Denormalized, for rendering comments
+  userName?: string
+  userUsername?: string
+  userAvatarUrl?: string
+}
+
+export async function createComment(
+  contractId: string,
+  betId: string,
+  text: string,
+  commenter: User
+) {
+  const ref = doc(getCommentsCollection(contractId), betId)
+  return await setDoc(ref, {
+    contractId,
+    betId,
+    text,
+    createdTime: Date.now(),
+    userName: commenter.name,
+    userUsername: commenter.username,
+    userAvatarUrl: commenter.avatarUrl,
+  })
+}
+
+function getCommentsCollection(contractId: string) {
+  return collection(db, 'contracts', contractId, 'comments')
+}
+
+export function listenForComments(
+  contractId: string,
+  setComments: (comments: Comment[]) => void
+) {
+  return onSnapshot(getCommentsCollection(contractId), (snap) => {
+    const comments = snap.docs.map((doc) => doc.data() as Comment)
+
+    comments.sort((c1, c2) => c1.createdTime - c2.createdTime)
+
+    setComments(comments)
+  })
+}
+
+// Return a map of betId -> comment
+export function mapCommentsByBetId(comments: Comment[]) {
+  const map: Record = {}
+  for (const comment of comments) {
+    map[comment.betId] = comment
+  }
+  return map
+}