Merge branch 'master' into 638

pull/661/head
Andrew Cravenho 6 years ago committed by GitHub
commit 5723fe512b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .gitignore
  2. 6
      apps/block_scout_web/assets/__tests__/lib/currency.js
  3. 2
      apps/block_scout_web/assets/__tests__/pages/chain.js
  4. 2
      apps/block_scout_web/assets/js/app.js
  5. 38
      apps/block_scout_web/assets/js/lib/currency.js
  6. 23
      apps/block_scout_web/assets/js/lib/market_history_chart.js
  7. 3
      apps/block_scout_web/assets/js/lib/stop_propagation.js
  8. 10
      apps/block_scout_web/assets/js/pages/chain.js
  9. 5
      apps/block_scout_web/assets/package-lock.json
  10. 1
      apps/block_scout_web/assets/package.json
  11. 42
      apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex
  12. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex
  13. 20
      apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex
  14. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  15. 2
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex
  16. 6
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  17. 7
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  18. 6
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  19. 19
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  20. 15
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  21. 38
      apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex
  22. 35
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  23. 75
      apps/block_scout_web/priv/gettext/default.pot
  24. 75
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  25. 48
      apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs
  26. 5
      apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex
  27. 21
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  28. 19
      apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs
  29. 9
      apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs
  30. 10
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  31. 25
      apps/indexer/lib/indexer/token_balances.ex
  32. 36
      apps/indexer/test/indexer/token_balances_test.exs
  33. 2
      coveralls.json

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,7 +1,11 @@
import { formatUsdValue } from '../../js/lib/currency' import { formatUsdValue } from '../../js/lib/currency'
test('formatUsdValue', () => { test('formatUsdValue', () => {
expect(formatUsdValue(0.0000001)).toEqual('< $0.000001 USD') window.localized = {
'Less than': 'Less than'
}
expect(formatUsdValue(0)).toEqual('$0.000000 USD')
expect(formatUsdValue(0.0000001)).toEqual('Less than $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')
expect(formatUsdValue(1.23456789)).toEqual('$1.23 USD') expect(formatUsdValue(1.23456789)).toEqual('$1.23 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'
@ -31,6 +32,7 @@ import './lib/pretty_json'
import './lib/try_api' import './lib/try_api'
import './lib/token_balance_dropdown' import './lib/token_balance_dropdown'
import './lib/token_transfers_toggle' import './lib/token_transfers_toggle'
import './lib/stop_propagation'
import './pages/address' import './pages/address'
import './pages/block' import './pages/block'

@ -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.000001) return '< $0.000001 USD' if (value === 0) return '$0.000000 USD'
if (value < 0.000001) return `${window.localized['Less than']} $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')}`,
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,

@ -0,0 +1,3 @@
import $ from 'jquery'
$('[data-selector="stop-propagation"]').click((event) => event.stopPropagation())

@ -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...") %>

@ -1,20 +0,0 @@
<div class="card">
<div class="card-body">
<h2 class="card-title"><%= gettext "Token Holdings" %></h2>
<!-- Dropdown -->
<div data-token-balance-dropdown
data-api_path=<%= address_token_balance_path(@conn, :index, :en, @address.hash) %>
class="icon-links ml-3 mb-3"
>
<p data-loading class="mb-0" stytle="display: none">
<i class="fa fa-spinner fa-spin"></i>
<%= gettext("Fetching tokens...") %>
</p>
<p data-error-message class="mb-0" style="display: none">
<%= gettext("Error tryng to fetch balances.") %>
</p>
</div>
</div>
</div>

@ -51,7 +51,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-section col-md-6 col-lg-4" data-selector="balance-card"> <div class="card-section col-md-12 col-lg-4" data-selector="balance-card">
<%= render BlockScoutWeb.AddressView, "_balance_card.html", conn: @conn, address: @address, exchange_rate: @exchange_rate %> <%= render BlockScoutWeb.AddressView, "_balance_card.html", conn: @conn, address: @address, exchange_rate: @exchange_rate %>
</div> </div>

@ -7,7 +7,7 @@
<span class="badge badge-primary tile-badge float-right mr-1"><%= gettext "GET" %></span> <span class="badge badge-primary tile-badge float-right mr-1"><%= gettext "GET" %></span>
<strong class="tile-label"><%= @action.name %></strong> <strong class="tile-label"><%= @action.name %></strong>
</h3> </h3>
<h4 class="text-dark"><%= raw @action.description %></h4> <h4 class="text-dark"><span data-selector="stop-propagation"><%= raw @action.description %></span></h4>
<code><%= raw query_params(@module_name, @action) %></code> <code><%= raw query_params(@module_name, @action) %></code>
</button> </button>
</div> </div>

@ -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,13 @@
</main> </main>
<%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %> <%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %>
</div> </div>
<script>
window.localized = {
'Less than': '<%= gettext("Less than") %>',
'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,14 +28,18 @@ 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} -> "#{gettext("Max of")} #{value}"
end end
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)

@ -6,7 +6,7 @@ msgstr ""
msgid "Block" msgid "Block"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:58 #: lib/block_scout_web/templates/chain/show.html.eex:56
#: lib/block_scout_web/templates/layout/_topnav.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:13
msgid "Blocks" msgid "Blocks"
msgstr "" msgstr ""
@ -43,14 +43,14 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26 #: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:36 #: lib/block_scout_web/templates/block_transaction/index.html.eex:36
#: lib/block_scout_web/templates/chain/show.html.eex:75 #: lib/block_scout_web/templates/chain/show.html.eex:73
#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #: lib/block_scout_web/templates/layout/_topnav.html.eex:18
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:46 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:46
#: lib/block_scout_web/templates/transaction/index.html.eex:56 #: lib/block_scout_web/templates/transaction/index.html.eex:56
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:82 #: lib/block_scout_web/templates/transaction/overview.html.eex:84
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -110,7 +110,7 @@ msgstr ""
msgid "Cumulative Gas Used" msgid "Cumulative Gas Used"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:93 #: lib/block_scout_web/templates/transaction/overview.html.eex:95
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -118,7 +118,7 @@ msgstr ""
msgid "Gas Price" msgid "Gas Price"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:63 #: lib/block_scout_web/templates/transaction/overview.html.eex:65
msgid "Input" msgid "Input"
msgstr "" msgstr ""
@ -130,7 +130,7 @@ msgstr ""
msgid "%{count} transactions in this block" msgid "%{count} transactions in this block"
msgstr "" msgstr ""
#: lib/block_scout_web/views/address_view.ex:15 #: lib/block_scout_web/views/address_view.ex:12
msgid "Address" msgid "Address"
msgstr "" msgstr ""
@ -146,7 +146,7 @@ msgstr ""
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:79 #: lib/block_scout_web/views/transaction_view.ex:81
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -198,8 +198,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/index.html.eex:16 #: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35 #: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:40 #: lib/block_scout_web/templates/transaction/overview.html.eex:40
#: lib/block_scout_web/views/transaction_view.ex:44 #: lib/block_scout_web/views/transaction_view.ex:46
#: lib/block_scout_web/views/transaction_view.ex:78 #: lib/block_scout_web/views/transaction_view.ex:80
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -267,11 +267,11 @@ msgstr ""
msgid "Next Page" msgid "Next Page"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:76 #: lib/block_scout_web/views/transaction_view.ex:78
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:77 #: lib/block_scout_web/views/transaction_view.ex:79
msgid "Out of Gas" msgid "Out of Gas"
msgstr "" msgstr ""
@ -291,7 +291,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22 #: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24 #: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:82 #: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16 #: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72 #: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether" msgid "Ether"
@ -391,11 +391,13 @@ msgstr ""
msgid "Avg Block Time" msgid "Avg Block Time"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:17
#: lib/block_scout_web/templates/layout/app.html.eex:24
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:10 #: lib/block_scout_web/templates/chain/show.html.eex:10
#: lib/block_scout_web/templates/layout/app.html.eex:25
msgid "Price" msgid "Price"
msgstr "" msgstr ""
@ -439,7 +441,7 @@ msgstr ""
msgid "Total Gas Used" msgid "Total Gas Used"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:124 #: lib/block_scout_web/views/transaction_view.ex:120
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -462,7 +464,7 @@ msgstr ""
msgid "Contract" msgid "Contract"
msgstr "" msgstr ""
#: lib/block_scout_web/views/address_view.ex:13 #: lib/block_scout_web/views/address_view.ex:10
msgid "Contract Address" msgid "Contract Address"
msgstr "" msgstr ""
@ -524,7 +526,7 @@ msgid "Newer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:122 #: lib/block_scout_web/views/transaction_view.ex:118
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -554,27 +556,27 @@ msgid "Twitter"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:57 #: lib/block_scout_web/templates/chain/show.html.eex:55
msgid "View All Blocks →" msgid "View All Blocks →"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:74 #: lib/block_scout_web/templates/chain/show.html.eex:72
msgid "View All Transactions →" msgid "View All Transactions →"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28 #: lib/block_scout_web/templates/chain/show.html.eex:26
msgid "Average block time" msgid "Average block time"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:36 #: lib/block_scout_web/templates/chain/show.html.eex:34
msgid "Total transactions" msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:44 #: lib/block_scout_web/templates/chain/show.html.eex:42
msgid "Wallet addresses" msgid "Wallet addresses"
msgstr "" msgstr ""
@ -621,7 +623,7 @@ msgid "Contract Address Pending"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:123 #: lib/block_scout_web/views/transaction_view.ex:119
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
@ -698,7 +700,7 @@ msgid "Block Confirmations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:105 #: lib/block_scout_web/templates/transaction/overview.html.eex:107
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
@ -714,14 +716,14 @@ msgid "There are no logs for this transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:98 #: lib/block_scout_web/templates/transaction/overview.html.eex:100
msgid "Used" msgid "Used"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4 #: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:121 #: lib/block_scout_web/views/transaction_view.ex:117
msgid "Token Transfer" msgid "Token Transfer"
msgstr "" msgstr ""
@ -739,7 +741,7 @@ msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:84 #: lib/block_scout_web/templates/address_transaction/index.html.eex:84
#: lib/block_scout_web/templates/chain/show.html.eex:71 #: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/transaction/index.html.eex:48 #: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in" msgid "More transactions have come in"
msgstr "" msgstr ""
@ -821,22 +823,15 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:16 #: lib/block_scout_web/templates/address/_balance_card.html.eex:19
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:16
msgid "Error tryng to fetch balances." msgid "Error tryng to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:12 #: lib/block_scout_web/templates/address/_balance_card.html.eex:15
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:12
msgid "Fetching tokens..." msgid "Fetching tokens..."
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:3
msgid "Token Holdings"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:38 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:38
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53
@ -995,3 +990,13 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68 #: lib/block_scout_web/templates/transaction/_tile.html.eex:68
msgid "View Less Transfers" msgid "View Less Transfers"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:23
msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of"
msgstr ""

@ -18,7 +18,7 @@ msgstr "Age"
msgid "Block" msgid "Block"
msgstr "Block" msgstr "Block"
#: lib/block_scout_web/templates/chain/show.html.eex:58 #: lib/block_scout_web/templates/chain/show.html.eex:56
#: lib/block_scout_web/templates/layout/_topnav.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:13
msgid "Blocks" msgid "Blocks"
msgstr "Blocks" msgstr "Blocks"
@ -55,14 +55,14 @@ msgstr "BlockScout"
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26 #: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:36 #: lib/block_scout_web/templates/block_transaction/index.html.eex:36
#: lib/block_scout_web/templates/chain/show.html.eex:75 #: lib/block_scout_web/templates/chain/show.html.eex:73
#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #: lib/block_scout_web/templates/layout/_topnav.html.eex:18
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:46 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:46
#: lib/block_scout_web/templates/transaction/index.html.eex:56 #: lib/block_scout_web/templates/transaction/index.html.eex:56
msgid "Transactions" msgid "Transactions"
msgstr "Transactions" msgstr "Transactions"
#: lib/block_scout_web/templates/transaction/overview.html.eex:82 #: lib/block_scout_web/templates/transaction/overview.html.eex:84
msgid "Value" msgid "Value"
msgstr "Value" msgstr "Value"
@ -122,7 +122,7 @@ msgstr "Transaction Details"
msgid "Cumulative Gas Used" msgid "Cumulative Gas Used"
msgstr "Cumulative Gas Used" msgstr "Cumulative Gas Used"
#: lib/block_scout_web/templates/transaction/overview.html.eex:93 #: lib/block_scout_web/templates/transaction/overview.html.eex:95
msgid "Gas" msgid "Gas"
msgstr "Gas" msgstr "Gas"
@ -130,7 +130,7 @@ msgstr "Gas"
msgid "Gas Price" msgid "Gas Price"
msgstr "Gas Price" msgstr "Gas Price"
#: lib/block_scout_web/templates/transaction/overview.html.eex:63 #: lib/block_scout_web/templates/transaction/overview.html.eex:65
msgid "Input" msgid "Input"
msgstr "Input" msgstr "Input"
@ -142,7 +142,7 @@ msgstr "%{confirmations} block confirmations"
msgid "%{count} transactions in this block" msgid "%{count} transactions in this block"
msgstr "%{count} transactions in this block" msgstr "%{count} transactions in this block"
#: lib/block_scout_web/views/address_view.ex:15 #: lib/block_scout_web/views/address_view.ex:12
msgid "Address" msgid "Address"
msgstr "Address" msgstr "Address"
@ -158,7 +158,7 @@ msgstr "From"
msgid "Overview" msgid "Overview"
msgstr "Overview" msgstr "Overview"
#: lib/block_scout_web/views/transaction_view.ex:79 #: lib/block_scout_web/views/transaction_view.ex:81
msgid "Success" msgid "Success"
msgstr "Success" msgstr "Success"
@ -210,8 +210,8 @@ msgstr "Showing %{count} Transactions"
#: lib/block_scout_web/templates/transaction/index.html.eex:16 #: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35 #: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:40 #: lib/block_scout_web/templates/transaction/overview.html.eex:40
#: lib/block_scout_web/views/transaction_view.ex:44 #: lib/block_scout_web/views/transaction_view.ex:46
#: lib/block_scout_web/views/transaction_view.ex:78 #: lib/block_scout_web/views/transaction_view.ex:80
msgid "Pending" msgid "Pending"
msgstr "Pending" msgstr "Pending"
@ -279,11 +279,11 @@ msgstr ""
msgid "Next Page" msgid "Next Page"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:76 #: lib/block_scout_web/views/transaction_view.ex:78
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:77 #: lib/block_scout_web/views/transaction_view.ex:79
msgid "Out of Gas" msgid "Out of Gas"
msgstr "" msgstr ""
@ -303,7 +303,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22 #: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24 #: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:82 #: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16 #: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72 #: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether" msgid "Ether"
@ -403,11 +403,13 @@ msgstr ""
msgid "Avg Block Time" msgid "Avg Block Time"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:17
#: lib/block_scout_web/templates/layout/app.html.eex:24
msgid "Market Cap" msgid "Market Cap"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:10 #: lib/block_scout_web/templates/chain/show.html.eex:10
#: lib/block_scout_web/templates/layout/app.html.eex:25
msgid "Price" msgid "Price"
msgstr "" msgstr ""
@ -451,7 +453,7 @@ msgstr ""
msgid "Total Gas Used" msgid "Total Gas Used"
msgstr "" msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:124 #: lib/block_scout_web/views/transaction_view.ex:120
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -474,7 +476,7 @@ msgstr ""
msgid "Contract" msgid "Contract"
msgstr "" msgstr ""
#: lib/block_scout_web/views/address_view.ex:13 #: lib/block_scout_web/views/address_view.ex:10
msgid "Contract Address" msgid "Contract Address"
msgstr "" msgstr ""
@ -536,7 +538,7 @@ msgid "Newer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:122 #: lib/block_scout_web/views/transaction_view.ex:118
msgid "Contract Creation" msgid "Contract Creation"
msgstr "" msgstr ""
@ -566,27 +568,27 @@ msgid "Twitter"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:57 #: lib/block_scout_web/templates/chain/show.html.eex:55
msgid "View All Blocks →" msgid "View All Blocks →"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:74 #: lib/block_scout_web/templates/chain/show.html.eex:72
msgid "View All Transactions →" msgid "View All Transactions →"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:28 #: lib/block_scout_web/templates/chain/show.html.eex:26
msgid "Average block time" msgid "Average block time"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:36 #: lib/block_scout_web/templates/chain/show.html.eex:34
msgid "Total transactions" msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:44 #: lib/block_scout_web/templates/chain/show.html.eex:42
msgid "Wallet addresses" msgid "Wallet addresses"
msgstr "" msgstr ""
@ -633,7 +635,7 @@ msgid "Contract Address Pending"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:123 #: lib/block_scout_web/views/transaction_view.ex:119
msgid "Contract Call" msgid "Contract Call"
msgstr "" msgstr ""
@ -710,7 +712,7 @@ msgid "Block Confirmations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:105 #: lib/block_scout_web/templates/transaction/overview.html.eex:107
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
@ -726,14 +728,14 @@ msgid "There are no logs for this transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:98 #: lib/block_scout_web/templates/transaction/overview.html.eex:100
msgid "Used" msgid "Used"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4 #: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:121 #: lib/block_scout_web/views/transaction_view.ex:117
msgid "Token Transfer" msgid "Token Transfer"
msgstr "" msgstr ""
@ -751,7 +753,7 @@ msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:84 #: lib/block_scout_web/templates/address_transaction/index.html.eex:84
#: lib/block_scout_web/templates/chain/show.html.eex:71 #: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/transaction/index.html.eex:48 #: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in" msgid "More transactions have come in"
msgstr "" msgstr ""
@ -833,22 +835,15 @@ msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:16 #: lib/block_scout_web/templates/address/_balance_card.html.eex:19
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:16
msgid "Error tryng to fetch balances." msgid "Error tryng to fetch balances."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:12 #: lib/block_scout_web/templates/address/_balance_card.html.eex:15
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:12
msgid "Fetching tokens..." msgid "Fetching tokens..."
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:3
msgid "Token Holdings"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:38 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:38
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53
@ -1007,3 +1002,13 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68 #: lib/block_scout_web/templates/transaction/_tile.html.eex:68
msgid "View Less Transfers" msgid "View Less Transfers"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:23
msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of"
msgstr ""

@ -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 = "max of 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

@ -5,11 +5,26 @@ defmodule Indexer.TokenBalances do
alias Explorer.Token.BalanceReader alias Explorer.Token.BalanceReader
@doc """
Fetches TokenBalances from specific Addresses and Blocks in the Blockchain
Every `TokenBalance` is fetched asynchronously, but in case an exception is raised (such as a
timeout) during the RPC call the particular TokenBalance request is ignored.
## token_balances
It is a list of a Map so that each map must have:
* `token_contract_address_hash` - The contract address that represents the Token in the blockchain.
* `address_hash` - The address_hash that we want to know the balance.
* `block_number` - The block number that the address_hash has the balance.
"""
def fetch_token_balances_from_blockchain(token_balances) do def fetch_token_balances_from_blockchain(token_balances) do
result = result =
token_balances token_balances
|> Task.async_stream(&fetch_token_balance/1) |> Task.async_stream(&fetch_token_balance/1, on_timeout: :kill_task)
|> Enum.map(&format_result/1) |> Stream.map(&format_task_results/1)
|> Enum.filter(&ignore_request_with_timeouts/1)
{:ok, result} {:ok, result}
end end
@ -34,5 +49,9 @@ defmodule Indexer.TokenBalances do
Map.merge(token_balance, %{value: nil, value_fetched_at: nil}) Map.merge(token_balance, %{value: nil, value_fetched_at: nil})
end end
def format_result({_, token_balance}), do: token_balance def format_task_results({:exit, :timeout}), do: {:error, :timeout}
def format_task_results({:ok, token_balance}), do: token_balance
def ignore_request_with_timeouts({:error, :timeout}), do: false
def ignore_request_with_timeouts(_token_balance), do: true
end end

@ -60,6 +60,32 @@ defmodule Indexer.TokenBalancesTest do
value_fetched_at: nil value_fetched_at: nil
} = List.first(result) } = List.first(result)
end end
test "ignores results that raised :timeout" do
address = insert(:address)
token = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
token_balance_params = [
%{
token_contract_address_hash: Hash.to_string(token.contract_address_hash),
address_hash: address_hash_string,
block_number: 1_000
},
%{
token_contract_address_hash: Hash.to_string(token.contract_address_hash),
address_hash: address_hash_string,
block_number: 1_001
}
]
get_balance_from_blockchain()
get_balance_from_blockchain_with_timeout()
{:ok, result} = TokenBalances.fetch_token_balances_from_blockchain(token_balance_params)
assert length(result) == 1
end
end end
defp get_balance_from_blockchain() do defp get_balance_from_blockchain() do
@ -79,6 +105,16 @@ defmodule Indexer.TokenBalancesTest do
) )
end end
defp get_balance_from_blockchain_with_timeout() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: _, method: _, params: [%{data: _, to: _}, _]}], _options ->
:timer.sleep(5001)
end
)
end
defp get_balance_from_blockchain_with_error() do defp get_balance_from_blockchain_with_error() do
expect( expect(
EthereumJSONRPC.Mox, EthereumJSONRPC.Mox,

@ -1,7 +1,7 @@
{ {
"coverage_options": { "coverage_options": {
"treat_no_relevant_lines_as_covered": true, "treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 94.5 "minimum_coverage": 88
}, },
"terminal_options": { "terminal_options": {
"file_column_width": 120 "file_column_width": 120

Loading…
Cancel
Save