diff --git a/common/user.ts b/common/user.ts index 97e349ed..f35cbdaf 100644 --- a/common/user.ts +++ b/common/user.ts @@ -7,4 +7,6 @@ export type User = { balance: number createdTime: number lastUpdatedTime: number + totalPnLCached: number + creatorVolumeCached: number } diff --git a/functions/src/index.ts b/functions/src/index.ts index f61e49b7..bc769ea2 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -10,3 +10,4 @@ export * from './stripe' export * from './sell-bet' export * from './create-contract' export * from './update-contract-metrics' +export * from './update-user-metrics' diff --git a/functions/src/stripe.ts b/functions/src/stripe.ts index 71ec4a71..2f054392 100644 --- a/functions/src/stripe.ts +++ b/functions/src/stripe.ts @@ -4,6 +4,14 @@ import Stripe from 'stripe' import { payUser } from './utils' +export type StripeTransaction = { + userId: string + manticDollarQuantity: number + sessionId: string + session: any + timestamp: number +} + const stripe = new Stripe(functions.config().stripe.apikey, { apiVersion: '2020-08-27', typescript: true, @@ -111,12 +119,15 @@ const issueMoneys = async (session: any) => { const { userId, manticDollarQuantity } = session.metadata const payout = Number.parseInt(manticDollarQuantity) - await firestore.collection('stripe-transactions').add({ + const transaction: StripeTransaction = { userId, manticDollarQuantity: payout, // save as number sessionId, session, - }) + timestamp: Date.now(), + } + + await firestore.collection('stripe-transactions').add(transaction) await payUser(userId, payout) diff --git a/functions/src/update-user-metrics.ts b/functions/src/update-user-metrics.ts new file mode 100644 index 00000000..6c81e4d1 --- /dev/null +++ b/functions/src/update-user-metrics.ts @@ -0,0 +1,85 @@ +import * as functions from 'firebase-functions' +import * as admin from 'firebase-admin' +import * as _ from 'lodash' + +import { getValues } from './utils' +import { Contract } from '../../common/contract' +import { Bet } from '../../common/bet' +import { User } from '../../common/user' +import { calculatePayout } from '../../common/calculate' +import { StripeTransaction } from '.' + +const firestore = admin.firestore() + +export const updateUserMetrics = functions.pubsub + .schedule('every 15 minutes') + .onRun(async () => { + const [users, contracts] = await Promise.all([ + getValues(firestore.collection('users')), + getValues(firestore.collection('contracts')), + ]) + + const contractsDict = _.fromPairs( + contracts.map((contract) => [contract.id, contract]) + ) + + await Promise.all( + users.map(async (user) => { + const investmentValue = await computeInvestmentValue( + user, + contractsDict + ) + const deposits = await getValues( + firestore + .collection('stripe-transactions') + .where('userId', '==', user.id) + ) + const totalDeposits = + 1000 + _.sumBy(deposits, (deposit) => deposit.manticDollarQuantity) + const totalValue = user.balance + investmentValue + + const totalPnL = totalValue - totalDeposits + + const creatorVolume = await computeTotalVolume(user, contractsDict) + + return firestore.collection('users').doc(user.id).update({ + totalPnLCached: totalPnL, + creatorVolumeCached: creatorVolume, + }) + }) + ) + }) + +const computeInvestmentValue = async ( + user: User, + contractsDict: _.Dictionary +) => { + const query = firestore.collectionGroup('bets').where('userId', '==', user.id) + const bets = await getValues(query) + + return _.sumBy(bets, (bet) => { + const contract = contractsDict[bet.contractId] + if (!contract || contract.isResolved) return 0 + if (bet.sale || bet.isSold) return 0 + + return calculatePayout(contract, bet, 'MKT') + }) +} + +const computeTotalVolume = async ( + user: User, + contractsDict: _.Dictionary +) => { + const creatorContracts = Object.values(contractsDict).filter( + (contract) => contract.creatorId === user.id + ) + const volumes = await Promise.all(creatorContracts.map(computeVolume)) + return _.sum(volumes) +} + +const computeVolume = async (contract: Contract) => { + const bets = await getValues( + firestore.collection(`contracts/${contract.id}/bets`) + ) + return _.sumBy(bets, (bet) => Math.abs(bet.amount)) +} diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index 1311bdd4..e4a07406 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -74,6 +74,8 @@ export function listenForLogin(onUser: (user: User | null) => void) { // TODO: use Firestore timestamp? createdTime: Date.now(), lastUpdatedTime: Date.now(), + totalPnLCached: 0, + creatorVolumeCached: 0, } await setUser(fbUser.uid, user) }