Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix exchange rate bugs #148

Merged
merged 3 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions libwallet/ext/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@ func (s *Service) GetXpub(xPub string) (xPubBalAndTxs *XpubBalAndTxs, err error)
func (s *Service) GetTicker(exchange string, market string) (ticker *Ticker, err error) {
switch exchange {
case Binance:
// Return early for dcr-ltc markets ad binance does not support this
ukane-philemon marked this conversation as resolved.
Show resolved Hide resolved
// atm.
if strings.EqualFold(market, "ltc-dcr") {
return &Ticker{}, nil
}

symbArr := strings.Split(market, "-")
if len(symbArr) != 2 {
return ticker, errors.New("invalid symbol format")
Expand Down
34 changes: 27 additions & 7 deletions libwallet/instantswap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package libwallet
import (
"context"
"math"
"strings"
"time"

"decred.org/dcrwallet/v3/errors"
Expand Down Expand Up @@ -87,9 +88,11 @@ func (mgr *AssetsManager) StartScheduler(ctx context.Context, params instantswap
return errors.E(op, "source wallet balance is less than or equals the set balance to maintain") // stop scheduling if the source wallet balance is less than or equals the set balance to maintain
}

fromCur := params.Order.FromCurrency
toCur := params.Order.ToCurrency
rateRequestParams := api.ExchangeRateRequest{
From: params.Order.FromCurrency,
To: params.Order.ToCurrency,
From: fromCur,
To: toCur,
Amount: DefaultRateRequestAmount, // amount needs to be greater than 0 to get the exchange rate
}
log.Info("Order Scheduler: getting exchange rate info")
Expand All @@ -103,17 +106,22 @@ func (mgr *AssetsManager) StartScheduler(ctx context.Context, params instantswap
params.MaxDeviationRate = DefaultMarketDeviation // default 5%
}

market := params.Order.FromCurrency + "-" + params.Order.ToCurrency
ticker, err := mgr.ExternalService.GetTicker(ext.Binance, market)
ticker, err := mgr.ExternalService.GetTicker(ext.Binance, MarketName(fromCur, toCur))
if err != nil {
log.Error("unable to get market rate from binance")
return errors.E(op, err)
}

exchangeServerRate := 1 / res.ExchangeRate
exchangeServerRate := res.EstimatedAmount // estimated receivable value for libwallet.DefaultRateRequestAmount (1)
binanceRate := ticker.LastTradePrice
log.Info(params.Order.ExchangeServer.Server+" rate: ", exchangeServerRate)
log.Info(ext.Binance+" rate: ", binanceRate)
/// Binance always returns ticker.LastTradePrice in's the quote asset
// unit e.g DCR-BTC, LTC-BTC. We will also do this when and if USDT is supported.
if strings.EqualFold(fromCur, "btc") {
binanceRate = 1 / ticker.LastTradePrice
}

log.Info(params.Order.ExchangeServer.Server+" rate: 1 %s ~= %f %s", fromCur, exchangeServerRate, toCur)
log.Info(ext.Binance+" rate: 1 %s ~= %f %s", fromCur, binanceRate, toCur)
ukane-philemon marked this conversation as resolved.
Show resolved Hide resolved

// check if the server rate deviates from the market rate by ± 5%
// exit if true
Expand Down Expand Up @@ -300,3 +308,15 @@ func (mgr *AssetsManager) IsOrderSchedulerRunning() bool {
func (mgr *AssetsManager) GetShedulerRuntime() string {
return time.Since(mgr.InstantSwap.SchedulerStartTime).Round(time.Second).String()
}

// MarketName is a convenience function that returns a proper market name for
// the from and to currencies used when fetching ticker data from binance.
func MarketName(fromCur, toCur string) string {
dcrToLtc := strings.EqualFold(fromCur, "dcr") && strings.EqualFold(toCur, "ltc")
btcToOthersExceptUsdt := strings.EqualFold(fromCur, "btc") && !strings.EqualFold(toCur, "usdt")
if dcrToLtc || btcToOthersExceptUsdt {
return toCur + "-" + fromCur // e.g DCR/BTC, LTC/BTC and not BTC/LTC or BTC/DCR
}

return fromCur + "-" + toCur
}
81 changes: 47 additions & 34 deletions ui/page/exchange/create_order_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"strings"

"gioui.org/font"
"gioui.org/layout"
Expand Down Expand Up @@ -296,7 +297,7 @@ func (pg *CreateOrderPage) HandleUserInteractions() {
switch evt.(type) {
case widget.ChangeEvent:
if pg.inputsNotEmpty(pg.fromAmountEditor.Edit.Editor) {
f, err := strconv.ParseFloat(pg.fromAmountEditor.Edit.Editor.Text(), 32)
fromAmt, err := strconv.ParseFloat(pg.fromAmountEditor.Edit.Editor.Text(), 32)
if err != nil {
// empty usd input
pg.toAmountEditor.Edit.Editor.SetText("")
Expand All @@ -307,7 +308,7 @@ func (pg *CreateOrderPage) HandleUserInteractions() {
}
pg.amountErrorText = ""
if pg.exchangeRate != -1 {
value := f / pg.exchangeRate
value := fromAmt * pg.exchangeRate
v := strconv.FormatFloat(value, 'f', 8, 64)
pg.amountErrorText = ""
pg.fromAmountEditor.Edit.LineColor = pg.Theme.Color.Gray2
Expand Down Expand Up @@ -396,7 +397,7 @@ func (pg *CreateOrderPage) HandleUserInteractions() {

func (pg *CreateOrderPage) updateAmount() {
if pg.inputsNotEmpty(pg.fromAmountEditor.Edit.Editor) {
f, err := strconv.ParseFloat(pg.fromAmountEditor.Edit.Editor.Text(), 32)
fromAmt, err := strconv.ParseFloat(pg.fromAmountEditor.Edit.Editor.Text(), 32)
if err != nil {
pg.toAmountEditor.Edit.Editor.SetText("")
pg.amountErrorText = values.String(values.StrInvalidAmount)
Expand All @@ -406,7 +407,7 @@ func (pg *CreateOrderPage) updateAmount() {
}
pg.amountErrorText = ""
if pg.exchangeRate != -1 {
value := f / pg.exchangeRate
value := fromAmt * pg.exchangeRate
v := strconv.FormatFloat(value, 'f', 8, 64)
pg.amountErrorText = ""
pg.fromAmountEditor.Edit.LineColor = pg.Theme.Color.Gray2
Expand Down Expand Up @@ -559,9 +560,9 @@ func (pg *CreateOrderPage) isExchangeAPIAllowed() bool {
return isAllowed
}

// isMultipleAssetTypeWalletAvailable checks if multiple asset types are available
// for exchange funtionality to run smoothly. Otherwise exchange functionality is
// disable till different asset type wallets are created.
// isMultipleAssetTypeWalletAvailable checks if multiple asset types are
// available for exchange functionality to run smoothly. Otherwise exchange
// functionality is disable till different asset type wallets are created.
func (pg *CreateOrderPage) isMultipleAssetTypeWalletAvailable() bool {
pg.errMsg = values.String(values.StrMinimumAssetType)
allWallets := len(pg.WL.AssetsManager.AllWallets())
Expand Down Expand Up @@ -625,7 +626,7 @@ func (pg *CreateOrderPage) Layout(gtx C) D {
overlay = layout.Stacked(func(gtx C) D {
return components.DisablePageWithOverlay(pg.Load, nil, gtxCopy, msg, navBtn)
})
// Disable main page from recieving events
// Disable main page from receiving events.
gtx = gtx.Disabled()
}
return layout.Stack{}.Layout(gtx, layout.Expanded(pg.layout), overlay)
Expand All @@ -638,9 +639,10 @@ func (pg *CreateOrderPage) Layout(gtx C) D {
Direction: layout.Center,
}.Layout2(gtx, func(gtx C) D {
return cryptomaterial.LinearLayout{
Width: gtx.Dp(values.MarginPadding550),
Width: gtx.Dp(values.MarginPadding600),
Height: cryptomaterial.MatchParent,
Alignment: layout.Middle,
Padding: layout.Inset{Top: values.MarginPadding20},
}.Layout2(gtx, func(gtx C) D {
return sp.Layout(pg.ParentWindow(), gtx)
})
Expand Down Expand Up @@ -848,17 +850,25 @@ func (pg *CreateOrderPage) layout(gtx C) D {
return pg.materialLoader.Layout(gtx)
}

if pg.exchangeRate != -1 {
fromCur := strings.ToUpper(pg.fromCurrency.String())
toCur := strings.ToUpper(pg.toCurrency.String())
missingAsset := fromCur == "" || toCur == ""
if pg.exchangeRate > 0 && !missingAsset {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
exchangeRate := values.StringF(values.StrServerRate, pg.exchangeSelector.SelectedExchange().Name, pg.exchangeRate)
serverName := pg.exchangeSelector.SelectedExchange().Name
exchangeRate := values.StringF(values.StrServerRate, serverName, fromCur, pg.exchangeRate, toCur)
txt := pg.Theme.Label(values.TextSize14, exchangeRate)
txt.Font.Weight = font.SemiBold
txt.Color = pg.Theme.Color.Gray1
return txt.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
binanceRate := values.StringF(values.StrBinanceRate, pg.binanceRate)
if pg.binanceRate == -1 {
return D{}
}

binanceRate := values.StringF(values.StrBinanceRate, fromCur, pg.binanceRate, toCur)
txt := pg.Theme.Label(values.TextSize14, binanceRate)
txt.Font.Weight = font.SemiBold
txt.Color = pg.Theme.Color.Gray1
Expand Down Expand Up @@ -1050,10 +1060,14 @@ func (pg *CreateOrderPage) updateExchangeRate() {
}

func (pg *CreateOrderPage) getExchangeRateInfo() error {
pg.exchangeRate = -1
pg.binanceRate = -1
pg.fetchingRate = true
fromCur := pg.fromCurrency.String()
toCur := pg.toCurrency.String()
params := api.ExchangeRateRequest{
From: pg.fromCurrency.String(),
To: pg.toCurrency.String(),
From: fromCur,
To: toCur,
Amount: libwallet.DefaultRateRequestAmount, // amount needs to be greater than 0 to get the exchange rate
}
res, err := pg.WL.AssetsManager.InstantSwap.GetExchangeRateInfo(pg.exchange, params)
Expand All @@ -1064,24 +1078,22 @@ func (pg *CreateOrderPage) getExchangeRateInfo() error {
return err
}

var binanceRate float64
ticker, err := pg.WL.AssetsManager.ExternalService.GetTicker(ext.Binance, values.String(values.StrDcrBtcPair))
ticker, err := pg.WL.AssetsManager.ExternalService.GetTicker(ext.Binance, libwallet.MarketName(fromCur, toCur))
if err != nil {
log.Error(err)
}
if ticker != nil {
switch pg.fromCurrency {
case libutils.DCRWalletAsset:
binanceRate = 1 / ticker.LastTradePrice
case libutils.BTCWalletAsset:
binanceRate = ticker.LastTradePrice
if ticker != nil && ticker.LastTradePrice > 0 {
pg.binanceRate = ticker.LastTradePrice
/// Binance always returns ticker.LastTradePrice in's the quote asset
// unit e.g DCR-BTC, LTC-BTC. We will also do this when and if USDT is supported.
if pg.fromCurrency == libutils.BTCWalletAsset {
pg.binanceRate = 1 / ticker.LastTradePrice
}
}

pg.exchangeRate = res.EstimatedAmount // estimated receivable value for libwallet.DefaultRateRequestAmount (1)
pg.min = res.Min
pg.max = res.Max
pg.exchangeRate = res.ExchangeRate
pg.binanceRate = binanceRate

pg.exchangeRateInfo = fmt.Sprintf(values.String(values.StrMinMax), pg.min, pg.max)
pg.updateAmount()
Expand All @@ -1092,8 +1104,8 @@ func (pg *CreateOrderPage) getExchangeRateInfo() error {
return nil
}

// loadOrderConfig loads the existing exchange configuration or creates a
// new one if none existed before.
// loadOrderConfig loads the existing exchange configuration or creates a new
// one if none existed before.
func (pg *CreateOrderPage) loadOrderConfig() {
sourceAccount, destinationAccount := int32(-1), int32(-1)
var sourceWallet, destinationWallet *load.WalletMapping
Expand All @@ -1118,13 +1130,13 @@ func (pg *CreateOrderPage) loadOrderConfig() {
sourceAccount = exchangeConfig.SourceAccountNumber
destinationAccount = exchangeConfig.DestinationAccountNumber
} else {
// New exchange configuration will be generated using the set asset types
// since none existed before.
// It two distinct asset type wallet don't exist execution does get here.
wals := pg.WL.AssetsManager.AllWallets()
pg.fromCurrency = wals[0].GetAssetType()
// New exchange configuration will be generated using the set asset
// types since none existed before. It two distinct asset type wallet
// don't exist execution does get here.
wallets := pg.WL.AssetsManager.AllWallets()
pg.fromCurrency = wallets[0].GetAssetType()

for _, w := range wals {
for _, w := range wallets {
if w.GetAssetType() != pg.fromCurrency {
pg.toCurrency = w.GetAssetType()
break
Expand Down Expand Up @@ -1212,7 +1224,8 @@ func (pg *CreateOrderPage) loadOrderConfig() {
pg.toAmountEditor.AssetTypeSelector.SetSelectedAssetType(pg.toCurrency)
}

// updateExchangeConfig Updates the newly created or modified exchange configuration.
// updateExchangeConfig Updates the newly created or modified exchange
// configuration.
func (pg *CreateOrderPage) updateExchangeConfig() {
configInfo := sharedW.ExchangeConfig{
SourceAsset: pg.fromCurrency,
Expand All @@ -1233,7 +1246,7 @@ func (pg *CreateOrderPage) listenForSyncNotifications() {
pg.OrderNotificationListener = listeners.NewOrderNotificationListener()
err := pg.WL.AssetsManager.InstantSwap.AddNotificationListener(pg.OrderNotificationListener, CreateOrderPageID)
if err != nil {
log.Errorf("Error adding instanswap notification listener: %v", err)
log.Errorf("Error adding instantswap notification listener: %v", err)
return
}

Expand Down
39 changes: 24 additions & 15 deletions ui/page/exchange/order_scheduler_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func newOrderSchedulerModalModal(l *load.Load, data *orderData) *orderSchedulerM
frequencySelector: NewFrequencySelector(l),
orderData: data,
copyRedirect: l.Theme.NewClickable(false),
binanceRate: -1,
exchangeRate: -1,
}

osm.cancelBtn = l.Theme.OutlineButton(values.String(values.StrCancel))
Expand Down Expand Up @@ -270,17 +272,21 @@ func (osm *orderSchedulerModal) Layout(gtx layout.Context) D {
return osm.materialLoader.Layout(gtx)
}

if osm.exchangeSelector.SelectedExchange() != nil && osm.exchangeRate != -1 {
fromCur := osm.fromCurrency.String()
toCur := osm.toCurrency.String()
missingAsset := fromCur == "" || toCur == ""
if osm.exchangeSelector.SelectedExchange() != nil && osm.exchangeRate != -1 && !missingAsset {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
exchangeRate := values.StringF(values.StrServerRate, osm.exchangeSelector.SelectedExchange().Name, osm.exchangeRate)
exName := osm.exchangeSelector.SelectedExchange().Name
exchangeRate := values.StringF(values.StrServerRate, exName, fromCur, osm.exchangeRate, toCur)
txt := osm.Theme.Label(values.TextSize14, exchangeRate)
txt.Font.Weight = font.SemiBold
txt.Color = osm.Theme.Color.Gray1
return txt.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
binanceRate := values.StringF(values.StrBinanceRate, osm.binanceRate)
binanceRate := values.StringF(values.StrBinanceRate, fromCur, osm.binanceRate, toCur)
txt := osm.Theme.Label(values.TextSize14, binanceRate)
txt.Font.Weight = font.SemiBold
txt.Color = osm.Theme.Color.Gray1
Expand Down Expand Up @@ -473,11 +479,15 @@ func (osm *orderSchedulerModal) startOrderScheduler() {
}

func (osm *orderSchedulerModal) getExchangeRateInfo() error {
osm.binanceRate = -1
osm.exchangeRate = -1
osm.fetchingRate = true
osm.rateError = false
fromCur := osm.fromCurrency.String()
toCur := osm.toCurrency.String()
params := api.ExchangeRateRequest{
From: osm.fromCurrency.String(),
To: osm.toCurrency.String(),
From: fromCur,
To: toCur,
Amount: libwallet.DefaultRateRequestAmount, // amount needs to be greater than 0 to get the exchange rate
}
res, err := osm.WL.AssetsManager.InstantSwap.GetExchangeRateInfo(osm.exchange, params)
Expand All @@ -487,24 +497,23 @@ func (osm *orderSchedulerModal) getExchangeRateInfo() error {
return err
}

ticker, err := osm.WL.AssetsManager.ExternalService.GetTicker(ext.Binance, values.String(values.StrDcrBtcPair))
ticker, err := osm.WL.AssetsManager.ExternalService.GetTicker(ext.Binance, libwallet.MarketName(fromCur, toCur))
if err != nil {
osm.rateError = true
osm.fetchingRate = false
return err
}

var binanceRate float64
switch osm.fromCurrency {
case utils.DCRWalletAsset:
binanceRate = ticker.LastTradePrice
case utils.BTCWalletAsset:
binanceRate = 1 / ticker.LastTradePrice
if ticker != nil && ticker.LastTradePrice > 0 {
osm.binanceRate = ticker.LastTradePrice
/// Binance always returns ticker.LastTradePrice in's the quote asset
// unit e.g DCR-BTC, LTC-BTC. We will also do this when and if USDT is supported.
if osm.fromCurrency == utils.BTCWalletAsset {
osm.binanceRate = 1 / ticker.LastTradePrice
}
}

osm.exchangeRate = 1 / res.ExchangeRate
osm.binanceRate = binanceRate

osm.exchangeRate = res.EstimatedAmount // estimated receivable value for libwallet.DefaultRateRequestAmount (1)
osm.fetchingRate = false
osm.rateError = false

Expand Down
3 changes: 2 additions & 1 deletion ui/values/dimensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ var (
MarginPadding377 = unit.Dp(377)
MarginPadding390 = unit.Dp(390)
MarginPadding450 = unit.Dp(450)
MarginPadding550 = unit.Dp(550)
MarginPadding500 = unit.Dp(500)
MarginPadding550 = unit.Dp(550)
MarginPadding600 = unit.Dp(600)

TextSize10 = unit.Sp(10)
TextSize12 = unit.Sp(12)
Expand Down
Loading
Loading