From 2d94c2f4a1a6e08a919c2295f6267fbe7c76360e Mon Sep 17 00:00:00 2001 From: ukane-philemon Date: Sun, 26 Jun 2022 01:14:09 +0100 Subject: [PATCH] changes based on bucks review --- client/core/core.go | 215 +++++++++--------- client/core/core_test.go | 82 +++---- client/core/exchangeratefetcher.go | 60 +++-- client/db/bolt/db.go | 4 +- client/db/interface.go | 4 +- client/webserver/api.go | 4 +- client/webserver/http.go | 14 +- client/webserver/live_test.go | 23 +- client/webserver/locales/en-us.go | 2 + client/webserver/site/src/html/markets.tmpl | 13 +- client/webserver/site/src/html/settings.tmpl | 6 +- client/webserver/site/src/html/wallets.tmpl | 9 +- client/webserver/site/src/js/app.ts | 37 +-- client/webserver/site/src/js/doc.ts | 4 +- client/webserver/site/src/js/markets.ts | 42 ++-- client/webserver/site/src/js/settings.ts | 8 +- client/webserver/site/src/js/wallets.ts | 55 ++--- .../src/localized_html/en-US/markets.tmpl | 13 +- .../src/localized_html/en-US/settings.tmpl | 6 +- .../src/localized_html/en-US/wallets.tmpl | 9 +- .../src/localized_html/pl-PL/markets.tmpl | 13 +- .../src/localized_html/pl-PL/settings.tmpl | 6 +- .../src/localized_html/pl-PL/wallets.tmpl | 9 +- .../src/localized_html/pt-BR/markets.tmpl | 13 +- .../src/localized_html/pt-BR/settings.tmpl | 6 +- .../src/localized_html/pt-BR/wallets.tmpl | 9 +- .../src/localized_html/zh-CN/markets.tmpl | 13 +- .../src/localized_html/zh-CN/settings.tmpl | 6 +- .../src/localized_html/zh-CN/wallets.tmpl | 9 +- client/webserver/webserver.go | 4 +- client/webserver/webserver_test.go | 20 +- 31 files changed, 348 insertions(+), 370 deletions(-) diff --git a/client/core/core.go b/client/core/core.go index 42395c7285..76a225248c 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -1134,8 +1134,11 @@ type Core struct { sentCommitsMtx sync.Mutex sentCommits map[order.Commitment]chan struct{} - ratesMtx sync.Mutex - exchangeRateSources map[string]*commonSource + ratesMtx sync.Mutex + fiatRateSources map[string]*commonRateSource + // stopFiatRateFetching will be used to shutdown fetchFiatExchangeRates + // goroutine when all rate sources have been disabled. + stopFiatRateFetching context.CancelFunc } // New is the constructor for a new Core. @@ -1229,7 +1232,7 @@ func New(cfg *Config) (*Core, error) { localePrinter: message.NewPrinter(lang), seedGenerationTime: seedGenerationTime, - exchangeRateSources: make(map[string]*commonSource), + fiatRateSources: make(map[string]*commonRateSource), } // Populate the initial user data. User won't include any DEX info yet, as @@ -1262,14 +1265,14 @@ func (c *Core) Run(ctx context.Context) { c.latencyQ.Run(ctx) }() - // Retrieve disabled exchange rate sources from database. + // Retrieve disabled fiat rate sources from database. disabledSources, err := c.db.DisabledRateSources() if err != nil { - c.log.Errorf("Unable to retrieve disabled exchange rate source: %v", err) + c.log.Errorf("Unable to retrieve disabled fiat rate source: %v", err) } - // Construct an exchange rate source. - for token, rateFetcher := range exchangeRateFetchers { + // Construct enabled fiat rate sources. + for token, rateFetcher := range fiatRateFetchers { var isDisabled bool for _, v := range disabledSources { if token == v { @@ -1278,16 +1281,16 @@ func (c *Core) Run(ctx context.Context) { } } if !isDisabled { - c.exchangeRateSources[token] = newcommonSource(c.log, rateFetcher) + c.fiatRateSources[token] = newCommonRateSource(c.log, rateFetcher) } } - // Start goroutine for exchange rate fetcher's if we have at least one - // exchange rate source enabled. - if len(c.exchangeRateSources) > 0 { - c.fetchExchangeRates() + // Start goroutine for fiat rate fetcher's if we have at least one + // source. + if len(c.fiatRateSources) != 0 { + c.fetchFiatExchangeRates() } else { - c.log.Debugf("no exchange rate source was initialized") + c.log.Debug("no fiat rate source initialized") } c.wg.Wait() // block here until all goroutines except DB complete @@ -7482,40 +7485,65 @@ func (c *Core) findActiveOrder(oid order.OrderID) (*trackedTrade, error) { return nil, fmt.Errorf("could not find active order with order id: %s", oid) } -// fetchExchangeRates starts the exchange rate source goroutine and schedules +// fetchFiatExchangeRates starts the fiat rate fetcher goroutine and schedules // refresh cycles. -func (core *Core) fetchExchangeRates() { +func (core *Core) fetchFiatExchangeRates() { + ctx, cancel := context.WithCancel(core.ctx) + core.stopFiatRateFetching = cancel + core.wg.Add(1) go func() { defer core.wg.Done() tick := time.NewTimer(0) // starts rate fetching immediately. for { select { - case <-tick.C: - if core.isConversionDisabled() { - tick.Stop() - return - } - core.refreshExchangeRate() - case <-core.ctx.Done(): + case <-ctx.Done(): tick.Stop() return + case <-tick.C: + core.refreshFiatRates() } tick = core.nextRateSourceTick() } }() } -// nextRateSourceTick checks the rate source' last request, and -// calculates when the next cycle should run. +// refreshFiatRates refreshes the fiat rates for rate sources whose values have +// not been updated since fiatRateRequestInterval. +func (core *Core) refreshFiatRates() { + ctx, cancel := context.WithTimeout(core.ctx, 4*time.Second) + defer cancel() + + var wg sync.WaitGroup + core.ratesMtx.Lock() + for _, source := range core.fiatRateSources { + if time.Since(source.lastTry()) > fiatRateRequestInterval { + wg.Add(1) + go func(source *commonRateSource) { + defer wg.Done() + source.refreshRates(ctx, core.assetMap()) + }(source) + } + } + core.ratesMtx.Unlock() + wg.Wait() + + fiatRatesMap := core.fetchAllFiatRates() + if len(fiatRatesMap) != 0 { + core.notify(newFiatRatesUpdate(fiatRatesMap)) + } +} + +// nextRateSourceTick checks the fiat rate source's last request, and calculates +// when the next cycle should run. func (core *Core) nextRateSourceTick() *time.Timer { core.ratesMtx.Lock() defer core.ratesMtx.Unlock() minTick := 10 * time.Second tOldest := time.Now() - for _, rateSource := range core.exchangeRateSources { - t := rateSource.lastTry() + for _, source := range core.fiatRateSources { + t := source.lastTry() if t.Before(tOldest) { tOldest = t } @@ -7527,38 +7555,36 @@ func (core *Core) nextRateSourceTick() *time.Timer { return time.NewTimer(tilNext) } -// ExchangeRateSources returns a list of exchange rate sources and their -// individual status. -func (core *Core) ExchangeRateSources() map[string]bool { +// FiatRateSources returns a list of fiat rate sources and their individual +// status. +func (core *Core) FiatRateSources() map[string]bool { core.ratesMtx.Lock() defer core.ratesMtx.Unlock() - - rateSources := make(map[string]bool, len(exchangeRateFetchers)) - for token := range exchangeRateFetchers { - rateSources[token] = core.exchangeRateSources[token] != nil + rateSources := make(map[string]bool, len(fiatRateFetchers)) + for token := range fiatRateFetchers { + rateSources[token] = core.fiatRateSources[token] != nil } return rateSources } -// fetchAllFiatRates returns exchange rate information for all supported assets -// that have a wallet. It also checks if exchange rates are expired and does -// some clean-up. +// fetchAllFiatRates returns fiat rate for all supported assets that have a +// wallet. It also checks if fiat rates are expired and does some clean-up. func (core *Core) fetchAllFiatRates() map[uint32]float64 { - core.ratesMtx.Lock() - defer core.ratesMtx.Unlock() supportedAssets := core.SupportedAssets() + + core.ratesMtx.Lock() fiatRatesMap := make(map[uint32]float64, len(supportedAssets)) for assetID := range supportedAssets { var rateSum float64 var sources int32 - for token, source := range core.exchangeRateSources { - // Remove exchange rate source with expired exchange rate data. + for token, source := range core.fiatRateSources { + // Remove fiat rate source with expired exchange rate data. if source.isExpired(fiatRateDataExpiry) { - delete(core.exchangeRateSources, token) + delete(core.fiatRateSources, token) continue } - rateInfo, found := source.assetRate(assetID) - if found && time.Since(rateInfo.lastUpdate) < fiatRateDataExpiry { + rateInfo := source.assetRate(assetID) + if rateInfo != nil && time.Since(rateInfo.lastUpdate) < fiatRateDataExpiry { sources++ rateSum += rateInfo.rate } @@ -7568,25 +7594,25 @@ func (core *Core) fetchAllFiatRates() map[uint32]float64 { } } - // Save disabled exchange rate source to database. + // Ensure disabled fiat rate fetchers are saved to database. core.saveDisabledRateSources() + core.ratesMtx.Unlock() return fiatRatesMap } -// isConversionDisabled checks if fiat rate fetch for assets is disabled. -// This is when either no exchange rate source is enabled or rates are -// not up-to-date. Individual exchange rate source can be disabled without -// disabling rate fetching, as long as there is at least one exchange -// rate source. +// isConversionDisabled checks if fiat rate fetch for assets is disabled. This +// is when either no fiat rate source is enabled or rates are not up-to-date. +// Individual fiat rate source can be disabled without disabling rate fetching, +// as long as there is at least one exchange rate source. func (core *Core) isConversionDisabled() bool { core.ratesMtx.Lock() defer core.ratesMtx.Unlock() - return len(core.exchangeRateSources) == 0 + return len(core.fiatRateSources) == 0 } -// ToggleRateSourceStatus toggles an exchange rate source status. If -// disable is true, the exchange rate source is disabled, otherwise the rate +// ToggleRateSourceStatus toggles a fiat rate source status. If +// disable is true, the fiat rate source is disabled, otherwise the rate // source is enabled. func (core *Core) ToggleRateSourceStatus(source string, disable bool) error { if disable { @@ -7595,101 +7621,82 @@ func (core *Core) ToggleRateSourceStatus(source string, disable bool) error { return core.enableRateSource(source) } -// enableRateSource enables an exchange rate source. +// enableRateSource enables a fiat rate source. func (core *Core) enableRateSource(source string) error { core.ratesMtx.Lock() defer core.ratesMtx.Unlock() // Check if it's an invalid rate source or it is already enabled. - rateFetcher, found := exchangeRateFetchers[source] + rateFetcher, found := fiatRateFetchers[source] if !found { - return errors.New("cannot enable unknown exchange rate source") - } else if core.exchangeRateSources[source] != nil { + return errors.New("cannot enable unknown fiat rate source") + } else if core.fiatRateSources[source] != nil { return nil // already enabled. } - // Build exchange rate source. - core.exchangeRateSources[source] = newcommonSource(core.log, rateFetcher) + // Build fiat rate source. + core.fiatRateSources[source] = newCommonRateSource(core.log, rateFetcher) - // If this is our first exchange rate source, start exchange rate fetcher + // If this is our first fiat rate source, start fiat rate fetcher // gorountine, else fetch rates. - if len(core.exchangeRateSources) == 1 { - core.fetchExchangeRates() + if len(core.fiatRateSources) == 1 { + core.fetchFiatExchangeRates() } else { go func() { ctx, cancel := context.WithTimeout(core.ctx, 4*time.Second) defer cancel() - core.exchangeRateSources[source].refreshRates(ctx, core.assetMap()) + core.fiatRateSources[source].refreshRates(ctx, core.assetMap()) }() } - // Update disabled exchange rate source. + // Update disabled fiat rate source. core.saveDisabledRateSources() - core.log.Infof("Enabled %s to fetch exchange rates.", source) + core.log.Infof("Enabled %s to fetch fiat rates.", source) return nil } -// disableRateSource disables an exchange rate source. +// disableRateSource disables an fiat rate source. func (core *Core) disableRateSource(source string) error { core.ratesMtx.Lock() defer core.ratesMtx.Unlock() - // Check if it's an invalid exchange rate source or it is already disabled. - _, found := exchangeRateFetchers[source] + // Check if it's an invalid fiat rate source or it is already + // disabled. + _, found := fiatRateFetchers[source] if !found { - return errors.New("cannot disable unknown exchange rate source") - } else if core.exchangeRateSources[source] == nil { + return errors.New("cannot disable unknown fiat rate source") + } else if core.fiatRateSources[source] == nil { return nil // already disabled. } - // Remove exchange rate source. - delete(core.exchangeRateSources, source) + // Remove fiat rate source. + delete(core.fiatRateSources, source) - // Save disabled exchange rate sources to database. + // Shutdown rate fetching if there are no exchange rate source. + if len(core.fiatRateSources) == 0 { + core.stopFiatRateFetching() + } + + // Save disabled fiat rate sources to database. core.saveDisabledRateSources() - core.log.Infof("Disabled %s from fetching exchange rates.", source) + core.log.Infof("Disabled %s from fetching fiat rates.", source) return nil } -// saveDisabledRateSources save disabled exchange rate sources to database. -// use under ratesMtx lock. +// saveDisabledRateSources save disabled fiat rate sources to database. +// Use under ratesMtx lock. func (core *Core) saveDisabledRateSources() { var disabled []string - for token := range exchangeRateFetchers { - if core.exchangeRateSources[token] == nil { + for token := range fiatRateFetchers { + if core.fiatRateSources[token] == nil { disabled = append(disabled, token) } } + err := core.db.SaveDisabledRateSources(disabled) if err != nil { - core.log.Errorf("Unable to save disabled exchange rate source to database: %v", err) - } -} - -// refreshExchangeRate refreshes the exchange rates for exchange rate sources -// whose values have not been updated since fiatRateRequestInterval. -func (core *Core) refreshExchangeRate() { - ctx, cancel := context.WithTimeout(core.ctx, 4*time.Second) - defer cancel() - - var wg sync.WaitGroup - core.ratesMtx.Lock() - for _, source := range core.exchangeRateSources { - if time.Since(source.lastTry()) > fiatRateRequestInterval { - wg.Add(1) - go func(source *commonSource) { - defer wg.Done() - source.refreshRates(ctx, core.assetMap()) - }(source) - } - } - core.ratesMtx.Unlock() - wg.Wait() - - fiatRatesMap := core.fetchAllFiatRates() - if len(fiatRatesMap) != 0 { - core.notify(newFiatRatesUpdate(fiatRatesMap)) + core.log.Errorf("Unable to save disabled fiat rate source to database: %v", err) } } diff --git a/client/core/core_test.go b/client/core/core_test.go index a6bc958341..7a76367014 100644 --- a/client/core/core_test.go +++ b/client/core/core_test.go @@ -1157,6 +1157,8 @@ func newTestRig() *testRig { locale: enUS, localePrinter: message.NewPrinter(language.AmericanEnglish), + + fiatRateSources: make(map[string]*commonRateSource), }, db: tdb, queue: queue, @@ -9076,7 +9078,6 @@ func TestToggleRateSourceStatus(t *testing.T) { rig := newTestRig() defer rig.shutdown() tCore := rig.core - tCore.exchangeRateSources = make(map[string]*commonSource) tests := []struct { name, source string @@ -9095,34 +9096,22 @@ func TestToggleRateSourceStatus(t *testing.T) { wantErr: false, }} - // Test disabling exchange rate source. + // Test disabling fiat rate source. for _, test := range tests { err := tCore.ToggleRateSourceStatus(test.source, true) - if test.wantErr { - if err == nil { - t.Fatalf("%s: expected error", test.name) - } - continue - } - if err != nil { - t.Fatalf("%s: unexpected error: %v", test.name, err) + if test.wantErr != (err != nil) { + t.Fatalf("%s: wantErr = %t, err = %v", test.name, test.wantErr, err) } } - // Test enabling exchange rate source. + // Test enabling fiat rate source. for _, test := range tests { if test.init { - tCore.exchangeRateSources[test.source] = newcommonSource(tCore.log, tFetcher) + tCore.fiatRateSources[test.source] = newCommonRateSource(tCore.log, tFetcher) } err := tCore.ToggleRateSourceStatus(test.source, false) - if test.wantErr { - if err == nil { - t.Fatalf("%s: expected error", test.name) - } - continue - } - if err != nil { - t.Fatalf("%s: unexpected error: %v", test.name, err) + if test.wantErr != (err != nil) { + t.Fatalf("%s: wantErr = %t, err = %v", test.name, test.wantErr, err) } } } @@ -9131,7 +9120,6 @@ func TestIsConversionDisabled(t *testing.T) { rig := newTestRig() defer rig.shutdown() tCore := rig.core - tCore.exchangeRateSources = make(map[string]*commonSource) tests := []struct { name string @@ -9146,36 +9134,29 @@ func TestIsConversionDisabled(t *testing.T) { for _, test := range tests { if test.init { - tCore.exchangeRateSources["testsource"] = newcommonSource(tCore.log, tFetcher) - } else if len(tCore.exchangeRateSources) != 0 { - for token := range tCore.exchangeRateSources { - delete(tCore.exchangeRateSources, token) + tCore.fiatRateSources["testsource"] = newCommonRateSource(tCore.log, tFetcher) + } else { + for token := range tCore.fiatRateSources { + delete(tCore.fiatRateSources, token) } } isDisabled := tCore.isConversionDisabled() - if test.disableConversion { - if isDisabled { - continue - } - t.Fatalf("%s: expected fiat conversion to be disabled", test.name) - } - if isDisabled { - t.Fatalf("%s: fiat conversion should not be diabled", test.name) + if test.disableConversion != isDisabled { + t.Fatalf("%s: expected %t, got %t", test.name, test.disableConversion, isDisabled) } } } -func TestExchangeRateSources(t *testing.T) { +func TestFiatRateSources(t *testing.T) { rig := newTestRig() defer rig.shutdown() tCore := rig.core - tCore.exchangeRateSources = make(map[string]*commonSource) - supportedFetchers := len(exchangeRateFetchers) - rateSources := tCore.ExchangeRateSources() + supportedFetchers := len(fiatRateFetchers) + rateSources := tCore.FiatRateSources() if len(rateSources) != supportedFetchers { - t.Fatalf("Expected %d number of exchange rate source/fetchers", supportedFetchers) + t.Fatalf("Expected %d number of fiat rate source/fetchers", supportedFetchers) } } @@ -9183,45 +9164,44 @@ func TestFetchAllFiatRates(t *testing.T) { rig := newTestRig() defer rig.shutdown() tCore := rig.core - tCore.exchangeRateSources = make(map[string]*commonSource) - // No exchange rate source initialized + // No fiat rate source initialized fiatRates := tCore.fetchAllFiatRates() if len(fiatRates) != 0 { t.Fatal("Unexpected asset rate values.") } - // Initialize exchange rate sources. - for token := range exchangeRateFetchers { - tCore.exchangeRateSources[token] = newcommonSource(tCore.log, tFetcher) + // Initialize fiat rate sources. + for token := range fiatRateFetchers { + tCore.fiatRateSources[token] = newCommonRateSource(tCore.log, tFetcher) } - // Fetch asset rates. + // Fetch fiat rates. tCore.wg.Add(1) go func() { defer tCore.wg.Done() - tCore.refreshExchangeRate() + tCore.refreshFiatRates() }() tCore.wg.Wait() - // Expects assets exchange rate values. + // Expects assets fiat rate values. fiatRates = tCore.fetchAllFiatRates() if len(fiatRates) != 2 { - t.Fatal("Expected assets exchange rate for two assets") + t.Fatal("Expected assets fiat rate for two assets") } - // Exchange rates for assets can expire, and exchange rate fetchers can be + // fiat rates for assets can expire, and fiat rate fetchers can be // removed if expired. time.Sleep(2 * time.Second) - for token, source := range tCore.exchangeRateSources { + for token, source := range tCore.fiatRateSources { if source.isExpired(2 * time.Second) { - delete(tCore.exchangeRateSources, token) + delete(tCore.fiatRateSources, token) } } fiatRates = tCore.fetchAllFiatRates() if len(fiatRates) != 0 { - t.Fatal("Unexpected assets exchange rate values, expected to ignore expired exchange rates.") + t.Fatal("Unexpected assets fiat rate values, expected to ignore expired fiat rates.") } if !tCore.isConversionDisabled() { diff --git a/client/core/exchangeratefetcher.go b/client/core/exchangeratefetcher.go index 77ddef06c2..2b9cf580a3 100644 --- a/client/core/exchangeratefetcher.go +++ b/client/core/exchangeratefetcher.go @@ -24,7 +24,7 @@ const ( // fiatRateDataExpiry : Any data older than fiatRateDataExpiry will be discarded. fiatRateDataExpiry = 60 * time.Minute - // Tokens. Used to identify exchange rate source. + // Tokens. Used to identify fiat rate source. messari = "messari" coinpaprika = "coinpaprika" dcrdataDotOrg = "dcrdata" @@ -38,24 +38,24 @@ var ( dcrBipId, _ = dex.BipSymbolID("dcr") ) -// exchangeRateFetchers is the list of all supported exchange rate fetchers. -var exchangeRateFetchers = map[string]rateFetcher{ +// fiatRateFetchers is the list of all supported fiat rate fetchers. +var fiatRateFetchers = map[string]rateFetcher{ coinpaprika: fetchCoinpaprikaRates, dcrdataDotOrg: fetchDcrdataRates, - messari: fetchMassariRates, + messari: fetchMessariRates, } -// fiatRateInfo holds the fiat exchange rate and the last update time for an +// fiatRateInfo holds the fiat rate and the last update time for an // asset. type fiatRateInfo struct { rate float64 lastUpdate time.Time } -// rateFetcher can fetch exchange rates for assets from an API. +// rateFetcher can fetch fiat rates for assets from an API. type rateFetcher func(context context.Context, logger dex.Logger, assets map[uint32]*SupportedAsset) map[uint32]float64 -type commonSource struct { +type commonRateSource struct { mtx sync.RWMutex lastRequest time.Time log dex.Logger @@ -64,22 +64,22 @@ type commonSource struct { } // logRequest sets the lastRequest time.Time. -func (source *commonSource) logRequest() { +func (source *commonRateSource) logRequest() { source.mtx.Lock() defer source.mtx.Unlock() source.lastRequest = time.Now() } -// lastTry is the last time the exchange rate fecther made a request. -func (source *commonSource) lastTry() time.Time { +// lastTry is the last time the fiat rate fecther made a request. +func (source *commonRateSource) lastTry() time.Time { source.mtx.RLock() defer source.mtx.RUnlock() return source.lastRequest } -// isExpired checks the last update time for all exchange rates against the +// isExpired checks the last update time for all fiat rates against the // provided expiryTime. -func (source *commonSource) isExpired(expiryTime time.Duration) bool { +func (source *commonRateSource) isExpired(expiryTime time.Duration) bool { source.mtx.Lock() defer source.mtx.Unlock() var expiredCount int @@ -92,20 +92,17 @@ func (source *commonSource) isExpired(expiryTime time.Duration) bool { return expiredCount == totalFiatRate && totalFiatRate != 0 } -// assetRate returns the exchange rate information for the assetID specified. -// found is true is the rate source has a value for the assetID specified. -func (source *commonSource) assetRate(assetID uint32) (fiatRateInfo, bool) { +// assetRate returns the fiat rate information for the assetID specified. +func (source *commonRateSource) assetRate(assetID uint32) *fiatRateInfo { source.mtx.Lock() defer source.mtx.Unlock() - rateInfo, found := source.fiatRates[assetID] - if !found { - return fiatRateInfo{}, false - } - return *rateInfo, true + rateInfo := source.fiatRates[assetID] + fiatRateInfo := *rateInfo + return &fiatRateInfo } -// refreshRates updates the last update time and the rate information for asset. -func (source *commonSource) refreshRates(ctx context.Context, assets map[uint32]*SupportedAsset) { +// refreshRates updates the last update time and the rate information for assets. +func (source *commonRateSource) refreshRates(ctx context.Context, assets map[uint32]*SupportedAsset) { source.logRequest() fiatRates := source.fetchRates(ctx, source.log, assets) source.mtx.Lock() @@ -118,16 +115,16 @@ func (source *commonSource) refreshRates(ctx context.Context, assets map[uint32] source.mtx.Unlock() } -// Used to initialize an exchange rate source. -func newcommonSource(logger dex.Logger, fetcher rateFetcher) *commonSource { - return &commonSource{ +// Used to initialize an fiat rate source. +func newCommonRateSource(logger dex.Logger, fetcher rateFetcher) *commonRateSource { + return &commonRateSource{ log: logger, fetchRates: fetcher, fiatRates: make(map[uint32]*fiatRateInfo), } } -// fetchCoinpaprikaRates retrieves and parses exchange rate data from the +// fetchCoinpaprikaRates retrieves and parses fiat rate data from the // coinpaprika API. coinpaprika response models the JSON data returned from the // coinpaprika API. See https://api.coinpaprika.com/#operation/getTickersById // for sample request and response information. @@ -140,8 +137,6 @@ func fetchCoinpaprikaRates(ctx context.Context, log dex.Logger, assets map[uint3 } res := new(struct { - Name string `json:"name"` - Symbol string `json:"symbol"` Quotes struct { Currency struct { Price float64 `json:"price"` @@ -179,7 +174,7 @@ func fetchCoinpaprikaRates(ctx context.Context, log dex.Logger, assets map[uint3 return fiatRates } -// fetchDcrdataRates retrieves and parses exchange rate data from dcrdataDotOrg +// fetchDcrdataRates retrieves and parses fiat rate data from dcrdata // exchange rate API. func fetchDcrdataRates(ctx context.Context, log dex.Logger, assets map[uint32]*SupportedAsset) map[uint32]float64 { assetBTC := assets[btcBipId] @@ -192,7 +187,6 @@ func fetchDcrdataRates(ctx context.Context, log dex.Logger, assets map[uint32]*S fiatRates := make(map[uint32]float64) res := new(struct { - Currency string `json:"btcIndex"` DcrPrice float64 `json:"dcrPrice"` BtcPrice float64 `json:"btcPrice"` }) @@ -227,12 +221,12 @@ func fetchDcrdataRates(ctx context.Context, log dex.Logger, assets map[uint32]*S return fiatRates } -// fetchMassariRates retrieves and parses exchange rate data from the massari +// fetchMessariRates retrieves and parses fiat rate data from the Messari // API. It returns the last error if any. messari response models the JSON data // returned from the messari API. See // https://messari.io/api/docs#operation/Get%20Asset%20Metrics for sample // request and response information. -func fetchMassariRates(ctx context.Context, log dex.Logger, assets map[uint32]*SupportedAsset) map[uint32]float64 { +func fetchMessariRates(ctx context.Context, log dex.Logger, assets map[uint32]*SupportedAsset) map[uint32]float64 { fiatRates := make(map[uint32]float64) for assetID, asset := range assets { if asset.Wallet == nil { @@ -242,8 +236,6 @@ func fetchMassariRates(ctx context.Context, log dex.Logger, assets map[uint32]*S res := new(struct { Data struct { - Name string `json:"name"` - Symbol string `json:"symbol"` MarketData struct { Price float64 `json:"price_usd"` } `json:"market_data"` diff --git a/client/db/bolt/db.go b/client/db/bolt/db.go index 2144ceeb19..013af5267d 100644 --- a/client/db/bolt/db.go +++ b/client/db/bolt/db.go @@ -2076,7 +2076,7 @@ func (db *BoltDB) DeleteInactiveMatches(ctx context.Context, olderThan *time.Tim return nil } -// SaveDisabledRateSources updates disabled exchange rate sources. +// SaveDisabledRateSources updates disabled fiat rate sources. func (db *BoltDB) SaveDisabledRateSources(disabledSources []string) error { return db.Update(func(tx *bbolt.Tx) error { bkt := tx.Bucket(appBucket) @@ -2087,7 +2087,7 @@ func (db *BoltDB) SaveDisabledRateSources(disabledSources []string) error { }) } -// DisabledRateSources retrieves a map of disabled exchange rate sources. +// DisabledRateSources retrieves a map of disabled fiat rate sources. func (db *BoltDB) DisabledRateSources() (disabledSources []string, err error) { return disabledSources, db.View(func(tx *bbolt.Tx) error { bkt := tx.Bucket(appBucket) diff --git a/client/db/interface.go b/client/db/interface.go index 66b0823ff2..27c5e4eb49 100644 --- a/client/db/interface.go +++ b/client/db/interface.go @@ -130,10 +130,10 @@ type DB interface { SetSeedGenerationTime(time uint64) error // SeedGenerationTime fetches the time when the app seed was generated. SeedGenerationTime() (uint64, error) - // DisabledRateSources retrieves disabled exchange rate sources from the + // DisabledRateSources retrieves disabled fiat rate sources from the // database. DisabledRateSources() ([]string, error) - // SaveDisabledRateSources saves disabled exchange rate sources in the + // SaveDisabledRateSources saves disabled fiat rate sources in the // database. SaveDisabledRateSources(disabledSources []string) error } diff --git a/client/webserver/api.go b/client/webserver/api.go index fd8099b747..12b9b9837a 100644 --- a/client/webserver/api.go +++ b/client/webserver/api.go @@ -1038,8 +1038,8 @@ func (s *WebServer) apiUser(w http.ResponseWriter, r *http.Request) { writeJSON(w, response, s.indent) } -// apiRateSourceSettings handles the /ratesourcesettings API request. -func (s *WebServer) apiRateSourceSettings(w http.ResponseWriter, r *http.Request) { +// apiToggleRateSource handles the /toggleratesource API request. +func (s *WebServer) apiToggleRateSource(w http.ResponseWriter, r *http.Request) { form := &struct { Disable bool `json:"disable"` Source string `json:"source"` diff --git a/client/webserver/http.go b/client/webserver/http.go index 7a6510499d..cdf8b78be4 100644 --- a/client/webserver/http.go +++ b/client/webserver/http.go @@ -241,14 +241,14 @@ func (s *WebServer) handleSettings(w http.ResponseWriter, r *http.Request) { common := commonArgs(r, "Settings | Decred DEX") data := &struct { CommonArguments - KnownExchanges []string - ExchangeRateSources map[string]bool - FiatCurrency string + KnownExchanges []string + FiatRateSources map[string]bool + FiatCurrency string }{ - CommonArguments: *common, - KnownExchanges: s.knownUnregisteredExchanges(common.UserInfo.Exchanges), - FiatCurrency: core.DefaultFiatCurrency, - ExchangeRateSources: s.core.ExchangeRateSources(), + CommonArguments: *common, + KnownExchanges: s.knownUnregisteredExchanges(common.UserInfo.Exchanges), + FiatCurrency: core.DefaultFiatCurrency, + FiatRateSources: s.core.FiatRateSources(), } s.sendTemplate(w, "settings", data) } diff --git a/client/webserver/live_test.go b/client/webserver/live_test.go index edc95f9606..d02c16c791 100644 --- a/client/webserver/live_test.go +++ b/client/webserver/live_test.go @@ -436,6 +436,7 @@ type TCore struct { noteFeed chan core.Notification orderMtx sync.Mutex epochOrders []*core.BookUpdate + fiatSources map[string]bool } // TDriver implements the interface required of all exchange wallets. @@ -481,6 +482,11 @@ func newTCore() *TCore { 145: randomBalance(145), }, noteFeed: make(chan core.Notification, 1), + fiatSources: map[string]bool{ + "dcrdata": true, + "messari": true, + "coinpaprika": true, + }, } } @@ -1400,6 +1406,18 @@ func (c *TCore) User() *core.User { Exchanges: exchanges, Initialized: c.inited, Assets: c.SupportedAssets(), + FiatRates: map[uint32]float64{ + 0: 21_208.61, // btc + 2: 59.08, // ltc + 42: 25.46, // dcr + 22: 0.5117, // mona + 28: 0.1599, // vtc + 141: 0.2048, // kmd + 3: 0.06769, // doge + 145: 114.68, // bch + 60: 1_209.51, // eth + }, + DisableConversion: false, } return user } @@ -1613,10 +1631,11 @@ func (c *TCore) UpdateDEXHost(string, string, []byte, interface{}) (*core.Exchan return nil, nil } func (c *TCore) ToggleRateSourceStatus(src string, disable bool) error { + c.fiatSources[src] = !disable return nil } -func (c *TCore) ExchangeRateSources() map[string]bool { - return nil +func (c *TCore) FiatRateSources() map[string]bool { + return c.fiatSources } func TestServer(t *testing.T) { diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go index 47923d0629..6717e1d18a 100644 --- a/client/webserver/locales/en-us.go +++ b/client/webserver/locales/en-us.go @@ -238,4 +238,6 @@ var EnUS = map[string]string{ "successful_cert_update": "Successfully updated certificate!", "update dex host": "Update DEX Host", "copied": "Copied!", + "amount_in_fiat": "Amount in Fiat", + "fiat_exchange_rate_sources": "Fiat Exchange Rate Sources", } diff --git a/client/webserver/site/src/html/markets.tmpl b/client/webserver/site/src/html/markets.tmpl index 68b4fb23a1..d68ea2f039 100644 --- a/client/webserver/site/src/html/markets.tmpl +++ b/client/webserver/site/src/html/markets.tmpl @@ -449,22 +449,25 @@ [[[Quantity]]] - / $
[[[Total]]] - / $ +
+
+ ~  USD
[[[Trading]]] - / $
+
+ ~  USD +
[[[Receiving Approximately]]] @@ -472,7 +475,9 @@ - / $ +
+
+ ~  USD
diff --git a/client/webserver/site/src/html/settings.tmpl b/client/webserver/site/src/html/settings.tmpl index 7f51f3883f..6ec44bf9c1 100644 --- a/client/webserver/site/src/html/settings.tmpl +++ b/client/webserver/site/src/html/settings.tmpl @@ -19,9 +19,9 @@
Fiat Currency: {{.FiatCurrency}}
-
- Exchange Rate Sources: - {{range $source, $enabled := .ExchangeRateSources}} +
+ [[[fiat_exchange_rate_sources]]]: + {{range $source, $enabled := .FiatRateSources}}
diff --git a/client/webserver/site/src/html/wallets.tmpl b/client/webserver/site/src/html/wallets.tmpl index 0d165f3fde..21823dcb3f 100644 --- a/client/webserver/site/src/html/wallets.tmpl +++ b/client/webserver/site/src/html/wallets.tmpl @@ -76,10 +76,7 @@
{{.Info.UnitInfo.ConventionalString .Wallet.Balance.Available}}
-
$ - +
USD
{{else}} 0.00000000 @@ -171,7 +168,9 @@
-
Value: $
+
+ [[[amount_in_fiat]]]: ~  USD +
diff --git a/client/webserver/site/src/js/app.ts b/client/webserver/site/src/js/app.ts index e2c699d198..b924be33f6 100644 --- a/client/webserver/site/src/js/app.ts +++ b/client/webserver/site/src/js/app.ts @@ -36,7 +36,6 @@ import { NoteElement, BalanceResponse, APIResponse, - RateNote, PageElement } from './registry' @@ -561,37 +560,15 @@ export default class Application { else if (!updateOrder(mkt, order)) mkt.orders.push(order) break } - case 'fiatrateupdate': { - const r: RateNote = note as RateNote - this.fiatRatesMap = r.fiatRates - if (!this.walletMap) break - for (const [assetID, wallet] of (Object.entries(this.walletMap) as [any, WalletState][])) { - if (!wallet) continue - const rate = r.fiatRates[assetID] - const fiatDiv = this.main.querySelector(`[data-conversion-target="${assetID}"]`) as PageElement - if (!fiatDiv || !fiatDiv.firstElementChild) continue - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(wallet.balance.available, rate, this.unitInfo(assetID)) - if (rate && !this.disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) - } - } - break case 'balance': { const n: BalanceNote = note as BalanceNote const asset = this.user.assets[n.assetID] // Balance updates can come before the user is fetched after login. if (!asset) break const w = asset.wallet - if (!w) break - w.balance = n.balance - const rate = this.fiatRatesMap[w.assetID] - const fiatDiv = this.main.querySelector(`[data-conversion-target="${n.assetID}"]`) as PageElement - if (!fiatDiv || !fiatDiv.firstElementChild) return - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(n.balance.available, rate, this.unitInfo(n.assetID)) - if (rate && !this.disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) - } + if (w) w.balance = n.balance break + } case 'feepayment': this.handleFeePaymentNote(note as FeePaymentNote) break @@ -607,11 +584,11 @@ export default class Application { const balances = this.main.querySelectorAll(`[data-balance-target="${wallet.assetID}"]`) balances.forEach(el => { el.textContent = Doc.formatFullPrecision(bal, asset.info.unitinfo) }) const rate = this.fiatRatesMap[wallet.assetID] - const fiatDiv = this.main.querySelector(`[data-conversion-target="${wallet.assetID}"]`) as PageElement - if (!fiatDiv || !fiatDiv.firstElementChild) break - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(bal, rate, asset.info.unitinfo) - if (rate && !this.disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) + const fiatDisplay = this.main.querySelector(`[data-conversion-target="${wallet.assetID}"]`) as PageElement + if (!fiatDisplay) break + fiatDisplay.textContent = Doc.formatFiatConversion(bal, rate, asset.info.unitinfo) + if (rate && !this.disableConversion) Doc.show(fiatDisplay.parentElement as Element) + else Doc.hide(fiatDisplay.parentElement as Element) } break case 'match': { diff --git a/client/webserver/site/src/js/doc.ts b/client/webserver/site/src/js/doc.ts index dd41d802f6..e242efaacc 100644 --- a/client/webserver/site/src/js/doc.ts +++ b/client/webserver/site/src/js/doc.ts @@ -236,10 +236,10 @@ export default class Doc { } /* - * formatAssetValue formats the value in atomic units to its representation in + * formatFiatConversion formats the value in atomic units to its representation in * conventional units and returns the fiat value as a string. */ - static formatAssetValue (vAtomic: number, rate: number, unitInfo?: UnitInfo): string { + static formatFiatConversion (vAtomic: number, rate: number, unitInfo?: UnitInfo): string { if (!rate || rate === 0) return 'unavailable' const prec = 2 const [v] = convertToConventional(vAtomic, unitInfo) diff --git a/client/webserver/site/src/js/markets.ts b/client/webserver/site/src/js/markets.ts index 33e9d9cef5..cfec3ca174 100644 --- a/client/webserver/site/src/js/markets.ts +++ b/client/webserver/site/src/js/markets.ts @@ -1391,52 +1391,30 @@ export default class MarketsPage extends BasePage { if (order.isLimit) { Doc.show(page.verifyLimit) Doc.hide(page.verifyMarket) - Doc.hide(page.vFiatTotal) - Doc.hide(page.vFiatQty) const orderDesc = `Limit ${buySellStr} Order` page.vOrderType.textContent = order.tifnow ? orderDesc + ' (immediate)' : orderDesc page.vRate.textContent = Doc.formatCoinValue(order.rate / this.market.rateConversionFactor) page.vQty.textContent = Doc.formatCoinValue(order.qty, baseAsset.info.unitinfo) const total = order.rate / OrderUtil.RateEncodingFactor * order.qty page.vTotal.textContent = Doc.formatCoinValue(total, quoteAsset.info.unitinfo) - // Format quantity fiat value. - if (page.vFiatQty.firstElementChild) { - const rate = app().fiatRatesMap[baseAsset.id] - page.vFiatQty.firstElementChild.textContent = Doc.formatAssetValue(order.qty, rate, baseAsset.info.unitinfo) - if (rate && !app().disableConversion) Doc.show(page.vFiatQty) - } // Format total fiat value. - if (page.vFiatTotal.firstElementChild) { - const rate = app().fiatRatesMap[quoteAsset.id] - page.vFiatTotal.firstElementChild.textContent = Doc.formatAssetValue(total, rate, quoteAsset.info.unitinfo) - if (rate && !app().disableConversion) Doc.show(page.vFiatTotal) - } + this.showFiatValue(quoteAsset.id, total, page.vFiatTotal) } else { Doc.hide(page.verifyLimit) Doc.show(page.verifyMarket) - Doc.hide(page.vmFromTotalFiat) page.vOrderType.textContent = `Market ${buySellStr} Order` - page.vmFromAsset.textContent = fromAsset.symbol.toUpperCase() page.vmFromTotal.textContent = Doc.formatCoinValue(order.qty / 1e8) + page.vmFromAsset.textContent = fromAsset.symbol.toUpperCase() // Format fromAsset fiat value. - if (page.vmFromTotalFiat.firstElementChild) { - const rate = app().fiatRatesMap[fromAsset.id] - page.vmFromTotalFiat.firstElementChild.textContent = Doc.formatAssetValue(order.qty, rate, fromAsset.info.unitinfo) - if (rate && !app().disableConversion) Doc.show(page.vmFromTotalFiat) - } + this.showFiatValue(fromAsset.id, order.qty, page.vmFromTotalFiat) const gap = this.midGap() if (gap) { Doc.show(page.vMarketEstimate) - Doc.hide(page.vmTotalFiat) const received = order.sell ? order.qty * gap : order.qty / gap page.vmToTotal.textContent = Doc.formatCoinValue(received, toAsset.info.unitinfo) page.vmToAsset.textContent = toAsset.symbol.toUpperCase() - // Format recieved value. - if (page.vmTotalFiat.firstElementChild) { - const rate = app().fiatRatesMap[toAsset.id] - page.vmTotalFiat.firstElementChild.textContent = Doc.formatAssetValue(received, rate, toAsset.info.unitinfo) - if (rate && !app().disableConversion) Doc.show(page.vmTotalFiat) - } + // Format recieved value to fiat equivalent. + this.showFiatValue(toAsset.id, received, page.vmTotalFiat) } else { Doc.hide(page.vMarketEstimate) } @@ -1466,6 +1444,16 @@ export default class MarketsPage extends BasePage { } } + // showFiatValue displays the fiat equivalent for an order quantity. + showFiatValue (assetID: number, qty: number, display: PageElement) { + if (display) { + const rate = app().fiatRatesMap[assetID] + display.textContent = Doc.formatFiatConversion(qty, rate, app().unitInfo(assetID)) + if (rate && !app().disableConversion) Doc.show(display.parentElement as Element) + else Doc.hide(display.parentElement as Element) + } + } + /* showVerifyForm displays form to verify an order */ async showVerifyForm () { const page = this.page diff --git a/client/webserver/site/src/js/settings.ts b/client/webserver/site/src/js/settings.ts index 5a60c978ab..8dd262e4e7 100644 --- a/client/webserver/site/src/js/settings.ts +++ b/client/webserver/site/src/js/settings.ts @@ -18,7 +18,7 @@ export default class SettingsPage extends BasePage { currentDEX: Exchange page: Record forms: PageElement[] - exchangeRateSources: PageElement[] + fiatRateSources: PageElement[] regAssetForm: forms.FeeAssetSelectionForm confirmRegisterForm: forms.ConfirmRegistrationForm newWalletForm: forms.NewWalletForm @@ -36,7 +36,7 @@ export default class SettingsPage extends BasePage { const page = this.page = Doc.idDescendants(body) this.forms = Doc.applySelector(page.forms, ':scope > form') - this.exchangeRateSources = Doc.applySelector(page.exchangeRateSources, 'input[type=checkbox]') + this.fiatRateSources = Doc.applySelector(page.fiatRateSources, 'input[type=checkbox]') Doc.bind(page.darkMode, 'click', () => { State.dark(page.darkMode.checked || false) @@ -59,9 +59,9 @@ export default class SettingsPage extends BasePage { this.showForm(page.dexAddrForm) }) - this.exchangeRateSources.forEach(src => { + this.fiatRateSources.forEach(src => { Doc.bind(src, 'change', async () => { - const res = await postJSON('/api/ratesourcesettings', { + const res = await postJSON('/api/toggleratesource', { disable: !src.checked, source: src.value }) diff --git a/client/webserver/site/src/js/wallets.ts b/client/webserver/site/src/js/wallets.ts index 4950220e2b..6a2873d278 100644 --- a/client/webserver/site/src/js/wallets.ts +++ b/client/webserver/site/src/js/wallets.ts @@ -202,11 +202,8 @@ export default class WalletsPage extends BasePage { bind(page.sendAvail, 'click', () => { const asset = this.sendAsset const bal = asset.wallet.balance.available - const unitInfo = asset.info.unitinfo - const rate = app().fiatRatesMap[asset.id] - if (rate && page.sendValue.firstElementChild) page.sendValue.firstElementChild.textContent = Doc.formatAssetValue(bal, rate, unitInfo) - else Doc.hide(page.sendValue) - page.sendAmt.value = String(bal / unitInfo.conventional.conversionFactor) + page.sendAmt.value = String(bal / asset.info.unitinfo.conventional.conversionFactor) + this.showFiatValue(asset.id, bal, page.sendValue) // Ensure we don't check subtract checkbox for assets that don't have a // withdraw method. if ((asset.wallet.traits & traitWithdrawer) === 0) page.subtractCheckBox.checked = false @@ -215,12 +212,9 @@ export default class WalletsPage extends BasePage { for (const [assetID, wallet] of (Object.entries(app().walletMap) as [any, WalletState][])) { if (!wallet) continue - const rate = app().fiatRatesMap[assetID] - const fiatDiv = this.page.walletTable.querySelector(`[data-conversion-target="${assetID}"]`) as PageElement - if (!fiatDiv || !fiatDiv.firstElementChild) continue - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(wallet.balance.available, rate, app().unitInfo(assetID)) - if (rate && !app().disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) + const fiatDisplay = this.page.walletTable.querySelector(`[data-conversion-target="${assetID}"]`) as PageElement + if (!fiatDisplay) continue + this.showFiatValue(assetID, wallet.balance.available, fiatDisplay) } // Display fiat value for current send amount. @@ -229,9 +223,7 @@ export default class WalletsPage extends BasePage { if (!asset) return const amt = parseFloat(page.sendAmt.value || '0') const conversionFactor = asset.info.unitinfo.conventional.conversionFactor - const rate = app().fiatRatesMap[asset.id] - if (rate && page.sendValue.firstElementChild) page.sendValue.firstElementChild.textContent = Doc.formatAssetValue(amt * conversionFactor, rate, app().unitInfo(asset.id)) - else Doc.hide(page.sendValue) + this.showFiatValue(asset.id, amt * conversionFactor, page.sendValue) }) // A link on the wallet reconfiguration form to show/hide the password field. @@ -580,10 +572,7 @@ export default class WalletsPage extends BasePage { page.sendPW.value = '' page.sendErr.textContent = '' - Doc.hide(page.sendValue) - const rate = app().fiatRatesMap[asset.id] - if (page.sendValue.firstElementChild) page.sendValue.firstElementChild.textContent = Doc.formatAssetValue(0, rate, app().unitInfo(asset.id)) - if (rate && !app().disableConversion) Doc.show(page.sendValue) + this.showFiatValue(asset.id, 0, page.sendValue) page.sendAvail.textContent = Doc.formatFullPrecision(wallet.balance.available, asset.info.unitinfo) page.sendLogo.src = Doc.logoPath(asset.symbol) page.sendName.textContent = asset.info.name @@ -754,17 +743,14 @@ export default class WalletsPage extends BasePage { } /* handleBalance handles notifications updating a wallet's balance and assets' - value in default fiat exchange rate. + value in default fiat rate. . */ handleBalanceNote (note: BalanceNote) { const td = Doc.safeSelector(this.page.walletTable, `[data-balance-target="${note.assetID}"]`) td.textContent = Doc.formatFullPrecision(note.balance.available, app().unitInfo(note.assetID)) - const rate = app().fiatRatesMap[note.assetID] - const fiatDiv = Doc.safeSelector(this.page.walletTable, `[data-conversion-target="${note.assetID}"]`) - if (!fiatDiv.firstElementChild) return - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(note.balance.available, rate, app().unitInfo(note.assetID)) - if (rate && !app().disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) + const fiatDisplay = Doc.safeSelector(this.page.walletTable, `[data-conversion-target="${note.assetID}"]`) + if (!fiatDisplay) return + this.showFiatValue(note.assetID, note.balance.available, fiatDisplay) } /* handleRatesNote handles fiat rate notifications, updating the fiat value of @@ -774,12 +760,19 @@ export default class WalletsPage extends BasePage { app().fiatRatesMap = note.fiatRates for (const [assetID, wallet] of (Object.entries(app().walletMap) as [any, WalletState][])) { if (!wallet) continue - const rate = note.fiatRates[assetID] - const fiatDiv = this.page.walletTable.querySelector(`[data-conversion-target="${assetID}"]`) as PageElement - if (!fiatDiv || !fiatDiv.firstElementChild) continue - fiatDiv.firstElementChild.textContent = Doc.formatAssetValue(wallet.balance.available, rate, app().unitInfo(assetID)) - if (rate && !app().disableConversion) Doc.show(fiatDiv) - else Doc.hide(fiatDiv) + const fiatDisplay = this.page.walletTable.querySelector(`[data-conversion-target="${assetID}"]`) as PageElement + if (!fiatDisplay) continue + this.showFiatValue(assetID, wallet.balance.available, fiatDisplay) + } + } + + // showFiatValue displays the fiat equivalent for the provided amount. + showFiatValue (assetID: number, amount: number, display: PageElement) { + if (display) { + const rate = app().fiatRatesMap[assetID] + display.textContent = Doc.formatFiatConversion(amount, rate, app().unitInfo(assetID)) + if (rate && !app().disableConversion) Doc.show(display.parentElement as Element) + else Doc.hide(display.parentElement as Element) } } diff --git a/client/webserver/site/src/localized_html/en-US/markets.tmpl b/client/webserver/site/src/localized_html/en-US/markets.tmpl index 264e569a65..9d0c7027d6 100644 --- a/client/webserver/site/src/localized_html/en-US/markets.tmpl +++ b/client/webserver/site/src/localized_html/en-US/markets.tmpl @@ -449,22 +449,25 @@ Quantity - / $
Total - / $ +
+
+ ~  USD
Trading - / $
+
+ ~  USD +
Receiving Approximately @@ -472,7 +475,9 @@ - / $ +
+
+ ~  USD
diff --git a/client/webserver/site/src/localized_html/en-US/settings.tmpl b/client/webserver/site/src/localized_html/en-US/settings.tmpl index 8466fbed3f..5e068aaa51 100644 --- a/client/webserver/site/src/localized_html/en-US/settings.tmpl +++ b/client/webserver/site/src/localized_html/en-US/settings.tmpl @@ -19,9 +19,9 @@
Fiat Currency: {{.FiatCurrency}}
-
- Exchange Rate Sources: - {{range $source, $enabled := .ExchangeRateSources}} +
+ Fiat Exchange Rate Sources: + {{range $source, $enabled := .FiatRateSources}}
diff --git a/client/webserver/site/src/localized_html/en-US/wallets.tmpl b/client/webserver/site/src/localized_html/en-US/wallets.tmpl index 100c5459be..57953c4c54 100644 --- a/client/webserver/site/src/localized_html/en-US/wallets.tmpl +++ b/client/webserver/site/src/localized_html/en-US/wallets.tmpl @@ -76,10 +76,7 @@
{{.Info.UnitInfo.ConventionalString .Wallet.Balance.Available}}
-
$ - +
USD
{{else}} 0.00000000 @@ -171,7 +168,9 @@
-
Value: $
+
+ Amount in Fiat: ~  USD +
diff --git a/client/webserver/site/src/localized_html/pl-PL/markets.tmpl b/client/webserver/site/src/localized_html/pl-PL/markets.tmpl index 33a06e5bec..acfd6f0f37 100644 --- a/client/webserver/site/src/localized_html/pl-PL/markets.tmpl +++ b/client/webserver/site/src/localized_html/pl-PL/markets.tmpl @@ -449,22 +449,25 @@ Ilość - / $
W sumie - / $ +
+
+ ~  USD
Wymiana - / $
+
+ ~  USD +
Otrzymując około @@ -472,7 +475,9 @@ - / $ +
+
+ ~  USD
diff --git a/client/webserver/site/src/localized_html/pl-PL/settings.tmpl b/client/webserver/site/src/localized_html/pl-PL/settings.tmpl index a5dfe518c4..e75d34a024 100644 --- a/client/webserver/site/src/localized_html/pl-PL/settings.tmpl +++ b/client/webserver/site/src/localized_html/pl-PL/settings.tmpl @@ -19,9 +19,9 @@
Fiat Currency: {{.FiatCurrency}}
-
- Exchange Rate Sources: - {{range $source, $enabled := .ExchangeRateSources}} +
+ Fiat Exchange Rate Sources: + {{range $source, $enabled := .FiatRateSources}}
diff --git a/client/webserver/site/src/localized_html/pl-PL/wallets.tmpl b/client/webserver/site/src/localized_html/pl-PL/wallets.tmpl index 868730b712..2a901619ac 100644 --- a/client/webserver/site/src/localized_html/pl-PL/wallets.tmpl +++ b/client/webserver/site/src/localized_html/pl-PL/wallets.tmpl @@ -76,10 +76,7 @@
{{.Info.UnitInfo.ConventionalString .Wallet.Balance.Available}}
-
$ - +
USD
{{else}} 0.00000000 @@ -171,7 +168,9 @@
-
Value: $
+
+ Amount in Fiat: ~  USD +
diff --git a/client/webserver/site/src/localized_html/pt-BR/markets.tmpl b/client/webserver/site/src/localized_html/pt-BR/markets.tmpl index 2918d94155..789a5ab559 100644 --- a/client/webserver/site/src/localized_html/pt-BR/markets.tmpl +++ b/client/webserver/site/src/localized_html/pt-BR/markets.tmpl @@ -449,22 +449,25 @@ Quantidade - / $
Total - / $ +
+
+ ~  USD
Trocando - / $
+
+ ~  USD +
Recebendo aproximadamente @@ -472,7 +475,9 @@ - / $ +
+
+ ~  USD
diff --git a/client/webserver/site/src/localized_html/pt-BR/settings.tmpl b/client/webserver/site/src/localized_html/pt-BR/settings.tmpl index d227e25b7a..fd7bfd14ed 100644 --- a/client/webserver/site/src/localized_html/pt-BR/settings.tmpl +++ b/client/webserver/site/src/localized_html/pt-BR/settings.tmpl @@ -19,9 +19,9 @@
Fiat Currency: {{.FiatCurrency}}
-
- Exchange Rate Sources: - {{range $source, $enabled := .ExchangeRateSources}} +
+ Fiat Exchange Rate Sources: + {{range $source, $enabled := .FiatRateSources}}
diff --git a/client/webserver/site/src/localized_html/pt-BR/wallets.tmpl b/client/webserver/site/src/localized_html/pt-BR/wallets.tmpl index 8dbed378b6..a6d0f5a678 100644 --- a/client/webserver/site/src/localized_html/pt-BR/wallets.tmpl +++ b/client/webserver/site/src/localized_html/pt-BR/wallets.tmpl @@ -76,10 +76,7 @@
{{.Info.UnitInfo.ConventionalString .Wallet.Balance.Available}}
-
$ - +
USD
{{else}} 0.00000000 @@ -171,7 +168,9 @@
-
Value: $
+
+ Amount in Fiat: ~  USD +
diff --git a/client/webserver/site/src/localized_html/zh-CN/markets.tmpl b/client/webserver/site/src/localized_html/zh-CN/markets.tmpl index 8853b2cc38..5c6d4ae304 100644 --- a/client/webserver/site/src/localized_html/zh-CN/markets.tmpl +++ b/client/webserver/site/src/localized_html/zh-CN/markets.tmpl @@ -449,22 +449,25 @@ 数量 - / $
Total - / $ +
+
+ ~  USD
Trading - / $
+
+ ~  USD +
Receiving Approximately @@ -472,7 +475,9 @@ - / $ +
+
+ ~  USD
diff --git a/client/webserver/site/src/localized_html/zh-CN/settings.tmpl b/client/webserver/site/src/localized_html/zh-CN/settings.tmpl index 0ff64b26d6..a11128ef20 100644 --- a/client/webserver/site/src/localized_html/zh-CN/settings.tmpl +++ b/client/webserver/site/src/localized_html/zh-CN/settings.tmpl @@ -19,9 +19,9 @@
Fiat Currency: {{.FiatCurrency}}
-
- Exchange Rate Sources: - {{range $source, $enabled := .ExchangeRateSources}} +
+ Fiat Exchange Rate Sources: + {{range $source, $enabled := .FiatRateSources}}
diff --git a/client/webserver/site/src/localized_html/zh-CN/wallets.tmpl b/client/webserver/site/src/localized_html/zh-CN/wallets.tmpl index 133d6738c8..431198da1c 100644 --- a/client/webserver/site/src/localized_html/zh-CN/wallets.tmpl +++ b/client/webserver/site/src/localized_html/zh-CN/wallets.tmpl @@ -76,10 +76,7 @@
{{.Info.UnitInfo.ConventionalString .Wallet.Balance.Available}}
-
$ - +
USD
{{else}} 0.00000000 @@ -171,7 +168,9 @@
-
Value: $
+
+ Amount in Fiat: ~  USD +
diff --git a/client/webserver/webserver.go b/client/webserver/webserver.go index 4686a29751..aa01240221 100644 --- a/client/webserver/webserver.go +++ b/client/webserver/webserver.go @@ -120,7 +120,7 @@ type clientCore interface { UpdateCert(host string, cert []byte) error UpdateDEXHost(oldHost, newHost string, appPW []byte, certI interface{}) (*core.Exchange, error) ToggleRateSourceStatus(src string, disable bool) error - ExchangeRateSources() map[string]bool + FiatRateSources() map[string]bool } var _ clientCore = (*core.Core)(nil) @@ -359,7 +359,7 @@ func New(cfg *Config) (*WebServer, error) { apiAuth.Post("/accelerationestimate", s.apiAccelerationEstimate) apiAuth.Post("/updatecert", s.apiUpdateCert) apiAuth.Post("/updatedexhost", s.apiUpdateDEXHost) - apiAuth.Post("/ratesourcesettings", s.apiRateSourceSettings) + apiAuth.Post("/toggleratesource", s.apiToggleRateSource) }) }) diff --git a/client/webserver/webserver_test.go b/client/webserver/webserver_test.go index b270d4d57c..fbdd2fef97 100644 --- a/client/webserver/webserver_test.go +++ b/client/webserver/webserver_test.go @@ -89,7 +89,7 @@ func (c *TCore) EstimateRegistrationTxFee(host string, certI interface{}, assetI func (c *TCore) ToggleRateSourceStatus(src string, disable bool) error { return c.rateSourceErr } -func (c *TCore) ExchangeRateSources() map[string]bool { +func (c *TCore) FiatRateSources() map[string]bool { return nil } @@ -720,7 +720,7 @@ func TestPasswordCache(t *testing.T) { } } -func TestAPI_RateSourceSettings(t *testing.T) { +func TestAPI_ToggleRatesource(t *testing.T) { s, tCore, shutdown, err := newTServer(t, false) if err != nil { t.Fatalf("error starting server: %v", err) @@ -734,15 +734,15 @@ func TestAPI_RateSourceSettings(t *testing.T) { Source string `json:"source"` } - // Test enabling exchange rate sources. + // Test enabling fiat rate sources. enableTests := []struct { name, source, want string wantErr error }{{ name: "Invalid rate source", source: "binance", - wantErr: errors.New("cannot enable unkown exchange rate source"), - want: `{"ok":false,"msg":"cannot enable unkown exchange rate source"}`, + wantErr: errors.New("cannot enable unkown fiat rate source"), + want: `{"ok":false,"msg":"cannot enable unkown fiat rate source"}`, }, { name: "ok valid source", source: "dcrdata", @@ -759,18 +759,18 @@ func TestAPI_RateSourceSettings(t *testing.T) { Source: test.source, } tCore.rateSourceErr = test.wantErr - ensureResponse(t, s.apiRateSourceSettings, test.want, reader, writer, body, nil) + ensureResponse(t, s.apiToggleRateSource, test.want, reader, writer, body, nil) } - // Test disabling exchange rate sources. + // Test disabling fiat rate sources. disableTests := []struct { name, source, want string wantErr error }{{ name: "Invalid rate source", source: "binance", - wantErr: errors.New("cannot disable unkown exchange rate source"), - want: `{"ok":false,"msg":"cannot disable unkown exchange rate source"}`, + wantErr: errors.New("cannot disable unkown fiat rate source"), + want: `{"ok":false,"msg":"cannot disable unkown fiat rate source"}`, }, { name: "ok valid source", source: "messari", @@ -787,6 +787,6 @@ func TestAPI_RateSourceSettings(t *testing.T) { Source: test.source, } tCore.rateSourceErr = test.wantErr - ensureResponse(t, s.apiRateSourceSettings, test.want, reader, writer, body, nil) + ensureResponse(t, s.apiToggleRateSource, test.want, reader, writer, body, nil) } }