Move all USD conversions to JS

pull/645/head
jimmay5469 6 years ago
parent ff6060be7b
commit cf8c97a338
  1. 5
      .gitignore
  2. 4
      apps/block_scout_web/assets/__tests__/lib/currency.js
  3. 2
      apps/block_scout_web/assets/__tests__/pages/chain.js
  4. 1
      apps/block_scout_web/assets/js/app.js
  5. 36
      apps/block_scout_web/assets/js/lib/currency.js
  6. 23
      apps/block_scout_web/assets/js/lib/market_history_chart.js
  7. 10
      apps/block_scout_web/assets/js/pages/chain.js
  8. 5
      apps/block_scout_web/assets/package-lock.json
  9. 1
      apps/block_scout_web/assets/package.json
  10. 42
      apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex
  11. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex
  12. 6
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  13. 6
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  14. 6
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  15. 19
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  16. 15
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  17. 38
      apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex
  18. 33
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  19. 48
      apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs
  20. 5
      apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex
  21. 21
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  22. 19
      apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs
  23. 9
      apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs
  24. 8
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs

5
.gitignore vendored

@ -1,6 +1,7 @@
# App artifacts # App artifacts
/_build /_build
/apps/*/cover /apps/*/cover
/apps/*/logs
/cover /cover
/db /db
/deps /deps
@ -15,8 +16,7 @@ erl_crash.dump
npm-debug.log npm-debug.log
# Static artifacts # Static artifacts
/apps/block_scout_web/assets/node_modules /apps/**/node_modules
/apps/explorer/node_modules
# Since we are building assets from assets/, # Since we are building assets from assets/,
# we ignore priv/static. You may want to comment # we ignore priv/static. You may want to comment
@ -30,7 +30,6 @@ npm-debug.log
# secrets files as long as you replace their contents by environment # secrets files as long as you replace their contents by environment
# variables. # variables.
/apps/*/config/*.secret.exs /apps/*/config/*.secret.exs
/apps/*/cover/
# Wallaby screenshots # Wallaby screenshots
screenshots/ screenshots/

@ -1,6 +1,10 @@
import { formatUsdValue } from '../../js/lib/currency' import { formatUsdValue } from '../../js/lib/currency'
test('formatUsdValue', () => { test('formatUsdValue', () => {
window.localized = {
'less than': 'less than'
}
expect(formatUsdValue(0)).toEqual('$0.000000 USD')
expect(formatUsdValue(0.0000001)).toEqual('< $0.000001 USD') expect(formatUsdValue(0.0000001)).toEqual('< $0.000001 USD')
expect(formatUsdValue(0.123456789)).toEqual('$0.123457 USD') expect(formatUsdValue(0.123456789)).toEqual('$0.123457 USD')
expect(formatUsdValue(0.1234)).toEqual('$0.123400 USD') expect(formatUsdValue(0.1234)).toEqual('$0.123400 USD')

@ -40,7 +40,6 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => {
msg: { msg: {
exchangeRate: { exchangeRate: {
availableSupply: 1000000, availableSupply: 1000000,
usdValue: 1.23,
marketCapUsd: 1230000 marketCapUsd: 1230000
}, },
marketHistoryData: { data: 'some stuff' } marketHistoryData: { data: 'some stuff' }
@ -50,7 +49,6 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => {
expect(output.availableSupply).toEqual(1000000) expect(output.availableSupply).toEqual(1000000)
expect(output.marketHistoryData).toEqual({ data: 'some stuff' }) expect(output.marketHistoryData).toEqual({ data: 'some stuff' })
expect(output.usdExchangeRate).toEqual(1.23)
expect(output.usdMarketCap).toEqual(1230000) expect(output.usdMarketCap).toEqual(1230000)
}) })

@ -21,6 +21,7 @@ import 'bootstrap'
import './locale' import './locale'
import './lib/clipboard_buttons' import './lib/clipboard_buttons'
import './lib/currency'
import './lib/from_now' import './lib/from_now'
import './lib/loading_element' import './lib/loading_element'
import './lib/market_history_chart' import './lib/market_history_chart'

@ -1,8 +1,44 @@
import $ from 'jquery'
import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import { BigNumber } from 'bignumber.js'
import socket from '../socket'
export function formatUsdValue (value) { export function formatUsdValue (value) {
if (value === 0) return '$0.000000 USD'
if (value < 0.000001) return '< $0.000001 USD' if (value < 0.000001) return '< $0.000001 USD'
if (value < 1) return `$${numeral(value).format('0.000000')} USD` if (value < 1) return `$${numeral(value).format('0.000000')} USD`
if (value < 100000) return `$${numeral(value).format('0,0.00')} USD` if (value < 100000) return `$${numeral(value).format('0,0.00')} USD`
return `$${numeral(value).format('0,0')} USD` return `$${numeral(value).format('0,0')} USD`
} }
function weiToEther (wei) {
return new BigNumber(wei).dividedBy('1000000000000000000').toNumber()
}
function etherToUSD (ether, usdExchangeRate) {
return new BigNumber(ether).multipliedBy(usdExchangeRate).toNumber()
}
function formatAllUsdValues () {
$('[data-usd-value]').each((i, el) => {
el.innerHTML = formatUsdValue(el.dataset.usdValue)
})
}
formatAllUsdValues()
function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExchangeRate) {
if (!el.dataset.hasOwnProperty('weiValue')) return
const ether = weiToEther(el.dataset.weiValue)
const usd = etherToUSD(ether, usdExchangeRate)
const formattedUsd = formatUsdValue(usd)
if (formattedUsd !== el.innerHTML) el.innerHTML = formattedUsd
}
function updateAllCalculatedUsdValues (usdExchangeRate) {
$('[data-usd-exchange-rate]').each((i, el) => tryUpdateCalculatedUsdValues(el, usdExchangeRate))
}
updateAllCalculatedUsdValues()
export const exchangeRateChannel = socket.channel(`exchange_rate:new_rate`)
exchangeRateChannel.join()
exchangeRateChannel.on('new_rate', (msg) => updateAllCalculatedUsdValues(humps.camelizeKeys(msg).exchangeRate.usdValue))

@ -1,6 +1,7 @@
import Chart from 'chart.js' import Chart from 'chart.js'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import { formatUsdValue } from '../lib/currency'
import sassVariables from '../../css/app.scss' import sassVariables from '../../css/app.scss'
const config = { const config = {
@ -33,7 +34,7 @@ const config = {
}, },
ticks: { ticks: {
beginAtZero: true, beginAtZero: true,
callback: (value, index, values) => formatPrice(value), callback: (value, index, values) => `$${numeral(value).format('0,0.00')} USD`,
maxTicksLimit: 4 maxTicksLimit: 4
} }
}, { }, {
@ -56,10 +57,10 @@ const config = {
callbacks: { callbacks: {
label: ({datasetIndex, yLabel}, {datasets}) => { label: ({datasetIndex, yLabel}, {datasets}) => {
const label = datasets[datasetIndex].label const label = datasets[datasetIndex].label
if (datasets[datasetIndex].label === 'Price') { if (datasets[datasetIndex].yAxisID === 'price') {
return `${label}: ${formatPrice(yLabel)}` return `${label}: ${formatUsdValue(yLabel)}`
} else if (datasets[datasetIndex].label === 'Market Cap') { } else if (datasets[datasetIndex].yAxisID === 'marketCap') {
return `${label}: ${formatMarketCap(yLabel)}` return `${label}: ${formatUsdValue(yLabel)}`
} else { } else {
return yLabel return yLabel
} }
@ -69,14 +70,6 @@ const config = {
} }
} }
function formatPrice (price) {
return `$${numeral(price).format('0,0.00[0000000000000000]')}`
}
function formatMarketCap (marketCap) {
return numeral(marketCap).format('($0,0a)')
}
function getPriceData (marketHistoryData) { function getPriceData (marketHistoryData) {
return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice})) return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice}))
} }
@ -88,7 +81,7 @@ function getMarketCapData (marketHistoryData, availableSupply) {
class MarketHistoryChart { class MarketHistoryChart {
constructor (el, availableSupply, marketHistoryData) { constructor (el, availableSupply, marketHistoryData) {
this.price = { this.price = {
label: 'Price', label: window.localized['Price'],
yAxisID: 'price', yAxisID: 'price',
data: getPriceData(marketHistoryData), data: getPriceData(marketHistoryData),
fill: false, fill: false,
@ -98,7 +91,7 @@ class MarketHistoryChart {
lineTension: 0 lineTension: 0
} }
this.marketCap = { this.marketCap = {
label: 'Market Cap', label: window.localized['Market Cap'],
yAxisID: 'marketCap', yAxisID: 'marketCap',
data: getMarketCapData(marketHistoryData, availableSupply), data: getMarketCapData(marketHistoryData, availableSupply),
fill: false, fill: false,

@ -4,7 +4,7 @@ import numeral from 'numeral'
import router from '../router' import router from '../router'
import socket from '../socket' import socket from '../socket'
import { updateAllAges } from '../lib/from_now' import { updateAllAges } from '../lib/from_now'
import { formatUsdValue } from '../lib/currency' import { exchangeRateChannel, formatUsdValue } from '../lib/currency'
import { batchChannel, initRedux } from '../utils' import { batchChannel, initRedux } from '../utils'
import { createMarketHistoryChart } from '../lib/market_history_chart' import { createMarketHistoryChart } from '../lib/market_history_chart'
@ -19,7 +19,6 @@ export const initialState = {
newBlock: null, newBlock: null,
newTransactions: [], newTransactions: [],
transactionCount: null, transactionCount: null,
usdExchangeRate: null,
usdMarketCap: null usdMarketCap: null
} }
@ -45,7 +44,6 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
availableSupply: action.msg.exchangeRate.availableSupply, availableSupply: action.msg.exchangeRate.availableSupply,
marketHistoryData: action.msg.marketHistoryData, marketHistoryData: action.msg.marketHistoryData,
usdExchangeRate: action.msg.exchangeRate.usdValue,
usdMarketCap: action.msg.exchangeRate.marketCapUsd usdMarketCap: action.msg.exchangeRate.marketCapUsd
}) })
} }
@ -85,8 +83,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, {
blocksChannel.join() blocksChannel.join()
blocksChannel.on('new_block', msg => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) })) blocksChannel.on('new_block', msg => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }))
const exchangeRateChannel = socket.channel(`exchange_rate:new_rate`)
exchangeRateChannel.join()
exchangeRateChannel.on('new_rate', (msg) => store.dispatch({ type: 'RECEIVED_NEW_EXCHANGE_RATE', msg: humps.camelizeKeys(msg) })) exchangeRateChannel.on('new_rate', (msg) => store.dispatch({ type: 'RECEIVED_NEW_EXCHANGE_RATE', msg: humps.camelizeKeys(msg) }))
const transactionsChannel = socket.channel(`transactions:new_transaction`) const transactionsChannel = socket.channel(`transactions:new_transaction`)
@ -103,7 +99,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, {
const $blockList = $('[data-selector="chain-block-list"]') const $blockList = $('[data-selector="chain-block-list"]')
const $channelBatching = $('[data-selector="channel-batching-message"]') const $channelBatching = $('[data-selector="channel-batching-message"]')
const $channelBatchingCount = $('[data-selector="channel-batching-count"]') const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $exchangeRate = $('[data-selector="exchange-rate"]')
const $marketCap = $('[data-selector="market-cap"]') const $marketCap = $('[data-selector="market-cap"]')
const $transactionsList = $('[data-selector="transactions-list"]') const $transactionsList = $('[data-selector="transactions-list"]')
const $transactionCount = $('[data-selector="transaction-count"]') const $transactionCount = $('[data-selector="transaction-count"]')
@ -114,9 +109,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, {
if (oldState.averageBlockTime !== state.averageBlockTime) { if (oldState.averageBlockTime !== state.averageBlockTime) {
$averageBlockTime.empty().append(state.averageBlockTime) $averageBlockTime.empty().append(state.averageBlockTime)
} }
if (oldState.usdExchangeRate !== state.usdExchangeRate) {
$exchangeRate.empty().append(formatUsdValue(state.usdExchangeRate))
}
if (oldState.usdMarketCap !== state.usdMarketCap) { if (oldState.usdMarketCap !== state.usdMarketCap) {
$marketCap.empty().append(formatUsdValue(state.usdMarketCap)) $marketCap.empty().append(formatUsdValue(state.usdMarketCap))
} }

@ -1448,6 +1448,11 @@
"integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
"dev": true "dev": true
}, },
"bignumber.js": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ=="
},
"binary-extensions": { "binary-extensions": {
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",

@ -20,6 +20,7 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.1.0-4", "@fortawesome/fontawesome-free": "^5.1.0-4",
"bignumber.js": "^7.2.1",
"bootstrap": "^4.1.0", "bootstrap": "^4.1.0",
"chart.js": "^2.7.2", "chart.js": "^2.7.2",
"clipboard": "^2.0.1", "clipboard": "^2.0.1",

@ -1,42 +0,0 @@
defmodule BlockScoutWeb.ExchangeRates.USD do
@moduledoc """
Struct and associated conversion functions for USD currency
"""
@typedoc """
Represents USD currency
* `:value` - value in USD
"""
@type t :: %__MODULE__{
value: Decimal.t() | nil
}
defstruct ~w(value)a
alias Explorer.Chain.Wei
alias Explorer.ExchangeRates.Token
def from(nil), do: null()
def from(%Decimal{} = usd_decimal) do
%__MODULE__{value: usd_decimal}
end
def from(nil, _), do: null()
def from(_, nil), do: null()
def from(%Wei{value: nil}, _), do: null()
def from(_, %Token{usd_value: nil}), do: null()
def from(%Wei{} = wei, %Token{usd_value: exchange_rate}) do
ether = Wei.to(wei, :ether)
%__MODULE__{value: Decimal.mult(ether, exchange_rate)}
end
def null do
%__MODULE__{value: nil}
end
end

@ -4,9 +4,12 @@
<span></span> <span></span>
<div class="text-right"> <div class="text-right">
<h3 class="text-white" data-test="address_balance"><%= balance(@address) %></h3> <h3 class="text-white" data-test="address_balance"><%= balance(@address) %></h3>
<span class="text-light"><%= formatted_usd(@address, @exchange_rate) %></span> <span class="text-light"
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>"
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
</span>
<div class="mt-3" data-token-balance-dropdown data-api_path=<%= address_token_balance_path(@conn, :index, :en, @address.hash) %> > <div class="mt-3" data-token-balance-dropdown data-api_path="<%= address_token_balance_path(@conn, :index, :en, @address.hash) %>">
<p data-loading class="mb-0 text-light" style="display: none;"> <p data-loading class="mb-0 text-light" style="display: none;">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
<%= gettext("Fetching tokens...") %> <%= gettext("Fetching tokens...") %>

@ -9,16 +9,14 @@
<span class="dashboard-banner-chart-legend-label"> <span class="dashboard-banner-chart-legend-label">
<%= gettext "Price" %> <%= gettext "Price" %>
</span> </span>
<span class="dashboard-banner-chart-legend-value" data-selector="exchange-rate"> <span class="dashboard-banner-chart-legend-value" data-selector="exchange-rate" data-wei-value="<%= Explorer.Chain.Wei.from(Decimal.new(1), :ether).value %>" data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
<%= format_exchange_rate(@exchange_rate) %>
</span> </span>
</div> </div>
<div class="dashboard-banner-chart-legend-item"> <div class="dashboard-banner-chart-legend-item">
<span class="dashboard-banner-chart-legend-label"> <span class="dashboard-banner-chart-legend-label">
<%= gettext "Market Cap" %> <%= gettext "Market Cap" %>
</span> </span>
<span class="dashboard-banner-chart-legend-value" data-selector="market-cap"> <span class="dashboard-banner-chart-legend-value" data-selector="market-cap" data-usd-value="<%= @exchange_rate.market_cap_usd %>">
<%= format_market_cap(@exchange_rate) %>
</span> </span>
</div> </div>
</div> </div>

@ -18,6 +18,12 @@
</main> </main>
<%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %> <%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %>
</div> </div>
<script>
window.localized = {
'Market Cap': '<%= gettext("Market Cap") %>',
'Price': '<%= gettext("Price") %>',
}
</script>
<script src="<%= static_path(@conn, "/js/app.js") %>"></script> <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
</body> </body>
</html> </html>

@ -55,7 +55,9 @@
<!-- TX Fee --> <!-- TX Fee -->
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"> <%= gettext "TX Fee" %> </dt> <dt class="col-sm-3 text-muted"> <%= gettext "TX Fee" %> </dt>
<dd class="col-sm-9"> <%= formatted_fee(@transaction, denomination: :ether) %> (<%= formatted_fee(@transaction, exchange_rate: @exchange_rate) %>)</dd> <dd class="col-sm-9">
<%= formatted_fee(@transaction, denomination: :ether) %> (<span data-wei-value=<%= fee(@transaction) %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>)
</dd>
</dl> </dl>
<!-- Input --> <!-- Input -->
@ -82,7 +84,7 @@
<h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2> <h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2>
<div class="text-right"> <div class="text-right">
<h3 class="text-white"> <%= value(@transaction) %></h3> <h3 class="text-white"> <%= value(@transaction) %></h3>
<span class="text-light"> <%= formatted_usd_value(@transaction, @exchange_rate) %></span> <span class="text-light" data-wei-value=<%= @transaction.value.value %> data-usd-exchange-rate=<%= @exchange_rate.usd_value %>></span>
</div> </div>
</div> </div>
</div> </div>

@ -1,10 +1,7 @@
defmodule BlockScoutWeb.AddressView do defmodule BlockScoutWeb.AddressView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias Explorer.Chain.{Address, Hash, SmartContract, Wei} alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.ExchangeRates.USD
@dialyzer :no_match @dialyzer :no_match
@ -37,20 +34,6 @@ defmodule BlockScoutWeb.AddressView do
def contract?(nil), do: true def contract?(nil), do: true
def formatted_usd(%Address{fetched_coin_balance: nil}, _), do: nil
def formatted_usd(%Address{fetched_coin_balance: balance}, %Token{} = exchange_rate) do
case Wei.cast(balance) do
{:ok, wei} ->
wei
|> USD.from(exchange_rate)
|> format_usd_value()
_ ->
nil
end
end
def hash(%Address{hash: hash}) do def hash(%Address{hash: hash}) do
to_string(hash) to_string(hash)
end end

@ -1,9 +1,6 @@
defmodule BlockScoutWeb.ChainView do defmodule BlockScoutWeb.ChainView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.ExchangeRates.USD
def encode_market_history_data(market_history_data) do def encode_market_history_data(market_history_data) do
market_history_data market_history_data
|> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end)
@ -13,16 +10,4 @@ defmodule BlockScoutWeb.ChainView do
_ -> [] _ -> []
end end
end end
def format_exchange_rate(%Token{usd_value: usd_value}) do
usd_value
|> USD.from()
|> format_usd_value()
end
def format_market_cap(%Token{market_cap_usd: market_cap}) do
market_cap
|> USD.from()
|> format_usd_value()
end
end end

@ -3,46 +3,8 @@ defmodule BlockScoutWeb.CurrencyHelpers do
Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values. Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values.
""" """
alias BlockScoutWeb.ExchangeRates.USD
alias BlockScoutWeb.Cldr.Number alias BlockScoutWeb.Cldr.Number
@doc """
Formats a `BlockScoutWeb.ExchangeRates.USD` value into USD and applies a unit label.
## Examples
iex> format_usd_value(%USD{value: Decimal.new(0.0000001)})
"< $0.000001 USD"
iex> format_usd_value(%USD{value: Decimal.new(0.123456789)})
"$0.123457 USD"
iex> format_usd_value(%USD{value: Decimal.new(0.1234)})
"$0.123400 USD"
iex> format_usd_value(%USD{value: Decimal.new(1.23456789)})
"$1.23 USD"
iex> format_usd_value(%USD{value: Decimal.new(1.2)})
"$1.20 USD"
iex> format_usd_value(%USD{value: Decimal.new(123456.789)})
"$123,457 USD"
"""
@spec format_usd_value(USD.t() | nil) :: binary() | nil
def format_usd_value(nil), do: nil
def format_usd_value(%USD{value: nil}), do: nil
def format_usd_value(%USD{value: value}) do
cond do
Decimal.cmp(value, "0.000001") == :lt -> "< $0.000001 USD"
Decimal.cmp(value, 1) == :lt -> "$#{Number.to_string!(value, format: "0.000000")} USD"
Decimal.cmp(value, 100_000) == :lt -> "$#{Number.to_string!(value, format: "#,###.00")} USD"
true -> "$#{Number.to_string!(value, format: "#,###")} USD"
end
end
@doc """ @doc """
Formats the given integer value to a currency format. Formats the given integer value to a currency format.

@ -4,9 +4,7 @@ defmodule BlockScoutWeb.TransactionView do
alias Cldr.Number alias Cldr.Number
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Address, InternalTransaction, Transaction, Wei} alias Explorer.Chain.{Address, InternalTransaction, Transaction, Wei}
alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.{AddressView, BlockView} alias BlockScoutWeb.{AddressView, BlockView}
alias BlockScoutWeb.ExchangeRates.USD
import BlockScoutWeb.Gettext import BlockScoutWeb.Gettext
@ -30,12 +28,16 @@ defmodule BlockScoutWeb.TransactionView do
def to_address_hash(%Transaction{to_address: %Address{hash: address_hash}}), do: address_hash def to_address_hash(%Transaction{to_address: %Address{hash: address_hash}}), do: address_hash
def fee(%Transaction{} = transaction) do
{_, value} = Chain.fee(transaction, :wei)
value
end
def formatted_fee(%Transaction{} = transaction, opts) do def formatted_fee(%Transaction{} = transaction, opts) do
transaction transaction
|> Chain.fee(:wei) |> Chain.fee(:wei)
|> fee_to_currency(opts) |> fee_to_denomination(opts)
|> case do |> case do
{_, nil} -> nil
{:actual, value} -> value {:actual, value} -> value
{:maximum, value} -> "<= " <> value {:maximum, value} -> "<= " <> value
end end
@ -80,12 +82,6 @@ defmodule BlockScoutWeb.TransactionView do
end end
end end
def formatted_usd_value(%Transaction{value: nil}, _token), do: nil
def formatted_usd_value(%Transaction{value: value}, token) do
format_usd_value(USD.from(value, token))
end
defdelegate formatted_timestamp(block), to: BlockView defdelegate formatted_timestamp(block), to: BlockView
def gas(%type{gas: gas}) when is_transaction_type(type) do def gas(%type{gas: gas}) when is_transaction_type(type) do
@ -137,23 +133,6 @@ defmodule BlockScoutWeb.TransactionView do
format_wei_value(value, :ether, include_unit_label: include_label?) format_wei_value(value, :ether, include_unit_label: include_label?)
end end
defp fee_to_currency(fee, options) do
case Keyword.fetch(options, :exchange_rate) do
{:ok, exchange_rate} -> fee_to_usd(fee, exchange_rate)
:error -> fee_to_denomination(fee, options)
end
end
defp fee_to_usd({fee_type, fee}, %Token{} = exchange_rate) do
formatted =
fee
|> Wei.from(:wei)
|> USD.from(exchange_rate)
|> format_usd_value()
{fee_type, formatted}
end
defp fee_to_denomination({fee_type, fee}, opts) do defp fee_to_denomination({fee_type, fee}, opts) do
denomination = Keyword.get(opts, :denomination) denomination = Keyword.get(opts, :denomination)
include_label? = Keyword.get(opts, :include_label, true) include_label? = Keyword.get(opts, :include_label, true)

@ -1,48 +0,0 @@
defmodule BlockScoutWeb.ExchangeRates.USDTest do
use ExUnit.Case, async: true
alias BlockScoutWeb.ExchangeRates.USD
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Wei
describe "from/2" do
test "with nil wei returns null object" do
token = %Token{usd_value: Decimal.new(0.5)}
assert USD.null() == USD.from(nil, token)
end
test "with nil token returns nil" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
assert USD.null() == USD.from(wei, nil)
end
test "without a wei value returns nil" do
wei = %Wei{value: nil}
token = %Token{usd_value: Decimal.new(0.5)}
assert USD.null() == USD.from(wei, token)
end
test "without an exchange rate returns nil" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
token = %Token{usd_value: nil}
assert USD.null() == USD.from(wei, token)
end
test "returns formatted usd value" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
token = %Token{usd_value: Decimal.new(0.5)}
assert %USD{value: Decimal.new(0.000005)} == USD.from(wei, token)
end
test "returns USD struct from decimal usd value" do
value = Decimal.new(0.000005)
assert %USD{value: ^value} = USD.from(value)
end
end
end

@ -6,7 +6,6 @@ defmodule BlockScoutWeb.ChainPage do
import Wallaby.Query, only: [css: 1, css: 2] import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.Transaction alias Explorer.Chain.Transaction
alias BlockScoutWeb.ChainView
def blocks(count: count) do def blocks(count: count) do
css("[data-selector='chain-block']", count: count) css("[data-selector='chain-block']", count: count)
@ -16,10 +15,6 @@ defmodule BlockScoutWeb.ChainPage do
css("[data-test='contract-creation'] [data-address-hash='#{hash}']") css("[data-test='contract-creation'] [data-address-hash='#{hash}']")
end end
def exchange_rate(token) do
css("[data-selector='exchange-rate']", text: ChainView.format_exchange_rate(token))
end
def non_loaded_transaction_count(count) do def non_loaded_transaction_count(count) do
css("[data-selector='channel-batching-count']", text: count) css("[data-selector='channel-batching-count']", text: count)
end end

@ -3,7 +3,6 @@ defmodule BlockScoutWeb.AddressViewTest do
alias Explorer.Chain.Data alias Explorer.Chain.Data
alias BlockScoutWeb.AddressView alias BlockScoutWeb.AddressView
alias Explorer.ExchangeRates.Token
describe "contract?/1" do describe "contract?/1" do
test "with a smart contract" do test "with a smart contract" do
@ -18,26 +17,6 @@ defmodule BlockScoutWeb.AddressViewTest do
end end
end end
describe "formatted_usd/2" do
test "without a fetched_coin_balance returns nil" do
address = build(:address, fetched_coin_balance: nil)
token = %Token{usd_value: Decimal.new(0.5)}
assert nil == AddressView.formatted_usd(address, token)
end
test "without a usd_value returns nil" do
address = build(:address)
token = %Token{usd_value: nil}
assert nil == AddressView.formatted_usd(address, token)
end
test "returns formatted usd value" do
address = build(:address, fetched_coin_balance: 10_000_000_000_000)
token = %Token{usd_value: Decimal.new(0.5)}
assert "$0.000005 USD" == AddressView.formatted_usd(address, token)
end
end
describe "qr_code/1" do describe "qr_code/1" do
test "it returns an encoded value" do test "it returns an encoded value" do
address = build(:address) address = build(:address)

@ -1,7 +1,6 @@
defmodule BlockScoutWeb.ChainViewTest do defmodule BlockScoutWeb.ChainViewTest do
use BlockScoutWeb.ConnCase, async: true use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.ChainView alias BlockScoutWeb.ChainView
describe "encode_market_history_data/1" do describe "encode_market_history_data/1" do
@ -17,22 +16,4 @@ defmodule BlockScoutWeb.ChainViewTest do
ChainView.encode_market_history_data(market_history_data) ChainView.encode_market_history_data(market_history_data)
end end
end end
describe "format_exchange_rate/1" do
test "returns a formatted usd value from a `Token`'s usd_value" do
token = %Token{usd_value: Decimal.new(5.45)}
assert "$5.45 USD" == ChainView.format_exchange_rate(token)
assert nil == ChainView.format_exchange_rate(%Token{usd_value: nil})
end
end
describe "format_market_cap/1" do
test "returns a formatted usd value from a `Token`'s market_cap_usd" do
token = %Token{market_cap_usd: Decimal.new(5.4)}
assert "$5.40 USD" == ChainView.format_market_cap(token)
assert nil == ChainView.format_market_cap(%Token{market_cap_usd: nil})
end
end
end end

@ -2,18 +2,9 @@ defmodule BlockScoutWeb.CurrencyHelpersTest do
use ExUnit.Case use ExUnit.Case
alias BlockScoutWeb.CurrencyHelpers alias BlockScoutWeb.CurrencyHelpers
alias BlockScoutWeb.ExchangeRates.USD
doctest BlockScoutWeb.CurrencyHelpers, import: true doctest BlockScoutWeb.CurrencyHelpers, import: true
test "with nil it returns nil" do
assert nil == CurrencyHelpers.format_usd_value(nil)
end
test "with USD.null() it returns nil" do
assert nil == CurrencyHelpers.format_usd_value(USD.null())
end
describe "format_according_to_decimals/1" do describe "format_according_to_decimals/1" do
test "formats the amount as value considering the given decimals" do test "formats the amount as value considering the given decimals" do
amount = Decimal.new(205_000_000_000_000) amount = Decimal.new(205_000_000_000_000)

@ -2,7 +2,6 @@ defmodule BlockScoutWeb.TransactionViewTest do
use BlockScoutWeb.ConnCase, async: true use BlockScoutWeb.ConnCase, async: true
alias Explorer.Chain.Wei alias Explorer.Chain.Wei
alias Explorer.ExchangeRates.Token
alias Explorer.Repo alias Explorer.Repo
alias BlockScoutWeb.TransactionView alias BlockScoutWeb.TransactionView
@ -18,21 +17,16 @@ defmodule BlockScoutWeb.TransactionViewTest do
gas_used: nil gas_used: nil
) )
token = %Token{usd_value: Decimal.new(0.50)}
expected_value = "<= 0.009 POA" expected_value = "<= 0.009 POA"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
assert "<= $0.004500 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token)
end end
test "with fee and exchange_rate" do test "with fee" do
{:ok, gas_price} = Wei.cast(3_000_000_000) {:ok, gas_price} = Wei.cast(3_000_000_000)
transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.new(1_034_234.0)) transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.new(1_034_234.0))
token = %Token{usd_value: Decimal.new(0.50)}
expected_value = "0.003102702 POA" expected_value = "0.003102702 POA"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
assert "$0.001551 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token)
end end
test "with fee but no available exchange_rate" do test "with fee but no available exchange_rate" do

Loading…
Cancel
Save