Implement "sell all shares" functionality in sellshares
and expose API (#696)
* Change `sellshares` to be able to sell all shares * Sell all shares properly on bet panel UI * Add API route for selling shares, document
This commit is contained in:
parent
ad46a60c4f
commit
b506e96548
|
@ -579,6 +579,26 @@ $ curl https://manifold.markets/api/v0/market/{marketId}/resolve -X POST \
|
||||||
]}'
|
]}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `POST /v0/market/[marketId]/sell`
|
||||||
|
|
||||||
|
Sells some quantity of shares in a market on behalf of the authorized user.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
- `outcome`: Required. One of `YES`, `NO`, or a `number` indicating the numeric
|
||||||
|
bucket ID, depending on the market type.
|
||||||
|
- `shares`: Optional. The amount of shares to sell of the outcome given
|
||||||
|
above. If not provided, all the shares you own will be sold.
|
||||||
|
|
||||||
|
Example request:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl https://manifold.markets/api/v0/market/{marketId}/sell -X POST \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Authorization: Key {...}' \
|
||||||
|
--data-raw '{"outcome": "YES", "shares": 10}'
|
||||||
|
```
|
||||||
|
|
||||||
### `GET /v0/bets`
|
### `GET /v0/bets`
|
||||||
|
|
||||||
Gets a list of bets, ordered by creation date descending.
|
Gets a list of bets, ordered by creation date descending.
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { redeemShares } from './redeem-shares'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string(),
|
contractId: z.string(),
|
||||||
shares: z.number(),
|
shares: z.number().optional(), // leave it out to sell all shares
|
||||||
outcome: z.enum(['YES', 'NO']),
|
outcome: z.enum(['YES', 'NO']),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -49,11 +49,12 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
|
||||||
|
|
||||||
const outcomeBets = userBets.filter((bet) => bet.outcome == outcome)
|
const outcomeBets = userBets.filter((bet) => bet.outcome == outcome)
|
||||||
const maxShares = sumBy(outcomeBets, (bet) => bet.shares)
|
const maxShares = sumBy(outcomeBets, (bet) => bet.shares)
|
||||||
|
const sharesToSell = shares ?? maxShares
|
||||||
|
|
||||||
if (!floatingLesserEqual(shares, maxShares))
|
if (!floatingLesserEqual(sharesToSell, maxShares))
|
||||||
throw new APIError(400, `You can only sell up to ${maxShares} shares.`)
|
throw new APIError(400, `You can only sell up to ${maxShares} shares.`)
|
||||||
|
|
||||||
const soldShares = Math.min(shares, maxShares)
|
const soldShares = Math.min(sharesToSell, maxShares)
|
||||||
|
|
||||||
const unfilledBetsSnap = await transaction.get(
|
const unfilledBetsSnap = await transaction.get(
|
||||||
getUnfilledBetsQuery(contractDoc)
|
getUnfilledBetsQuery(contractDoc)
|
||||||
|
|
|
@ -771,7 +771,9 @@ export function SellPanel(props: {
|
||||||
const betDisabled = isSubmitting || !amount || error
|
const betDisabled = isSubmitting || !amount || error
|
||||||
|
|
||||||
// Sell all shares if remaining shares would be < 1
|
// Sell all shares if remaining shares would be < 1
|
||||||
const sellQuantity = amount === Math.floor(shares) ? shares : amount
|
const isSellingAllShares = amount === Math.floor(shares)
|
||||||
|
|
||||||
|
const sellQuantity = isSellingAllShares ? shares : amount
|
||||||
|
|
||||||
async function submitSell() {
|
async function submitSell() {
|
||||||
if (!user || !amount) return
|
if (!user || !amount) return
|
||||||
|
@ -780,7 +782,7 @@ export function SellPanel(props: {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
await sellShares({
|
await sellShares({
|
||||||
shares: sellQuantity,
|
shares: isSellingAllShares ? undefined : amount,
|
||||||
outcome: sharesOutcome,
|
outcome: sharesOutcome,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
})
|
})
|
||||||
|
|
28
web/pages/api/v0/market/[id]/sell.ts
Normal file
28
web/pages/api/v0/market/[id]/sell.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import {
|
||||||
|
CORS_ORIGIN_MANIFOLD,
|
||||||
|
CORS_ORIGIN_LOCALHOST,
|
||||||
|
} from 'common/envs/constants'
|
||||||
|
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||||
|
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||||
|
|
||||||
|
export const config = { api: { bodyParser: true } }
|
||||||
|
|
||||||
|
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
await applyCorsHeaders(req, res, {
|
||||||
|
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||||
|
methods: 'POST',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { id } = req.query
|
||||||
|
const contractId = id as string
|
||||||
|
|
||||||
|
if (req.body) req.body.contractId = contractId
|
||||||
|
try {
|
||||||
|
const backendRes = await fetchBackend(req, 'sellshares')
|
||||||
|
await forwardResponse(res, backendRes)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error talking to cloud function: ', err)
|
||||||
|
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user