151 lines
3.7 KiB
Go
151 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"gopkg.in/guregu/null.v4"
|
|
)
|
|
|
|
type Market struct {
|
|
Id int
|
|
Description string
|
|
Active bool
|
|
}
|
|
|
|
type Share struct {
|
|
Id string
|
|
MarketId int
|
|
Description string
|
|
}
|
|
|
|
type Order struct {
|
|
Session
|
|
Share
|
|
Market
|
|
Invoice
|
|
Id string
|
|
ShareId string `form:"share_id"`
|
|
Side string `form:"side"`
|
|
Price int `form:"price"`
|
|
Quantity int `form:"quantity"`
|
|
InvoiceId string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
type Invoice struct {
|
|
Session
|
|
Id string
|
|
Msats int
|
|
ReceivedMsats int
|
|
Preimage string
|
|
PaymentRequest string
|
|
PaymentHash string
|
|
CreatedAt time.Time
|
|
ExpiresAt time.Time
|
|
ConfirmedAt null.Time
|
|
HeldSince null.Time
|
|
}
|
|
|
|
func costFunction(b float64, q1 float64, q2 float64) float64 {
|
|
// reference: http://blog.oddhead.com/2006/10/30/implementing-hansons-market-maker/
|
|
return b * math.Log(math.Pow(math.E, q1/b)+math.Pow(math.E, q2/b))
|
|
}
|
|
|
|
// logarithmic market scoring rule (LMSR) market maker from Robin Hanson:
|
|
// https://mason.gmu.edu/~rhanson/mktscore.pdf1
|
|
func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 {
|
|
b := float64(funding)
|
|
fq1 := float64(q1)
|
|
fq2 := float64(q2)
|
|
fdq1 := float64(dq1)
|
|
return costFunction(b, fq1+fdq1, fq2) - costFunction(b, fq1, fq2)
|
|
}
|
|
|
|
func order(c echo.Context) error {
|
|
marketId := c.Param("id")
|
|
// (TBD) Step 0: If SELL order, check share balance of user
|
|
// (TBD) Step 1: respond with HODL invoice
|
|
o := new(Order)
|
|
if err := c.Bind(o); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "bad request")
|
|
}
|
|
session := c.Get("session").(Session)
|
|
o.Pubkey = session.Pubkey
|
|
amount := o.Quantity * o.Price
|
|
invoice, err := lnd.CreateInvoice(session.Pubkey, amount*1000)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.InvoiceId = invoice.Id
|
|
if err := db.CreateOrder(o); err != nil {
|
|
return err
|
|
}
|
|
qr, err := ToQR(invoice.PaymentRequest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go lnd.CheckInvoice(invoice.PaymentHash)
|
|
data := map[string]any{
|
|
"session": c.Get("session"),
|
|
"ENV": ENV,
|
|
"lnurl": invoice.PaymentRequest,
|
|
"qr": qr,
|
|
"Invoice": invoice,
|
|
"RedirectURL": fmt.Sprintf("https://%s/market/%s", PUBLIC_URL, marketId),
|
|
}
|
|
return c.Render(http.StatusPaymentRequired, "invoice.html", data)
|
|
// Step 2: After payment, confirm order if no matching order was found
|
|
// if err := db.CreateOrder(o); err != nil {
|
|
// if strings.Contains(err.Error(), "violates check constraint") {
|
|
// return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
|
// }
|
|
// return err
|
|
// }
|
|
// (TBD) Step 3:
|
|
// Settle hodl invoice when matching order was found
|
|
// else cancel hodl invoice if expired
|
|
// ...
|
|
// return market(c)
|
|
}
|
|
|
|
func market(c echo.Context) error {
|
|
marketId, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Bad Request")
|
|
}
|
|
|
|
var market Market
|
|
if err = db.FetchMarket(int(marketId), &market); err == sql.ErrNoRows {
|
|
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
var shares []Share
|
|
if err = db.FetchShares(market.Id, &shares); err != nil {
|
|
return err
|
|
}
|
|
var orders []Order
|
|
if err = db.FetchOrders(&FetchOrdersWhere{MarketId: market.Id, Confirmed: true}, &orders); err != nil {
|
|
return err
|
|
}
|
|
data := map[string]any{
|
|
"session": c.Get("session"),
|
|
"ENV": ENV,
|
|
"Id": market.Id,
|
|
"Description": market.Description,
|
|
// shares are sorted by description in descending order
|
|
// that's how we know that YES must be the first share
|
|
"YesShare": shares[0],
|
|
"NoShare": shares[1],
|
|
"Orders": orders,
|
|
}
|
|
return c.Render(http.StatusOK, "market.html", data)
|
|
}
|