Make liquidity great again (#1020)
* add subsidy * drizzle liquidity * update liquidity panel * remove addliquidity * update cloud functions index * remove json endpoints * imports * drizzle liquidity: add velocity; dev script; run every minute * adjust speed * logging * liquidity button, dialog * modal size * modal * info table * pay back excess liquidity * remove client withdrawal * house liquidity subsidy * disable liquidity button if market resolved or closed * format tip amount
This commit is contained in:
parent
8bb9885aee
commit
0ec15ff2f8
|
@ -1,4 +1,4 @@
|
||||||
import { addCpmmLiquidity, getCpmmLiquidity } from './calculate-cpmm'
|
import { getCpmmLiquidity } from './calculate-cpmm'
|
||||||
import { CPMMContract } from './contract'
|
import { CPMMContract } from './contract'
|
||||||
import { LiquidityProvision } from './liquidity-provision'
|
import { LiquidityProvision } from './liquidity-provision'
|
||||||
|
|
||||||
|
@ -8,25 +8,23 @@ export const getNewLiquidityProvision = (
|
||||||
contract: CPMMContract,
|
contract: CPMMContract,
|
||||||
newLiquidityProvisionId: string
|
newLiquidityProvisionId: string
|
||||||
) => {
|
) => {
|
||||||
const { pool, p, totalLiquidity } = contract
|
const { pool, p, totalLiquidity, subsidyPool } = contract
|
||||||
|
|
||||||
const { newPool, newP } = addCpmmLiquidity(pool, p, amount)
|
const liquidity = getCpmmLiquidity(pool, p)
|
||||||
|
|
||||||
const liquidity =
|
|
||||||
getCpmmLiquidity(newPool, newP) - getCpmmLiquidity(pool, newP)
|
|
||||||
|
|
||||||
const newLiquidityProvision: LiquidityProvision = {
|
const newLiquidityProvision: LiquidityProvision = {
|
||||||
id: newLiquidityProvisionId,
|
id: newLiquidityProvisionId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
amount,
|
amount,
|
||||||
pool: newPool,
|
pool,
|
||||||
p: newP,
|
p,
|
||||||
liquidity,
|
liquidity,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTotalLiquidity = (totalLiquidity ?? 0) + amount
|
const newTotalLiquidity = (totalLiquidity ?? 0) + amount
|
||||||
|
const newSubsidyPool = (subsidyPool ?? 0) + amount
|
||||||
|
|
||||||
return { newLiquidityProvision, newPool, newP, newTotalLiquidity }
|
return { newLiquidityProvision, newTotalLiquidity, newSubsidyPool }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { sum, groupBy, mapValues, sumBy } from 'lodash'
|
import { groupBy, mapValues, sumBy } from 'lodash'
|
||||||
import { LimitBet } from './bet'
|
import { LimitBet } from './bet'
|
||||||
|
|
||||||
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
||||||
import { LiquidityProvision } from './liquidity-provision'
|
import { LiquidityProvision } from './liquidity-provision'
|
||||||
import { computeFills } from './new-bet'
|
import { computeFills } from './new-bet'
|
||||||
import { binarySearch } from './util/algos'
|
import { binarySearch } from './util/algos'
|
||||||
import { addObjects } from './util/object'
|
|
||||||
|
|
||||||
export type CpmmState = {
|
export type CpmmState = {
|
||||||
pool: { [outcome: string]: number }
|
pool: { [outcome: string]: number }
|
||||||
|
@ -267,48 +266,22 @@ export function addCpmmLiquidity(
|
||||||
return { newPool, liquidity, newP }
|
return { newPool, liquidity, newP }
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateLiquidityDelta = (p: number) => (l: LiquidityProvision) => {
|
export function getCpmmLiquidityPoolWeights(liquidities: LiquidityProvision[]) {
|
||||||
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
const userAmounts = groupBy(liquidities, (w) => w.userId)
|
||||||
|
const totalAmount = sumBy(liquidities, (w) => w.amount)
|
||||||
|
|
||||||
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
return mapValues(
|
||||||
const newLiquidity = getCpmmLiquidity(newPool, p)
|
userAmounts,
|
||||||
|
(amounts) => sumBy(amounts, (w) => w.amount) / totalAmount
|
||||||
const liquidity = newLiquidity - oldLiquidity
|
|
||||||
return liquidity
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCpmmLiquidityPoolWeights(
|
|
||||||
state: CpmmState,
|
|
||||||
liquidities: LiquidityProvision[],
|
|
||||||
excludeAntes: boolean
|
|
||||||
) {
|
|
||||||
const calcLiqudity = calculateLiquidityDelta(state.p)
|
|
||||||
const liquidityShares = liquidities.map(calcLiqudity)
|
|
||||||
const shareSum = sum(liquidityShares)
|
|
||||||
|
|
||||||
const weights = liquidityShares.map((shares, i) => ({
|
|
||||||
weight: shares / shareSum,
|
|
||||||
providerId: liquidities[i].userId,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const includedWeights = excludeAntes
|
|
||||||
? weights.filter((_, i) => !liquidities[i].isAnte)
|
|
||||||
: weights
|
|
||||||
|
|
||||||
const userWeights = groupBy(includedWeights, (w) => w.providerId)
|
|
||||||
const totalUserWeights = mapValues(userWeights, (userWeight) =>
|
|
||||||
sumBy(userWeight, (w) => w.weight)
|
|
||||||
)
|
)
|
||||||
return totalUserWeights
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserLiquidityShares(
|
export function getUserLiquidityShares(
|
||||||
userId: string,
|
userId: string,
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
liquidities: LiquidityProvision[],
|
liquidities: LiquidityProvision[]
|
||||||
excludeAntes: boolean
|
|
||||||
) {
|
) {
|
||||||
const weights = getCpmmLiquidityPoolWeights(state, liquidities, excludeAntes)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
const userWeight = weights[userId] ?? 0
|
const userWeight = weights[userId] ?? 0
|
||||||
|
|
||||||
return mapValues(state.pool, (shares) => userWeight * shares)
|
return mapValues(state.pool, (shares) => userWeight * shares)
|
||||||
|
|
|
@ -91,7 +91,8 @@ export type CPMM = {
|
||||||
mechanism: 'cpmm-1'
|
mechanism: 'cpmm-1'
|
||||||
pool: { [outcome: string]: number }
|
pool: { [outcome: string]: number }
|
||||||
p: number // probability constant in y^p * n^(1-p) = k
|
p: number // probability constant in y^p * n^(1-p) = k
|
||||||
totalLiquidity: number // in M$
|
totalLiquidity: number // for historical reasons, this the total subsidy amount added in M$
|
||||||
|
subsidyPool: number // current value of subsidy pool in M$
|
||||||
prob: number
|
prob: number
|
||||||
probChanges: {
|
probChanges: {
|
||||||
day: number
|
day: number
|
||||||
|
|
|
@ -16,3 +16,5 @@ export const BETTING_STREAK_BONUS_MAX = econ?.BETTING_STREAK_BONUS_MAX ?? 25
|
||||||
export const BETTING_STREAK_RESET_HOUR = econ?.BETTING_STREAK_RESET_HOUR ?? 7
|
export const BETTING_STREAK_RESET_HOUR = econ?.BETTING_STREAK_RESET_HOUR ?? 7
|
||||||
export const FREE_MARKETS_PER_USER_MAX = econ?.FREE_MARKETS_PER_USER_MAX ?? 5
|
export const FREE_MARKETS_PER_USER_MAX = econ?.FREE_MARKETS_PER_USER_MAX ?? 5
|
||||||
export const COMMENT_BOUNTY_AMOUNT = econ?.COMMENT_BOUNTY_AMOUNT ?? 250
|
export const COMMENT_BOUNTY_AMOUNT = econ?.COMMENT_BOUNTY_AMOUNT ?? 250
|
||||||
|
|
||||||
|
export const UNIQUE_BETTOR_LIQUIDITY = 20
|
||||||
|
|
|
@ -112,6 +112,7 @@ const getBinaryCpmmProps = (initialProb: number, ante: number) => {
|
||||||
mechanism: 'cpmm-1',
|
mechanism: 'cpmm-1',
|
||||||
outcomeType: 'BINARY',
|
outcomeType: 'BINARY',
|
||||||
totalLiquidity: ante,
|
totalLiquidity: ante,
|
||||||
|
subsidyPool: 0,
|
||||||
initialProbability: p,
|
initialProbability: p,
|
||||||
p,
|
p,
|
||||||
pool: pool,
|
pool: pool,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import { Bet } from './bet'
|
import { Bet } from './bet'
|
||||||
import { getProbability } from './calculate'
|
import { getProbability } from './calculate'
|
||||||
import { getCpmmLiquidityPoolWeights } from './calculate-cpmm'
|
import { getCpmmLiquidityPoolWeights } from './calculate-cpmm'
|
||||||
|
@ -56,10 +55,10 @@ export const getLiquidityPoolPayouts = (
|
||||||
outcome: string,
|
outcome: string,
|
||||||
liquidities: LiquidityProvision[]
|
liquidities: LiquidityProvision[]
|
||||||
) => {
|
) => {
|
||||||
const { pool } = contract
|
const { pool, subsidyPool } = contract
|
||||||
const finalPool = pool[outcome]
|
const finalPool = pool[outcome] + subsidyPool
|
||||||
|
|
||||||
const weights = getCpmmLiquidityPoolWeights(contract, liquidities, false)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
|
|
||||||
return Object.entries(weights).map(([providerId, weight]) => ({
|
return Object.entries(weights).map(([providerId, weight]) => ({
|
||||||
userId: providerId,
|
userId: providerId,
|
||||||
|
@ -95,10 +94,10 @@ export const getLiquidityPoolProbPayouts = (
|
||||||
p: number,
|
p: number,
|
||||||
liquidities: LiquidityProvision[]
|
liquidities: LiquidityProvision[]
|
||||||
) => {
|
) => {
|
||||||
const { pool } = contract
|
const { pool, subsidyPool } = contract
|
||||||
const finalPool = p * pool.YES + (1 - p) * pool.NO
|
const finalPool = p * pool.YES + (1 - p) * pool.NO + subsidyPool
|
||||||
|
|
||||||
const weights = getCpmmLiquidityPoolWeights(contract, liquidities, false)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
|
|
||||||
return Object.entries(weights).map(([providerId, weight]) => ({
|
return Object.entries(weights).map(([providerId, weight]) => ({
|
||||||
userId: providerId,
|
userId: providerId,
|
||||||
|
|
|
@ -60,6 +60,16 @@ export function formatLargeNumber(num: number, sigfigs = 2): string {
|
||||||
return `${numStr}${suffix[i] ?? ''}`
|
return `${numStr}${suffix[i] ?? ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shortFormatNumber(num: number): string {
|
||||||
|
if (num < 1000) return showPrecision(num, 3)
|
||||||
|
|
||||||
|
const suffix = ['', 'K', 'M', 'B', 'T', 'Q']
|
||||||
|
const i = Math.floor(Math.log10(num) / 3)
|
||||||
|
|
||||||
|
const numStr = showPrecision(num / Math.pow(10, 3 * i), 2)
|
||||||
|
return `${numStr}${suffix[i] ?? ''}`
|
||||||
|
}
|
||||||
|
|
||||||
export function toCamelCase(words: string) {
|
export function toCamelCase(words: string) {
|
||||||
const camelCase = words
|
const camelCase = words
|
||||||
.split(' ')
|
.split(' ')
|
||||||
|
|
|
@ -3,24 +3,18 @@ import { z } from 'zod'
|
||||||
|
|
||||||
import { Contract, CPMMContract } from '../../common/contract'
|
import { Contract, CPMMContract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
|
||||||
import { getNewLiquidityProvision } from '../../common/add-liquidity'
|
import { getNewLiquidityProvision } from '../../common/add-liquidity'
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
import {
|
|
||||||
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
|
||||||
HOUSE_LIQUIDITY_PROVIDER_ID,
|
|
||||||
} from '../../common/antes'
|
|
||||||
import { isProd } from './utils'
|
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string(),
|
contractId: z.string(),
|
||||||
amount: z.number().gt(0),
|
amount: z.number().gt(0),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addliquidity = newEndpoint({}, async (req, auth) => {
|
export const addsubsidy = newEndpoint({}, async (req, auth) => {
|
||||||
const { amount, contractId } = validate(bodySchema, req.body)
|
const { amount, contractId } = validate(bodySchema, req.body)
|
||||||
|
|
||||||
if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
|
if (!isFinite(amount) || amount < 1) throw new APIError(400, 'Invalid amount')
|
||||||
|
|
||||||
// run as transaction to prevent race conditions
|
// run as transaction to prevent race conditions
|
||||||
return await firestore.runTransaction(async (transaction) => {
|
return await firestore.runTransaction(async (transaction) => {
|
||||||
|
@ -50,7 +44,7 @@ export const addliquidity = newEndpoint({}, async (req, auth) => {
|
||||||
.collection(`contracts/${contractId}/liquidity`)
|
.collection(`contracts/${contractId}/liquidity`)
|
||||||
.doc()
|
.doc()
|
||||||
|
|
||||||
const { newLiquidityProvision, newPool, newP, newTotalLiquidity } =
|
const { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } =
|
||||||
getNewLiquidityProvision(
|
getNewLiquidityProvision(
|
||||||
user.id,
|
user.id,
|
||||||
amount,
|
amount,
|
||||||
|
@ -58,21 +52,10 @@ export const addliquidity = newEndpoint({}, async (req, auth) => {
|
||||||
newLiquidityProvisionDoc.id
|
newLiquidityProvisionDoc.id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (newP !== undefined && !isFinite(newP)) {
|
transaction.update(contractDoc, {
|
||||||
return {
|
subsidyPool: newSubsidyPool,
|
||||||
status: 'error',
|
|
||||||
message: 'Liquidity injection rejected due to overflow error.',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.update(
|
|
||||||
contractDoc,
|
|
||||||
removeUndefinedProps({
|
|
||||||
pool: newPool,
|
|
||||||
p: newP,
|
|
||||||
totalLiquidity: newTotalLiquidity,
|
totalLiquidity: newTotalLiquidity,
|
||||||
})
|
} as Partial<CPMMContract>)
|
||||||
)
|
|
||||||
|
|
||||||
const newBalance = user.balance - amount
|
const newBalance = user.balance - amount
|
||||||
const newTotalDeposits = user.totalDeposits - amount
|
const newTotalDeposits = user.totalDeposits - amount
|
||||||
|
@ -93,41 +76,3 @@ export const addliquidity = newEndpoint({}, async (req, auth) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
export const addHouseLiquidity = (contract: CPMMContract, amount: number) => {
|
|
||||||
return firestore.runTransaction(async (transaction) => {
|
|
||||||
const newLiquidityProvisionDoc = firestore
|
|
||||||
.collection(`contracts/${contract.id}/liquidity`)
|
|
||||||
.doc()
|
|
||||||
|
|
||||||
const providerId = isProd()
|
|
||||||
? HOUSE_LIQUIDITY_PROVIDER_ID
|
|
||||||
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
|
||||||
|
|
||||||
const { newLiquidityProvision, newPool, newP, newTotalLiquidity } =
|
|
||||||
getNewLiquidityProvision(
|
|
||||||
providerId,
|
|
||||||
amount,
|
|
||||||
contract,
|
|
||||||
newLiquidityProvisionDoc.id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (newP !== undefined && !isFinite(newP)) {
|
|
||||||
throw new APIError(
|
|
||||||
500,
|
|
||||||
'Liquidity injection rejected due to overflow error.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.update(
|
|
||||||
firestore.doc(`contracts/${contract.id}`),
|
|
||||||
removeUndefinedProps({
|
|
||||||
pool: newPool,
|
|
||||||
p: newP,
|
|
||||||
totalLiquidity: newTotalLiquidity,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
|
|
||||||
})
|
|
||||||
}
|
|
69
functions/src/drizzle-liquidity.ts
Normal file
69
functions/src/drizzle-liquidity.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import * as functions from 'firebase-functions'
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
|
import { CPMMContract } from '../../common/contract'
|
||||||
|
import { batchedWaitAll } from '../../common/util/promise'
|
||||||
|
import { APIError } from '../../common/api'
|
||||||
|
import { addCpmmLiquidity } from '../../common/calculate-cpmm'
|
||||||
|
import { formatMoneyWithDecimals } from '../../common/util/format'
|
||||||
|
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
export const drizzleLiquidity = async () => {
|
||||||
|
const snap = await firestore
|
||||||
|
.collection('contracts')
|
||||||
|
.where('subsidyPool', '>', 1e-7)
|
||||||
|
.get()
|
||||||
|
|
||||||
|
const contractIds = snap.docs.map((doc) => doc.id)
|
||||||
|
console.log('found', contractIds.length, 'markets to drizzle')
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
await batchedWaitAll(
|
||||||
|
contractIds.map((cid) => () => drizzleMarket(cid)),
|
||||||
|
10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drizzleLiquidityScheduler = functions.pubsub
|
||||||
|
.schedule('* * * * *') // every minute
|
||||||
|
.onRun(drizzleLiquidity)
|
||||||
|
|
||||||
|
const drizzleMarket = async (contractId: string) => {
|
||||||
|
await firestore.runTransaction(async (trans) => {
|
||||||
|
const snap = await trans.get(firestore.doc(`contracts/${contractId}`))
|
||||||
|
const contract = snap.data() as CPMMContract
|
||||||
|
const { subsidyPool, pool, p, slug, popularityScore } = contract
|
||||||
|
if ((subsidyPool ?? 0) < 1e-7) return
|
||||||
|
|
||||||
|
const r = Math.random()
|
||||||
|
const logPopularity = Math.log10((popularityScore ?? 0) + 1)
|
||||||
|
const v = Math.max(1, Math.min(5, logPopularity))
|
||||||
|
const amount = subsidyPool <= 0.5 ? subsidyPool : r * v * 0.01 * subsidyPool
|
||||||
|
|
||||||
|
const { newPool, newP } = addCpmmLiquidity(pool, p, amount)
|
||||||
|
|
||||||
|
if (!isFinite(newP)) {
|
||||||
|
throw new APIError(
|
||||||
|
500,
|
||||||
|
'Liquidity injection rejected due to overflow error.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await trans.update(firestore.doc(`contracts/${contract.id}`), {
|
||||||
|
pool: newPool,
|
||||||
|
p: newP,
|
||||||
|
subsidyPool: subsidyPool - amount,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'added subsidy',
|
||||||
|
formatMoneyWithDecimals(amount),
|
||||||
|
'of',
|
||||||
|
formatMoneyWithDecimals(subsidyPool),
|
||||||
|
'pool to',
|
||||||
|
slug
|
||||||
|
)
|
||||||
|
console.log()
|
||||||
|
})
|
||||||
|
}
|
42
functions/src/helpers/add-house-subsidy.ts
Normal file
42
functions/src/helpers/add-house-subsidy.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
|
import { CPMMContract } from '../../../common/contract'
|
||||||
|
import { isProd } from '../utils'
|
||||||
|
import {
|
||||||
|
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||||
|
HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||||
|
} from '../../../common/antes'
|
||||||
|
import { getNewLiquidityProvision } from '../../../common/add-liquidity'
|
||||||
|
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
export const addHouseSubsidy = (contractId: string, amount: number) => {
|
||||||
|
return firestore.runTransaction(async (transaction) => {
|
||||||
|
const newLiquidityProvisionDoc = firestore
|
||||||
|
.collection(`contracts/${contractId}/liquidity`)
|
||||||
|
.doc()
|
||||||
|
|
||||||
|
const providerId = isProd()
|
||||||
|
? HOUSE_LIQUIDITY_PROVIDER_ID
|
||||||
|
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
||||||
|
|
||||||
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
|
const snap = await contractDoc.get()
|
||||||
|
const contract = snap.data() as CPMMContract
|
||||||
|
|
||||||
|
const { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } =
|
||||||
|
getNewLiquidityProvision(
|
||||||
|
providerId,
|
||||||
|
amount,
|
||||||
|
contract,
|
||||||
|
newLiquidityProvisionDoc.id
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction.update(contractDoc, {
|
||||||
|
subsidyPool: newSubsidyPool,
|
||||||
|
totalLiquidity: newTotalLiquidity,
|
||||||
|
} as Partial<CPMMContract>)
|
||||||
|
|
||||||
|
transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
|
||||||
|
})
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ export * from './reset-weekly-emails-flags'
|
||||||
export * from './on-update-contract-follow'
|
export * from './on-update-contract-follow'
|
||||||
export * from './on-update-like'
|
export * from './on-update-like'
|
||||||
export * from './weekly-portfolio-emails'
|
export * from './weekly-portfolio-emails'
|
||||||
|
export * from './drizzle-liquidity'
|
||||||
|
|
||||||
// v2
|
// v2
|
||||||
export * from './health'
|
export * from './health'
|
||||||
|
@ -44,8 +45,6 @@ export * from './sell-bet'
|
||||||
export * from './sell-shares'
|
export * from './sell-shares'
|
||||||
export * from './claim-manalink'
|
export * from './claim-manalink'
|
||||||
export * from './create-market'
|
export * from './create-market'
|
||||||
export * from './add-liquidity'
|
|
||||||
export * from './withdraw-liquidity'
|
|
||||||
export * from './create-group'
|
export * from './create-group'
|
||||||
export * from './resolve-market'
|
export * from './resolve-market'
|
||||||
export * from './unsubscribe'
|
export * from './unsubscribe'
|
||||||
|
@ -53,6 +52,7 @@ export * from './stripe'
|
||||||
export * from './mana-bonus-email'
|
export * from './mana-bonus-email'
|
||||||
export * from './close-market'
|
export * from './close-market'
|
||||||
export * from './update-comment-bounty'
|
export * from './update-comment-bounty'
|
||||||
|
export * from './add-subsidy'
|
||||||
|
|
||||||
import { health } from './health'
|
import { health } from './health'
|
||||||
import { transact } from './transact'
|
import { transact } from './transact'
|
||||||
|
@ -65,9 +65,7 @@ import { sellbet } from './sell-bet'
|
||||||
import { sellshares } from './sell-shares'
|
import { sellshares } from './sell-shares'
|
||||||
import { claimmanalink } from './claim-manalink'
|
import { claimmanalink } from './claim-manalink'
|
||||||
import { createmarket } from './create-market'
|
import { createmarket } from './create-market'
|
||||||
import { addliquidity } from './add-liquidity'
|
|
||||||
import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
|
import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
|
||||||
import { withdrawliquidity } from './withdraw-liquidity'
|
|
||||||
import { creategroup } from './create-group'
|
import { creategroup } from './create-group'
|
||||||
import { resolvemarket } from './resolve-market'
|
import { resolvemarket } from './resolve-market'
|
||||||
import { closemarket } from './close-market'
|
import { closemarket } from './close-market'
|
||||||
|
@ -78,6 +76,7 @@ import { acceptchallenge } from './accept-challenge'
|
||||||
import { createpost } from './create-post'
|
import { createpost } from './create-post'
|
||||||
import { savetwitchcredentials } from './save-twitch-credentials'
|
import { savetwitchcredentials } from './save-twitch-credentials'
|
||||||
import { updatemetrics } from './update-metrics'
|
import { updatemetrics } from './update-metrics'
|
||||||
|
import { addsubsidy } from './add-subsidy'
|
||||||
|
|
||||||
const toCloudFunction = ({ opts, handler }: EndpointDefinition) => {
|
const toCloudFunction = ({ opts, handler }: EndpointDefinition) => {
|
||||||
return onRequest(opts, handler as any)
|
return onRequest(opts, handler as any)
|
||||||
|
@ -93,10 +92,9 @@ const sellBetFunction = toCloudFunction(sellbet)
|
||||||
const sellSharesFunction = toCloudFunction(sellshares)
|
const sellSharesFunction = toCloudFunction(sellshares)
|
||||||
const claimManalinkFunction = toCloudFunction(claimmanalink)
|
const claimManalinkFunction = toCloudFunction(claimmanalink)
|
||||||
const createMarketFunction = toCloudFunction(createmarket)
|
const createMarketFunction = toCloudFunction(createmarket)
|
||||||
const addLiquidityFunction = toCloudFunction(addliquidity)
|
const addSubsidyFunction = toCloudFunction(addsubsidy)
|
||||||
const addCommentBounty = toCloudFunction(addcommentbounty)
|
const addCommentBounty = toCloudFunction(addcommentbounty)
|
||||||
const awardCommentBounty = toCloudFunction(awardcommentbounty)
|
const awardCommentBounty = toCloudFunction(awardcommentbounty)
|
||||||
const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity)
|
|
||||||
const createGroupFunction = toCloudFunction(creategroup)
|
const createGroupFunction = toCloudFunction(creategroup)
|
||||||
const resolveMarketFunction = toCloudFunction(resolvemarket)
|
const resolveMarketFunction = toCloudFunction(resolvemarket)
|
||||||
const closeMarketFunction = toCloudFunction(closemarket)
|
const closeMarketFunction = toCloudFunction(closemarket)
|
||||||
|
@ -121,8 +119,7 @@ export {
|
||||||
sellSharesFunction as sellshares,
|
sellSharesFunction as sellshares,
|
||||||
claimManalinkFunction as claimmanalink,
|
claimManalinkFunction as claimmanalink,
|
||||||
createMarketFunction as createmarket,
|
createMarketFunction as createmarket,
|
||||||
addLiquidityFunction as addliquidity,
|
addSubsidyFunction as addsubsidy,
|
||||||
withdrawLiquidityFunction as withdrawliquidity,
|
|
||||||
createGroupFunction as creategroup,
|
createGroupFunction as creategroup,
|
||||||
resolveMarketFunction as resolvemarket,
|
resolveMarketFunction as resolvemarket,
|
||||||
closeMarketFunction as closemarket,
|
closeMarketFunction as closemarket,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
BETTING_STREAK_BONUS_MAX,
|
BETTING_STREAK_BONUS_MAX,
|
||||||
BETTING_STREAK_RESET_HOUR,
|
BETTING_STREAK_RESET_HOUR,
|
||||||
UNIQUE_BETTOR_BONUS_AMOUNT,
|
UNIQUE_BETTOR_BONUS_AMOUNT,
|
||||||
|
UNIQUE_BETTOR_LIQUIDITY,
|
||||||
} from '../../common/economy'
|
} from '../../common/economy'
|
||||||
import {
|
import {
|
||||||
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||||
|
@ -34,6 +35,7 @@ import { APIError } from '../../common/api'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { DAY_MS } from '../../common/util/time'
|
import { DAY_MS } from '../../common/util/time'
|
||||||
import { BettingStreakBonusTxn, UniqueBettorBonusTxn } from '../../common/txn'
|
import { BettingStreakBonusTxn, UniqueBettorBonusTxn } from '../../common/txn'
|
||||||
|
import { addHouseSubsidy } from './helpers/add-house-subsidy'
|
||||||
import {
|
import {
|
||||||
StreakerBadge,
|
StreakerBadge,
|
||||||
streakerBadgeRarityThresholds,
|
streakerBadgeRarityThresholds,
|
||||||
|
@ -108,7 +110,7 @@ const updateBettingStreak = async (
|
||||||
|
|
||||||
const newBettingStreak = (bettor?.currentBettingStreak ?? 0) + 1
|
const newBettingStreak = (bettor?.currentBettingStreak ?? 0) + 1
|
||||||
// Otherwise, add 1 to their betting streak
|
// Otherwise, add 1 to their betting streak
|
||||||
await trans.update(userDoc, {
|
trans.update(userDoc, {
|
||||||
currentBettingStreak: newBettingStreak,
|
currentBettingStreak: newBettingStreak,
|
||||||
lastBetTime: bet.createdTime,
|
lastBetTime: bet.createdTime,
|
||||||
})
|
})
|
||||||
|
@ -198,7 +200,7 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||||
log(`Got ${previousUniqueBettorIds} unique bettors`)
|
log(`Got ${previousUniqueBettorIds} unique bettors`)
|
||||||
isNewUniqueBettor && log(`And a new unique bettor ${bettor.id}`)
|
isNewUniqueBettor && log(`And a new unique bettor ${bettor.id}`)
|
||||||
|
|
||||||
await trans.update(contractDoc, {
|
trans.update(contractDoc, {
|
||||||
uniqueBettorIds: newUniqueBettorIds,
|
uniqueBettorIds: newUniqueBettorIds,
|
||||||
uniqueBettorCount: newUniqueBettorIds.length,
|
uniqueBettorCount: newUniqueBettorIds.length,
|
||||||
})
|
})
|
||||||
|
@ -211,8 +213,13 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||||
return { newUniqueBettorIds }
|
return { newUniqueBettorIds }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!newUniqueBettorIds) return
|
if (!newUniqueBettorIds) return
|
||||||
|
|
||||||
|
if (oldContract.mechanism === 'cpmm-1') {
|
||||||
|
await addHouseSubsidy(oldContract.id, UNIQUE_BETTOR_LIQUIDITY)
|
||||||
|
}
|
||||||
|
|
||||||
const bonusTxnDetails = {
|
const bonusTxnDetails = {
|
||||||
contractId: oldContract.id,
|
contractId: oldContract.id,
|
||||||
uniqueNewBettorId: bettor.id,
|
uniqueNewBettorId: bettor.id,
|
||||||
|
@ -222,7 +229,9 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||||
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
||||||
const fromSnap = await firestore.doc(`users/${fromUserId}`).get()
|
const fromSnap = await firestore.doc(`users/${fromUserId}`).get()
|
||||||
if (!fromSnap.exists) throw new APIError(400, 'From user not found.')
|
if (!fromSnap.exists) throw new APIError(400, 'From user not found.')
|
||||||
|
|
||||||
const fromUser = fromSnap.data() as User
|
const fromUser = fromSnap.data() as User
|
||||||
|
|
||||||
const result = await firestore.runTransaction(async (trans) => {
|
const result = await firestore.runTransaction(async (trans) => {
|
||||||
const bonusTxn: TxnData = {
|
const bonusTxn: TxnData = {
|
||||||
fromId: fromUser.id,
|
fromId: fromUser.id,
|
||||||
|
@ -235,7 +244,9 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||||
description: JSON.stringify(bonusTxnDetails),
|
description: JSON.stringify(bonusTxnDetails),
|
||||||
data: bonusTxnDetails,
|
data: bonusTxnDetails,
|
||||||
} as Omit<UniqueBettorBonusTxn, 'id' | 'createdTime'>
|
} as Omit<UniqueBettorBonusTxn, 'id' | 'createdTime'>
|
||||||
|
|
||||||
const { status, message, txn } = await runTxn(trans, bonusTxn)
|
const { status, message, txn } = await runTxn(trans, bonusTxn)
|
||||||
|
|
||||||
return { status, newUniqueBettorIds, message, txn }
|
return { status, newUniqueBettorIds, message, txn }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,15 @@ import {
|
||||||
RESOLUTIONS,
|
RESOLUTIONS,
|
||||||
} from '../../common/contract'
|
} from '../../common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getContractPath, getUser, getValues, isProd, log, payUser, revalidateStaticProps } from './utils'
|
import {
|
||||||
|
getContractPath,
|
||||||
|
getUser,
|
||||||
|
getValues,
|
||||||
|
isProd,
|
||||||
|
log,
|
||||||
|
payUser,
|
||||||
|
revalidateStaticProps,
|
||||||
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
getLoanPayouts,
|
getLoanPayouts,
|
||||||
getPayouts,
|
getPayouts,
|
||||||
|
@ -145,6 +153,7 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
|
||||||
resolutions,
|
resolutions,
|
||||||
collectedFees,
|
collectedFees,
|
||||||
}),
|
}),
|
||||||
|
subsidyPool: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
await contractDoc.update(updatedContract)
|
await contractDoc.update(updatedContract)
|
||||||
|
|
8
functions/src/scripts/drizzle.ts
Normal file
8
functions/src/scripts/drizzle.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin()
|
||||||
|
|
||||||
|
import { drizzleLiquidity } from '../drizzle-liquidity'
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
drizzleLiquidity().then(() => process.exit())
|
||||||
|
}
|
|
@ -19,8 +19,6 @@ import { sellbet } from './sell-bet'
|
||||||
import { sellshares } from './sell-shares'
|
import { sellshares } from './sell-shares'
|
||||||
import { claimmanalink } from './claim-manalink'
|
import { claimmanalink } from './claim-manalink'
|
||||||
import { createmarket } from './create-market'
|
import { createmarket } from './create-market'
|
||||||
import { addliquidity } from './add-liquidity'
|
|
||||||
import { withdrawliquidity } from './withdraw-liquidity'
|
|
||||||
import { creategroup } from './create-group'
|
import { creategroup } from './create-group'
|
||||||
import { resolvemarket } from './resolve-market'
|
import { resolvemarket } from './resolve-market'
|
||||||
import { unsubscribe } from './unsubscribe'
|
import { unsubscribe } from './unsubscribe'
|
||||||
|
@ -61,10 +59,8 @@ addJsonEndpointRoute('/sellbet', sellbet)
|
||||||
addJsonEndpointRoute('/sellshares', sellshares)
|
addJsonEndpointRoute('/sellshares', sellshares)
|
||||||
addJsonEndpointRoute('/claimmanalink', claimmanalink)
|
addJsonEndpointRoute('/claimmanalink', claimmanalink)
|
||||||
addJsonEndpointRoute('/createmarket', createmarket)
|
addJsonEndpointRoute('/createmarket', createmarket)
|
||||||
addJsonEndpointRoute('/addliquidity', addliquidity)
|
|
||||||
addJsonEndpointRoute('/addCommentBounty', addcommentbounty)
|
addJsonEndpointRoute('/addCommentBounty', addcommentbounty)
|
||||||
addJsonEndpointRoute('/awardCommentBounty', awardcommentbounty)
|
addJsonEndpointRoute('/awardCommentBounty', awardcommentbounty)
|
||||||
addJsonEndpointRoute('/withdrawliquidity', withdrawliquidity)
|
|
||||||
addJsonEndpointRoute('/creategroup', creategroup)
|
addJsonEndpointRoute('/creategroup', creategroup)
|
||||||
addJsonEndpointRoute('/resolvemarket', resolvemarket)
|
addJsonEndpointRoute('/resolvemarket', resolvemarket)
|
||||||
addJsonEndpointRoute('/unsubscribe', unsubscribe)
|
addJsonEndpointRoute('/unsubscribe', unsubscribe)
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
import * as admin from 'firebase-admin'
|
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
import { CPMMContract } from '../../common/contract'
|
|
||||||
import { User } from '../../common/user'
|
|
||||||
import { subtractObjects } from '../../common/util/object'
|
|
||||||
import { LiquidityProvision } from '../../common/liquidity-provision'
|
|
||||||
import { getUserLiquidityShares } from '../../common/calculate-cpmm'
|
|
||||||
import { Bet } from '../../common/bet'
|
|
||||||
import { getProbability } from '../../common/calculate'
|
|
||||||
import { noFees } from '../../common/fees'
|
|
||||||
|
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
|
||||||
import { redeemShares } from './redeem-shares'
|
|
||||||
|
|
||||||
const bodySchema = z.object({
|
|
||||||
contractId: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const withdrawliquidity = newEndpoint({}, async (req, auth) => {
|
|
||||||
const { contractId } = validate(bodySchema, req.body)
|
|
||||||
|
|
||||||
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 liquidityCollection = firestore.collection(
|
|
||||||
`contracts/${contractId}/liquidity`
|
|
||||||
)
|
|
||||||
|
|
||||||
const liquiditiesSnap = await trans.get(liquidityCollection)
|
|
||||||
|
|
||||||
const liquidities = liquiditiesSnap.docs.map(
|
|
||||||
(doc) => doc.data() as LiquidityProvision
|
|
||||||
)
|
|
||||||
|
|
||||||
const userShares = getUserLiquidityShares(
|
|
||||||
auth.uid,
|
|
||||||
contract,
|
|
||||||
liquidities,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 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 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)
|
|
||||||
|
|
||||||
trans.update(contractDoc, {
|
|
||||||
pool: newPool,
|
|
||||||
totalLiquidity: newTotalLiquidity,
|
|
||||||
})
|
|
||||||
|
|
||||||
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: 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 })
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
|
@ -7,7 +7,6 @@ import { capitalize } from 'lodash'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney, formatPercent } from 'common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { contractPool, updateContract } from 'web/lib/firebase/contracts'
|
import { contractPool, updateContract } from 'web/lib/firebase/contracts'
|
||||||
import { LiquidityBountyPanel } from 'web/components/contract/liquidity-bounty-panel'
|
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Modal } from '../layout/modal'
|
import { Modal } from '../layout/modal'
|
||||||
import { Title } from '../title'
|
import { Title } from '../title'
|
||||||
|
@ -55,6 +54,7 @@ export function ContractInfoDialog(props: {
|
||||||
outcomeType,
|
outcomeType,
|
||||||
id,
|
id,
|
||||||
elasticity,
|
elasticity,
|
||||||
|
pool,
|
||||||
} = contract
|
} = contract
|
||||||
|
|
||||||
const typeDisplay =
|
const typeDisplay =
|
||||||
|
@ -172,10 +172,25 @@ export function ContractInfoDialog(props: {
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>Liquidity subsidies</td>
|
||||||
<td>
|
<td>
|
||||||
{mechanism === 'cpmm-1' ? 'Liquidity pool' : 'Betting pool'}
|
{mechanism === 'cpmm-1'
|
||||||
|
? formatMoney(contract.totalLiquidity)
|
||||||
|
: formatMoney(100)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Pool</td>
|
||||||
|
<td>
|
||||||
|
{mechanism === 'cpmm-1' && outcomeType === 'BINARY'
|
||||||
|
? `${Math.round(pool.YES)} YES, ${Math.round(pool.NO)} NO`
|
||||||
|
: mechanism === 'cpmm-1' && outcomeType === 'PSEUDO_NUMERIC'
|
||||||
|
? `${Math.round(pool.YES)} HIGHER, ${Math.round(
|
||||||
|
pool.NO
|
||||||
|
)} LOWER`
|
||||||
|
: contractPool(contract)}
|
||||||
</td>
|
</td>
|
||||||
<td>{contractPool(contract)}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* Show a path to Firebase if user is an admin, or we're on localhost */}
|
{/* Show a path to Firebase if user is an admin, or we're on localhost */}
|
||||||
|
@ -228,7 +243,6 @@ export function ContractInfoDialog(props: {
|
||||||
<Row className="flex-wrap">
|
<Row className="flex-wrap">
|
||||||
<DuplicateContractButton contract={contract} />
|
<DuplicateContractButton contract={contract} />
|
||||||
</Row>
|
</Row>
|
||||||
{!contract.resolution && <LiquidityBountyPanel contract={contract} />}
|
|
||||||
</Col>
|
</Col>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { FollowMarketButton } from 'web/components/follow-market-button'
|
||||||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
||||||
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../tooltip'
|
||||||
|
import { LiquidityButton } from './liquidity-button'
|
||||||
|
|
||||||
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
const { contract } = props
|
const { contract } = props
|
||||||
|
@ -18,6 +19,9 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<FollowMarketButton contract={contract} user={user} />
|
<FollowMarketButton contract={contract} user={user} />
|
||||||
|
{contract.mechanism === 'cpmm-1' && (
|
||||||
|
<LiquidityButton contract={contract} user={user} />
|
||||||
|
)}
|
||||||
<LikeMarketButton contract={contract} user={user} />
|
<LikeMarketButton contract={contract} user={user} />
|
||||||
<Tooltip text="Share" placement="bottom" noTap noFade>
|
<Tooltip text="Share" placement="bottom" noTap noFade>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,248 +0,0 @@
|
||||||
import clsx from 'clsx'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { Contract, CPMMContract } from 'common/contract'
|
|
||||||
import { formatMoney } from 'common/util/format'
|
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/api'
|
|
||||||
import { AmountInput } from 'web/components/amount-input'
|
|
||||||
import { Row } from 'web/components/layout/row'
|
|
||||||
import { useUserLiquidity } from 'web/hooks/use-liquidity'
|
|
||||||
import { Tabs } from 'web/components/layout/tabs'
|
|
||||||
import { NoLabel, YesLabel } from 'web/components/outcome-label'
|
|
||||||
import { Col } from 'web/components/layout/col'
|
|
||||||
import { track } from 'web/lib/service/analytics'
|
|
||||||
import { InfoTooltip } from 'web/components/info-tooltip'
|
|
||||||
import { BETTORS, PRESENT_BET } from 'common/user'
|
|
||||||
import { buildArray } from 'common/util/array'
|
|
||||||
import { useAdmin } from 'web/hooks/use-admin'
|
|
||||||
import { AlertBox } from '../alert-box'
|
|
||||||
import { Spacer } from '../layout/spacer'
|
|
||||||
|
|
||||||
export function LiquidityBountyPanel(props: { contract: Contract }) {
|
|
||||||
const { contract } = props
|
|
||||||
|
|
||||||
const isCPMM = contract.mechanism === 'cpmm-1'
|
|
||||||
const user = useUser()
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const lpShares = isCPMM && useUserLiquidity(contract, user?.id ?? '')
|
|
||||||
|
|
||||||
const [showWithdrawal, setShowWithdrawal] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showWithdrawal && lpShares && lpShares.YES && lpShares.NO)
|
|
||||||
setShowWithdrawal(true)
|
|
||||||
}, [showWithdrawal, lpShares])
|
|
||||||
|
|
||||||
const isCreator = user?.id === contract.creatorId
|
|
||||||
const isAdmin = useAdmin()
|
|
||||||
|
|
||||||
if (!isCreator && !isAdmin && !showWithdrawal) return <></>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tabs
|
|
||||||
tabs={buildArray(
|
|
||||||
(isCreator || isAdmin) &&
|
|
||||||
isCPMM && {
|
|
||||||
title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
|
|
||||||
content: <AddLiquidityPanel contract={contract} />,
|
|
||||||
},
|
|
||||||
showWithdrawal &&
|
|
||||||
isCPMM && {
|
|
||||||
title: 'Withdraw',
|
|
||||||
content: (
|
|
||||||
<WithdrawLiquidityPanel
|
|
||||||
contract={contract}
|
|
||||||
lpShares={lpShares as { YES: number; NO: number }}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
(isCreator || isAdmin) &&
|
|
||||||
isCPMM && {
|
|
||||||
title: 'Pool',
|
|
||||||
content: <ViewLiquidityPanel contract={contract} />,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
|
||||||
const { contract } = props
|
|
||||||
const { id: contractId, slug } = contract
|
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const [amount, setAmount] = useState<number | undefined>(undefined)
|
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
|
||||||
const [isSuccess, setIsSuccess] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
const onAmountChange = (amount: number | undefined) => {
|
|
||||||
setIsSuccess(false)
|
|
||||||
setAmount(amount)
|
|
||||||
|
|
||||||
// Check for errors.
|
|
||||||
if (amount !== undefined) {
|
|
||||||
if (user && user.balance < amount) {
|
|
||||||
setError('Insufficient balance')
|
|
||||||
} else if (amount < 1) {
|
|
||||||
setError('Minimum amount: ' + formatMoney(1))
|
|
||||||
} else {
|
|
||||||
setError(undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
if (!amount) return
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setIsSuccess(false)
|
|
||||||
|
|
||||||
addLiquidity({ amount, contractId })
|
|
||||||
.then((_) => {
|
|
||||||
setIsSuccess(true)
|
|
||||||
setError(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
.catch((_) => setError('Server error'))
|
|
||||||
|
|
||||||
track('add liquidity', { amount, contractId, slug })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mb-4 text-gray-500">
|
|
||||||
Contribute your M$ to make this market more accurate.{' '}
|
|
||||||
<InfoTooltip
|
|
||||||
text={`More liquidity stabilizes the market, encouraging ${BETTORS} to ${PRESENT_BET}.`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<AmountInput
|
|
||||||
amount={amount}
|
|
||||||
onChange={onAmountChange}
|
|
||||||
label="M$"
|
|
||||||
error={error}
|
|
||||||
disabled={isLoading}
|
|
||||||
inputClassName="w-28"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={clsx('btn btn-primary ml-2', isLoading && 'btn-disabled')}
|
|
||||||
onClick={submit}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{isSuccess && amount && (
|
|
||||||
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isLoading && <div>Processing...</div>}
|
|
||||||
|
|
||||||
<Spacer h={2} />
|
|
||||||
<AlertBox
|
|
||||||
title="Withdrawals ending"
|
|
||||||
text="Manifold is moving to a new system for handling subsidization. As part of this process, liquidity withdrawals will be disabled shortly. Feel free to withdraw any outstanding liquidity you've added now."
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ViewLiquidityPanel(props: { contract: CPMMContract }) {
|
|
||||||
const { contract } = props
|
|
||||||
const { pool } = contract
|
|
||||||
const { YES: yesShares, NO: noShares } = pool
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col className="mb-4">
|
|
||||||
<div className="mb-4 text-gray-500">
|
|
||||||
The liquidity pool for this market currently contains:
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
{yesShares.toFixed(2)} <YesLabel /> shares
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{noShares.toFixed(2)} <NoLabel /> shares
|
|
||||||
</span>
|
|
||||||
</Col>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function WithdrawLiquidityPanel(props: {
|
|
||||||
contract: CPMMContract
|
|
||||||
lpShares: { YES: number; NO: number }
|
|
||||||
}) {
|
|
||||||
const { contract, lpShares } = props
|
|
||||||
const { YES: yesShares, NO: noShares } = lpShares
|
|
||||||
|
|
||||||
const [_error, setError] = useState<string | undefined>(undefined)
|
|
||||||
const [isSuccess, setIsSuccess] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
setIsLoading(true)
|
|
||||||
setIsSuccess(false)
|
|
||||||
|
|
||||||
withdrawLiquidity({ contractId: contract.id })
|
|
||||||
.then((_) => {
|
|
||||||
setIsSuccess(true)
|
|
||||||
setError(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
.catch((_) => setError('Server error'))
|
|
||||||
|
|
||||||
track('withdraw liquidity')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSuccess)
|
|
||||||
return (
|
|
||||||
<div className="text-gray-500">
|
|
||||||
Success! Your liquidity was withdrawn.
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!yesShares && !noShares)
|
|
||||||
return (
|
|
||||||
<div className="text-gray-500">
|
|
||||||
You do not have any liquidity positions to withdraw.
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<div className="mb-4 text-gray-500">
|
|
||||||
Your liquidity position is currently:
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{yesShares.toFixed(2)} <YesLabel /> shares
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{noShares.toFixed(2)} <NoLabel /> shares
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Row className="mt-4 mb-2">
|
|
||||||
<button
|
|
||||||
className={clsx(
|
|
||||||
'btn btn-outline btn-sm ml-2',
|
|
||||||
isLoading && 'btn-disabled'
|
|
||||||
)}
|
|
||||||
onClick={submit}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
Withdraw
|
|
||||||
</button>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{isLoading && <div>Processing...</div>}
|
|
||||||
</Col>
|
|
||||||
)
|
|
||||||
}
|
|
92
web/components/contract/liquidity-button.tsx
Normal file
92
web/components/contract/liquidity-button.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
import { Button } from 'web/components/button'
|
||||||
|
import { formatMoney, shortFormatNumber } from 'common/util/format'
|
||||||
|
import { Col } from 'web/components/layout/col'
|
||||||
|
import { Tooltip } from '../tooltip'
|
||||||
|
import { CPMMContract } from 'common/contract'
|
||||||
|
import { User } from 'common/user'
|
||||||
|
import { useLiquidity } from 'web/hooks/use-liquidity'
|
||||||
|
import { LiquidityModal } from './liquidity-modal'
|
||||||
|
|
||||||
|
export function LiquidityButton(props: {
|
||||||
|
contract: CPMMContract
|
||||||
|
user: User | undefined | null
|
||||||
|
}) {
|
||||||
|
const { contract, user } = props
|
||||||
|
const { totalLiquidity: total } = contract
|
||||||
|
|
||||||
|
const lp = useLiquidity(contract.id)
|
||||||
|
const userActive = lp?.find((l) => l.userId === user?.id) !== undefined
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
const disabled =
|
||||||
|
contract.isResolved || (contract.closeTime ?? Infinity) < Date.now()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
text={`${formatMoney(total)} in liquidity subsidies`}
|
||||||
|
placement="bottom"
|
||||||
|
noTap
|
||||||
|
noFade
|
||||||
|
>
|
||||||
|
<LiquidityIconButton
|
||||||
|
total={total}
|
||||||
|
userActive={userActive}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<LiquidityModal contract={contract} isOpen={open} setOpen={setOpen} />
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LiquidityIconButton(props: {
|
||||||
|
total: number
|
||||||
|
onClick: () => void
|
||||||
|
userActive: boolean
|
||||||
|
isCompact?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
}) {
|
||||||
|
const { total, userActive, isCompact, onClick, disabled } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size={'sm'}
|
||||||
|
className={clsx(
|
||||||
|
'max-w-xs self-center pt-1',
|
||||||
|
isCompact && 'px-0 py-0',
|
||||||
|
disabled && 'hover:bg-inherit'
|
||||||
|
)}
|
||||||
|
color={'gray-white'}
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Col className={'relative items-center sm:flex-row'}>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'text-xl sm:text-2xl',
|
||||||
|
total > 0 ? 'mr-2' : '',
|
||||||
|
userActive ? '' : 'grayscale'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
💧
|
||||||
|
</span>
|
||||||
|
{total > 0 && (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'bg-greyscale-5 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1',
|
||||||
|
total > 99
|
||||||
|
? 'text-[0.4rem] sm:text-[0.5rem]'
|
||||||
|
: 'sm:text-2xs text-[0.5rem]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{shortFormatNumber(total)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
108
web/components/contract/liquidity-modal.tsx
Normal file
108
web/components/contract/liquidity-modal.tsx
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import { CPMMContract } from 'common/contract'
|
||||||
|
import { formatMoney } from 'common/util/format'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useUser } from 'web/hooks/use-user'
|
||||||
|
import { addSubsidy } from 'web/lib/firebase/api'
|
||||||
|
import { track } from 'web/lib/service/analytics'
|
||||||
|
import { AmountInput } from '../amount-input'
|
||||||
|
import { Button } from '../button'
|
||||||
|
import { InfoTooltip } from '../info-tooltip'
|
||||||
|
import { Col } from '../layout/col'
|
||||||
|
import { Modal } from '../layout/modal'
|
||||||
|
import { Row } from '../layout/row'
|
||||||
|
import { Title } from '../title'
|
||||||
|
|
||||||
|
export function LiquidityModal(props: {
|
||||||
|
contract: CPMMContract
|
||||||
|
isOpen: boolean
|
||||||
|
setOpen: (open: boolean) => void
|
||||||
|
}) {
|
||||||
|
const { contract, isOpen, setOpen } = props
|
||||||
|
const { totalLiquidity } = contract
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={isOpen} setOpen={setOpen} size="sm">
|
||||||
|
<Col className="gap-2.5 rounded bg-white p-4 pb-8 sm:gap-4">
|
||||||
|
<Title className="!mt-0 !mb-2" text="💧 Add a subsidy" />
|
||||||
|
|
||||||
|
<div>Total liquidity subsidies: {formatMoney(totalLiquidity)}</div>
|
||||||
|
<AddLiquidityPanel contract={contract as CPMMContract} />
|
||||||
|
</Col>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
||||||
|
const { contract } = props
|
||||||
|
const { id: contractId, slug } = contract
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
|
||||||
|
const [amount, setAmount] = useState<number | undefined>(undefined)
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const onAmountChange = (amount: number | undefined) => {
|
||||||
|
setIsSuccess(false)
|
||||||
|
setAmount(amount)
|
||||||
|
|
||||||
|
// Check for errors.
|
||||||
|
if (amount !== undefined) {
|
||||||
|
if (user && user.balance < amount) {
|
||||||
|
setError('Insufficient balance')
|
||||||
|
} else if (amount < 1) {
|
||||||
|
setError('Minimum amount: ' + formatMoney(1))
|
||||||
|
} else {
|
||||||
|
setError(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (!amount) return
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setIsSuccess(false)
|
||||||
|
|
||||||
|
addSubsidy({ amount, contractId })
|
||||||
|
.then((_) => {
|
||||||
|
setIsSuccess(true)
|
||||||
|
setError(undefined)
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
.catch((_) => setError('Server error'))
|
||||||
|
|
||||||
|
track('add liquidity', { amount, contractId, slug })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 text-gray-500">
|
||||||
|
Contribute your M$ to make this market more accurate by subsidizing
|
||||||
|
trading.{' '}
|
||||||
|
<InfoTooltip text="Liquidity is how much money traders can make if they're right. The more traders can earn, the greater the incentive to find the correct probability." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<AmountInput
|
||||||
|
amount={amount}
|
||||||
|
onChange={onAmountChange}
|
||||||
|
label="M$"
|
||||||
|
error={error}
|
||||||
|
disabled={isLoading}
|
||||||
|
inputClassName="w-16 mr-4"
|
||||||
|
/>
|
||||||
|
<Button size="md" color="blue" onClick={submit} disabled={isLoading}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{isSuccess && amount && (
|
||||||
|
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && <div>Processing...</div>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { HeartIcon } from '@heroicons/react/outline'
|
|
||||||
import { Button } from 'web/components/button'
|
|
||||||
import { formatMoney } from 'common/util/format'
|
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import { HeartIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
|
import { Button } from 'web/components/button'
|
||||||
|
import { formatMoney, shortFormatNumber } from 'common/util/format'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../tooltip'
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ export function TipButton(props: {
|
||||||
: 'sm:text-2xs text-[0.5rem]'
|
: 'sm:text-2xs text-[0.5rem]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{totalTipped}
|
{shortFormatNumber(totalTipped)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -21,11 +21,6 @@ export const useLiquidity = (contractId: string) => {
|
||||||
export const useUserLiquidity = (contract: CPMMContract, userId: string) => {
|
export const useUserLiquidity = (contract: CPMMContract, userId: string) => {
|
||||||
const liquidities = useLiquidity(contract.id)
|
const liquidities = useLiquidity(contract.id)
|
||||||
|
|
||||||
const userShares = getUserLiquidityShares(
|
const userShares = getUserLiquidityShares(userId, contract, liquidities ?? [])
|
||||||
userId,
|
|
||||||
contract,
|
|
||||||
liquidities ?? [],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
return userShares
|
return userShares
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@ export function changeUserInfo(params: any) {
|
||||||
return call(getFunctionUrl('changeuserinfo'), 'POST', params)
|
return call(getFunctionUrl('changeuserinfo'), 'POST', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addLiquidity(params: any) {
|
export function addSubsidy(params: any) {
|
||||||
return call(getFunctionUrl('addliquidity'), 'POST', params)
|
return call(getFunctionUrl('addsubsidy'), 'POST', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addCommentBounty(params: any) {
|
export function addCommentBounty(params: any) {
|
||||||
|
@ -54,10 +54,6 @@ export function awardCommentBounty(params: any) {
|
||||||
return call(getFunctionUrl('awardcommentbounty'), 'POST', params)
|
return call(getFunctionUrl('awardcommentbounty'), 'POST', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withdrawLiquidity(params: any) {
|
|
||||||
return call(getFunctionUrl('withdrawliquidity'), 'POST', params)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createMarket(params: any) {
|
export function createMarket(params: any) {
|
||||||
return call(getFunctionUrl('createmarket'), 'POST', params)
|
return call(getFunctionUrl('createmarket'), 'POST', params)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user