diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts
new file mode 100644
index 00000000..0b0796f6
--- /dev/null
+++ b/functions/src/create-contract.ts
@@ -0,0 +1,149 @@
+import * as functions from 'firebase-functions'
+import * as admin from 'firebase-admin'
+
+import { randomString } from './util/random-string'
+import { slugify } from './util/slugify'
+import { Contract } from './types/contract'
+import { getUser } from './utils'
+import { payUser } from '.'
+import { User } from './types/user'
+
+export const createContract = functions
+  .runWith({ minInstances: 1 })
+  .https.onCall(
+    async (
+      data: {
+        question: string
+        description: string
+        initialProb: number
+        ante?: number
+        closeTime?: number
+      },
+      context
+    ) => {
+      const userId = context?.auth?.uid
+      if (!userId) return { status: 'error', message: 'Not authorized' }
+
+      const creator = await getUser(userId)
+      if (!creator) return { status: 'error', message: 'User not found' }
+
+      const { question, description, initialProb, ante, closeTime } = data
+
+      if (ante !== undefined && (ante < 0 || ante > creator.balance))
+        return { status: 'error', message: 'Invalid ante' }
+
+      console.log(
+        'creating contract for',
+        creator.username,
+        'on',
+        question,
+        'ante:',
+        ante || 0
+      )
+
+      const slug = await getSlug(question)
+
+      const contractRef = firestore.collection('contracts').doc()
+
+      const contract = getNewContract(
+        contractRef.id,
+        slug,
+        creator,
+        question,
+        description,
+        initialProb,
+        ante,
+        closeTime
+      )
+
+      if (ante) await payUser([creator.id, -ante])
+
+      await contractRef.create(contract)
+      return { status: 'success', contract }
+    }
+  )
+
+const getSlug = async (question: string) => {
+  const proposedSlug = slugify(question).substring(0, 35)
+
+  const preexistingContract = await getContractFromSlug(proposedSlug)
+
+  return preexistingContract
+    ? proposedSlug + '-' + randomString()
+    : proposedSlug
+}
+
+function getNewContract(
+  id: string,
+  slug: string,
+  creator: User,
+  question: string,
+  description: string,
+  initialProb: number,
+  ante?: number,
+  closeTime?: number
+) {
+  const { startYes, startNo, poolYes, poolNo } = calcStartPool(
+    initialProb,
+    ante
+  )
+
+  const contract: Contract = {
+    id,
+    slug,
+    outcomeType: 'BINARY',
+
+    creatorId: creator.id,
+    creatorName: creator.name,
+    creatorUsername: creator.username,
+
+    question: question.trim(),
+    description: description.trim(),
+
+    startPool: { YES: startYes, NO: startNo },
+    pool: { YES: poolYes, NO: poolNo },
+    totalShares: { YES: 0, NO: 0 },
+    totalBets: { YES: 0, NO: 0 },
+    isResolved: false,
+
+    createdTime: Date.now(),
+    lastUpdatedTime: Date.now(),
+  }
+
+  if (closeTime) contract.closeTime = closeTime
+
+  return contract
+}
+
+const calcStartPool = (
+  initialProbInt: number,
+  ante?: number,
+  phantomAnte = 200
+) => {
+  const p = initialProbInt / 100.0
+  const totalAnte = phantomAnte + (ante || 0)
+
+  const poolYes =
+    p === 0.5
+      ? p * totalAnte
+      : -(totalAnte * (-p + Math.sqrt((-1 + p) * -p))) / (-1 + 2 * p)
+
+  const poolNo = totalAnte - poolYes
+
+  const f = phantomAnte / totalAnte
+  const startYes = f * poolYes
+  const startNo = f * poolNo
+
+  return { startYes, startNo, poolYes, poolNo }
+}
+
+const firestore = admin.firestore()
+
+export async function getContractFromSlug(slug: string) {
+  const snap = await firestore
+    .collection('contracts')
+    .where('slug', '==', slug)
+    .get()
+
+  return snap.empty ? undefined : (snap.docs[0].data() as Contract)
+}
diff --git a/functions/src/index.ts b/functions/src/index.ts
index dfb09c89..1d462422 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -6,3 +6,4 @@ export * from './keep-awake'
 export * from './place-bet'
 export * from './resolve-market'
 export * from './sell-bet'
+export * from './create-contract'
diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts
index 4862703f..aad9d32c 100644
--- a/functions/src/resolve-market.ts
+++ b/functions/src/resolve-market.ts
@@ -222,7 +222,7 @@ const getMktPayouts = (truePool: number, contract: Contract, bets: Bet[]) => {
   ]
 }
 
-const payUser = ([userId, payout]: [string, number]) => {
+export const payUser = ([userId, payout]: [string, number]) => {
   return firestore.runTransaction(async (transaction) => {
     const userDoc = firestore.doc(`users/${userId}`)
     const userSnap = await transaction.get(userDoc)
diff --git a/functions/src/types/contract.ts b/functions/src/types/contract.ts
index 2cb86ac2..816cb9f7 100644
--- a/functions/src/types/contract.ts
+++ b/functions/src/types/contract.ts
@@ -4,6 +4,7 @@ export type Contract = {
 
   creatorId: string
   creatorName: string
+  creatorUsername: string
 
   question: string
   description: string // More info about what the contract is about
diff --git a/web/lib/util/random-string.ts b/functions/src/util/random-string.ts
similarity index 100%
rename from web/lib/util/random-string.ts
rename to functions/src/util/random-string.ts
diff --git a/web/lib/util/slugify.ts b/functions/src/util/slugify.ts
similarity index 100%
rename from web/lib/util/slugify.ts
rename to functions/src/util/slugify.ts
diff --git a/web/lib/firebase/init.ts b/web/lib/firebase/init.ts
index 98734946..ba10545a 100644
--- a/web/lib/firebase/init.ts
+++ b/web/lib/firebase/init.ts
@@ -2,8 +2,8 @@ import { getFirestore } from '@firebase/firestore'
 import { initializeApp } from 'firebase/app'
 
 // TODO: Reenable this when we have a way to set the Firebase db in dev
-// export const isProd = process.env.NODE_ENV === 'production'
-export const isProd = true
+export const isProd = process.env.NODE_ENV === 'production'
+// export const isProd = true
 
 const firebaseConfig = isProd
   ? {
diff --git a/web/lib/service/create-contract.ts b/web/lib/service/create-contract.ts
deleted file mode 100644
index 7c7a59df..00000000
--- a/web/lib/service/create-contract.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import {
-  Contract,
-  getContractFromSlug,
-  pushNewContract,
-} from '../firebase/contracts'
-import { User } from '../firebase/users'
-import { randomString } from '../util/random-string'
-import { slugify } from '../util/slugify'
-
-// consider moving to cloud function for security
-export async function createContract(
-  question: string,
-  description: string,
-  initialProb: number,
-  creator: User,
-  closeTime?: number
-) {
-  const proposedSlug = slugify(question).substring(0, 35)
-
-  const preexistingContract = await getContractFromSlug(proposedSlug)
-
-  const slug = preexistingContract
-    ? proposedSlug + '-' + randomString()
-    : proposedSlug
-
-  const { startYes, startNo } = calcStartPool(initialProb)
-
-  const contract: Omit<Contract, 'id'> = {
-    slug,
-    outcomeType: 'BINARY',
-
-    creatorId: creator.id,
-    creatorName: creator.name,
-    creatorUsername: creator.username,
-
-    question: question.trim(),
-    description: description.trim(),
-
-    startPool: { YES: startYes, NO: startNo },
-    pool: { YES: startYes, NO: startNo },
-    totalShares: { YES: 0, NO: 0 },
-    totalBets: { YES: 0, NO: 0 },
-    isResolved: false,
-
-    // TODO: Set create time to Firestore timestamp
-    createdTime: Date.now(),
-    lastUpdatedTime: Date.now(),
-  }
-  if (closeTime) {
-    contract.closeTime = closeTime
-  }
-
-  return await pushNewContract(contract)
-}
-
-export function calcStartPool(initialProbInt: number, initialCapital = 200) {
-  const p = initialProbInt / 100.0
-
-  const startYes =
-    p === 0.5
-      ? p * initialCapital
-      : -(initialCapital * (-p + Math.sqrt((-1 + p) * -p))) / (-1 + 2 * p)
-
-  const startNo = initialCapital - startYes
-
-  return { startYes, startNo }
-}
diff --git a/web/pages/create.tsx b/web/pages/create.tsx
index d14df94b..c4303148 100644
--- a/web/pages/create.tsx
+++ b/web/pages/create.tsx
@@ -1,15 +1,16 @@
 import router from 'next/router'
 import { useEffect, useState } from 'react'
+import clsx from 'clsx'
+import dayjs from 'dayjs'
+import { getFunctions, httpsCallable } from 'firebase/functions'
 
 import { CreatorContractsList } from '../components/contracts-list'
 import { Spacer } from '../components/layout/spacer'
 import { Title } from '../components/title'
 import { useUser } from '../hooks/use-user'
-import { path } from '../lib/firebase/contracts'
-import { createContract } from '../lib/service/create-contract'
+import { Contract, path } from '../lib/firebase/contracts'
 import { Page } from '../components/page'
-import clsx from 'clsx'
-import dayjs from 'dayjs'
+import { formatMoney } from '../lib/util/format'
 
 // Allow user to create a new contract
 export default function NewContract() {
@@ -22,29 +23,28 @@ export default function NewContract() {
   const [initialProb, setInitialProb] = useState(50)
   const [question, setQuestion] = useState('')
   const [description, setDescription] = useState('')
+
+  const [ante, setAnte] = useState<number | undefined>(0)
+  const [anteError, setAnteError] = useState('')
   const [closeDate, setCloseDate] = useState('')
+
   const [isSubmitting, setIsSubmitting] = useState(false)
   const [collapsed, setCollapsed] = useState(true)
 
-  // Given a date string like '2022-04-02',
-  // return the time just before midnight on that date (in the user's local time), as millis since epoch
-  function dateToMillis(date: string) {
-    return dayjs(date)
-      .set('hour', 23)
-      .set('minute', 59)
-      .set('second', 59)
-      .valueOf()
-  }
-  const closeTime = dateToMillis(closeDate)
+  const closeTime = dateToMillis(closeDate) || undefined
   // We'd like this to look like "Apr 2, 2022, 23:59:59 PM PT" but timezones are hard with dayjs
-  const formattedCloseTime = new Date(closeTime).toString()
+  const formattedCloseTime = closeTime ? new Date(closeTime).toString() : ''
+
+  const user = useUser()
+  const remainingBalance = (user?.balance || 0) - (ante || 0)
 
   const isValid =
     initialProb > 0 &&
     initialProb < 100 &&
     question.length > 0 &&
+    (ante === undefined || (ante >= 0 && ante <= remainingBalance)) &&
     // If set, closeTime must be in the future
-    (!closeDate || closeTime > Date.now())
+    (!closeTime || closeTime > Date.now())
 
   async function submit() {
     // TODO: Tell users why their contract is invalid
@@ -52,14 +52,31 @@ export default function NewContract() {
 
     setIsSubmitting(true)
 
-    const contract = await createContract(
+    const result: any = await createContract({
       question,
       description,
       initialProb,
-      creator,
-      closeTime
-    )
-    await router.push(path(contract))
+      ante,
+      closeTime: closeTime || undefined,
+    }).then((r) => r.data || {})
+
+    if (result.status !== 'success') {
+      console.log('error creating contract', result)
+      return
+    }
+
+    await router.push(path(result.contract as Contract))
+  }
+
+  function onAnteChange(str: string) {
+    const amount = parseInt(str)
+
+    if (str && isNaN(amount)) return
+
+    setAnte(str ? amount : undefined)
+
+    if (user && user.balance < amount) setAnteError('Insufficient balance')
+    else setAnteError('')
   }
 
   const descriptionPlaceholder = `e.g. This market will resolve to “Yes” if, by June 2, 2021, 11:59:59 PM ET, Paxlovid (also known under PF-07321332)...`
@@ -113,7 +130,7 @@ export default function NewContract() {
 
           <div className="form-control">
             <label className="label">
-              <span className="label-text">Description (optional)</span>
+              <span className="label-text">Description</span>
             </label>
             <textarea
               className="textarea w-full h-24 textarea-bordered"
@@ -145,8 +162,40 @@ export default function NewContract() {
                 }}
               />
             </div>
+
             <div className="collapse-content !p-0 m-0 !bg-transparent">
               <div className="form-control mb-1">
+                <label className="label">
+                  <span className="label-text">Subsidize your market</span>
+                </label>
+
+                <label className="input-group">
+                  <span className="text-sm bg-gray-200">M$</span>
+                  <input
+                    className={clsx(
+                      'input input-bordered',
+                      anteError && 'input-error'
+                    )}
+                    type="text"
+                    placeholder="0"
+                    maxLength={9}
+                    value={ante ?? ''}
+                    disabled={isSubmitting}
+                    onChange={(e) => onAnteChange(e.target.value)}
+                  />
+                </label>
+
+                <div className="mt-3 mb-1 text-sm text-gray-400">
+                  Remaining balance
+                </div>
+                <div>
+                  {formatMoney(remainingBalance > 0 ? remainingBalance : 0)}
+                </div>
+              </div>
+
+              <Spacer h={4} />
+
+              <div className="form-control">
                 <label className="label">
                   <span className="label-text">Close date (optional)</span>
                 </label>
@@ -156,6 +205,7 @@ export default function NewContract() {
                   onClick={(e) => e.stopPropagation()}
                   onChange={(e) => setCloseDate(e.target.value || '')}
                   min={new Date().toISOString().split('T')[0]}
+                  disabled={isSubmitting}
                   value={closeDate}
                 />
               </div>
@@ -173,14 +223,17 @@ export default function NewContract() {
           <div className="flex justify-end my-4">
             <button
               type="submit"
-              className="btn btn-primary"
+              className={clsx(
+                'btn btn-primary',
+                isSubmitting && 'loading disabled'
+              )}
               disabled={isSubmitting || !isValid}
               onClick={(e) => {
                 e.preventDefault()
                 submit()
               }}
             >
-              Create market
+              {isSubmitting ? 'Creating...' : 'Create market'}
             </button>
           </div>
         </form>
@@ -194,3 +247,16 @@ export default function NewContract() {
     </Page>
   )
 }
+
+const functions = getFunctions()
+export const createContract = httpsCallable(functions, 'createContract')
+
+// Given a date string like '2022-04-02',
+// return the time just before midnight on that date (in the user's local time), as millis since epoch
+function dateToMillis(date: string) {
+  return dayjs(date)
+    .set('hour', 23)
+    .set('minute', 59)
+    .set('second', 59)
+    .valueOf()
+}