Merge branch 'main' into limit-orders
This commit is contained in:
parent
5be2ea8583
commit
89ac26417b
|
@ -64,11 +64,7 @@ function calculateCpmmShares(
|
|||
: n + bet - (k * (bet + y) ** -p) ** (1 / (1 - p))
|
||||
}
|
||||
|
||||
export function getCpmmLiquidityFee(
|
||||
state: CpmmState,
|
||||
bet: number,
|
||||
outcome: string
|
||||
) {
|
||||
export function getCpmmFees(state: CpmmState, bet: number, outcome: string) {
|
||||
const prob = getCpmmProbabilityAfterBetBeforeFees(state, outcome, bet)
|
||||
const betP = outcome === 'YES' ? 1 - prob : prob
|
||||
|
||||
|
@ -89,7 +85,7 @@ export function calculateCpmmSharesAfterFee(
|
|||
outcome: string
|
||||
) {
|
||||
const { pool, p } = state
|
||||
const { remainingBet } = getCpmmLiquidityFee(state, bet, outcome)
|
||||
const { remainingBet } = getCpmmFees(state, bet, outcome)
|
||||
|
||||
return calculateCpmmShares(pool, p, remainingBet, outcome)
|
||||
}
|
||||
|
@ -100,9 +96,7 @@ export function calculateCpmmPurchase(
|
|||
outcome: string
|
||||
) {
|
||||
const { pool, p } = state
|
||||
const { remainingBet, fees } = getCpmmLiquidityFee(state, bet, outcome)
|
||||
// const remainingBet = bet
|
||||
// const fees = noFees
|
||||
const { remainingBet, fees } = getCpmmFees(state, bet, outcome)
|
||||
|
||||
const shares = calculateCpmmShares(pool, p, remainingBet, outcome)
|
||||
const { YES: y, NO: n } = pool
|
||||
|
|
|
@ -76,14 +76,14 @@ const computeFill = (
|
|||
? Math.min(matchedBet.limitProb, limitProb ?? 1)
|
||||
: Math.max(matchedBet.limitProb, limitProb ?? 0)
|
||||
|
||||
const poolAmount =
|
||||
const buyAmount =
|
||||
limit === undefined
|
||||
? amount
|
||||
: Math.min(amount, calculateCpmmAmount(cpmmState, limit, outcome))
|
||||
|
||||
const { shares, newPool, newP, fees } = calculateCpmmPurchase(
|
||||
cpmmState,
|
||||
poolAmount,
|
||||
buyAmount,
|
||||
outcome
|
||||
)
|
||||
const newState = { pool: newPool, p: newP }
|
||||
|
@ -92,7 +92,7 @@ const computeFill = (
|
|||
maker: {
|
||||
matchedBetId: null,
|
||||
shares,
|
||||
amount: poolAmount,
|
||||
amount: buyAmount,
|
||||
state: newState,
|
||||
fees,
|
||||
timestamp,
|
||||
|
@ -100,7 +100,7 @@ const computeFill = (
|
|||
taker: {
|
||||
matchedBetId: null,
|
||||
shares,
|
||||
amount: poolAmount,
|
||||
amount: buyAmount,
|
||||
timestamp,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,105 +1,96 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Contract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
import { removeUndefinedProps } from '../../common/util/object'
|
||||
import { redeemShares } from './redeem-shares'
|
||||
import { getNewLiquidityProvision } from '../../common/add-liquidity'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
|
||||
export const addLiquidity = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||
async (
|
||||
data: {
|
||||
amount: number
|
||||
contractId: string
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
const bodySchema = z.object({
|
||||
contractId: z.string(),
|
||||
amount: z.number().gt(0),
|
||||
})
|
||||
|
||||
const { amount, contractId } = data
|
||||
export const addliquidity = newEndpoint({}, async (req, auth) => {
|
||||
const { amount, contractId } = validate(bodySchema, req.body)
|
||||
|
||||
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||
return { status: 'error', message: 'Invalid amount' }
|
||||
if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
|
||||
|
||||
// run as transaction to prevent race conditions
|
||||
return await firestore
|
||||
.runTransaction(async (transaction) => {
|
||||
const userDoc = firestore.doc(`users/${userId}`)
|
||||
const userSnap = await transaction.get(userDoc)
|
||||
if (!userSnap.exists)
|
||||
return { status: 'error', message: 'User not found' }
|
||||
const user = userSnap.data() as User
|
||||
// run as transaction to prevent race conditions
|
||||
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)
|
||||
return { status: 'error', message: 'Invalid contract' }
|
||||
const contract = contractSnap.data() as Contract
|
||||
if (
|
||||
contract.mechanism !== 'cpmm-1' ||
|
||||
(contract.outcomeType !== 'BINARY' &&
|
||||
contract.outcomeType !== 'PSEUDO_NUMERIC')
|
||||
)
|
||||
return { status: 'error', message: 'Invalid contract' }
|
||||
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.mechanism !== 'cpmm-1' ||
|
||||
(contract.outcomeType !== 'BINARY' &&
|
||||
contract.outcomeType !== 'PSEUDO_NUMERIC')
|
||||
)
|
||||
throw new APIError(400, 'Invalid contract')
|
||||
|
||||
const { closeTime } = contract
|
||||
if (closeTime && Date.now() > closeTime)
|
||||
return { status: 'error', message: 'Trading is closed' }
|
||||
const { closeTime } = contract
|
||||
if (closeTime && Date.now() > closeTime)
|
||||
throw new APIError(400, 'Trading is closed')
|
||||
|
||||
if (user.balance < amount)
|
||||
return { status: 'error', message: 'Insufficient balance' }
|
||||
if (user.balance < amount) throw new APIError(400, 'Insufficient balance')
|
||||
|
||||
const newLiquidityProvisionDoc = firestore
|
||||
.collection(`contracts/${contractId}/liquidity`)
|
||||
.doc()
|
||||
const newLiquidityProvisionDoc = firestore
|
||||
.collection(`contracts/${contractId}/liquidity`)
|
||||
.doc()
|
||||
|
||||
const { newLiquidityProvision, newPool, newP, newTotalLiquidity } =
|
||||
getNewLiquidityProvision(
|
||||
user,
|
||||
amount,
|
||||
contract,
|
||||
newLiquidityProvisionDoc.id
|
||||
)
|
||||
|
||||
if (newP !== undefined && !isFinite(newP)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'Liquidity injection rejected due to overflow error.',
|
||||
}
|
||||
}
|
||||
|
||||
transaction.update(
|
||||
contractDoc,
|
||||
removeUndefinedProps({
|
||||
pool: newPool,
|
||||
p: newP,
|
||||
totalLiquidity: newTotalLiquidity,
|
||||
})
|
||||
const { newLiquidityProvision, newPool, newP, newTotalLiquidity } =
|
||||
getNewLiquidityProvision(
|
||||
user,
|
||||
amount,
|
||||
contract,
|
||||
newLiquidityProvisionDoc.id
|
||||
)
|
||||
|
||||
const newBalance = user.balance - amount
|
||||
const newTotalDeposits = user.totalDeposits - amount
|
||||
|
||||
if (!isFinite(newBalance)) {
|
||||
throw new Error('Invalid user balance for ' + user.username)
|
||||
if (newP !== undefined && !isFinite(newP)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'Liquidity injection rejected due to overflow error.',
|
||||
}
|
||||
}
|
||||
|
||||
transaction.update(userDoc, {
|
||||
balance: newBalance,
|
||||
totalDeposits: newTotalDeposits,
|
||||
transaction.update(
|
||||
contractDoc,
|
||||
removeUndefinedProps({
|
||||
pool: newPool,
|
||||
p: newP,
|
||||
totalLiquidity: newTotalLiquidity,
|
||||
})
|
||||
)
|
||||
|
||||
transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
|
||||
const newBalance = user.balance - amount
|
||||
const newTotalDeposits = user.totalDeposits - amount
|
||||
|
||||
return { status: 'success', newLiquidityProvision }
|
||||
if (!isFinite(newBalance)) {
|
||||
throw new APIError(500, 'Invalid user balance for ' + user.username)
|
||||
}
|
||||
|
||||
transaction.update(userDoc, {
|
||||
balance: newBalance,
|
||||
totalDeposits: newTotalDeposits,
|
||||
})
|
||||
.then(async (result) => {
|
||||
await redeemShares(userId, contractId)
|
||||
return result
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
|
||||
|
||||
return newLiquidityProvision
|
||||
})
|
||||
.then(async (result) => {
|
||||
await redeemShares(auth.uid, contractId)
|
||||
return result
|
||||
})
|
||||
})
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { getUser } from './utils'
|
||||
import { Contract } from '../../common/contract'
|
||||
|
@ -11,37 +11,23 @@ import {
|
|||
} from '../../common/util/clean-username'
|
||||
import { removeUndefinedProps } from '../../common/util/object'
|
||||
import { Answer } from '../../common/answer'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
|
||||
export const changeUserInfo = functions
|
||||
.runWith({ minInstances: 1 })
|
||||
.https.onCall(
|
||||
async (
|
||||
data: {
|
||||
username?: string
|
||||
name?: string
|
||||
avatarUrl?: string
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
const bodySchema = z.object({
|
||||
username: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
avatarUrl: z.string().optional(),
|
||||
})
|
||||
|
||||
const user = await getUser(userId)
|
||||
if (!user) return { status: 'error', message: 'User not found' }
|
||||
export const changeuserinfo = newEndpoint({}, async (req, auth) => {
|
||||
const { username, name, avatarUrl } = validate(bodySchema, req.body)
|
||||
|
||||
const { username, name, avatarUrl } = data
|
||||
const user = await getUser(auth.uid)
|
||||
if (!user) throw new APIError(400, 'User not found')
|
||||
|
||||
return await changeUser(user, { username, name, avatarUrl })
|
||||
.then(() => {
|
||||
console.log('succesfully changed', user.username, 'to', data)
|
||||
return { status: 'success' }
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('Error', e.message)
|
||||
return { status: 'error', message: e.message }
|
||||
})
|
||||
}
|
||||
)
|
||||
await changeUser(user, { username, name, avatarUrl })
|
||||
return { message: 'Successfully changed user info.' }
|
||||
})
|
||||
|
||||
export const changeUser = async (
|
||||
user: User,
|
||||
|
@ -55,14 +41,14 @@ export const changeUser = async (
|
|||
if (update.username) {
|
||||
update.username = cleanUsername(update.username)
|
||||
if (!update.username) {
|
||||
throw new Error('Invalid username')
|
||||
throw new APIError(400, 'Invalid username')
|
||||
}
|
||||
|
||||
const sameNameUser = await transaction.get(
|
||||
firestore.collection('users').where('username', '==', update.username)
|
||||
)
|
||||
if (!sameNameUser.empty) {
|
||||
throw new Error('Username already exists')
|
||||
throw new APIError(400, 'Username already exists')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,17 +90,10 @@ export const changeUser = async (
|
|||
)
|
||||
const answerUpdate: Partial<Answer> = removeUndefinedProps(update)
|
||||
|
||||
await transaction.update(userRef, userUpdate)
|
||||
|
||||
await Promise.all(
|
||||
commentSnap.docs.map((d) => transaction.update(d.ref, commentUpdate))
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
answerSnap.docs.map((d) => transaction.update(d.ref, answerUpdate))
|
||||
)
|
||||
|
||||
await contracts.docs.map((d) => transaction.update(d.ref, contractUpdate))
|
||||
transaction.update(userRef, userUpdate)
|
||||
commentSnap.docs.forEach((d) => transaction.update(d.ref, commentUpdate))
|
||||
answerSnap.docs.forEach((d) => transaction.update(d.ref, answerUpdate))
|
||||
contracts.docs.forEach((d) => transaction.update(d.ref, contractUpdate))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,102 +1,104 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { User } from 'common/user'
|
||||
import { Manalink } from 'common/manalink'
|
||||
import { runTxn, TxnData } from './transact'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
|
||||
export const claimManalink = functions
|
||||
.runWith({ minInstances: 1 })
|
||||
.https.onCall(async (slug: string, context) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
const bodySchema = z.object({
|
||||
slug: z.string(),
|
||||
})
|
||||
|
||||
// Run as transaction to prevent race conditions.
|
||||
return await firestore.runTransaction(async (transaction) => {
|
||||
// Look up the manalink
|
||||
const manalinkDoc = firestore.doc(`manalinks/${slug}`)
|
||||
const manalinkSnap = await transaction.get(manalinkDoc)
|
||||
if (!manalinkSnap.exists) {
|
||||
return { status: 'error', message: 'Manalink not found' }
|
||||
}
|
||||
const manalink = manalinkSnap.data() as Manalink
|
||||
export const claimmanalink = newEndpoint({}, async (req, auth) => {
|
||||
const { slug } = validate(bodySchema, req.body)
|
||||
|
||||
const { amount, fromId, claimedUserIds } = manalink
|
||||
// Run as transaction to prevent race conditions.
|
||||
return await firestore.runTransaction(async (transaction) => {
|
||||
// Look up the manalink
|
||||
const manalinkDoc = firestore.doc(`manalinks/${slug}`)
|
||||
const manalinkSnap = await transaction.get(manalinkDoc)
|
||||
if (!manalinkSnap.exists) {
|
||||
throw new APIError(400, 'Manalink not found')
|
||||
}
|
||||
const manalink = manalinkSnap.data() as Manalink
|
||||
|
||||
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||
return { status: 'error', message: 'Invalid amount' }
|
||||
const { amount, fromId, claimedUserIds } = manalink
|
||||
|
||||
const fromDoc = firestore.doc(`users/${fromId}`)
|
||||
const fromSnap = await transaction.get(fromDoc)
|
||||
if (!fromSnap.exists) {
|
||||
return { status: 'error', message: `User ${fromId} not found` }
|
||||
}
|
||||
const fromUser = fromSnap.data() as User
|
||||
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||
throw new APIError(500, 'Invalid amount')
|
||||
|
||||
// Only permit one redemption per user per link
|
||||
if (claimedUserIds.includes(userId)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: `${fromUser.name} already redeemed manalink ${slug}`,
|
||||
}
|
||||
}
|
||||
const fromDoc = firestore.doc(`users/${fromId}`)
|
||||
const fromSnap = await transaction.get(fromDoc)
|
||||
if (!fromSnap.exists) {
|
||||
throw new APIError(500, `User ${fromId} not found`)
|
||||
}
|
||||
const fromUser = fromSnap.data() as User
|
||||
|
||||
// Disallow expired or maxed out links
|
||||
if (manalink.expiresTime != null && manalink.expiresTime < Date.now()) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: `Manalink ${slug} expired on ${new Date(
|
||||
manalink.expiresTime
|
||||
).toLocaleString()}`,
|
||||
}
|
||||
}
|
||||
if (
|
||||
manalink.maxUses != null &&
|
||||
manalink.maxUses <= manalink.claims.length
|
||||
) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: `Manalink ${slug} has reached its max uses of ${manalink.maxUses}`,
|
||||
}
|
||||
}
|
||||
// Only permit one redemption per user per link
|
||||
if (claimedUserIds.includes(auth.uid)) {
|
||||
throw new APIError(400, `You already redeemed manalink ${slug}`)
|
||||
}
|
||||
|
||||
if (fromUser.balance < amount) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: `Insufficient balance: ${fromUser.name} needed ${amount} for this manalink but only had ${fromUser.balance} `,
|
||||
}
|
||||
}
|
||||
// Disallow expired or maxed out links
|
||||
if (manalink.expiresTime != null && manalink.expiresTime < Date.now()) {
|
||||
throw new APIError(
|
||||
400,
|
||||
`Manalink ${slug} expired on ${new Date(
|
||||
manalink.expiresTime
|
||||
).toLocaleString()}`
|
||||
)
|
||||
}
|
||||
if (
|
||||
manalink.maxUses != null &&
|
||||
manalink.maxUses <= manalink.claims.length
|
||||
) {
|
||||
throw new APIError(
|
||||
400,
|
||||
`Manalink ${slug} has reached its max uses of ${manalink.maxUses}`
|
||||
)
|
||||
}
|
||||
|
||||
// Actually execute the txn
|
||||
const data: TxnData = {
|
||||
fromId,
|
||||
fromType: 'USER',
|
||||
toId: userId,
|
||||
toType: 'USER',
|
||||
amount,
|
||||
token: 'M$',
|
||||
category: 'MANALINK',
|
||||
description: `Manalink ${slug} claimed: ${amount} from ${fromUser.username} to ${userId}`,
|
||||
}
|
||||
const result = await runTxn(transaction, data)
|
||||
const txnId = result.txn?.id
|
||||
if (!txnId) {
|
||||
return { status: 'error', message: result.message }
|
||||
}
|
||||
if (fromUser.balance < amount) {
|
||||
throw new APIError(
|
||||
400,
|
||||
`Insufficient balance: ${fromUser.name} needed ${amount} for this manalink but only had ${fromUser.balance} `
|
||||
)
|
||||
}
|
||||
|
||||
// Update the manalink object with this info
|
||||
const claim = {
|
||||
toId: userId,
|
||||
txnId,
|
||||
claimedTime: Date.now(),
|
||||
}
|
||||
transaction.update(manalinkDoc, {
|
||||
claimedUserIds: [...claimedUserIds, userId],
|
||||
claims: [...manalink.claims, claim],
|
||||
})
|
||||
// Actually execute the txn
|
||||
const data: TxnData = {
|
||||
fromId,
|
||||
fromType: 'USER',
|
||||
toId: auth.uid,
|
||||
toType: 'USER',
|
||||
amount,
|
||||
token: 'M$',
|
||||
category: 'MANALINK',
|
||||
description: `Manalink ${slug} claimed: ${amount} from ${fromUser.username} to ${auth.uid}`,
|
||||
}
|
||||
const result = await runTxn(transaction, data)
|
||||
const txnId = result.txn?.id
|
||||
if (!txnId) {
|
||||
throw new APIError(
|
||||
500,
|
||||
result.message ?? 'An error occurred posting the transaction.'
|
||||
)
|
||||
}
|
||||
|
||||
return { status: 'success', message: 'Manalink claimed' }
|
||||
// Update the manalink object with this info
|
||||
const claim = {
|
||||
toId: auth.uid,
|
||||
txnId,
|
||||
claimedTime: Date.now(),
|
||||
}
|
||||
transaction.update(manalinkDoc, {
|
||||
claimedUserIds: [...claimedUserIds, auth.uid],
|
||||
claims: [...manalink.claims, claim],
|
||||
})
|
||||
|
||||
return { message: 'Manalink claimed' }
|
||||
})
|
||||
})
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Contract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
|
@ -7,122 +7,103 @@ import { getNewMultiBetInfo } from '../../common/new-bet'
|
|||
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
||||
import { getContract, getValues } from './utils'
|
||||
import { sendNewAnswerEmail } from './emails'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
|
||||
export const createAnswer = functions
|
||||
.runWith({ minInstances: 1, secrets: ['MAILGUN_KEY'] })
|
||||
.https.onCall(
|
||||
async (
|
||||
data: {
|
||||
contractId: string
|
||||
amount: number
|
||||
text: string
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
const bodySchema = z.object({
|
||||
contractId: z.string().max(MAX_ANSWER_LENGTH),
|
||||
amount: z.number().gt(0),
|
||||
text: z.string(),
|
||||
})
|
||||
|
||||
const { contractId, amount, text } = data
|
||||
const opts = { secrets: ['MAILGUN_KEY'] }
|
||||
|
||||
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||
return { status: 'error', message: 'Invalid amount' }
|
||||
export const createanswer = newEndpoint(opts, async (req, auth) => {
|
||||
const { contractId, amount, text } = validate(bodySchema, req.body)
|
||||
|
||||
if (!text || typeof text !== 'string' || text.length > MAX_ANSWER_LENGTH)
|
||||
return { status: 'error', message: 'Invalid text' }
|
||||
if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
|
||||
|
||||
// Run as transaction to prevent race conditions.
|
||||
const result = await firestore.runTransaction(async (transaction) => {
|
||||
const userDoc = firestore.doc(`users/${userId}`)
|
||||
const userSnap = await transaction.get(userDoc)
|
||||
if (!userSnap.exists)
|
||||
return { status: 'error', message: 'User not found' }
|
||||
const user = userSnap.data() as User
|
||||
// Run as transaction to prevent race conditions.
|
||||
const answer = 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
|
||||
|
||||
if (user.balance < amount)
|
||||
return { status: 'error', message: 'Insufficient balance' }
|
||||
if (user.balance < amount) throw new APIError(400, 'Insufficient balance')
|
||||
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const contractSnap = await transaction.get(contractDoc)
|
||||
if (!contractSnap.exists)
|
||||
return { status: 'error', message: 'Invalid contract' }
|
||||
const contract = contractSnap.data() as Contract
|
||||
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 !== 'FREE_RESPONSE')
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'Requires a free response contract',
|
||||
}
|
||||
if (contract.outcomeType !== 'FREE_RESPONSE')
|
||||
throw new APIError(400, 'Requires a free response contract')
|
||||
|
||||
const { closeTime, volume } = contract
|
||||
if (closeTime && Date.now() > closeTime)
|
||||
return { status: 'error', message: 'Trading is closed' }
|
||||
const { closeTime, volume } = contract
|
||||
if (closeTime && Date.now() > closeTime)
|
||||
throw new APIError(400, 'Trading is closed')
|
||||
|
||||
const [lastAnswer] = await getValues<Answer>(
|
||||
firestore
|
||||
.collection(`contracts/${contractId}/answers`)
|
||||
.orderBy('number', 'desc')
|
||||
.limit(1)
|
||||
)
|
||||
const [lastAnswer] = await getValues<Answer>(
|
||||
firestore
|
||||
.collection(`contracts/${contractId}/answers`)
|
||||
.orderBy('number', 'desc')
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if (!lastAnswer)
|
||||
return { status: 'error', message: 'Could not fetch last answer' }
|
||||
if (!lastAnswer) throw new APIError(500, 'Could not fetch last answer')
|
||||
|
||||
const number = lastAnswer.number + 1
|
||||
const id = `${number}`
|
||||
const number = lastAnswer.number + 1
|
||||
const id = `${number}`
|
||||
|
||||
const newAnswerDoc = firestore
|
||||
.collection(`contracts/${contractId}/answers`)
|
||||
.doc(id)
|
||||
const newAnswerDoc = firestore
|
||||
.collection(`contracts/${contractId}/answers`)
|
||||
.doc(id)
|
||||
|
||||
const answerId = newAnswerDoc.id
|
||||
const { username, name, avatarUrl } = user
|
||||
const answerId = newAnswerDoc.id
|
||||
const { username, name, avatarUrl } = user
|
||||
|
||||
const answer: Answer = {
|
||||
id,
|
||||
number,
|
||||
contractId,
|
||||
createdTime: Date.now(),
|
||||
userId: user.id,
|
||||
username,
|
||||
name,
|
||||
avatarUrl,
|
||||
text,
|
||||
}
|
||||
transaction.create(newAnswerDoc, answer)
|
||||
|
||||
const loanAmount = 0
|
||||
|
||||
const { newBet, newPool, newTotalShares, newTotalBets } =
|
||||
getNewMultiBetInfo(answerId, amount, contract, loanAmount)
|
||||
|
||||
const newBalance = user.balance - amount
|
||||
const betDoc = firestore
|
||||
.collection(`contracts/${contractId}/bets`)
|
||||
.doc()
|
||||
transaction.create(betDoc, {
|
||||
id: betDoc.id,
|
||||
userId: user.id,
|
||||
...newBet,
|
||||
})
|
||||
transaction.update(userDoc, { balance: newBalance })
|
||||
transaction.update(contractDoc, {
|
||||
pool: newPool,
|
||||
totalShares: newTotalShares,
|
||||
totalBets: newTotalBets,
|
||||
answers: [...(contract.answers ?? []), answer],
|
||||
volume: volume + amount,
|
||||
})
|
||||
|
||||
return { status: 'success', answerId, betId: betDoc.id, answer }
|
||||
})
|
||||
|
||||
const { answer } = result
|
||||
const contract = await getContract(contractId)
|
||||
|
||||
if (answer && contract) await sendNewAnswerEmail(answer, contract)
|
||||
|
||||
return result
|
||||
const answer: Answer = {
|
||||
id,
|
||||
number,
|
||||
contractId,
|
||||
createdTime: Date.now(),
|
||||
userId: user.id,
|
||||
username,
|
||||
name,
|
||||
avatarUrl,
|
||||
text,
|
||||
}
|
||||
)
|
||||
transaction.create(newAnswerDoc, answer)
|
||||
|
||||
const loanAmount = 0
|
||||
|
||||
const { newBet, newPool, newTotalShares, newTotalBets } =
|
||||
getNewMultiBetInfo(answerId, amount, contract, loanAmount)
|
||||
|
||||
const newBalance = user.balance - amount
|
||||
const betDoc = firestore.collection(`contracts/${contractId}/bets`).doc()
|
||||
transaction.create(betDoc, {
|
||||
id: betDoc.id,
|
||||
userId: user.id,
|
||||
...newBet,
|
||||
})
|
||||
transaction.update(userDoc, { balance: newBalance })
|
||||
transaction.update(contractDoc, {
|
||||
pool: newPool,
|
||||
totalShares: newTotalShares,
|
||||
totalBets: newTotalBets,
|
||||
answers: [...(contract.answers ?? []), answer],
|
||||
volume: volume + amount,
|
||||
})
|
||||
|
||||
return answer
|
||||
})
|
||||
|
||||
const contract = await getContract(contractId)
|
||||
|
||||
if (answer && contract) await sendNewAnswerEmail(answer, contract)
|
||||
|
||||
return answer
|
||||
})
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
|
|
@ -3,12 +3,9 @@ import * as admin from 'firebase-admin'
|
|||
admin.initializeApp()
|
||||
|
||||
// v1
|
||||
// export * from './keep-awake'
|
||||
export * from './claim-manalink'
|
||||
export * from './transact'
|
||||
export * from './stripe'
|
||||
export * from './create-user'
|
||||
export * from './create-answer'
|
||||
export * from './on-create-bet'
|
||||
export * from './on-create-comment-on-contract'
|
||||
export * from './on-view'
|
||||
|
@ -16,9 +13,7 @@ export * from './unsubscribe'
|
|||
export * from './update-metrics'
|
||||
export * from './update-stats'
|
||||
export * from './backup-db'
|
||||
export * from './change-user-info'
|
||||
export * from './market-close-notifications'
|
||||
export * from './add-liquidity'
|
||||
export * from './on-create-answer'
|
||||
export * from './on-update-contract'
|
||||
export * from './on-create-contract'
|
||||
|
@ -33,11 +28,15 @@ export * from './on-create-txn'
|
|||
|
||||
// v2
|
||||
export * from './health'
|
||||
export * from './change-user-info'
|
||||
export * from './create-answer'
|
||||
export * from './place-bet'
|
||||
export * from './cancel-bet'
|
||||
export * from './sell-bet'
|
||||
export * from './sell-shares'
|
||||
export * from './claim-manalink'
|
||||
export * from './create-contract'
|
||||
export * from './add-liquidity'
|
||||
export * from './withdraw-liquidity'
|
||||
export * from './create-group'
|
||||
export * from './resolve-market'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { CPMMContract } from '../../common/contract'
|
||||
import { User } from '../../common/user'
|
||||
|
@ -10,129 +10,107 @@ import { Bet } from '../../common/bet'
|
|||
import { getProbability } from '../../common/calculate'
|
||||
import { noFees } from '../../common/fees'
|
||||
|
||||
import { APIError } from './api'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
import { redeemShares } from './redeem-shares'
|
||||
|
||||
export const withdrawLiquidity = functions
|
||||
.runWith({ minInstances: 1 })
|
||||
.https.onCall(
|
||||
async (
|
||||
data: {
|
||||
contractId: string
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
const bodySchema = z.object({
|
||||
contractId: z.string(),
|
||||
})
|
||||
|
||||
const { contractId } = data
|
||||
if (!contractId)
|
||||
return { status: 'error', message: 'Missing contract id' }
|
||||
export const withdrawliquidity = newEndpoint({}, async (req, auth) => {
|
||||
const { contractId } = validate(bodySchema, req.body)
|
||||
|
||||
return await firestore
|
||||
.runTransaction(async (trans) => {
|
||||
const lpDoc = firestore.doc(`users/${userId}`)
|
||||
const lpSnap = await trans.get(lpDoc)
|
||||
if (!lpSnap.exists) throw new APIError(400, 'User not found.')
|
||||
const lp = lpSnap.data() as User
|
||||
return await firestore
|
||||
.runTransaction(async (trans) => {
|
||||
const lpDoc = firestore.doc(`users/${auth.uid}`)
|
||||
const lpSnap = await trans.get(lpDoc)
|
||||
if (!lpSnap.exists) throw new APIError(400, 'User not found.')
|
||||
const lp = lpSnap.data() as User
|
||||
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const contractSnap = await trans.get(contractDoc)
|
||||
if (!contractSnap.exists)
|
||||
throw new APIError(400, 'Contract not found.')
|
||||
const contract = contractSnap.data() as CPMMContract
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const contractSnap = await trans.get(contractDoc)
|
||||
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
||||
const contract = contractSnap.data() as CPMMContract
|
||||
|
||||
const liquidityCollection = firestore.collection(
|
||||
`contracts/${contractId}/liquidity`
|
||||
)
|
||||
const liquidityCollection = firestore.collection(
|
||||
`contracts/${contractId}/liquidity`
|
||||
)
|
||||
|
||||
const liquiditiesSnap = await trans.get(liquidityCollection)
|
||||
const liquiditiesSnap = await trans.get(liquidityCollection)
|
||||
|
||||
const liquidities = liquiditiesSnap.docs.map(
|
||||
(doc) => doc.data() as LiquidityProvision
|
||||
)
|
||||
const liquidities = liquiditiesSnap.docs.map(
|
||||
(doc) => doc.data() as LiquidityProvision
|
||||
)
|
||||
|
||||
const userShares = getUserLiquidityShares(
|
||||
userId,
|
||||
contract,
|
||||
liquidities
|
||||
)
|
||||
const userShares = getUserLiquidityShares(auth.uid, contract, liquidities)
|
||||
|
||||
// zero all added amounts for now
|
||||
// can add support for partial withdrawals in the future
|
||||
liquiditiesSnap.docs
|
||||
.filter(
|
||||
(_, i) =>
|
||||
!liquidities[i].isAnte && liquidities[i].userId === userId
|
||||
)
|
||||
.forEach((doc) => trans.update(doc.ref, { amount: 0 }))
|
||||
// zero all added amounts for now
|
||||
// can add support for partial withdrawals in the future
|
||||
liquiditiesSnap.docs
|
||||
.filter(
|
||||
(_, i) => !liquidities[i].isAnte && liquidities[i].userId === auth.uid
|
||||
)
|
||||
.forEach((doc) => trans.update(doc.ref, { amount: 0 }))
|
||||
|
||||
const payout = Math.min(...Object.values(userShares))
|
||||
if (payout <= 0) return {}
|
||||
const payout = Math.min(...Object.values(userShares))
|
||||
if (payout <= 0) return {}
|
||||
|
||||
const newBalance = lp.balance + payout
|
||||
const newTotalDeposits = lp.totalDeposits + payout
|
||||
trans.update(lpDoc, {
|
||||
balance: newBalance,
|
||||
totalDeposits: newTotalDeposits,
|
||||
} as Partial<User>)
|
||||
const newBalance = lp.balance + payout
|
||||
const newTotalDeposits = lp.totalDeposits + payout
|
||||
trans.update(lpDoc, {
|
||||
balance: newBalance,
|
||||
totalDeposits: newTotalDeposits,
|
||||
} as Partial<User>)
|
||||
|
||||
const newPool = subtractObjects(contract.pool, userShares)
|
||||
const newPool = subtractObjects(contract.pool, userShares)
|
||||
|
||||
const minPoolShares = Math.min(...Object.values(newPool))
|
||||
const adjustedTotal = contract.totalLiquidity - payout
|
||||
const minPoolShares = Math.min(...Object.values(newPool))
|
||||
const adjustedTotal = contract.totalLiquidity - payout
|
||||
|
||||
// total liquidity is a bogus number; use minPoolShares to prevent from going negative
|
||||
const newTotalLiquidity = Math.max(adjustedTotal, minPoolShares)
|
||||
// total liquidity is a bogus number; use minPoolShares to prevent from going negative
|
||||
const newTotalLiquidity = Math.max(adjustedTotal, minPoolShares)
|
||||
|
||||
trans.update(contractDoc, {
|
||||
pool: newPool,
|
||||
totalLiquidity: newTotalLiquidity,
|
||||
})
|
||||
trans.update(contractDoc, {
|
||||
pool: newPool,
|
||||
totalLiquidity: newTotalLiquidity,
|
||||
})
|
||||
|
||||
const prob = getProbability(contract)
|
||||
const prob = getProbability(contract)
|
||||
|
||||
// surplus shares become user's bets
|
||||
const bets = Object.entries(userShares)
|
||||
.map(([outcome, shares]) =>
|
||||
shares - payout < 1 // don't create bet if less than 1 share
|
||||
? undefined
|
||||
: ({
|
||||
userId: userId,
|
||||
contractId: contract.id,
|
||||
amount:
|
||||
(outcome === 'YES' ? prob : 1 - prob) * (shares - payout),
|
||||
shares: shares - payout,
|
||||
outcome,
|
||||
probBefore: prob,
|
||||
probAfter: prob,
|
||||
createdTime: Date.now(),
|
||||
isLiquidityProvision: true,
|
||||
fees: noFees,
|
||||
} as Omit<Bet, 'id'>)
|
||||
)
|
||||
.filter((x) => x !== undefined)
|
||||
// surplus shares become user's bets
|
||||
const bets = Object.entries(userShares)
|
||||
.map(([outcome, shares]) =>
|
||||
shares - payout < 1 // don't create bet if less than 1 share
|
||||
? undefined
|
||||
: ({
|
||||
userId: auth.uid,
|
||||
contractId: contract.id,
|
||||
amount:
|
||||
(outcome === 'YES' ? prob : 1 - prob) * (shares - payout),
|
||||
shares: shares - payout,
|
||||
outcome,
|
||||
probBefore: prob,
|
||||
probAfter: prob,
|
||||
createdTime: Date.now(),
|
||||
isLiquidityProvision: true,
|
||||
fees: noFees,
|
||||
} as Omit<Bet, 'id'>)
|
||||
)
|
||||
.filter((x) => x !== undefined)
|
||||
|
||||
for (const bet of bets) {
|
||||
const doc = firestore
|
||||
.collection(`contracts/${contract.id}/bets`)
|
||||
.doc()
|
||||
trans.create(doc, { id: doc.id, ...bet })
|
||||
}
|
||||
for (const bet of bets) {
|
||||
const doc = firestore.collection(`contracts/${contract.id}/bets`).doc()
|
||||
trans.create(doc, { id: doc.id, ...bet })
|
||||
}
|
||||
|
||||
return userShares
|
||||
})
|
||||
.then(async (result) => {
|
||||
// redeem surplus bet with pre-existing bets
|
||||
await redeemShares(userId, contractId)
|
||||
|
||||
console.log('userid', userId, 'withdraws', result)
|
||||
return { status: 'success', userShares: result }
|
||||
})
|
||||
.catch((e) => {
|
||||
return { status: 'error', message: e.message }
|
||||
})
|
||||
}
|
||||
)
|
||||
return userShares
|
||||
})
|
||||
.then(async (result) => {
|
||||
// redeem surplus bet with pre-existing bets
|
||||
await redeemShares(auth.uid, contractId)
|
||||
console.log('userid', auth.uid, 'withdraws', result)
|
||||
return result
|
||||
})
|
||||
})
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
|
|
@ -6,7 +6,7 @@ import { findBestMatch } from 'string-similarity'
|
|||
import { FreeResponseContract } from 'common/contract'
|
||||
import { BuyAmountInput } from '../amount-input'
|
||||
import { Col } from '../layout/col'
|
||||
import { createAnswer } from 'web/lib/firebase/fn-call'
|
||||
import { APIError, createAnswer } from 'web/lib/firebase/api-call'
|
||||
import { Row } from '../layout/row'
|
||||
import {
|
||||
formatMoney,
|
||||
|
@ -46,20 +46,23 @@ export function CreateAnswerPanel(props: { contract: FreeResponseContract }) {
|
|||
if (canSubmit) {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const result = await createAnswer({
|
||||
contractId: contract.id,
|
||||
text,
|
||||
amount: betAmount,
|
||||
}).then((r) => r.data)
|
||||
|
||||
setIsSubmitting(false)
|
||||
|
||||
if (result.status === 'success') {
|
||||
try {
|
||||
await createAnswer({
|
||||
contractId: contract.id,
|
||||
text,
|
||||
amount: betAmount,
|
||||
})
|
||||
setText('')
|
||||
setBetAmount(10)
|
||||
setAmountError(undefined)
|
||||
setPossibleDuplicateAnswer(undefined)
|
||||
} else setAmountError(result.message)
|
||||
} catch (e) {
|
||||
if (e instanceof APIError) {
|
||||
setAmountError(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import { useUserContractBets } from 'web/hooks/use-user-bets'
|
|||
import {
|
||||
calculateCpmmSale,
|
||||
getCpmmProbability,
|
||||
getCpmmLiquidityFee,
|
||||
getCpmmFees,
|
||||
} from 'common/calculate-cpmm'
|
||||
import {
|
||||
getFormattedMappedValue,
|
||||
|
@ -356,7 +356,7 @@ function BuyPanel(props: {
|
|||
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
|
||||
const currentReturnPercent = formatPercent(currentReturn)
|
||||
|
||||
const cpmmFees = getCpmmLiquidityFee(
|
||||
const cpmmFees = getCpmmFees(
|
||||
contract,
|
||||
betAmount ?? 0,
|
||||
betChoice ?? 'YES'
|
||||
|
|
|
@ -134,6 +134,13 @@ export function QuickBet(props: { contract: Contract; user: User }) {
|
|||
})
|
||||
}
|
||||
|
||||
if (outcomeType === 'FREE_RESPONSE')
|
||||
return (
|
||||
<Col className="relative -my-4 -mr-5 min-w-[5.5rem] justify-center gap-2 pr-5 pl-1 align-middle">
|
||||
<QuickOutcomeView contract={contract} previewProb={previewProb} />
|
||||
</Col>
|
||||
)
|
||||
|
||||
return (
|
||||
<Col
|
||||
className={clsx(
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'
|
|||
import { CPMMContract } from 'common/contract'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/fn-call'
|
||||
import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/api-call'
|
||||
import { AmountInput } from './amount-input'
|
||||
import { Row } from './layout/row'
|
||||
import { useUserLiquidity } from 'web/hooks/use-liquidity'
|
||||
|
@ -90,14 +90,10 @@ function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
|||
setIsSuccess(false)
|
||||
|
||||
addLiquidity({ amount, contractId })
|
||||
.then((r) => {
|
||||
if (r.status === 'success') {
|
||||
setIsSuccess(true)
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
} else {
|
||||
setError('Server error')
|
||||
}
|
||||
.then((_) => {
|
||||
setIsSuccess(true)
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
})
|
||||
.catch((_) => setError('Server error'))
|
||||
|
||||
|
|
|
@ -50,6 +50,21 @@ export function getFunctionUrl(name: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function createAnswer(params: any) {
|
||||
return call(getFunctionUrl('createanswer'), 'POST', params)
|
||||
}
|
||||
export function changeUserInfo(params: any) {
|
||||
return call(getFunctionUrl('changeuserinfo'), 'POST', params)
|
||||
}
|
||||
|
||||
export function addLiquidity(params: any) {
|
||||
return call(getFunctionUrl('addliquidity'), 'POST', params)
|
||||
}
|
||||
|
||||
export function withdrawLiquidity(params: any) {
|
||||
return call(getFunctionUrl('withdrawliquidity'), 'POST', params)
|
||||
}
|
||||
|
||||
export function createMarket(params: any) {
|
||||
return call(getFunctionUrl('createmarket'), 'POST', params)
|
||||
}
|
||||
|
@ -74,6 +89,10 @@ export function sellBet(params: any) {
|
|||
return call(getFunctionUrl('sellbet'), 'POST', params)
|
||||
}
|
||||
|
||||
export function claimManalink(params: any) {
|
||||
return call(getFunctionUrl('claimmanalink'), 'POST', params)
|
||||
}
|
||||
|
||||
export function createGroup(params: any) {
|
||||
return call(getFunctionUrl('creategroup'), 'POST', params)
|
||||
}
|
||||
|
|
|
@ -9,26 +9,11 @@ import { safeLocalStorage } from '../util/local'
|
|||
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
|
||||
httpsCallable<RequestData, ResponseData>(functions, name)
|
||||
|
||||
export const withdrawLiquidity = cloudFunction<
|
||||
{ contractId: string },
|
||||
{ status: 'error' | 'success'; userShares: { [outcome: string]: number } }
|
||||
>('withdrawLiquidity')
|
||||
|
||||
export const transact = cloudFunction<
|
||||
Omit<Txn, 'id' | 'createdTime'>,
|
||||
{ status: 'error' | 'success'; message?: string; txn?: Txn }
|
||||
>('transact')
|
||||
|
||||
export const createAnswer = cloudFunction<
|
||||
{ contractId: string; text: string; amount: number },
|
||||
{
|
||||
status: 'error' | 'success'
|
||||
message?: string
|
||||
answerId?: string
|
||||
betId?: string
|
||||
}
|
||||
>('createAnswer')
|
||||
|
||||
export const createUser: () => Promise<User | null> = () => {
|
||||
const local = safeLocalStorage()
|
||||
let deviceToken = local?.getItem('device-token')
|
||||
|
@ -41,24 +26,3 @@ export const createUser: () => Promise<User | null> = () => {
|
|||
.then((r) => (r.data as any)?.user || null)
|
||||
.catch(() => null)
|
||||
}
|
||||
|
||||
export const changeUserInfo = (data: {
|
||||
username?: string
|
||||
name?: string
|
||||
avatarUrl?: string
|
||||
}) => {
|
||||
return cloudFunction('changeUserInfo')(data)
|
||||
.then((r) => r.data as { status: string; message?: string })
|
||||
.catch((e) => ({ status: 'error', message: e.message }))
|
||||
}
|
||||
|
||||
export const addLiquidity = (data: { amount: number; contractId: string }) => {
|
||||
return cloudFunction('addLiquidity')(data)
|
||||
.then((r) => r.data as { status: string })
|
||||
.catch((e) => ({ status: 'error', message: e.message }))
|
||||
}
|
||||
|
||||
export const claimManalink = cloudFunction<
|
||||
string,
|
||||
{ status: 'error' | 'success'; message?: string }
|
||||
>('claimManalink')
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
|
|||
import { useState } from 'react'
|
||||
import { SEO } from 'web/components/SEO'
|
||||
import { Title } from 'web/components/title'
|
||||
import { claimManalink } from 'web/lib/firebase/fn-call'
|
||||
import { claimManalink } from 'web/lib/firebase/api-call'
|
||||
import { useManalink } from 'web/lib/firebase/manalinks'
|
||||
import { ManalinkCard } from 'web/components/manalink-card'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
|
@ -42,10 +42,7 @@ export default function ClaimPage() {
|
|||
if (user == null) {
|
||||
await firebaseLogin()
|
||||
}
|
||||
const result = await claimManalink(manalink.slug)
|
||||
if (result.data.status == 'error') {
|
||||
throw new Error(result.data.message)
|
||||
}
|
||||
await claimManalink({ slug: manalink.slug })
|
||||
user && router.push(`/${user.username}?claimed-mana=yes`)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Title } from 'web/components/title'
|
|||
import { usePrivateUser, useUser } from 'web/hooks/use-user'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
||||
import { changeUserInfo } from 'web/lib/firebase/fn-call'
|
||||
import { changeUserInfo } from 'web/lib/firebase/api-call'
|
||||
import { uploadImage } from 'web/lib/firebase/storage'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
|
@ -85,12 +85,9 @@ export default function ProfilePage() {
|
|||
|
||||
if (newName) {
|
||||
setName(newName)
|
||||
|
||||
await changeUserInfo({ name: newName })
|
||||
.catch(() => ({ status: 'error' }))
|
||||
.then((r) => {
|
||||
if (r.status === 'error') setName(user?.name || '')
|
||||
})
|
||||
await changeUserInfo({ name: newName }).catch((_) =>
|
||||
setName(user?.name || '')
|
||||
)
|
||||
} else {
|
||||
setName(user?.name || '')
|
||||
}
|
||||
|
@ -101,11 +98,9 @@ export default function ProfilePage() {
|
|||
|
||||
if (newUsername) {
|
||||
setUsername(newUsername)
|
||||
await changeUserInfo({ username: newUsername })
|
||||
.catch(() => ({ status: 'error' }))
|
||||
.then((r) => {
|
||||
if (r.status === 'error') setUsername(user?.username || '')
|
||||
})
|
||||
await changeUserInfo({ username: newUsername }).catch((_) =>
|
||||
setUsername(user?.username || '')
|
||||
)
|
||||
} else {
|
||||
setUsername(user?.username || '')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user