diff --git a/common/envs/constants.ts b/common/envs/constants.ts
index ba460d58..0502322a 100644
--- a/common/envs/constants.ts
+++ b/common/envs/constants.ts
@@ -21,7 +21,10 @@ export function isWhitelisted(email?: string) {
}
// TODO: Before open sourcing, we should turn these into env vars
-export function isAdmin(email: string) {
+export function isAdmin(email?: string) {
+ if (!email) {
+ return false
+ }
return ENV_CONFIG.adminEmails.includes(email)
}
diff --git a/common/envs/prod.ts b/common/envs/prod.ts
index b3b552eb..6bf781b7 100644
--- a/common/envs/prod.ts
+++ b/common/envs/prod.ts
@@ -74,6 +74,7 @@ export const PROD_CONFIG: EnvConfig = {
'iansphilips@gmail.com', // Ian
'd4vidchee@gmail.com', // D4vid
'federicoruizcassarino@gmail.com', // Fede
+ 'ingawei@gmail.com', //Inga
],
visibility: 'PUBLIC',
diff --git a/firestore.rules b/firestore.rules
index 6f2ea90a..08214b10 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -14,7 +14,8 @@ service cloud.firestore {
'manticmarkets@gmail.com',
'iansphilips@gmail.com',
'd4vidchee@gmail.com',
- 'federicoruizcassarino@gmail.com'
+ 'federicoruizcassarino@gmail.com',
+ 'ingawei@gmail.com'
]
}
diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts
index b867b609..44293898 100644
--- a/functions/src/resolve-market.ts
+++ b/functions/src/resolve-market.ts
@@ -16,7 +16,7 @@ import {
groupPayoutsByUser,
Payout,
} from '../../common/payouts'
-import { isManifoldId } from '../../common/envs/constants'
+import { isAdmin, isManifoldId } from '../../common/envs/constants'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
import { APIError, newEndpoint, validate } from './api'
@@ -76,13 +76,18 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
throw new APIError(404, 'No contract exists with the provided ID')
const contract = contractSnap.data() as Contract
const { creatorId, closeTime } = contract
+ const firebaseUser = await admin.auth().getUser(auth.uid)
const { value, resolutions, probabilityInt, outcome } = getResolutionParams(
contract,
req.body
)
- if (creatorId !== auth.uid && !isManifoldId(auth.uid))
+ if (
+ creatorId !== auth.uid &&
+ !isManifoldId(auth.uid) &&
+ !isAdmin(firebaseUser.email)
+ )
throw new APIError(403, 'User is not creator of contract')
if (contract.resolution) throw new APIError(400, 'Contract already resolved')
diff --git a/web/components/answers/answer-resolve-panel.tsx b/web/components/answers/answer-resolve-panel.tsx
index 0a4ac1e1..4594ea35 100644
--- a/web/components/answers/answer-resolve-panel.tsx
+++ b/web/components/answers/answer-resolve-panel.tsx
@@ -11,6 +11,8 @@ import { ResolveConfirmationButton } from '../confirmation-button'
import { removeUndefinedProps } from 'common/util/object'
export function AnswerResolvePanel(props: {
+ isAdmin: boolean
+ isCreator: boolean
contract: FreeResponseContract | MultipleChoiceContract
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
setResolveOption: (
@@ -18,7 +20,14 @@ export function AnswerResolvePanel(props: {
) => void
chosenAnswers: { [answerId: string]: number }
}) {
- const { contract, resolveOption, setResolveOption, chosenAnswers } = props
+ const {
+ contract,
+ resolveOption,
+ setResolveOption,
+ chosenAnswers,
+ isAdmin,
+ isCreator,
+ } = props
const answers = Object.keys(chosenAnswers)
const [isSubmitting, setIsSubmitting] = useState(false)
@@ -76,7 +85,14 @@ export function AnswerResolvePanel(props: {
return (
- Resolve your market
+
+ Resolve your market
+ {isAdmin && !isCreator && (
+
+ ADMIN
+
+ )}
+
)}
- {user?.id === creatorId && !resolution && (
- <>
-
-
- >
- )}
+ {(user?.id === creatorId || (isAdmin && needsAdminToResolve(contract))) &&
+ !resolution && (
+ <>
+
+
+ >
+ )}
)
}
diff --git a/web/components/numeric-resolution-panel.tsx b/web/components/numeric-resolution-panel.tsx
index dce36ab9..70fbf01f 100644
--- a/web/components/numeric-resolution-panel.tsx
+++ b/web/components/numeric-resolution-panel.tsx
@@ -12,11 +12,13 @@ import { BucketInput } from './bucket-input'
import { getPseudoProbability } from 'common/pseudo-numeric'
export function NumericResolutionPanel(props: {
+ isAdmin: boolean
+ isCreator: boolean
creator: User
contract: NumericContract | PseudoNumericContract
className?: string
}) {
- const { contract, className } = props
+ const { contract, className, isAdmin, isCreator } = props
const { min, max, outcomeType } = contract
const [outcomeMode, setOutcomeMode] = useState<
@@ -78,10 +80,20 @@ export function NumericResolutionPanel(props: {
: 'btn-disabled'
return (
-
- Resolve market
+
+ {isAdmin && !isCreator && (
+
+ ADMIN
+
+ )}
+ Resolve market
- Outcome
+ Outcome
diff --git a/web/components/resolution-panel.tsx b/web/components/resolution-panel.tsx
index 5a7b993e..6f36331e 100644
--- a/web/components/resolution-panel.tsx
+++ b/web/components/resolution-panel.tsx
@@ -12,11 +12,13 @@ import { getProbability } from 'common/calculate'
import { BinaryContract, resolution } from 'common/contract'
export function ResolutionPanel(props: {
+ isAdmin: boolean
+ isCreator: boolean
creator: User
contract: BinaryContract
className?: string
}) {
- const { contract, className } = props
+ const { contract, className, isAdmin, isCreator } = props
// const earnedFees =
// contract.mechanism === 'dpm-2'
@@ -66,7 +68,12 @@ export function ResolutionPanel(props: {
: 'btn-disabled'
return (
-
+
+ {isAdmin && !isCreator && (
+
+ ADMIN
+
+ )}
Resolve market
Outcome
diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx
index de0c7807..2c011c90 100644
--- a/web/pages/[username]/[contractSlug].tsx
+++ b/web/pages/[username]/[contractSlug].tsx
@@ -45,6 +45,8 @@ import {
import { ContractsGrid } from 'web/components/contract/contracts-grid'
import { Title } from 'web/components/title'
import { usePrefetch } from 'web/hooks/use-prefetch'
+import { useAdmin } from 'web/hooks/use-admin'
+import dayjs from 'dayjs'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: {
@@ -110,19 +112,28 @@ export default function ContractPage(props: {
)
}
+// requires an admin to resolve a week after market closes
+export function needsAdminToResolve(contract: Contract) {
+ return !contract.isResolved && dayjs().diff(contract.closeTime, 'day') > 7
+}
+
export function ContractPageSidebar(props: {
user: User | null | undefined
contract: Contract
}) {
const { contract, user } = props
const { creatorId, isResolved, outcomeType } = contract
-
const isCreator = user?.id === creatorId
const isBinary = outcomeType === 'BINARY'
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
const isNumeric = outcomeType === 'NUMERIC'
const allowTrade = tradingAllowed(contract)
- const allowResolve = !isResolved && isCreator && !!user
+ const isAdmin = useAdmin()
+ const allowResolve =
+ !isResolved &&
+ (isCreator || (needsAdminToResolve(contract) && isAdmin)) &&
+ !!user
+
const hasSidePanel =
(isBinary || isNumeric || isPseudoNumeric) && (allowTrade || allowResolve)
@@ -139,9 +150,19 @@ export function ContractPageSidebar(props: {
))}
{allowResolve &&
(isNumeric || isPseudoNumeric ? (
-
+
) : (
-
+
))}
) : null
@@ -154,10 +175,8 @@ export function ContractPageContent(
}
) {
const { backToHome, comments, user } = props
-
const contract = useContractWithPreload(props.contract) ?? props.contract
usePrefetch(user?.id)
-
useTracking(
'view market',
{