diff --git a/functions/src/add-bounty.ts b/functions/src/add-bounty.ts new file mode 100644 index 00000000..2f5dee11 --- /dev/null +++ b/functions/src/add-bounty.ts @@ -0,0 +1,53 @@ +import * as admin from 'firebase-admin' +import { z } from 'zod' + +import { Contract } from '../../common/contract' +import { User } from '../../common/user' +import { APIError, newEndpoint, validate } from './api' + +const firestore = admin.firestore() + +const bodySchema = z.object({ + contractId: z.string(), + amount: z.number().gt(0), +}) + +export const addbounty = newEndpoint({}, async (req, auth) => { + const { amount, contractId } = validate(bodySchema, req.body) + + 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 (contract.outcomeType !== 'BOUNTY') + throw new APIError(400, "Can't add bounties to non-BOUNTY contracts") + + const { closeTime } = contract + if (closeTime && Date.now() > closeTime) + throw new APIError(400, 'Contract is closed') + + if (user.balance < amount) throw new APIError(400, 'Insufficient balance') + + transaction.update(userDoc, { + balance: user.balance - amount, + totalDeposits: user.totalDeposits - amount, + }) + + const existingPrize = contract.prizes[user.id] ?? 0 + + transaction.update(contractDoc, { + prizes: { + ...contract.prizes, + [user.id]: existingPrize + amount, + }, + }) + + return { status: 'success' } + }) +}) diff --git a/functions/src/index.ts b/functions/src/index.ts index 07b37648..5e83ac6f 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -66,6 +66,7 @@ import { stripewebhook, createcheckoutsession } from './stripe' import { getcurrentuser } from './get-current-user' import { acceptchallenge } from './accept-challenge' import { getcustomtoken } from './get-custom-token' +import { addbounty } from './add-bounty' const toCloudFunction = ({ opts, handler }: EndpointDefinition) => { return onRequest(opts, handler as any) @@ -91,6 +92,7 @@ const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession) const getCurrentUserFunction = toCloudFunction(getcurrentuser) const acceptChallenge = toCloudFunction(acceptchallenge) const getCustomTokenFunction = toCloudFunction(getcustomtoken) +const addBountyFunction = toCloudFunction(addbounty) export { healthFunction as health, @@ -114,4 +116,5 @@ export { getCurrentUserFunction as getcurrentuser, acceptChallenge as acceptchallenge, getCustomTokenFunction as getcustomtoken, + addBountyFunction as addbounty, } diff --git a/functions/src/serve.ts b/functions/src/serve.ts index bf96db20..85066cd0 100644 --- a/functions/src/serve.ts +++ b/functions/src/serve.ts @@ -27,6 +27,7 @@ import { unsubscribe } from './unsubscribe' import { stripewebhook, createcheckoutsession } from './stripe' import { getcurrentuser } from './get-current-user' import { getcustomtoken } from './get-custom-token' +import { addbounty } from './add-bounty' type Middleware = (req: Request, res: Response, next: NextFunction) => void const app = express() @@ -65,6 +66,7 @@ addJsonEndpointRoute('/resolvemarket', resolvemarket) addJsonEndpointRoute('/unsubscribe', unsubscribe) addJsonEndpointRoute('/createcheckoutsession', createcheckoutsession) addJsonEndpointRoute('/getcurrentuser', getcurrentuser) +addJsonEndpointRoute('/addbounty', addbounty) addEndpointRoute('/getcustomtoken', getcustomtoken) addEndpointRoute('/stripewebhook', stripewebhook, express.raw()) diff --git a/web/lib/firebase/api.ts b/web/lib/firebase/api.ts index 5f250ce7..0dcf0dd3 100644 --- a/web/lib/firebase/api.ts +++ b/web/lib/firebase/api.ts @@ -88,3 +88,7 @@ export function acceptChallenge(params: any) { export function getCurrentUser(params: any) { return call(getFunctionUrl('getcurrentuser'), 'GET', params) } + +export function addBounty(params: any) { + return call(getFunctionUrl('addbounty'), 'POST', params) +}