diff --git a/common/contract.ts b/common/contract.ts
index 2a8f897a..343bc750 100644
--- a/common/contract.ts
+++ b/common/contract.ts
@@ -57,6 +57,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
   uniqueBettorIds?: string[]
   uniqueBettorCount?: number
   popularityScore?: number
+  followerCount?: number
 } & T
 
 export type BinaryContract = Contract & Binary
diff --git a/common/notification.ts b/common/notification.ts
index 0a69f89d..f10bd3f6 100644
--- a/common/notification.ts
+++ b/common/notification.ts
@@ -70,3 +70,4 @@ export type notification_reason_types =
   | 'challenge_accepted'
   | 'betting_streak_incremented'
   | 'loan_income'
+  | 'you_follow_contract'
diff --git a/common/user.ts b/common/user.ts
index 9927a3d3..b278300c 100644
--- a/common/user.ts
+++ b/common/user.ts
@@ -42,6 +42,7 @@ export type User = {
   shouldShowWelcome?: boolean
   lastBetTime?: number
   currentBettingStreak?: number
+  hasSeenContractFollowModal?: boolean
 }
 
 export type PrivateUser = {
diff --git a/dev.sh b/dev.sh
index ca3246ac..d392646e 100755
--- a/dev.sh
+++ b/dev.sh
@@ -24,7 +24,7 @@ then
   npx concurrently \
       -n FIRESTORE,FUNCTIONS,NEXT,TS \
       -c green,white,magenta,cyan \
-      "yarn --cwd=functions firestore" \
+      "yarn --cwd=functions localDbScript" \
       "cross-env FIRESTORE_EMULATOR_HOST=localhost:8080 yarn --cwd=functions dev" \
       "cross-env NEXT_PUBLIC_FUNCTIONS_URL=http://localhost:8088 \
                NEXT_PUBLIC_FIREBASE_EMULATE=TRUE \
diff --git a/firestore.rules b/firestore.rules
index b28ac6a5..0e5a759b 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -23,7 +23,7 @@ service cloud.firestore {
       allow read;
       allow update: if userId == request.auth.uid
                        && request.resource.data.diff(resource.data).affectedKeys()
-                                                                    .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime','shouldShowWelcome']);
+                                                                    .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime','shouldShowWelcome', 'hasSeenContractFollowModal']);
       // User referral rules
       allow update: if userId == request.auth.uid
                          && request.resource.data.diff(resource.data).affectedKeys()
@@ -44,6 +44,11 @@ service cloud.firestore {
       allow read;
     }
 
+    match /contracts/{contractId}/follows/{userId} {
+      allow read;
+      allow create, delete: if userId == request.auth.uid;
+    }
+
     match /contracts/{contractId}/challenges/{challengeId}{
       allow read;
       allow create: if request.auth.uid == request.resource.data.creatorId;
diff --git a/functions/package.json b/functions/package.json
index 63ef9b5d..c8f295fc 100644
--- a/functions/package.json
+++ b/functions/package.json
@@ -13,7 +13,7 @@
     "deploy": "firebase deploy --only functions",
     "logs": "firebase functions:log",
     "dev": "nodemon src/serve.ts",
-    "firestore": "firebase emulators:start --only firestore --import=./firestore_export",
+    "localDbScript": "firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export",
     "serve": "firebase use dev && yarn build && firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export",
     "db:update-local-from-remote": "yarn db:backup-remote && gsutil rsync -r gs://$npm_package_config_firestore/firestore_export ./firestore_export",
     "db:backup-local": "firebase emulators:export --force ./firestore_export",
diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts
index 3fb1f9c3..035126c5 100644
--- a/functions/src/create-notification.ts
+++ b/functions/src/create-notification.ts
@@ -7,7 +7,7 @@ import {
 } from '../../common/notification'
 import { User } from '../../common/user'
 import { Contract } from '../../common/contract'
-import { getValues } from './utils'
+import { getValues, log } from './utils'
 import { Comment } from '../../common/comment'
 import { uniq } from 'lodash'
 import { Bet, LimitBet } from '../../common/bet'
@@ -33,19 +33,12 @@ export const createNotification = async (
   sourceText: string,
   miscData?: {
     contract?: Contract
-    relatedSourceType?: notification_source_types
     recipients?: string[]
     slug?: string
     title?: string
   }
 ) => {
-  const {
-    contract: sourceContract,
-    relatedSourceType,
-    recipients,
-    slug,
-    title,
-  } = miscData ?? {}
+  const { contract: sourceContract, recipients, slug, title } = miscData ?? {}
 
   const shouldGetNotification = (
     userId: string,
@@ -90,24 +83,6 @@ export const createNotification = async (
     )
   }
 
-  const notifyLiquidityProviders = async (
-    userToReasonTexts: user_to_reason_texts,
-    contract: Contract
-  ) => {
-    const liquidityProviders = await firestore
-      .collection(`contracts/${contract.id}/liquidity`)
-      .get()
-    const liquidityProvidersIds = uniq(
-      liquidityProviders.docs.map((doc) => doc.data().userId)
-    )
-    liquidityProvidersIds.forEach((userId) => {
-      if (!shouldGetNotification(userId, userToReasonTexts)) return
-      userToReasonTexts[userId] = {
-        reason: 'on_contract_with_users_shares_in',
-      }
-    })
-  }
-
   const notifyUsersFollowers = async (
     userToReasonTexts: user_to_reason_texts
   ) => {
@@ -129,23 +104,6 @@ export const createNotification = async (
     })
   }
 
-  const notifyRepliedUser = (
-    userToReasonTexts: user_to_reason_texts,
-    relatedUserId: string,
-    relatedSourceType: notification_source_types
-  ) => {
-    if (!shouldGetNotification(relatedUserId, userToReasonTexts)) return
-    if (relatedSourceType === 'comment') {
-      userToReasonTexts[relatedUserId] = {
-        reason: 'reply_to_users_comment',
-      }
-    } else if (relatedSourceType === 'answer') {
-      userToReasonTexts[relatedUserId] = {
-        reason: 'reply_to_users_answer',
-      }
-    }
-  }
-
   const notifyFollowedUser = (
     userToReasonTexts: user_to_reason_texts,
     followedUserId: string
@@ -182,71 +140,6 @@ export const createNotification = async (
       }
   }
 
-  const notifyOtherAnswerersOnContract = async (
-    userToReasonTexts: user_to_reason_texts,
-    sourceContract: Contract
-  ) => {
-    const answers = await getValues<Answer>(
-      firestore
-        .collection('contracts')
-        .doc(sourceContract.id)
-        .collection('answers')
-    )
-    const recipientUserIds = uniq(answers.map((answer) => answer.userId))
-    recipientUserIds.forEach((userId) => {
-      if (shouldGetNotification(userId, userToReasonTexts))
-        userToReasonTexts[userId] = {
-          reason: 'on_contract_with_users_answer',
-        }
-    })
-  }
-
-  const notifyOtherCommentersOnContract = async (
-    userToReasonTexts: user_to_reason_texts,
-    sourceContract: Contract
-  ) => {
-    const comments = await getValues<Comment>(
-      firestore
-        .collection('contracts')
-        .doc(sourceContract.id)
-        .collection('comments')
-    )
-    const recipientUserIds = uniq(comments.map((comment) => comment.userId))
-    recipientUserIds.forEach((userId) => {
-      if (shouldGetNotification(userId, userToReasonTexts))
-        userToReasonTexts[userId] = {
-          reason: 'on_contract_with_users_comment',
-        }
-    })
-  }
-
-  const notifyBettorsOnContract = async (
-    userToReasonTexts: user_to_reason_texts,
-    sourceContract: Contract
-  ) => {
-    const betsSnap = await firestore
-      .collection(`contracts/${sourceContract.id}/bets`)
-      .get()
-    const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
-    // filter bets for only users that have an amount invested still
-    const recipientUserIds = uniq(bets.map((bet) => bet.userId)).filter(
-      (userId) => {
-        return (
-          getContractBetMetrics(
-            sourceContract,
-            bets.filter((bet) => bet.userId === userId)
-          ).invested > 0
-        )
-      }
-    )
-    recipientUserIds.forEach((userId) => {
-      if (shouldGetNotification(userId, userToReasonTexts))
-        userToReasonTexts[userId] = {
-          reason: 'on_contract_with_users_shares_in',
-        }
-    })
-  }
-
   const notifyUserAddedToGroup = (
     userToReasonTexts: user_to_reason_texts,
     relatedUserId: string
@@ -266,58 +159,289 @@ export const createNotification = async (
     }
   }
 
-  const getUsersToNotify = async () => {
-    const userToReasonTexts: user_to_reason_texts = {}
-    // The following functions modify the userToReasonTexts object in place.
-    if (sourceType === 'follow' && recipients?.[0]) {
-      notifyFollowedUser(userToReasonTexts, recipients[0])
-    } else if (
-      sourceType === 'group' &&
-      sourceUpdateType === 'created' &&
-      recipients
-    ) {
-      recipients.forEach((r) => notifyUserAddedToGroup(userToReasonTexts, r))
-    }
+  const userToReasonTexts: user_to_reason_texts = {}
+  // The following functions modify the userToReasonTexts object in place.
 
-    // The following functions need sourceContract to be defined.
-    if (!sourceContract) return userToReasonTexts
-
-    if (
-      sourceType === 'comment' ||
-      sourceType === 'answer' ||
-      (sourceType === 'contract' &&
-        (sourceUpdateType === 'updated' || sourceUpdateType === 'resolved'))
-    ) {
-      if (sourceType === 'comment') {
-        if (recipients?.[0] && relatedSourceType)
-          notifyRepliedUser(userToReasonTexts, recipients[0], relatedSourceType)
-        if (sourceText) notifyTaggedUsers(userToReasonTexts, recipients ?? [])
-      }
-      await notifyContractCreator(userToReasonTexts, sourceContract)
-      await notifyOtherAnswerersOnContract(userToReasonTexts, sourceContract)
-      await notifyLiquidityProviders(userToReasonTexts, sourceContract)
-      await notifyBettorsOnContract(userToReasonTexts, sourceContract)
-      await notifyOtherCommentersOnContract(userToReasonTexts, sourceContract)
-    } else if (sourceType === 'contract' && sourceUpdateType === 'created') {
-      await notifyUsersFollowers(userToReasonTexts)
-      notifyTaggedUsers(userToReasonTexts, recipients ?? [])
-    } else if (sourceType === 'contract' && sourceUpdateType === 'closed') {
-      await notifyContractCreator(userToReasonTexts, sourceContract, {
-        force: true,
-      })
-    } else if (sourceType === 'liquidity' && sourceUpdateType === 'created') {
-      await notifyContractCreator(userToReasonTexts, sourceContract)
-    } else if (sourceType === 'bonus' && sourceUpdateType === 'created') {
-      // Note: the daily bonus won't have a contract attached to it
-      await notifyContractCreatorOfUniqueBettorsBonus(
-        userToReasonTexts,
-        sourceContract.creatorId
-      )
-    }
-    return userToReasonTexts
+  if (sourceType === 'follow' && recipients?.[0]) {
+    notifyFollowedUser(userToReasonTexts, recipients[0])
+  } else if (
+    sourceType === 'group' &&
+    sourceUpdateType === 'created' &&
+    recipients
+  ) {
+    recipients.forEach((r) => notifyUserAddedToGroup(userToReasonTexts, r))
+  } else if (
+    sourceType === 'contract' &&
+    sourceUpdateType === 'created' &&
+    sourceContract
+  ) {
+    await notifyUsersFollowers(userToReasonTexts)
+    notifyTaggedUsers(userToReasonTexts, recipients ?? [])
+  } else if (
+    sourceType === 'contract' &&
+    sourceUpdateType === 'closed' &&
+    sourceContract
+  ) {
+    await notifyContractCreator(userToReasonTexts, sourceContract, {
+      force: true,
+    })
+  } else if (
+    sourceType === 'liquidity' &&
+    sourceUpdateType === 'created' &&
+    sourceContract
+  ) {
+    await notifyContractCreator(userToReasonTexts, sourceContract)
+  } else if (
+    sourceType === 'bonus' &&
+    sourceUpdateType === 'created' &&
+    sourceContract
+  ) {
+    // Note: the daily bonus won't have a contract attached to it
+    await notifyContractCreatorOfUniqueBettorsBonus(
+      userToReasonTexts,
+      sourceContract.creatorId
+    )
   }
 
-  const userToReasonTexts = await getUsersToNotify()
+  await createUsersNotifications(userToReasonTexts)
+}
+
+export const createCommentOrAnswerOrUpdatedContractNotification = async (
+  sourceId: string,
+  sourceType: notification_source_types,
+  sourceUpdateType: notification_source_update_types,
+  sourceUser: User,
+  idempotencyKey: string,
+  sourceText: string,
+  sourceContract: Contract,
+  miscData?: {
+    relatedSourceType?: notification_source_types
+    repliedUserId?: string
+    taggedUserIds?: string[]
+  }
+) => {
+  const { relatedSourceType, repliedUserId, taggedUserIds } = miscData ?? {}
+
+  const createUsersNotifications = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    await Promise.all(
+      Object.keys(userToReasonTexts).map(async (userId) => {
+        const notificationRef = firestore
+          .collection(`/users/${userId}/notifications`)
+          .doc(idempotencyKey)
+        const notification: Notification = {
+          id: idempotencyKey,
+          userId,
+          reason: userToReasonTexts[userId].reason,
+          createdTime: Date.now(),
+          isSeen: false,
+          sourceId,
+          sourceType,
+          sourceUpdateType,
+          sourceContractId: sourceContract.id,
+          sourceUserName: sourceUser.name,
+          sourceUserUsername: sourceUser.username,
+          sourceUserAvatarUrl: sourceUser.avatarUrl,
+          sourceText,
+          sourceContractCreatorUsername: sourceContract.creatorUsername,
+          sourceContractTitle: sourceContract.question,
+          sourceContractSlug: sourceContract.slug,
+          sourceSlug: sourceContract.slug,
+          sourceTitle: sourceContract.question,
+        }
+        await notificationRef.set(removeUndefinedProps(notification))
+      })
+    )
+  }
+
+  // get contract follower documents and check here if they're a follower
+  const contractFollowersSnap = await firestore
+    .collection(`contracts/${sourceContract.id}/follows`)
+    .get()
+  const contractFollowersIds = contractFollowersSnap.docs.map(
+    (doc) => doc.data().id
+  )
+  log('contractFollowerIds', contractFollowersIds)
+
+  const stillFollowingContract = (userId: string) => {
+    return contractFollowersIds.includes(userId)
+  }
+
+  const shouldGetNotification = (
+    userId: string,
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    return (
+      sourceUser.id != userId &&
+      !Object.keys(userToReasonTexts).includes(userId)
+    )
+  }
+
+  const notifyContractFollowers = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    for (const userId of contractFollowersIds) {
+      if (shouldGetNotification(userId, userToReasonTexts))
+        userToReasonTexts[userId] = {
+          reason: 'you_follow_contract',
+        }
+    }
+  }
+
+  const notifyContractCreator = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    if (
+      shouldGetNotification(sourceContract.creatorId, userToReasonTexts) &&
+      stillFollowingContract(sourceContract.creatorId)
+    )
+      userToReasonTexts[sourceContract.creatorId] = {
+        reason: 'on_users_contract',
+      }
+  }
+
+  const notifyOtherAnswerersOnContract = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    const answers = await getValues<Answer>(
+      firestore
+        .collection('contracts')
+        .doc(sourceContract.id)
+        .collection('answers')
+    )
+    const recipientUserIds = uniq(answers.map((answer) => answer.userId))
+    recipientUserIds.forEach((userId) => {
+      if (
+        shouldGetNotification(userId, userToReasonTexts) &&
+        stillFollowingContract(userId)
+      )
+        userToReasonTexts[userId] = {
+          reason: 'on_contract_with_users_answer',
+        }
+    })
+  }
+
+  const notifyOtherCommentersOnContract = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    const comments = await getValues<Comment>(
+      firestore
+        .collection('contracts')
+        .doc(sourceContract.id)
+        .collection('comments')
+    )
+    const recipientUserIds = uniq(comments.map((comment) => comment.userId))
+    recipientUserIds.forEach((userId) => {
+      if (
+        shouldGetNotification(userId, userToReasonTexts) &&
+        stillFollowingContract(userId)
+      )
+        userToReasonTexts[userId] = {
+          reason: 'on_contract_with_users_comment',
+        }
+    })
+  }
+
+  const notifyBettorsOnContract = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    const betsSnap = await firestore
+      .collection(`contracts/${sourceContract.id}/bets`)
+      .get()
+    const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
+    // filter bets for only users that have an amount invested still
+    const recipientUserIds = uniq(bets.map((bet) => bet.userId)).filter(
+      (userId) => {
+        return (
+          getContractBetMetrics(
+            sourceContract,
+            bets.filter((bet) => bet.userId === userId)
+          ).invested > 0
+        )
+      }
+    )
+    recipientUserIds.forEach((userId) => {
+      if (
+        shouldGetNotification(userId, userToReasonTexts) &&
+        stillFollowingContract(userId)
+      )
+        userToReasonTexts[userId] = {
+          reason: 'on_contract_with_users_shares_in',
+        }
+    })
+  }
+
+  const notifyRepliedUser = (
+    userToReasonTexts: user_to_reason_texts,
+    relatedUserId: string,
+    relatedSourceType: notification_source_types
+  ) => {
+    if (
+      shouldGetNotification(relatedUserId, userToReasonTexts) &&
+      stillFollowingContract(relatedUserId)
+    ) {
+      if (relatedSourceType === 'comment') {
+        userToReasonTexts[relatedUserId] = {
+          reason: 'reply_to_users_comment',
+        }
+      } else if (relatedSourceType === 'answer') {
+        userToReasonTexts[relatedUserId] = {
+          reason: 'reply_to_users_answer',
+        }
+      }
+    }
+  }
+
+  const notifyTaggedUsers = (
+    userToReasonTexts: user_to_reason_texts,
+    userIds: (string | undefined)[]
+  ) => {
+    userIds.forEach((id) => {
+      console.log('tagged user: ', id)
+      // Allowing non-following users to get tagged
+      if (id && shouldGetNotification(id, userToReasonTexts))
+        userToReasonTexts[id] = {
+          reason: 'tagged_user',
+        }
+    })
+  }
+
+  const notifyLiquidityProviders = async (
+    userToReasonTexts: user_to_reason_texts
+  ) => {
+    const liquidityProviders = await firestore
+      .collection(`contracts/${sourceContract.id}/liquidity`)
+      .get()
+    const liquidityProvidersIds = uniq(
+      liquidityProviders.docs.map((doc) => doc.data().userId)
+    )
+    liquidityProvidersIds.forEach((userId) => {
+      if (
+        shouldGetNotification(userId, userToReasonTexts) &&
+        stillFollowingContract(userId)
+      ) {
+        userToReasonTexts[userId] = {
+          reason: 'on_contract_with_users_shares_in',
+        }
+      }
+    })
+  }
+  const userToReasonTexts: user_to_reason_texts = {}
+
+  if (sourceType === 'comment') {
+    if (repliedUserId && relatedSourceType)
+      notifyRepliedUser(userToReasonTexts, repliedUserId, relatedSourceType)
+    if (sourceText) notifyTaggedUsers(userToReasonTexts, taggedUserIds ?? [])
+  }
+  await notifyContractCreator(userToReasonTexts)
+  await notifyOtherAnswerersOnContract(userToReasonTexts)
+  await notifyLiquidityProviders(userToReasonTexts)
+  await notifyBettorsOnContract(userToReasonTexts)
+  await notifyOtherCommentersOnContract(userToReasonTexts)
+  // if they weren't added previously, add them now
+  await notifyContractFollowers(userToReasonTexts)
+
   await createUsersNotifications(userToReasonTexts)
 }
 
diff --git a/functions/src/follow-market.ts b/functions/src/follow-market.ts
new file mode 100644
index 00000000..3fc05120
--- /dev/null
+++ b/functions/src/follow-market.ts
@@ -0,0 +1,36 @@
+import * as admin from 'firebase-admin'
+
+const firestore = admin.firestore()
+
+export const addUserToContractFollowers = async (
+  contractId: string,
+  userId: string
+) => {
+  const followerDoc = await firestore
+    .collection(`contracts/${contractId}/follows`)
+    .doc(userId)
+    .get()
+  if (followerDoc.exists) return
+  await firestore
+    .collection(`contracts/${contractId}/follows`)
+    .doc(userId)
+    .set({
+      id: userId,
+      createdTime: Date.now(),
+    })
+}
+
+export const removeUserFromContractFollowers = async (
+  contractId: string,
+  userId: string
+) => {
+  const followerDoc = await firestore
+    .collection(`contracts/${contractId}/follows`)
+    .doc(userId)
+    .get()
+  if (!followerDoc.exists) return
+  await firestore
+    .collection(`contracts/${contractId}/follows`)
+    .doc(userId)
+    .delete()
+}
diff --git a/functions/src/index.ts b/functions/src/index.ts
index 26a1ddf6..012ba241 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -30,6 +30,7 @@ export * from './score-contracts'
 export * from './weekly-markets-emails'
 export * from './reset-betting-streaks'
 export * from './reset-weekly-emails-flag'
+export * from './on-update-contract-follow'
 
 // v2
 export * from './health'
diff --git a/functions/src/on-create-answer.ts b/functions/src/on-create-answer.ts
index 6af5e699..611bf23b 100644
--- a/functions/src/on-create-answer.ts
+++ b/functions/src/on-create-answer.ts
@@ -1,6 +1,6 @@
 import * as functions from 'firebase-functions'
 import { getContract, getUser } from './utils'
-import { createNotification } from './create-notification'
+import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
 import { Answer } from '../../common/answer'
 
 export const onCreateAnswer = functions.firestore
@@ -20,14 +20,13 @@ export const onCreateAnswer = functions.firestore
 
     const answerCreator = await getUser(answer.userId)
     if (!answerCreator) throw new Error('Could not find answer creator')
-
-    await createNotification(
+    await createCommentOrAnswerOrUpdatedContractNotification(
       answer.id,
       'answer',
       'created',
       answerCreator,
       eventId,
       answer.text,
-      { contract }
+      contract
     )
   })
diff --git a/functions/src/on-create-comment-on-contract.ts b/functions/src/on-create-comment-on-contract.ts
index 9f19dfcc..8651bde0 100644
--- a/functions/src/on-create-comment-on-contract.ts
+++ b/functions/src/on-create-comment-on-contract.ts
@@ -6,8 +6,9 @@ import { ContractComment } from '../../common/comment'
 import { sendNewCommentEmail } from './emails'
 import { Bet } from '../../common/bet'
 import { Answer } from '../../common/answer'
-import { createNotification } from './create-notification'
+import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
 import { parseMentions, richTextToString } from '../../common/util/parse'
+import { addUserToContractFollowers } from './follow-market'
 
 const firestore = admin.firestore()
 
@@ -35,6 +36,8 @@ export const onCreateCommentOnContract = functions
     const commentCreator = await getUser(comment.userId)
     if (!commentCreator) throw new Error('Could not find comment creator')
 
+    await addUserToContractFollowers(contract.id, commentCreator.id)
+
     await firestore
       .collection('contracts')
       .doc(contract.id)
@@ -77,18 +80,19 @@ export const onCreateCommentOnContract = functions
       ? comments.find((c) => c.id === comment.replyToCommentId)?.userId
       : answer?.userId
 
-    const recipients = uniq(
-      compact([...parseMentions(comment.content), repliedUserId])
-    )
-
-    await createNotification(
+    await createCommentOrAnswerOrUpdatedContractNotification(
       comment.id,
       'comment',
       'created',
       commentCreator,
       eventId,
       richTextToString(comment.content),
-      { contract, relatedSourceType, recipients }
+      contract,
+      {
+        relatedSourceType,
+        repliedUserId,
+        taggedUserIds: compact(parseMentions(comment.content)),
+      }
     )
 
     const recipientUserIds = uniq([
diff --git a/functions/src/on-create-contract.ts b/functions/src/on-create-contract.ts
index 3785ecc9..d9826f6c 100644
--- a/functions/src/on-create-contract.ts
+++ b/functions/src/on-create-contract.ts
@@ -5,6 +5,7 @@ import { createNotification } from './create-notification'
 import { Contract } from '../../common/contract'
 import { parseMentions, richTextToString } from '../../common/util/parse'
 import { JSONContent } from '@tiptap/core'
+import { addUserToContractFollowers } from './follow-market'
 
 export const onCreateContract = functions
   .runWith({ secrets: ['MAILGUN_KEY'] })
@@ -18,6 +19,7 @@ export const onCreateContract = functions
 
     const desc = contract.description as JSONContent
     const mentioned = parseMentions(desc)
+    await addUserToContractFollowers(contract.id, contractCreator.id)
 
     await createNotification(
       contract.id,
diff --git a/functions/src/on-create-liquidity-provision.ts b/functions/src/on-create-liquidity-provision.ts
index 6ec092a5..56a01bbb 100644
--- a/functions/src/on-create-liquidity-provision.ts
+++ b/functions/src/on-create-liquidity-provision.ts
@@ -2,6 +2,7 @@ import * as functions from 'firebase-functions'
 import { getContract, getUser } from './utils'
 import { createNotification } from './create-notification'
 import { LiquidityProvision } from 'common/liquidity-provision'
+import { addUserToContractFollowers } from './follow-market'
 
 export const onCreateLiquidityProvision = functions.firestore
   .document('contracts/{contractId}/liquidity/{liquidityId}')
@@ -18,6 +19,7 @@ export const onCreateLiquidityProvision = functions.firestore
 
     const liquidityProvider = await getUser(liquidity.userId)
     if (!liquidityProvider) throw new Error('Could not find liquidity provider')
+    await addUserToContractFollowers(contract.id, liquidityProvider.id)
 
     await createNotification(
       contract.id,
diff --git a/functions/src/on-update-contract-follow.ts b/functions/src/on-update-contract-follow.ts
new file mode 100644
index 00000000..f7d54fe8
--- /dev/null
+++ b/functions/src/on-update-contract-follow.ts
@@ -0,0 +1,45 @@
+import * as functions from 'firebase-functions'
+import * as admin from 'firebase-admin'
+import { FieldValue } from 'firebase-admin/firestore'
+
+export const onDeleteContractFollow = functions.firestore
+  .document('contracts/{contractId}/follows/{userId}')
+  .onDelete(async (change, context) => {
+    const { contractId } = context.params as {
+      contractId: string
+    }
+    const firestore = admin.firestore()
+    const contract = await firestore
+      .collection(`contracts`)
+      .doc(contractId)
+      .get()
+    if (!contract.exists) throw new Error('Could not find contract')
+
+    await firestore
+      .collection(`contracts`)
+      .doc(contractId)
+      .update({
+        followerCount: FieldValue.increment(-1),
+      })
+  })
+
+export const onCreateContractFollow = functions.firestore
+  .document('contracts/{contractId}/follows/{userId}')
+  .onCreate(async (change, context) => {
+    const { contractId } = context.params as {
+      contractId: string
+    }
+    const firestore = admin.firestore()
+    const contract = await firestore
+      .collection(`contracts`)
+      .doc(contractId)
+      .get()
+    if (!contract.exists) throw new Error('Could not find contract')
+
+    await firestore
+      .collection(`contracts`)
+      .doc(contractId)
+      .update({
+        followerCount: FieldValue.increment(1),
+      })
+  })
diff --git a/functions/src/on-update-contract.ts b/functions/src/on-update-contract.ts
index 28523eae..d7ecd56e 100644
--- a/functions/src/on-update-contract.ts
+++ b/functions/src/on-update-contract.ts
@@ -1,6 +1,6 @@
 import * as functions from 'firebase-functions'
 import { getUser } from './utils'
-import { createNotification } from './create-notification'
+import { createCommentOrAnswerOrUpdatedContractNotification } from './create-notification'
 import { Contract } from '../../common/contract'
 
 export const onUpdateContract = functions.firestore
@@ -29,14 +29,14 @@ export const onUpdateContract = functions.firestore
           resolutionText = `${contract.resolutionValue}`
       }
 
-      await createNotification(
+      await createCommentOrAnswerOrUpdatedContractNotification(
         contract.id,
         'contract',
         'resolved',
         contractUpdater,
         eventId,
         resolutionText,
-        { contract }
+        contract
       )
     } else if (
       previousValue.closeTime !== contract.closeTime ||
@@ -52,14 +52,14 @@ export const onUpdateContract = functions.firestore
         sourceText = contract.question
       }
 
-      await createNotification(
+      await createCommentOrAnswerOrUpdatedContractNotification(
         contract.id,
         'contract',
         'updated',
         contractUpdater,
         eventId,
         sourceText,
-        { contract }
+        contract
       )
     }
   })
diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts
index 44a96210..237019a4 100644
--- a/functions/src/place-bet.ts
+++ b/functions/src/place-bet.ts
@@ -22,6 +22,7 @@ import { LimitBet } from '../../common/bet'
 import { floatingEqual } from '../../common/util/math'
 import { redeemShares } from './redeem-shares'
 import { log } from './utils'
+import { addUserToContractFollowers } from 'functions/src/follow-market'
 
 const bodySchema = z.object({
   contractId: z.string(),
@@ -167,6 +168,8 @@ export const placebet = newEndpoint({}, async (req, auth) => {
     return { betId: betDoc.id, makers, newBet }
   })
 
+  await addUserToContractFollowers(contractId, auth.uid)
+
   log('Main transaction finished.')
 
   if (result.newBet.amount !== 0) {
diff --git a/functions/src/scripts/backfill-contract-followers.ts b/functions/src/scripts/backfill-contract-followers.ts
new file mode 100644
index 00000000..9b936654
--- /dev/null
+++ b/functions/src/scripts/backfill-contract-followers.ts
@@ -0,0 +1,75 @@
+import * as admin from 'firebase-admin'
+
+import { initAdmin } from './script-init'
+initAdmin()
+
+import { getValues } from '../utils'
+import { Contract } from 'common/lib/contract'
+import { Comment } from 'common/lib/comment'
+import { uniq } from 'lodash'
+import { Bet } from 'common/lib/bet'
+import {
+  DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
+  HOUSE_LIQUIDITY_PROVIDER_ID,
+} from 'common/lib/antes'
+
+const firestore = admin.firestore()
+
+async function backfillContractFollowers() {
+  console.log('Backfilling contract followers')
+  const contracts = await getValues<Contract>(
+    firestore.collection('contracts').where('isResolved', '==', false)
+  )
+  let count = 0
+  for (const contract of contracts) {
+    const comments = await getValues<Comment>(
+      firestore.collection('contracts').doc(contract.id).collection('comments')
+    )
+    const commenterIds = uniq(comments.map((comment) => comment.userId))
+    const betsSnap = await firestore
+      .collection(`contracts/${contract.id}/bets`)
+      .get()
+    const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
+    // filter bets for only users that have an amount invested still
+    const bettorIds = uniq(bets.map((bet) => bet.userId))
+    const liquidityProviders = await firestore
+      .collection(`contracts/${contract.id}/liquidity`)
+      .get()
+    const liquidityProvidersIds = uniq(
+      liquidityProviders.docs.map((doc) => doc.data().userId)
+      // exclude free market liquidity provider
+    ).filter(
+      (id) =>
+        id !== HOUSE_LIQUIDITY_PROVIDER_ID ||
+        id !== DEV_HOUSE_LIQUIDITY_PROVIDER_ID
+    )
+    const followerIds = uniq([
+      ...commenterIds,
+      ...bettorIds,
+      ...liquidityProvidersIds,
+      contract.creatorId,
+    ])
+    for (const followerId of followerIds) {
+      await firestore
+        .collection(`contracts/${contract.id}/follows`)
+        .doc(followerId)
+        .set({ id: followerId, createdTime: Date.now() })
+    }
+    // Perhaps handled by the trigger?
+    // const followerCount = followerIds.length
+    // await firestore
+    //   .collection(`contracts`)
+    //   .doc(contract.id)
+    //   .update({ followerCount: followerCount })
+    count += 1
+    if (count % 100 === 0) {
+      console.log(`${count} contracts processed`)
+    }
+  }
+}
+
+if (require.main === module) {
+  backfillContractFollowers()
+    .then(() => process.exit())
+    .catch(console.log)
+}
diff --git a/functions/src/sell-shares.ts b/functions/src/sell-shares.ts
index d9f99de3..0e669f39 100644
--- a/functions/src/sell-shares.ts
+++ b/functions/src/sell-shares.ts
@@ -13,6 +13,7 @@ import { floatingEqual, floatingLesserEqual } from '../../common/util/math'
 import { getUnfilledBetsQuery, updateMakers } from './place-bet'
 import { FieldValue } from 'firebase-admin/firestore'
 import { redeemShares } from './redeem-shares'
+import { removeUserFromContractFollowers } from 'functions/src/follow-market'
 
 const bodySchema = z.object({
   contractId: z.string(),
@@ -123,9 +124,12 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
       })
     )
 
-    return { newBet, makers }
+    return { newBet, makers, maxShares, soldShares }
   })
 
+  if (result.maxShares === result.soldShares) {
+    await removeUserFromContractFollowers(contractId, auth.uid)
+  }
   const userIds = uniq(result.makers.map((maker) => maker.bet.userId))
   await Promise.all(userIds.map((userId) => redeemShares(userId, contractId)))
   log('Share redemption transaction finished.')
diff --git a/web/components/NotificationSettings.tsx b/web/components/NotificationSettings.tsx
index 7a839a7a..6d8aa25f 100644
--- a/web/components/NotificationSettings.tsx
+++ b/web/components/NotificationSettings.tsx
@@ -9,6 +9,8 @@ import { Row } from 'web/components/layout/row'
 import clsx from 'clsx'
 import { CheckIcon, XIcon } from '@heroicons/react/outline'
 import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
+import { Col } from 'web/components/layout/col'
+import { FollowMarketModal } from 'web/components/contract/follow-market-modal'
 
 export function NotificationSettings() {
   const user = useUser()
@@ -17,6 +19,7 @@ export function NotificationSettings() {
   const [emailNotificationSettings, setEmailNotificationSettings] =
     useState<notification_subscribe_types>('all')
   const [privateUser, setPrivateUser] = useState<PrivateUser | null>(null)
+  const [showModal, setShowModal] = useState(false)
 
   useEffect(() => {
     if (user) listenForPrivateUser(user.id, setPrivateUser)
@@ -121,12 +124,20 @@ export function NotificationSettings() {
   }
 
   function NotificationSettingLine(props: {
-    label: string
+    label: string | React.ReactNode
     highlight: boolean
+    onClick?: () => void
   }) {
-    const { label, highlight } = props
+    const { label, highlight, onClick } = props
     return (
-      <Row className={clsx('my-1 text-gray-300', highlight && '!text-black')}>
+      <Row
+        className={clsx(
+          'my-1 gap-1 text-gray-300',
+          highlight && '!text-black',
+          onClick ? 'cursor-pointer' : ''
+        )}
+        onClick={onClick}
+      >
         {highlight ? <CheckIcon height={20} /> : <XIcon height={20} />}
         {label}
       </Row>
@@ -148,31 +159,45 @@ export function NotificationSettings() {
         toggleClassName={'w-24'}
       />
       <div className={'mt-4 text-sm'}>
-        <div>
-          <div className={''}>
-            You will receive notifications for:
-            <NotificationSettingLine
-              label={"Resolution of questions you've interacted with"}
-              highlight={notificationSettings !== 'none'}
-            />
-            <NotificationSettingLine
-              highlight={notificationSettings !== 'none'}
-              label={'Activity on your own questions, comments, & answers'}
-            />
-            <NotificationSettingLine
-              highlight={notificationSettings !== 'none'}
-              label={"Activity on questions you're betting on"}
-            />
-            <NotificationSettingLine
-              highlight={notificationSettings !== 'none'}
-              label={"Income & referral bonuses you've received"}
-            />
-            <NotificationSettingLine
-              label={"Activity on questions you've ever bet or commented on"}
-              highlight={notificationSettings === 'all'}
-            />
-          </div>
-        </div>
+        <Col className={''}>
+          <Row className={'my-1'}>
+            You will receive notifications for these general events:
+          </Row>
+          <NotificationSettingLine
+            highlight={notificationSettings !== 'none'}
+            label={"Income & referral bonuses you've received"}
+          />
+          <Row className={'my-1'}>
+            You will receive new comment, answer, & resolution notifications on
+            questions:
+          </Row>
+          <NotificationSettingLine
+            highlight={notificationSettings !== 'none'}
+            label={
+              <span>
+                That <span className={'font-bold'}>you follow </span>- you
+                auto-follow questions if:
+              </span>
+            }
+            onClick={() => setShowModal(true)}
+          />
+          <Col
+            className={clsx(
+              'mb-2 ml-8',
+              'gap-1 text-gray-300',
+              notificationSettings !== 'none' && '!text-black'
+            )}
+          >
+            <Row>• You create it</Row>
+            <Row>• You bet, comment on, or answer it</Row>
+            <Row>• You add liquidity to it</Row>
+            <Row>
+              • If you select 'Less' and you've commented on or answered a
+              question, you'll only receive notification on direct replies to
+              your comments or answers
+            </Row>
+          </Col>
+        </Col>
       </div>
       <div className={'mt-4'}>Email Notifications</div>
       <ChoicesToggleGroup
@@ -205,6 +230,7 @@ export function NotificationSettings() {
           />
         </div>
       </div>
+      <FollowMarketModal setOpen={setShowModal} open={showModal} />
     </div>
   )
 }
diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx
index bba30776..2aa2d6df 100644
--- a/web/components/contract/contract-overview.tsx
+++ b/web/components/contract/contract-overview.tsx
@@ -22,6 +22,7 @@ import { ContractDescription } from './contract-description'
 import { ContractDetails } from './contract-details'
 import { NumericGraph } from './numeric-graph'
 import { ShareRow } from './share-row'
+import { FollowMarketButton } from 'web/components/follow-market-button'
 
 export const ContractOverview = (props: {
   contract: Contract
@@ -44,47 +45,57 @@ export const ContractOverview = (props: {
           <div className="text-2xl text-indigo-700 md:text-3xl">
             <Linkify text={question} />
           </div>
+          {(outcomeType === 'FREE_RESPONSE' ||
+            outcomeType === 'MULTIPLE_CHOICE') &&
+            !resolution && (
+              <div className={'xl:hidden'}>
+                <FollowMarketButton contract={contract} user={user} />
+              </div>
+            )}
+          <Row className={'hidden gap-3 xl:flex'}>
+            <FollowMarketButton contract={contract} user={user} />
 
-          {isBinary && (
-            <BinaryResolutionOrChance
-              className="hidden items-end xl:flex"
-              contract={contract}
-              large
-            />
-          )}
+            {isBinary && (
+              <BinaryResolutionOrChance
+                className="items-end"
+                contract={contract}
+                large
+              />
+            )}
 
-          {isPseudoNumeric && (
-            <PseudoNumericResolutionOrExpectation
-              contract={contract}
-              className="hidden items-end xl:flex"
-            />
-          )}
+            {isPseudoNumeric && (
+              <PseudoNumericResolutionOrExpectation
+                contract={contract}
+                className="items-end"
+              />
+            )}
 
-          {outcomeType === 'NUMERIC' && (
-            <NumericResolutionOrExpectation
-              contract={contract}
-              className="hidden items-end xl:flex"
-            />
-          )}
+            {outcomeType === 'NUMERIC' && (
+              <NumericResolutionOrExpectation
+                contract={contract}
+                className="items-end"
+              />
+            )}
+          </Row>
         </Row>
 
         {isBinary ? (
           <Row className="items-center justify-between gap-4 xl:hidden">
             <BinaryResolutionOrChance contract={contract} />
-
-            {tradingAllowed(contract) && (
-              <BetButton contract={contract as CPMMBinaryContract} />
-            )}
+            <Row className={'items-start gap-2'}>
+              <FollowMarketButton contract={contract} user={user} />
+              {tradingAllowed(contract) && (
+                <BetButton contract={contract as CPMMBinaryContract} />
+              )}
+            </Row>
           </Row>
         ) : isPseudoNumeric ? (
           <Row className="items-center justify-between gap-4 xl:hidden">
             <PseudoNumericResolutionOrExpectation contract={contract} />
-            {tradingAllowed(contract) && <BetButton contract={contract} />}
-          </Row>
-        ) : isPseudoNumeric ? (
-          <Row className="items-center justify-between gap-4 xl:hidden">
-            <PseudoNumericResolutionOrExpectation contract={contract} />
-            {tradingAllowed(contract) && <BetButton contract={contract} />}
+            <Row className={'items-start gap-2'}>
+              <FollowMarketButton contract={contract} user={user} />
+              {tradingAllowed(contract) && <BetButton contract={contract} />}
+            </Row>
           </Row>
         ) : (
           (outcomeType === 'FREE_RESPONSE' ||
diff --git a/web/components/contract/follow-market-modal.tsx b/web/components/contract/follow-market-modal.tsx
new file mode 100644
index 00000000..3dfb7ff4
--- /dev/null
+++ b/web/components/contract/follow-market-modal.tsx
@@ -0,0 +1,33 @@
+import { Col } from 'web/components/layout/col'
+import { Modal } from 'web/components/layout/modal'
+import React from 'react'
+
+export const FollowMarketModal = (props: {
+  open: boolean
+  setOpen: (b: boolean) => void
+  title?: string
+}) => {
+  const { open, setOpen, title } = props
+  return (
+    <Modal open={open} setOpen={setOpen}>
+      <Col className="items-center gap-4 rounded-md bg-white px-8 py-6">
+        <span className={'text-8xl'}>❤️</span>
+        <span className="text-xl">{title ? title : 'Following questions'}</span>
+        <Col className={'gap-2'}>
+          <span className={'text-indigo-700'}>• What is following?</span>
+          <span className={'ml-2'}>
+            You can receive notifications on questions you're interested in by
+            clicking the ❤️ button on a question.
+          </span>
+          <span className={'text-indigo-700'}>
+            • What types of notifications will I receive?
+          </span>
+          <span className={'ml-2'}>
+            You'll receive in-app notifications for new comments, answers, and
+            updates to the question.
+          </span>
+        </Col>
+      </Col>
+    </Modal>
+  )
+}
diff --git a/web/components/follow-market-button.tsx b/web/components/follow-market-button.tsx
new file mode 100644
index 00000000..0a8ff4b4
--- /dev/null
+++ b/web/components/follow-market-button.tsx
@@ -0,0 +1,76 @@
+import { Button } from 'web/components/button'
+import {
+  Contract,
+  followContract,
+  unFollowContract,
+} from 'web/lib/firebase/contracts'
+import toast from 'react-hot-toast'
+import { CheckIcon, HeartIcon } from '@heroicons/react/outline'
+import clsx from 'clsx'
+import { User } from 'common/user'
+import { useContractFollows } from 'web/hooks/use-follows'
+import { firebaseLogin, updateUser } from 'web/lib/firebase/users'
+import { track } from 'web/lib/service/analytics'
+import { FollowMarketModal } from 'web/components/contract/follow-market-modal'
+import { useState } from 'react'
+
+export const FollowMarketButton = (props: {
+  contract: Contract
+  user: User | undefined | null
+}) => {
+  const { contract, user } = props
+  const followers = useContractFollows(contract.id)
+  const [open, setOpen] = useState(false)
+
+  return (
+    <Button
+      size={'lg'}
+      color={'gray-white'}
+      onClick={async () => {
+        if (!user) return firebaseLogin()
+        if (followers?.includes(user.id)) {
+          await unFollowContract(contract.id, user.id)
+          toast('Notifications from this market are now silenced.', {
+            icon: <CheckIcon className={'text-primary h-5 w-5'} />,
+          })
+          track('Unfollow Market', {
+            slug: contract.slug,
+          })
+        } else {
+          await followContract(contract.id, user.id)
+          toast('You are now following this market!', {
+            icon: <CheckIcon className={'text-primary h-5 w-5'} />,
+          })
+          track('Follow Market', {
+            slug: contract.slug,
+          })
+        }
+        if (!user.hasSeenContractFollowModal) {
+          await updateUser(user.id, {
+            hasSeenContractFollowModal: true,
+          })
+          setOpen(true)
+        }
+      }}
+    >
+      {followers?.includes(user?.id ?? 'nope') ? (
+        <HeartIcon
+          className={clsx('h-6 w-6  fill-red-600 stroke-red-600 xl:h-7 xl:w-7')}
+          aria-hidden="true"
+        />
+      ) : (
+        <HeartIcon
+          className={clsx('h-6 w-6 xl:h-7 xl:w-7')}
+          aria-hidden="true"
+        />
+      )}
+      <FollowMarketModal
+        open={open}
+        setOpen={setOpen}
+        title={`You ${
+          followers?.includes(user?.id ?? 'nope') ? 'followed' : 'unfollowed'
+        } a question!`}
+      />
+    </Button>
+  )
+}
diff --git a/web/hooks/use-follows.ts b/web/hooks/use-follows.ts
index 2a8caaea..2b418658 100644
--- a/web/hooks/use-follows.ts
+++ b/web/hooks/use-follows.ts
@@ -1,5 +1,6 @@
 import { useEffect, useState } from 'react'
 import { listenForFollowers, listenForFollows } from 'web/lib/firebase/users'
+import { contracts, listenForContractFollows } from 'web/lib/firebase/contracts'
 
 export const useFollows = (userId: string | null | undefined) => {
   const [followIds, setFollowIds] = useState<string[] | undefined>()
@@ -29,3 +30,13 @@ export const useFollowers = (userId: string | undefined) => {
 
   return followerIds
 }
+
+export const useContractFollows = (contractId: string) => {
+  const [followIds, setFollowIds] = useState<string[] | undefined>()
+
+  useEffect(() => {
+    return listenForContractFollows(contractId, setFollowIds)
+  }, [contractId])
+
+  return followIds
+}
diff --git a/web/hooks/use-notifications.ts b/web/hooks/use-notifications.ts
index ecc4ce2a..32500943 100644
--- a/web/hooks/use-notifications.ts
+++ b/web/hooks/use-notifications.ts
@@ -147,6 +147,7 @@ export function useUnseenPreferredNotifications(
 const lessPriorityReasons = [
   'on_contract_with_users_comment',
   'on_contract_with_users_answer',
+  // Notifications not currently generated for users who've sold their shares
   'on_contract_with_users_shares_out',
   // Not sure if users will want to see these w/ less:
   // 'on_contract_with_users_shares_in',
diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts
index 9fe1e59c..fc205b6a 100644
--- a/web/lib/firebase/contracts.ts
+++ b/web/lib/firebase/contracts.ts
@@ -212,6 +212,29 @@ export function listenForContract(
   return listenForValue<Contract>(contractRef, setContract)
 }
 
+export function listenForContractFollows(
+  contractId: string,
+  setFollowIds: (followIds: string[]) => void
+) {
+  const follows = collection(contracts, contractId, 'follows')
+  return listenForValues<{ id: string }>(follows, (docs) =>
+    setFollowIds(docs.map(({ id }) => id))
+  )
+}
+
+export async function followContract(contractId: string, userId: string) {
+  const followDoc = doc(collection(contracts, contractId, 'follows'), userId)
+  return await setDoc(followDoc, {
+    id: userId,
+    createdTime: Date.now(),
+  })
+}
+
+export async function unFollowContract(contractId: string, userId: string) {
+  const followDoc = doc(collection(contracts, contractId, 'follows'), userId)
+  await deleteDoc(followDoc)
+}
+
 function chooseRandomSubset(contracts: Contract[], count: number) {
   const fiveMinutes = 5 * 60 * 1000
   const seed = Math.round(Date.now() / fiveMinutes).toString()