manifold/functions/src/emails.ts

362 lines
8.5 KiB
TypeScript
Raw Normal View History

Cfmm (#64) * cpmm initial commit: common logic, cloud functions * remove unnecessary property * contract type * rename 'calculate.ts' => 'calculate-dpm.ts' * rename dpm calculations * use focus hook * mechanism-agnostic calculations * bet panel: use new calculations * use new calculations * delete markets cloud function * use correct contract type in scripts / functions * calculate fixed payouts; bets list calculations * new bet: use calculateCpmmPurchase * getOutcomeProbabilityAfterBet * use deductFixedFees * fix auto-refactor * fix antes * separate logic to payouts-dpm, payouts-fixed * liquidity provision tracking * remove comment * liquidity label * create liquidity provision even if no ante bet * liquidity fee * use all bets for getFixedCancelPayouts * updateUserBalance: allow negative balances * store initialProbability in contracts * turn on liquidity fee; turn off creator fee * Include time param in tweet url, so image preview is re-fetched * share redemption * cpmm ContractBetsTable display * formatMoney: handle minus zero * filter out redemption bets * track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution * small fixes * small fixes * Redeem shares pays back loans first * Fix initial point on graph * calculateCpmmPurchase: deduct creator fee * Filter out redemption bets from feed * set env to dev for user-testing purposes * creator fees messaging * new cfmm: k = y^(1-p) * n^p * addCpmmLiquidity * correct price function * enable fees * handle overflow * liquidity provision tracking * raise fees * Fix merge error * fix dpm free response payout for single outcome * Fix DPM payout calculation * Remove hardcoding as dev Co-authored-by: James Grugett <jahooma@gmail.com>
2022-03-15 22:27:51 +00:00
import * as _ from 'lodash'
import { DOMAIN, PROJECT_ID } from 'common/envs/constants'
import { Answer } from 'common/answer'
import { Bet } from 'common/bet'
import { getProbability } from 'common/calculate'
import { Comment } from 'common/comment'
import { Contract, FreeResponseContract } from 'common/contract'
import { DPM_CREATOR_FEE } from 'common/fees'
import { PrivateUser, User } from 'common/user'
import { formatMoney, formatPercent } from 'common/util/format'
import { sendTemplateEmail } from './send-email'
import { getPrivateUser, getUser } from './utils'
export const sendMarketResolutionEmail = async (
userId: string,
investment: number,
payout: number,
creator: User,
creatorPayout: number,
contract: Contract,
resolution: string,
resolutionProbability?: number,
resolutions?: { [outcome: string]: number }
) => {
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
privateUser.unsubscribedFromResolutionEmails ||
!privateUser.email
)
return
const user = await getUser(userId)
if (!user) return
Cfmm (#64) * cpmm initial commit: common logic, cloud functions * remove unnecessary property * contract type * rename 'calculate.ts' => 'calculate-dpm.ts' * rename dpm calculations * use focus hook * mechanism-agnostic calculations * bet panel: use new calculations * use new calculations * delete markets cloud function * use correct contract type in scripts / functions * calculate fixed payouts; bets list calculations * new bet: use calculateCpmmPurchase * getOutcomeProbabilityAfterBet * use deductFixedFees * fix auto-refactor * fix antes * separate logic to payouts-dpm, payouts-fixed * liquidity provision tracking * remove comment * liquidity label * create liquidity provision even if no ante bet * liquidity fee * use all bets for getFixedCancelPayouts * updateUserBalance: allow negative balances * store initialProbability in contracts * turn on liquidity fee; turn off creator fee * Include time param in tweet url, so image preview is re-fetched * share redemption * cpmm ContractBetsTable display * formatMoney: handle minus zero * filter out redemption bets * track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution * small fixes * small fixes * Redeem shares pays back loans first * Fix initial point on graph * calculateCpmmPurchase: deduct creator fee * Filter out redemption bets from feed * set env to dev for user-testing purposes * creator fees messaging * new cfmm: k = y^(1-p) * n^p * addCpmmLiquidity * correct price function * enable fees * handle overflow * liquidity provision tracking * raise fees * Fix merge error * fix dpm free response payout for single outcome * Fix DPM payout calculation * Remove hardcoding as dev Co-authored-by: James Grugett <jahooma@gmail.com>
2022-03-15 22:27:51 +00:00
const outcome = toDisplayResolution(
contract,
resolution,
resolutionProbability,
resolutions
)
2022-01-10 22:07:44 +00:00
const subject = `Resolved ${outcome}: ${contract.question}`
const creatorPayoutText =
userId === creator.id
? ` (plus ${formatMoney(creatorPayout)} in commissions)`
: ''
2022-01-10 22:07:44 +00:00
const templateData: market_resolved_template = {
userId: user.id,
2022-01-10 22:07:44 +00:00
name: user.name,
creatorName: creator.name,
question: contract.question,
outcome,
investment: `${Math.floor(investment)}`,
payout: `${Math.floor(payout)}${creatorPayoutText}`,
url: `https://${DOMAIN}/${creator.username}/${contract.slug}`,
2022-01-10 22:07:44 +00:00
}
2022-01-10 22:07:44 +00:00
// Modify template here:
// https://app.mailgun.com/app/sending/domains/mg.manifold.markets/templates/edit/market-resolved/initial
// Mailgun username: james@mantic.markets
await sendTemplateEmail(
privateUser.email,
subject,
'market-resolved',
templateData
)
}
2022-02-08 11:26:33 +00:00
Cfmm (#64) * cpmm initial commit: common logic, cloud functions * remove unnecessary property * contract type * rename 'calculate.ts' => 'calculate-dpm.ts' * rename dpm calculations * use focus hook * mechanism-agnostic calculations * bet panel: use new calculations * use new calculations * delete markets cloud function * use correct contract type in scripts / functions * calculate fixed payouts; bets list calculations * new bet: use calculateCpmmPurchase * getOutcomeProbabilityAfterBet * use deductFixedFees * fix auto-refactor * fix antes * separate logic to payouts-dpm, payouts-fixed * liquidity provision tracking * remove comment * liquidity label * create liquidity provision even if no ante bet * liquidity fee * use all bets for getFixedCancelPayouts * updateUserBalance: allow negative balances * store initialProbability in contracts * turn on liquidity fee; turn off creator fee * Include time param in tweet url, so image preview is re-fetched * share redemption * cpmm ContractBetsTable display * formatMoney: handle minus zero * filter out redemption bets * track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution * small fixes * small fixes * Redeem shares pays back loans first * Fix initial point on graph * calculateCpmmPurchase: deduct creator fee * Filter out redemption bets from feed * set env to dev for user-testing purposes * creator fees messaging * new cfmm: k = y^(1-p) * n^p * addCpmmLiquidity * correct price function * enable fees * handle overflow * liquidity provision tracking * raise fees * Fix merge error * fix dpm free response payout for single outcome * Fix DPM payout calculation * Remove hardcoding as dev Co-authored-by: James Grugett <jahooma@gmail.com>
2022-03-15 22:27:51 +00:00
type market_resolved_template = {
userId: string
name: string
creatorName: string
question: string
outcome: string
investment: string
payout: string
url: string
}
const toDisplayResolution = (
contract: Contract,
resolution: string,
resolutionProbability?: number,
resolutions?: { [outcome: string]: number }
) => {
if (contract.outcomeType === 'BINARY') {
const prob = resolutionProbability ?? getProbability(contract)
const display = {
YES: 'YES',
NO: 'NO',
CANCEL: 'N/A',
MKT: formatPercent(prob ?? 0),
}[resolution]
return display || resolution
}
if (resolution === 'MKT' && resolutions) return 'MULTI'
if (resolution === 'CANCEL') return 'N/A'
Cfmm (#64) * cpmm initial commit: common logic, cloud functions * remove unnecessary property * contract type * rename 'calculate.ts' => 'calculate-dpm.ts' * rename dpm calculations * use focus hook * mechanism-agnostic calculations * bet panel: use new calculations * use new calculations * delete markets cloud function * use correct contract type in scripts / functions * calculate fixed payouts; bets list calculations * new bet: use calculateCpmmPurchase * getOutcomeProbabilityAfterBet * use deductFixedFees * fix auto-refactor * fix antes * separate logic to payouts-dpm, payouts-fixed * liquidity provision tracking * remove comment * liquidity label * create liquidity provision even if no ante bet * liquidity fee * use all bets for getFixedCancelPayouts * updateUserBalance: allow negative balances * store initialProbability in contracts * turn on liquidity fee; turn off creator fee * Include time param in tweet url, so image preview is re-fetched * share redemption * cpmm ContractBetsTable display * formatMoney: handle minus zero * filter out redemption bets * track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution * small fixes * small fixes * Redeem shares pays back loans first * Fix initial point on graph * calculateCpmmPurchase: deduct creator fee * Filter out redemption bets from feed * set env to dev for user-testing purposes * creator fees messaging * new cfmm: k = y^(1-p) * n^p * addCpmmLiquidity * correct price function * enable fees * handle overflow * liquidity provision tracking * raise fees * Fix merge error * fix dpm free response payout for single outcome * Fix DPM payout calculation * Remove hardcoding as dev Co-authored-by: James Grugett <jahooma@gmail.com>
2022-03-15 22:27:51 +00:00
const answer = (contract as FreeResponseContract).answers?.find(
(a) => a.id === resolution
)
if (answer) return answer.text
Cfmm (#64) * cpmm initial commit: common logic, cloud functions * remove unnecessary property * contract type * rename 'calculate.ts' => 'calculate-dpm.ts' * rename dpm calculations * use focus hook * mechanism-agnostic calculations * bet panel: use new calculations * use new calculations * delete markets cloud function * use correct contract type in scripts / functions * calculate fixed payouts; bets list calculations * new bet: use calculateCpmmPurchase * getOutcomeProbabilityAfterBet * use deductFixedFees * fix auto-refactor * fix antes * separate logic to payouts-dpm, payouts-fixed * liquidity provision tracking * remove comment * liquidity label * create liquidity provision even if no ante bet * liquidity fee * use all bets for getFixedCancelPayouts * updateUserBalance: allow negative balances * store initialProbability in contracts * turn on liquidity fee; turn off creator fee * Include time param in tweet url, so image preview is re-fetched * share redemption * cpmm ContractBetsTable display * formatMoney: handle minus zero * filter out redemption bets * track fees on contract and bets; change fee schedule for cpmm markets; only pay out creator fees at resolution * small fixes * small fixes * Redeem shares pays back loans first * Fix initial point on graph * calculateCpmmPurchase: deduct creator fee * Filter out redemption bets from feed * set env to dev for user-testing purposes * creator fees messaging * new cfmm: k = y^(1-p) * n^p * addCpmmLiquidity * correct price function * enable fees * handle overflow * liquidity provision tracking * raise fees * Fix merge error * fix dpm free response payout for single outcome * Fix DPM payout calculation * Remove hardcoding as dev Co-authored-by: James Grugett <jahooma@gmail.com>
2022-03-15 22:27:51 +00:00
return `#${resolution}`
}
2022-02-08 11:26:33 +00:00
export const sendWelcomeEmail = async (
user: User,
privateUser: PrivateUser
) => {
if (!privateUser || !privateUser.email) return
const { name, id: userId } = user
const firstName = name.split(' ')[0]
2022-02-08 11:26:33 +00:00
const emailType = 'generic'
const unsubscribeLink = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/unsubscribe?id=${userId}&type=${emailType}`
await sendTemplateEmail(
privateUser.email,
2022-02-08 11:26:33 +00:00
'Welcome to Manifold Markets!',
'welcome',
{
name: firstName,
unsubscribeLink,
},
{
from: 'David from Manifold <david@manifold.markets>',
}
)
}
2022-02-08 11:26:33 +00:00
// TODO: use manalinks to give out M$500
export const sendOneWeekBonusEmail = async (
user: User,
privateUser: PrivateUser
) => {
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromGenericEmails
)
return
2022-02-22 22:35:53 +00:00
const { name, id: userId } = user
const firstName = name.split(' ')[0]
2022-02-22 22:35:53 +00:00
const emailType = 'generic'
const unsubscribeLink = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/unsubscribe?id=${userId}&type=${emailType}`
2022-02-08 11:26:33 +00:00
await sendTemplateEmail(
privateUser.email,
'Manifold one week anniversary gift',
'one-week',
{
name: firstName,
unsubscribeLink,
manalink: '', // TODO
},
{
from: 'David from Manifold <david@manifold.markets>',
}
)
}
export const sendThankYouEmail = async (
user: User,
privateUser: PrivateUser
) => {
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromGenericEmails
)
return
const { name, id: userId } = user
const firstName = name.split(' ')[0]
const emailType = 'generic'
const unsubscribeLink = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/unsubscribe?id=${userId}&type=${emailType}`
await sendTemplateEmail(
privateUser.email,
'Thanks for your Manifold purchase',
'thank-you',
{
name: firstName,
unsubscribeLink,
},
{
from: 'David from Manifold <david@manifold.markets>',
}
2022-02-08 11:26:33 +00:00
)
}
export const sendMarketCloseEmail = async (
user: User,
privateUser: PrivateUser,
contract: Contract
) => {
if (
!privateUser ||
privateUser.unsubscribedFromResolutionEmails ||
!privateUser.email
)
return
const { username, name, id: userId } = user
const firstName = name.split(' ')[0]
const { question, slug, volume, mechanism, collectedFees } = contract
const url = `https://${DOMAIN}/${username}/${slug}`
await sendTemplateEmail(
privateUser.email,
'Your market has closed',
'market-close',
{
question,
url,
userId,
name: firstName,
volume: formatMoney(volume),
creatorFee:
mechanism === 'dpm-2'
? `${DPM_CREATOR_FEE * 100}% of the profits`
: formatMoney(collectedFees.creatorFee),
}
)
}
export const sendNewCommentEmail = async (
userId: string,
commentCreator: User,
contract: Contract,
comment: Comment,
bet?: Bet,
answer?: Answer
) => {
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromCommentEmails
)
return
const { question, creatorUsername, slug } = contract
const marketUrl = `https://${DOMAIN}/${creatorUsername}/${slug}`
const unsubscribeUrl = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/unsubscribe?id=${userId}&type=market-comment`
const { name: commentorName, avatarUrl: commentorAvatarUrl } = commentCreator
const { text } = comment
let betDescription = ''
if (bet) {
const { amount, sale } = bet
betDescription = `${sale || amount < 0 ? 'sold' : 'bought'} ${formatMoney(
2022-04-28 03:34:50 +00:00
Math.abs(amount)
)}`
}
const subject = `Comment on ${question}`
const from = `${commentorName} <info@manifold.markets>`
if (contract.outcomeType === 'FREE_RESPONSE') {
const answerText = answer?.text ?? ''
const answerNumber = `#${answer?.id ?? ''}`
await sendTemplateEmail(
privateUser.email,
subject,
'market-answer-comment',
{
answer: answerText,
answerNumber,
commentorName,
commentorAvatarUrl: commentorAvatarUrl ?? '',
comment: text,
marketUrl,
unsubscribeUrl,
betDescription,
},
{ from }
)
} else {
if (bet) {
betDescription = `${betDescription} of ${toDisplayResolution(
contract,
bet.outcome
)}`
}
await sendTemplateEmail(
privateUser.email,
subject,
'market-comment',
{
commentorName,
commentorAvatarUrl: commentorAvatarUrl ?? '',
comment: text,
marketUrl,
unsubscribeUrl,
betDescription,
},
{ from }
)
}
}
export const sendNewAnswerEmail = async (
answer: Answer,
contract: Contract
) => {
// Send to just the creator for now.
const { creatorId: userId } = contract
// Don't send the creator's own answers.
if (answer.userId === userId) return
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromAnswerEmails
)
return
const { question, creatorUsername, slug } = contract
const { name, avatarUrl, text } = answer
const marketUrl = `https://${DOMAIN}/${creatorUsername}/${slug}`
const unsubscribeUrl = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/unsubscribe?id=${userId}&type=market-answer`
const subject = `New answer on ${question}`
const from = `${name} <info@manifold.markets>`
await sendTemplateEmail(
privateUser.email,
subject,
'market-answer',
{
name,
avatarUrl: avatarUrl ?? '',
answer: text,
marketUrl,
unsubscribeUrl,
},
{ from }
)
}