diff --git a/go.mod b/go.mod index c53f99f3..9eeace25 100644 --- a/go.mod +++ b/go.mod @@ -33,11 +33,11 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tyler-smith/go-bip39 v1.1.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.29.0 golang.org/x/image v0.21.0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.9.0 google.golang.org/api v0.201.0 gopkg.in/DataDog/dd-trace-go.v1 v1.69.0 ) @@ -123,7 +123,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 // indirect github.com/kevinburke/rest v0.0.0-20240617045629-3ed0ad3487f0 // indirect - github.com/klauspost/compress v1.17.1 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/kyokomi/emoji/v2 v2.2.13 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -181,11 +181,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/go.sum b/go.sum index bcb10daa..62983c70 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,7 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -463,6 +464,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -538,6 +540,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= @@ -574,6 +577,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -588,6 +592,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -621,6 +626,7 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -629,6 +635,7 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -638,6 +645,7 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/routes/dao_coin_exchange_with_fees.go b/routes/dao_coin_exchange_with_fees.go index c17674e5..87bd3577 100644 --- a/routes/dao_coin_exchange_with_fees.go +++ b/routes/dao_coin_exchange_with_fees.go @@ -592,7 +592,7 @@ func GetQuoteBasePkidFromBuyingSellingPkids( } } -type GetBaseCurrencyPriceRequest struct { +type BaseCurrencyPriceRequestEntry struct { BaseCurrencyPublicKeyBase58Check string `safeForLogging:"true"` // Only deso, focus, and usdc supported QuoteCurrencyPublicKeyBase58Check string `safeForLogging:"true"` @@ -601,7 +601,11 @@ type GetBaseCurrencyPriceRequest struct { BaseCurrencyQuantityToSell float64 `safeForLogging:"true"` } -type GetBaseCurrencyPriceResponse struct { +type GetBaseCurrencyPriceRequest struct { + Entries []*BaseCurrencyPriceRequestEntry `safeForLogging:"true"` +} + +type BaseCurrencyPriceResponseEntry struct { // It's useful to include the quote currency price used for USD conversions QuoteCurrencyPriceInUsd float64 `safeForLogging:"true"` @@ -621,49 +625,41 @@ type GetBaseCurrencyPriceResponse struct { ExecutionPriceInUsd float64 `safeForLogging:"true"` } -func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req *http.Request) { - decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) - requestData := GetBaseCurrencyPriceRequest{} - if err := decoder.Decode(&requestData); err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Problem parsing request body: %v", err)) - return - } +type GetBaseCurrencyPriceResponse struct { + Entries []*BaseCurrencyPriceResponseEntry `safeForLogging:"true"` +} - utxoView, err := fes.backendServer.GetMempool().GetAugmentedUniversalView() - if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error fetching mempool view: %v", err)) - return - } +func (fes *APIServer) GetBaseCurrencyPriceForEntry( + utxoView *lib.UtxoView, + entry *BaseCurrencyPriceRequestEntry, +) (*BaseCurrencyPriceResponseEntry, error) { quotePkid, err := fes.getPKIDFromPublicKeyBase58CheckOrDESOString( utxoView, - requestData.QuoteCurrencyPublicKeyBase58Check, + entry.QuoteCurrencyPublicKeyBase58Check, ) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error getting quote pkid: %v", err)) - return + return nil, fmt.Errorf("Error getting quote pkid: %v", err) } basePkid, err := fes.getPKIDFromPublicKeyBase58CheckOrDESOString( utxoView, - requestData.BaseCurrencyPublicKeyBase58Check, + entry.BaseCurrencyPublicKeyBase58Check, ) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error getting base pkid: %v", err)) - return + return nil, fmt.Errorf("Error getting base pkid: %v", err) } + // Super annoying, but it takes two fetches to get all the orders for a market ordersSide1, err := utxoView.GetAllDAOCoinLimitOrdersForThisDAOCoinPair( basePkid, quotePkid) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error getting limit orders: %v", err)) - return + return nil, fmt.Errorf("Error getting limit orders: %v", err) } ordersSide2, err := utxoView.GetAllDAOCoinLimitOrdersForThisDAOCoinPair( quotePkid, basePkid) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error getting limit orders: %v", err)) - return + return nil, fmt.Errorf("Error getting limit orders: %v", err) } allOrders := append(ordersSide1, ordersSide2...) @@ -692,8 +688,7 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * lib.PkToString(order.SellingDAOCoinCreatorPKID[:], fes.Params), order.ScaledExchangeRateCoinsToSellPerCoinToBuy) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice for bid: Error calculating price: %v", err)) - return + return nil, fmt.Errorf("GetBaseCurrencyPrice for bid: Error calculating price: %v", err) } // The quantity is tricky. If we had a bid order then the quantity is just the @@ -706,8 +701,7 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * DAOCoinLimitOrderOperationTypeString(order.OperationType.String()), order.QuantityToFillInBaseUnits) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error calculating quantity: %v", err)) - return + return nil, fmt.Errorf("GetBaseCurrencyPrice: Error calculating quantity: %v", err) } if order.OperationType == lib.DAOCoinLimitOrderOperationTypeASK { // If the order is an ask, then the quantity is the amount of quote currency @@ -731,13 +725,11 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * lib.PkToString(order.SellingDAOCoinCreatorPKID[:], fes.Params), order.ScaledExchangeRateCoinsToSellPerCoinToBuy) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice for ask: Error calculating price: %v", err)) - return + return nil, fmt.Errorf("GetBaseCurrencyPrice for ask: Error calculating price: %v", err) } if priceFloat == 0.0 { // We should never see an order with a zero price so error if we see one. - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Zero price order: %v", order)) - return + return nil, fmt.Errorf("GetBaseCurrencyPrice: Zero price order: %v", order) } priceFloat = 1.0 / priceFloat @@ -751,8 +743,7 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * DAOCoinLimitOrderOperationTypeString(order.OperationType.String()), order.QuantityToFillInBaseUnits) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error calculating quantity: %v", err)) - return + return nil, fmt.Errorf("GetBaseCurrencyPrice: Error calculating quantity: %v", err) } if order.OperationType == lib.DAOCoinLimitOrderOperationTypeBID { // If the order is an bid, then the quantity is the amount of quote currency @@ -792,7 +783,7 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * // Iterate through the bids "filling" orders until we hit the base currency // quantity we're looking for. - baseCurrencyToFill := big.NewFloat(requestData.BaseCurrencyQuantityToSell) + baseCurrencyToFill := big.NewFloat(entry.BaseCurrencyQuantityToSell) for _, bid := range simpleBidOrders { // If the amount filled plus the amount we're about to fill is greater // than the amount we're looking to fill, then we just partially fill @@ -826,15 +817,13 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * // Get the price of the quote currency in usd. Use the mid price quoteCurrencyPriceInUsdStr, _, _, err := fes.GetQuoteCurrencyPriceInUsd( - requestData.QuoteCurrencyPublicKeyBase58Check) + entry.QuoteCurrencyPublicKeyBase58Check) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Problem getting quote currency price in usd: %v", err)) - return + return nil, fmt.Errorf("Problem getting quote currency price in usd: %v", err) } quoteCurrencyPriceInUsd, err := strconv.ParseFloat(quoteCurrencyPriceInUsdStr, 64) if err != nil { - _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Problem parsing quote currency price in usd: %v", err)) - return + return nil, fmt.Errorf("Problem parsing quote currency price in usd: %v", err) } // If any of these are too big for the Float64() it's better to best-effort @@ -846,7 +835,9 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * priceInQuoteCurrencyFloat, _ := priceInQuoteCurrency.Float64() executionPriceInUsdFloat, _ := big.NewFloat(0).Mul( priceInQuoteCurrency, big.NewFloat(quoteCurrencyPriceInUsd)).Float64() - res := &GetBaseCurrencyPriceResponse{ + + // Useful for computing "cashout" values on the wallet page + responseEntry := &BaseCurrencyPriceResponseEntry{ QuoteCurrencyPriceInUsd: quoteCurrencyPriceInUsd, // Traditional price values @@ -864,7 +855,38 @@ func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req * ExecutionPriceInQuoteCurrency: priceInQuoteCurrencyFloat, ExecutionPriceInUsd: executionPriceInUsdFloat, } - if err := json.NewEncoder(ww).Encode(res); err != nil { + + return responseEntry, nil +} + +func (fes *APIServer) GetBaseCurrencyPriceEndpoint(ww http.ResponseWriter, req *http.Request) { + decoder := json.NewDecoder(io.LimitReader(req.Body, MaxRequestBodySizeBytes)) + requestData := GetBaseCurrencyPriceRequest{} + if err := decoder.Decode(&requestData); err != nil { + _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Problem parsing request body: %v", err)) + return + } + + utxoView, err := fes.backendServer.GetMempool().GetAugmentedUniversalView() + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: Error fetching mempool view: %v", err)) + return + } + + response := &GetBaseCurrencyPriceResponse{ + Entries: []*BaseCurrencyPriceResponseEntry{}, + } + + for _, entry := range requestData.Entries { + respEntry, err := fes.GetBaseCurrencyPriceForEntry(utxoView, entry) + if err != nil { + _AddBadRequestError(ww, fmt.Sprintf("GetBaseCurrencyPrice: %v", err)) + return + } + response.Entries = append(response.Entries, respEntry) + } + + if err := json.NewEncoder(ww).Encode(response); err != nil { _AddBadRequestError(ww, fmt.Sprintf("GetQuoteCurrencyPriceInUsd: Problem encoding response: %v", err)) return }