Create sell orders
This commit is contained in:
parent
00475b7914
commit
fd6111b590
|
@ -47,7 +47,7 @@ CREATE TABLE orders(
|
|||
side ORDER_SIDE NOT NULL,
|
||||
quantity BIGINT NOT NULL,
|
||||
price BIGINT NOT NULL,
|
||||
invoice_id UUID NOT NULL REFERENCES invoices(id),
|
||||
invoice_id UUID REFERENCES invoices(id),
|
||||
order_id UUID REFERENCES orders(id)
|
||||
);
|
||||
ALTER TABLE orders ADD CONSTRAINT order_price CHECK(price > 0 AND price < 100);
|
||||
|
|
12
db/market.go
12
db/market.go
|
@ -107,7 +107,7 @@ func (db *DB) FetchOrders(where *FetchOrdersWhere, orders *[]Order) error {
|
|||
func (db *DB) CreateOrder(tx *sql.Tx, ctx context.Context, order *Order) error {
|
||||
if _, err := tx.ExecContext(ctx, ""+
|
||||
"INSERT INTO orders(share_id, pubkey, side, quantity, price, invoice_id) "+
|
||||
"VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"VALUES ($1, $2, $3, $4, $5, CASE WHEN $6 = '' THEN NULL ELSE $6::UUID END)",
|
||||
order.ShareId, order.Pubkey, order.Side, order.Quantity, order.Price, order.InvoiceId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ func (db *DB) FetchUserOrders(pubkey string, orders *[]Order) error {
|
|||
"FROM orders o " +
|
||||
"JOIN invoices i ON o.invoice_id = i.id " +
|
||||
"JOIN shares s ON o.share_id = s.id " +
|
||||
"WHERE o.pubkey = $1 AND i.confirmed_at IS NOT NULL " +
|
||||
"WHERE o.pubkey = $1 AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL) OR o.side = 'SELL' ) " +
|
||||
"ORDER BY o.created_at DESC"
|
||||
rows, err := db.Query(query, pubkey)
|
||||
if err != nil {
|
||||
|
@ -153,7 +153,7 @@ func (db *DB) FetchMarketOrders(marketId int64, orders *[]Order) error {
|
|||
"FROM orders o " +
|
||||
"JOIN shares s ON o.share_id = s.id " +
|
||||
"JOIN invoices i ON i.id = o.invoice_id " +
|
||||
"WHERE s.market_id = $1 AND i.confirmed_at IS NOT NULL " +
|
||||
"WHERE s.market_id = $1 AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL) OR o.side = 'SELL' ) " +
|
||||
"ORDER BY o.created_at DESC"
|
||||
rows, err := db.Query(query, marketId)
|
||||
if err != nil {
|
||||
|
@ -266,16 +266,16 @@ func (db *DB) FetchMarketStats(marketId int64, stats *MarketStats) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) FetchUserBalance(marketId int64, pubkey string, balance *map[string]any) error {
|
||||
func (db *DB) FetchUserBalance(tx *sql.Tx, ctx context.Context, marketId int, pubkey string, balance *map[string]any) error {
|
||||
query := "" +
|
||||
"SELECT s.description, " +
|
||||
"SUM(CASE WHEN o.side = 'BUY' THEN o.quantity ELSE -o.quantity END) " +
|
||||
"FROM orders o " +
|
||||
"JOIN invoices i ON i.id = o.invoice_id " +
|
||||
"JOIN shares s ON s.id = o.share_id " +
|
||||
"WHERE o.pubkey = $1 AND s.market_id = $2 AND i.confirmed_at IS NOT NULL AND o.order_id IS NOT NULL " +
|
||||
"WHERE o.pubkey = $1 AND s.market_id = $2 AND ( (o.side = 'BUY' AND i.confirmed_at IS NOT NULL AND o.order_id IS NOT NULL) OR o.side = 'SELL' ) " +
|
||||
"GROUP BY o.pubkey, s.description"
|
||||
rows, err := db.Query(query, pubkey, marketId)
|
||||
rows, err := tx.QueryContext(ctx, query, pubkey, marketId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ func HandleMarket(sc context.ServerContext) echo.HandlerFunc {
|
|||
err error
|
||||
data map[string]any
|
||||
u db.User
|
||||
tx *sql.Tx
|
||||
)
|
||||
if marketId, err = strconv.ParseInt(c.Param("id"), 10, 64); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
||||
|
@ -48,8 +49,15 @@ func HandleMarket(sc context.ServerContext) echo.HandlerFunc {
|
|||
}
|
||||
if session := c.Get("session"); session != nil {
|
||||
u = session.(db.User)
|
||||
ctx, cancel := context_.WithTimeout(context_.TODO(), 10*time.Second)
|
||||
defer cancel()
|
||||
if tx, err = sc.Db.BeginTx(ctx, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
uBalance := make(map[string]any)
|
||||
if err = sc.Db.FetchUserBalance(marketId, u.Pubkey, &uBalance); err != nil {
|
||||
if err = sc.Db.FetchUserBalance(tx, ctx, int(marketId), u.Pubkey, &uBalance); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
lib.Merge(&data, &map[string]any{"user": uBalance})
|
||||
|
@ -181,43 +189,61 @@ func HandleOrder(sc context.ServerContext) echo.HandlerFunc {
|
|||
}
|
||||
description = fmt.Sprintf("%s %d %s shares @ %d sats [market:%d]", strings.ToUpper(o.Side), o.Quantity, s.Description, o.Price, s.MarketId)
|
||||
|
||||
// TODO: if SELL order, check share balance of user
|
||||
if o.Side == "BUY" {
|
||||
// === Create invoice ===
|
||||
// We do this for BUY and SELL orders such that we can continue to use `invoice.confirmed_at IS NOT NULL`
|
||||
// to check for confirmed orders
|
||||
if invoice, err = sc.Lnd.CreateInvoice(tx, ctx, sc.Db, o.Pubkey, msats, description); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
// Create QR code to pay HODL invoice
|
||||
if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Create HODL invoice
|
||||
if invoice, err = sc.Lnd.CreateInvoice(tx, ctx, sc.Db, o.Pubkey, msats, description); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
// Create QR code to pay HODL invoice
|
||||
if qr, err = lib.ToQR(invoice.PaymentRequest); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if hash, err = lntypes.MakeHashFromStr(invoice.Hash); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
// Create (unconfirmed) order
|
||||
o.InvoiceId = invoice.Id
|
||||
if err := sc.Db.CreateOrder(tx, ctx, &o); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
// need to commit before starting to poll invoice status
|
||||
tx.Commit()
|
||||
go sc.Lnd.CheckInvoice(sc.Db, hash)
|
||||
|
||||
// TODO: find matching orders
|
||||
|
||||
data = map[string]any{
|
||||
"id": invoice.Id,
|
||||
"bolt11": invoice.PaymentRequest,
|
||||
"amount": msats,
|
||||
"qr": qr,
|
||||
}
|
||||
return c.JSON(http.StatusPaymentRequired, data)
|
||||
}
|
||||
|
||||
// Create (unconfirmed) order
|
||||
o.InvoiceId = invoice.Id
|
||||
// sell order: check user balance
|
||||
balance := make(map[string]any)
|
||||
if err = sc.Db.FetchUserBalance(tx, ctx, s.MarketId, o.Pubkey, &balance); err != nil {
|
||||
return err
|
||||
}
|
||||
if balance[s.Description].(int) < int(o.Quantity) {
|
||||
tx.Rollback()
|
||||
return c.JSON(http.StatusBadRequest, nil)
|
||||
}
|
||||
// SELL orders don't require payment by user
|
||||
if err := sc.Db.CreateOrder(tx, ctx, &o); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// need to commit before starting to poll invoice status
|
||||
tx.Commit()
|
||||
go sc.Lnd.CheckInvoice(sc.Db, hash)
|
||||
|
||||
// TODO: find matching orders
|
||||
|
||||
data = map[string]any{
|
||||
"id": invoice.Id,
|
||||
"bolt11": invoice.PaymentRequest,
|
||||
"amount": msats,
|
||||
"qr": qr,
|
||||
}
|
||||
return c.JSON(http.StatusPaymentRequired, data)
|
||||
return c.JSON(http.StatusCreated, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,11 +38,20 @@ function mouseover (oid) {
|
|||
|
||||
const click = (order) => {
|
||||
// redirect to form with prefilled inputs to match order
|
||||
const stake = order.quantity * (100 - order.price)
|
||||
const certainty = (100 - order.price) / 100
|
||||
const share = order.ShareDescription === 'YES' ? 'NO' : 'YES'
|
||||
const side = 'BUY'
|
||||
router.push(`/market/${marketId}/form?stake=${stake}&certainty=${certainty}&side=${side}&share=${share}`)
|
||||
if (order.side === 'BUY') {
|
||||
// match BUY YES with BUY NO and vice versa
|
||||
const stake = order.quantity * (100 - order.price)
|
||||
const certainty = (100 - order.price) / 100
|
||||
const share = order.ShareDescription === 'YES' ? 'NO' : 'YES'
|
||||
router.push(`/market/${marketId}/form/buy?stake=${stake}&certainty=${certainty}&share=${share}`)
|
||||
}
|
||||
if (order.side === 'SELL') {
|
||||
// match SELL YES with BUY YES and vice versa
|
||||
const stake = order.quantity * order.price
|
||||
const certainty = order.price / 100
|
||||
const share = order.ShareDescription === 'YES' ? 'YES' : 'NO'
|
||||
router.push(`/market/${marketId}/form/buy?stake=${stake}&certainty=${certainty}&share=${share}`)
|
||||
}
|
||||
}
|
||||
|
||||
const orders = ref([])
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<label>you sell:</label>
|
||||
<label>{{ shares }} {{ selected }} shares @ {{ price }} sats</label>
|
||||
<label>you make:</label>
|
||||
<label>+{{ format(profit) }} sats</label>
|
||||
<button class="col-span-2" type="submit">submit sell order</button>
|
||||
<label>{{ format(profit) }} sats</label>
|
||||
<button class="col-span-2" type="submit" :disabled="disabled">submit sell order</button>
|
||||
</form>
|
||||
<div v-if="err" class="red text-center">{{ err }}</div>
|
||||
</div>
|
||||
|
@ -34,7 +34,7 @@ const showForm = computed(() => selected.value !== null)
|
|||
const err = ref(null)
|
||||
|
||||
// how many shares wants the user sell?
|
||||
const shares = ref(route.query.shares || 1)
|
||||
const shares = ref(route.query.shares || 0)
|
||||
// at which price?
|
||||
const price = ref(route.query.price || 50)
|
||||
// how high is the potential reward?
|
||||
|
@ -56,18 +56,12 @@ await fetch(url)
|
|||
.catch(console.error)
|
||||
// Currently, we only support binary markets.
|
||||
// (only events which can be answered with YES and NO)
|
||||
const yesShareId = computed(() => {
|
||||
return market?.value.Shares.find(s => s.Description === 'YES').Id
|
||||
})
|
||||
const noShareId = computed(() => {
|
||||
return market?.value.Shares.find(s => s.Description === 'NO').Id
|
||||
})
|
||||
const shareId = computed(() => {
|
||||
return selected.value === 'YES' ? yesShareId.value : noShareId.value
|
||||
})
|
||||
const userShares = computed(() => {
|
||||
return selected.value === 'YES' ? market.value.user.YES : market.value.user.NO
|
||||
})
|
||||
const yesShareId = computed(() => market?.value.Shares.find(s => s.Description === 'YES').Id)
|
||||
const noShareId = computed(() => market?.value.Shares.find(s => s.Description === 'NO').Id)
|
||||
const shareId = computed(() => selected.value === 'YES' ? yesShareId.value : noShareId.value)
|
||||
const userShares = computed(() => (selected.value === 'YES' ? market.value.user?.YES : market.value.user?.NO) || 0)
|
||||
|
||||
const disabled = computed(() => userShares.value === 0)
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!session.isAuthenticated) return router.push('/login')
|
||||
|
|
Loading…
Reference in New Issue