Land #128: Replace form modules with proper view module functions

pull/131/head
Luke Imhoff 7 years ago committed by GitHub
commit 3e55171fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      .circleci/config.yml
  2. 2
      .credo.exs
  3. 61
      apps/explorer/lib/explorer/chain.ex
  4. 13
      apps/explorer/lib/explorer/chain/block.ex
  5. 10
      apps/explorer/lib/explorer/chain/gas.ex
  6. 44
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  7. 143
      apps/explorer/lib/explorer/chain/transaction.ex
  8. 105
      apps/explorer/lib/explorer/chain/wei.ex
  9. 5
      apps/explorer/test/explorer/chain/wei_test.exs
  10. 56
      apps/explorer/test/explorer/chain_test.exs
  11. 2
      apps/explorer/test/support/factories/chain/receipt_factory.ex
  12. 13
      apps/explorer_web/README.md
  13. 4
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex
  14. 4
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex
  15. 6
      apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
  16. 4
      apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
  17. 32
      apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex
  18. 11
      apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex
  19. 18
      apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex
  20. 22
      apps/explorer_web/lib/explorer_web/forms/block_form.ex
  21. 42
      apps/explorer_web/lib/explorer_web/forms/pending_transaction_form.ex
  22. 112
      apps/explorer_web/lib/explorer_web/forms/transaction_form.ex
  23. 20
      apps/explorer_web/lib/explorer_web/helpers/wei_converter.ex
  24. 2
      apps/explorer_web/lib/explorer_web/templates/address/show.html.eex
  25. 8
      apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex
  26. 8
      apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex
  27. 4
      apps/explorer_web/lib/explorer_web/templates/block/index.html.eex
  28. 4
      apps/explorer_web/lib/explorer_web/templates/block/show.html.eex
  29. 8
      apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
  30. 2
      apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
  31. 22
      apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex
  32. 4
      apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex
  33. 27
      apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex
  34. 8
      apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex
  35. 6
      apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex
  36. 6
      apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex
  37. 8
      apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
  38. 13
      apps/explorer_web/lib/explorer_web/views/block_view.ex
  39. 5
      apps/explorer_web/lib/explorer_web/views/chain_view.ex
  40. 24
      apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex
  41. 76
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  42. 2
      apps/explorer_web/mix.exs
  43. 164
      apps/explorer_web/priv/gettext/default.pot
  44. 164
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  45. 27
      apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs
  46. 28
      apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs
  47. 33
      apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs
  48. 34
      apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs
  49. 20
      apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs
  50. 3
      apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs
  51. 17
      apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs
  52. 30
      apps/explorer_web/test/explorer_web/forms/block_form_test.exs
  53. 37
      apps/explorer_web/test/explorer_web/forms/pending_transaction_form_test.exs
  54. 308
      apps/explorer_web/test/explorer_web/forms/transaction_form_test.exs
  55. 19
      apps/explorer_web/test/explorer_web/helpers/wei_converter_test.exs
  56. 57
      apps/explorer_web/test/explorer_web/views/transaction_view_test.exs
  57. 2
      coveralls.json
  58. 3
      mix.lock

@ -259,6 +259,30 @@ jobs:
- store_test_results:
path: apps/explorer_web/assets/test
gettext:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.4
environment:
MIX_ENV: test
working_directory: ~/app
steps:
- attach_workspace:
at: .
- run: mix local.hex --force
- run:
name: Check for missed translations
command: |
mix gettext.extract --merge | tee stdout.txt
! grep "Wrote " stdout.txt
working_directory: "apps/explorer_web"
- store_artifacts:
path: apps/explorer_web/priv/gettext
sobelow:
docker:
# Ensure .tool-versions matches
@ -361,6 +385,9 @@ workflows:
- eslint:
requires:
- build
- gettext:
requires:
- build
- sobelow:
requires:
- build

@ -63,7 +63,7 @@
# You can customize the priority of any check
# Priority values are: `low, normal, high, higher`
#
{Credo.Check.Design.AliasUsage, priority: :low},
{Credo.Check.Design.AliasUsage, excluded_lastnames: ~w(Number Time), priority: :low},
# For some checks, you can also set other parameters
#

@ -5,7 +5,17 @@ defmodule Explorer.Chain do
import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3]
alias Explorer.Chain.{Address, Block, BlockTransaction, InternalTransaction, Log, Transaction}
alias Explorer.Chain.{
Address,
Block,
BlockTransaction,
InternalTransaction,
Log,
Receipt,
Transaction,
Wei
}
alias Explorer.Repo.NewRelic, as: Repo
# Types
@ -162,6 +172,16 @@ defmodule Explorer.Chain do
address_to_transactions(address, Keyword.put(options, :direction, :from))
end
@doc """
The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`.
"""
@spec gas_price(Transaction.t(), :wei) :: Wei.t()
@spec gas_price(Transaction.t(), :gwei) :: Wei.gwei()
@spec gas_price(Transaction.t(), :ether) :: Wei.ether()
def gas_price(%Transaction{gas_price: gas_price}, unit) do
Wei.to(gas_price, unit)
end
@doc """
Converts `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`.
@ -417,6 +437,31 @@ defmodule Explorer.Chain do
transaction_hash_to_logs(hash, options)
end
@doc """
Converts `transaction` with its `receipt` loaded to the status of the `t:Explorer.Chain.Transaction.t/0`.
## Returns
* `:failed` - the transaction failed without running out of gas
* `:pending` - the transaction has not be confirmed in a block yet
* `:out_of_gas` - the transaction failed because it ran out of gas
* `:success` - the transaction has been confirmed in a block
"""
@spec transaction_to_status(Transaction.t()) :: :failed | :pending | :out_of_gas | :success
def transaction_to_status(%Transaction{receipt: nil}), do: :pending
def transaction_to_status(%Transaction{receipt: %Receipt{status: 1}}), do: :success
def transaction_to_status(%Transaction{
gas: gas,
receipt: %Receipt{gas_used: gas_used, status: 0}
})
when gas_used >= gas do
:out_of_gas
end
def transaction_to_status(%Transaction{receipt: %Receipt{status: 0}}), do: :failed
@doc """
Updates `balance` of `t:Explorer.Address.t/0` with `hash`.
@ -436,6 +481,20 @@ defmodule Explorer.Chain do
end
end
@doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
"""
@spec value(InternalTransaction.t(), :wei) :: Wei.t()
@spec value(InternalTransaction.t(), :gwei) :: Wei.gwei()
@spec value(InternalTransaction.t(), :ether) :: Wei.ether()
@spec value(Transaction.t(), :wei) :: Wei.t()
@spec value(Transaction.t(), :gwei) :: Wei.gwei()
@spec value(Transaction.t(), :ether) :: Wei.ether()
def value(%type{value: value}, unit) when type in [InternalTransaction, Transaction] do
Wei.to(value, unit)
end
## Private Functions
defp address_id_to_transactions(address_id, named_arguments)

@ -7,7 +7,7 @@ defmodule Explorer.Chain.Block do
use Explorer.Schema
alias Explorer.Chain.{BlockTransaction, Hash, Transaction}
alias Explorer.Chain.{BlockTransaction, Gas, Hash, Transaction}
# Types
@ -18,13 +18,6 @@ defmodule Explorer.Chain.Block do
"""
@type difficulty :: Decimal.t()
@typedoc """
A measurement roughly equivalent to computational steps. Every operation has a gas expenditure; for most operations
it is ~3-10, although some expensive operations have expenditures up to 700 and a transaction itself has an
expenditure of 21000.
"""
@type gas :: non_neg_integer()
@typedoc """
Number of the block in the chain.
"""
@ -52,8 +45,8 @@ defmodule Explorer.Chain.Block do
@type t :: %__MODULE__{
block_transactions: %Ecto.Association.NotLoaded{} | [BlockTransaction.t()],
difficulty: difficulty(),
gas_limit: gas(),
gas_used: gas(),
gas_limit: Gas.t(),
gas_used: Gas.t(),
hash: Hash.t(),
miner: Address.hash(),
nonce: Hash.t(),

@ -0,0 +1,10 @@
defmodule Explorer.Chain.Gas do
@moduledoc """
A measurement roughly equivalent to computational steps. Every operation has a gas expenditure; for most operations
it is ~3-10, although some expensive operations have expenditures up to 700 and a transaction itself has an
expenditure of 21000.
"""
@typedoc @moduledoc
@type t :: non_neg_integer()
end

@ -3,7 +3,49 @@ defmodule Explorer.Chain.InternalTransaction do
use Explorer.Schema
alias Explorer.Chain.{Address, Transaction}
alias Explorer.Chain.{Address, Gas, Transaction, Wei}
@typedoc """
* `"call"`
* `"callcode"`
* `"delegatecall"`
* `"none"`
* `"staticcall"
"""
@type call_type :: String.t()
@typedoc """
* `call_type` - the type of call
* `from_address` - the source of the `value`
* `from_address_id` - foreign key for `from_address`
* `gas` - the amount of gas allowed
* `gas_used` - the amount of gas used
* `index` - the index of this internal transaction inside the `transaction`
* `input` - input bytes to the call
* `output` - output bytes from the call
* `to_address` - the sink of the `value`
* `to_address_id` - foreign key for `to_address`
* `trace_address` - list of traces
* `transaction` - transaction in which this transaction occured
* `transaction_id` - foreign key for `transaction`
* `value` - value of transfered from `from_address` to `to_address`
"""
@type t :: %__MODULE__{
call_type: call_type,
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_id: non_neg_integer(),
gas: Gas.t(),
gas_used: Gas.t(),
index: non_neg_integer(),
input: String.t(),
output: String.t(),
to_address: %Ecto.Association.NotLoaded{} | Address.t(),
to_address_id: non_neg_integer(),
trace_address: [non_neg_integer()],
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_id: non_neg_integer(),
value: Wei.t()
}
schema "internal_transactions" do
field(:call_type, :string)

@ -3,19 +3,130 @@ defmodule Explorer.Chain.Transaction do
use Explorer.Schema
alias Explorer.Chain.{Address, BlockTransaction, InternalTransaction, Receipt}
alias Explorer.Chain.{Address, Block, BlockTransaction, Hash, InternalTransaction, Receipt, Wei}
# Constants
@required_attrs ~w(hash value gas gas_price input nonce public_key r s
standard_v transaction_index v)a
@optional_attrs ~w(to_address_id from_address_id)a
# Types
@typedoc """
The full public key of the signer of the transaction.
"""
@type public_key :: String.t()
@typedoc """
X coordinate module n in
[Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
(EDCSA)
"""
@type r :: String.t()
@typedoc """
Y coordinate module n in
[Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
(EDCSA)
"""
@type s :: String.t()
@typedoc """
For message signatures, we use a trick called public key recovery. The fact is that if you have the full R point
(not just its X coordinate) and `t:s/0`, and a message, you can compute for which public key this would be a valid
signature. What this allows is to 'verify' a message with an address, without needing to know the full key (we just to
public key recovery on the signature, and then hash the recovered key and compare it with the address).
However, this means we need the full R coordinates. There can be up to 4 different points with a given
"X coordinate modulo n". (2 because each X coordinate has two possible Y coordinates, and 2 because r+n may still be a
valid X coordinate). That number between 0 and 3 is standard_v.
| `standard_v` | X | Y |
|---------------|--------|------|
| `0` | lower | even |
| `1` | lower | odd |
| `2` | higher | even |
| `3` | higher | odd |
**Note: that `2` and `3` are exceedingly rarely, and will in practice only ever be seen in specifically generated
examples.**
"""
@type standard_v :: 0..3
@typedoc """
`t:standard_v/0` + `27`
| `v` | X | Y |
|------|--------|------|
| `27` | lower | even |
| `28` | lower | odd |
| `29` | higher | even |
| `30` | higher | odd |
**Note: that `29` and `30` are exceedingly rarely, and will in practice only ever be seen in specifically generated
examples.**
"""
@type v :: 27..30
@typedoc """
How much the sender is willing to pay in wei per unit of gas.
"""
@type wei_per_gas :: non_neg_integer()
@typedoc """
* `block_transaction` - joins this transaction to its `block`
* `block` - the block in which this transaction was mined/validated
* `from_address` - the source of `value`
* `from_address_id` - foreign key of `from_address`
* `gas` - Gas provided by the sender
* `gas_price` - How much the sender is willing to pay for `gas`
* `hash` - hash of contents of this transaction
* `input`- data sent along with the transaction
* `internal_transactions` - transactions (value transfers) created while executing contract used for this transaction
* `nonce` - the number of transaction made by the sender prior to this one
* `public_key` - public key of the signer of the transaction
* `r` - the R field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as
the X coordinate of a point R, modulo the curve order n.
* `s` - The S field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as
the X coordinate of a point R, modulo the curve order n.
* `standard_v` - The standardized V field of the signature
* `to_address` - sink of `value`
* `to_address_id` - `to_address` foreign key
* `transaction_index` - index of this transaction in `block`
* `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address`
"""
@type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t(),
block_transaction: %Ecto.Association.NotLoaded{} | BlockTransaction.t(),
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_id: non_neg_integer(),
gas: Gas.t(),
gas_price: wei_per_gas,
hash: Hash.t(),
input: String.t(),
internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
nonce: non_neg_integer(),
public_key: public_key(),
r: r(),
receipt: %Ecto.Association.NotLoaded{} | Receipt.t(),
s: s(),
standard_v: standard_v(),
to_address: %Ecto.Association.NotLoaded{} | Address.t(),
to_address_id: non_neg_integer(),
transaction_index: non_neg_integer(),
v: v(),
value: Wei.t()
}
# Schema
schema "transactions" do
has_one(:receipt, Receipt)
has_one(:block_transaction, BlockTransaction)
has_one(:block, through: [:block_transaction, :block])
belongs_to(:from_address, Address)
belongs_to(:to_address, Address)
has_many(:internal_transactions, InternalTransaction)
field(:hash, :string)
field(:value, :decimal)
field(:gas, :decimal)
field(:gas_price, :decimal)
field(:hash, :string)
field(:input, :string)
field(:nonce, :integer)
field(:public_key, :string)
@ -24,13 +135,17 @@ defmodule Explorer.Chain.Transaction do
field(:standard_v, :string)
field(:transaction_index, :string)
field(:v, :string)
timestamps()
end
field(:value, :decimal)
@required_attrs ~w(hash value gas gas_price input nonce public_key r s
standard_v transaction_index v)a
timestamps()
@optional_attrs ~w(to_address_id from_address_id)a
has_one(:block_transaction, BlockTransaction)
has_one(:block, through: [:block_transaction, :block])
belongs_to(:from_address, Address)
has_many(:internal_transactions, InternalTransaction)
has_one(:receipt, Receipt)
belongs_to(:to_address, Address)
end
@doc false
def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do

@ -0,0 +1,105 @@
defmodule Explorer.Chain.Wei do
@moduledoc """
The smallest fractional unit of Ether. Using wei instead of ether allows code to do integer match instead of using
floats.
Etymology of "wei" comes from [Wei Dai ()](https://en.wikipedia.org/wiki/Wei_Dai), a
[cypherpunk](https://en.wikipedia.org/wiki/Cypherpunk) who came up with b-money, which outlined modern
cryptocurrencies.
"""
@typedoc """
Ether is the default unit Ethereum and its side chains are measured in when displaying values to humans.
10<sup>18</sup> wei is 1 ether.
"""
@type ether :: Decimal.t()
@typedoc """
Short for giga-wei
* 10<sup>9</sup> wei is one gwei
"""
@type gwei :: Decimal.t()
@typedoc """
The unit to convert `t:wei/0` to.
"""
@type unit :: :wei | :gwei | :ether
@typedoc @moduledoc
@type t :: Decimal.t()
# Constants
@wei_per_ether Decimal.new(1_000_000_000_000_000_000)
@wei_per_gwei Decimal.new(1_000_000_000)
## Functions
@doc """
Convert wei to itself.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :wei)
Decimal.new(1)
Convert `t:gwei/0` to wei.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :gwei)
Decimal.new(1_000_000_000)
Convert `t:ether/0` to wei.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :ether)
Decimal.new(1_000_000_000_000_000_000)
"""
@spec from(ether(), :ether) :: t()
def from(ether, :ether) do
Decimal.mult(ether, @wei_per_ether)
end
@spec from(gwei(), :gwei) :: t()
def from(gwei, :gwei) do
Decimal.mult(gwei, @wei_per_gwei)
end
@spec from(t(), :wei) :: t()
def from(wei, :wei), do: wei
@doc """
Convert wei to itself.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :wei)
Decimal.new(1)
Convert wei to `t:gwei/0`.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :gwei)
Decimal.new("1e-9")
iex> Explorer.Chain.Wei.to(Decimal.new("1e9"), :gwei)
Decimal.new(1)
Convert wei to `t:ether/0`.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :ether)
Decimal.new("1e-18")
iex> Explorer.Chain.Wei.to(Decimal.new("1e18"), :ether)
Decimal.new(1)
"""
@spec to(t(), :ether) :: ether()
def to(wei, :ether) do
Decimal.div(wei, @wei_per_ether)
end
@spec to(t(), :gwei) :: gwei()
def to(wei, :gwei) do
Decimal.div(wei, @wei_per_gwei)
end
@spec to(t(), :wei) :: t()
def to(wei, :wei), do: wei
end

@ -0,0 +1,5 @@
defmodule Explorer.Chain.WeiTest do
use ExUnit.Case, async: true
doctest Explorer.Chain.Wei
end

@ -182,6 +182,27 @@ defmodule Explorer.ChainTest do
end
end
describe "gas_price/2" do
test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1)
end
test ":gwei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) ==
Decimal.new("1e-9")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
end
test ":ether unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) ==
Decimal.new("1e-18")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) ==
Decimal.new(1)
end
end
describe "from_address_to_transactions/2" do
test "without transactions" do
address = insert(:address)
@ -754,4 +775,39 @@ defmodule Explorer.ChainTest do
assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xtwizzlers")
end
end
describe "value/2" do
test "with InternalTransaction.t with :wei" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :wei) == Decimal.new(1)
end
test "with InternalTransaction.t with :gwei" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :gwei) ==
Decimal.new("1e-9")
assert Chain.value(%InternalTransaction{value: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
end
test "with InternalTransaction.t with :ether" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :ether) ==
Decimal.new("1e-18")
assert Chain.value(%InternalTransaction{value: Decimal.new("1e18")}, :ether) ==
Decimal.new(1)
end
test "with Transaction.t with :wei" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :wei) == Decimal.new(1)
end
test "with Transaction.t with :gwei" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :gwei) == Decimal.new("1e-9")
assert Chain.value(%Transaction{value: Decimal.new("1e9")}, :gwei) == Decimal.new(1)
end
test "with Transaction.t with :ether" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :ether) == Decimal.new("1e-18")
assert Chain.value(%Transaction{value: Decimal.new("1e18")}, :ether) == Decimal.new(1)
end
end
end

@ -5,7 +5,7 @@ defmodule Explorer.Chain.ReceiptFactory do
%Explorer.Chain.Receipt{
cumulative_gas_used: Enum.random(21_000..100_000),
gas_used: Enum.random(21_000..100_000),
status: Enum.random(1..2),
status: Enum.random(0..1),
index: sequence("")
}
end

@ -32,13 +32,14 @@ You can also run IEx (Interactive Elixir): `$ iex -S mix phx.server` (This can b
### Testing
* Build the assets: `$ cd assets && npm run build`
* Format the Elixir code: `$ mix format`
* Run the test suite with coverage: `$ mix coveralls.html`
* Lint the Elixir code: `$ mix credo --strict`
* Build the assets: `cd assets && npm run build`
* Format the Elixir code: `mix format`
* Run the test suite with coverage: `mix coveralls.html`
* Lint the Elixir code: `mix credo --strict`
* Run the dialyzer: `mix dialyzer --halt-exit-status`
* Check the Elixir code for vulnerabilities: `$ mix sobelow --config`
* Lint the JavaScript code: `$ cd assets && npm run eslint`
* Check the Elixir code for vulnerabilities: `mix sobelow --config`
* Update translations templates and translations and check there are no uncommitted changes: `mix gettext.extract --merge`
* Lint the JavaScript code: `cd assets && npm run eslint`
## Internationalization

@ -6,7 +6,6 @@ defmodule ExplorerWeb.AddressTransactionFromController do
use ExplorerWeb, :controller
alias Explorer.Chain
alias ExplorerWeb.TransactionForm
def index(conn, %{"address_id" => from_address_hash} = params) do
case Chain.hash_to_address(from_address_hash) do
@ -23,8 +22,7 @@ defmodule ExplorerWeb.AddressTransactionFromController do
pagination: params
)
entries = Enum.map(page.entries, &TransactionForm.build_and_merge/1)
render(conn, "index.html", transactions: Map.put(page, :entries, entries))
render(conn, "index.html", page: page)
{:error, :not_found} ->
not_found(conn)

@ -6,7 +6,6 @@ defmodule ExplorerWeb.AddressTransactionToController do
use ExplorerWeb, :controller
alias Explorer.Chain
alias ExplorerWeb.TransactionForm
def index(conn, %{"address_id" => to_address_hash} = params) do
case Chain.hash_to_address(to_address_hash) do
@ -23,8 +22,7 @@ defmodule ExplorerWeb.AddressTransactionToController do
pagination: params
)
entries = Enum.map(page.entries, &TransactionForm.build_and_merge/1)
render(conn, "index.html", transactions: Map.put(page, :entries, entries))
render(conn, "index.html", page: page)
{:error, :not_found} ->
not_found(conn)

@ -2,7 +2,6 @@ defmodule ExplorerWeb.BlockController do
use ExplorerWeb, :controller
alias Explorer.Chain
alias ExplorerWeb.BlockForm
def index(conn, params) do
blocks =
@ -14,9 +13,8 @@ defmodule ExplorerWeb.BlockController do
def show(conn, %{"id" => number}) do
case Chain.number_to_block(number) do
{:ok, block} ->
block_form = BlockForm.build(block)
render(conn, "show.html", block: block_form)
block_transaction_count = Chain.block_to_transaction_count(block)
render(conn, "show.html", block: block, block_transaction_count: block_transaction_count)
{:error, :not_found} ->
not_found(conn)

@ -4,7 +4,6 @@ defmodule ExplorerWeb.BlockTransactionController do
import ExplorerWeb.Chain, only: [param_to_block_number: 1]
alias Explorer.Chain
alias ExplorerWeb.TransactionForm
def index(conn, %{"block_id" => formatted_block_number} = params) do
with {:ok, block_number} <- param_to_block_number(formatted_block_number),
@ -21,8 +20,7 @@ defmodule ExplorerWeb.BlockTransactionController do
pagination: params
)
entries = Enum.map(page.entries, &TransactionForm.build_and_merge/1)
render(conn, "index.html", transactions: Map.put(page, :entries, entries))
render(conn, "index.html", page: page)
else
{:error, :invalid} ->
not_found(conn)

@ -3,32 +3,34 @@ defmodule ExplorerWeb.PendingTransactionController do
alias Explorer.Chain
alias Explorer.Chain.Transaction
alias ExplorerWeb.PendingTransactionForm
def index(conn, %{"last_seen" => last_seen_id} = _) do
total = Chain.transaction_count(pending: true)
entries =
last_seen_id
|> Chain.transactions_recently_before_id(
necessity_by_association: %{
from_address: :optional,
to_address: :optional
},
transactions =
Chain.transactions_recently_before_id(
last_seen_id,
necessity_by_association: %{from_address: :optional, to_address: :optional},
pending: true
)
|> Enum.map(&PendingTransactionForm.build/1)
last = List.last(entries) || Transaction.null()
last_seen_transaction_id =
case transactions do
[] ->
nil
_ ->
transactions
|> Stream.map(fn %Transaction{id: id} -> id end)
|> Enum.max()
end
render(
conn,
"index.html",
transactions: %{
entries: entries,
total_entries: total,
last_seen: last.id
}
last_seen_transaction_id: last_seen_transaction_id,
transaction_count: total,
transactions: transactions
)
end

@ -3,14 +3,13 @@ defmodule ExplorerWeb.TransactionController do
alias Explorer.Chain
alias Explorer.Chain.Transaction
alias ExplorerWeb.TransactionForm
def index(conn, %{"last_seen" => last_seen_id}) do
total = Chain.transaction_count()
entries =
last_seen_id
|> Chain.transactions_recently_before_id(
Chain.transactions_recently_before_id(
last_seen_id,
necessity_by_association: %{
block: :required,
from_address: :optional,
@ -18,7 +17,6 @@ defmodule ExplorerWeb.TransactionController do
receipt: :required
}
)
|> Enum.map(&TransactionForm.build_and_merge/1)
last = List.last(entries) || Transaction.null()
@ -59,13 +57,14 @@ defmodule ExplorerWeb.TransactionController do
necessity_by_association: %{from_address: :required, to_address: :required}
)
transaction_form = TransactionForm.build_and_merge(transaction)
max_block_number = Chain.max_block_number()
render(
conn,
"show.html",
internal_transactions: internal_transactions,
transaction: transaction_form
max_block_number: max_block_number,
transaction: transaction
)
{:error, :not_found} ->

@ -2,12 +2,16 @@ defmodule ExplorerWeb.TransactionLogController do
use ExplorerWeb, :controller
alias Explorer.Chain
alias ExplorerWeb.TransactionForm
def index(conn, %{"transaction_id" => transaction_hash} = params) do
case Chain.hash_to_transaction(
transaction_hash,
necessity_by_association: %{from_address: :required, to_address: :required}
necessity_by_association: %{
block: :optional,
from_address: :required,
receipt: :optional,
to_address: :required
}
) do
{:ok, transaction} ->
logs =
@ -17,9 +21,15 @@ defmodule ExplorerWeb.TransactionLogController do
pagination: params
)
transaction_form = TransactionForm.build_and_merge(transaction)
max_block_number = Chain.max_block_number()
render(conn, "index.html", logs: logs, transaction: transaction_form)
render(
conn,
"index.html",
logs: logs,
max_block_number: max_block_number,
transaction: transaction
)
{:error, :not_found} ->
not_found(conn)

@ -1,22 +0,0 @@
defmodule ExplorerWeb.BlockForm do
@moduledoc false
alias Explorer.Chain
def build(block) do
block
|> Map.merge(%{
age: calculate_age(block),
formatted_timestamp: format_timestamp(block),
transactions_count: Chain.block_to_transaction_count(block)
})
end
def calculate_age(block) do
block.timestamp |> Timex.from_now()
end
def format_timestamp(block) do
block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)
end
end

@ -1,42 +0,0 @@
defmodule ExplorerWeb.PendingTransactionForm do
@moduledoc "Format a pending Transaction for display."
import ExplorerWeb.Gettext
alias Explorer.Chain.{Address, Transaction}
# Functions
def build(transaction) do
Map.merge(transaction, %{
first_seen: first_seen(transaction),
formatted_status: gettext("Pending"),
from_address_hash: from_address_hash(transaction),
last_seen: last_seen(transaction),
status: :pending,
to_address_hash: to_address_hash(transaction)
})
end
def first_seen(transaction) do
transaction.inserted_at |> Timex.from_now()
end
def from_address_hash(%Transaction{from_address: from_address}) do
case from_address do
%Address{hash: hash} -> hash
_ -> nil
end
end
def last_seen(transaction) do
transaction.updated_at |> Timex.from_now()
end
def to_address_hash(%Transaction{to_address: to_address}) do
case to_address do
%Address{hash: hash} -> hash
_ -> nil
end
end
end

@ -1,112 +0,0 @@
defmodule ExplorerWeb.TransactionForm do
@moduledoc "Format a Block and a Transaction for display."
import ExplorerWeb.Gettext
alias Cldr.Number
alias Explorer.Chain
alias Explorer.Chain.{Receipt, Transaction}
def build(transaction) do
block = block(transaction)
receipt = receipt(transaction)
status = status(transaction, receipt || Receipt.null())
%{
block_number: block |> block_number,
age: block |> block_age,
formatted_age: block |> format_age,
formatted_timestamp: block |> format_timestamp,
cumulative_gas_used: block |> cumulative_gas_used,
to_address_hash: transaction |> to_address_hash,
from_address_hash: transaction |> from_address_hash,
confirmations: block |> confirmations,
status: status,
formatted_status: status |> format_status,
first_seen: transaction |> first_seen,
last_seen: transaction |> last_seen
}
end
def build_and_merge(transaction) do
Map.merge(transaction, build(transaction))
end
def block(%Transaction{block: block}) do
if Ecto.assoc_loaded?(block) do
block
else
nil
end
end
def block_age(block) do
(block && block.timestamp |> Timex.from_now()) || gettext("Pending")
end
def block_number(block) do
(block && block.number) || ""
end
def confirmations(nil), do: 0
def confirmations(block) do
Chain.confirmations(block, max_block_number: Chain.max_block_number())
end
def cumulative_gas_used(block) do
(block && block.gas_used |> Number.to_string!()) || gettext("Pending")
end
def first_seen(transaction) do
transaction.inserted_at |> Timex.from_now()
end
def format_age(block) do
(block && "#{block_age(block)} (#{format_timestamp(block)})") || gettext("Pending")
end
def format_status(status) do
%{
out_of_gas: gettext("Out of Gas"),
failed: gettext("Failed"),
success: gettext("Success"),
pending: gettext("Pending")
}
|> Map.fetch!(status)
end
def format_timestamp(block) do
(block && block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)) ||
gettext("Pending")
end
def from_address_hash(transaction) do
(transaction.to_address && transaction.from_address.hash) || nil
end
def last_seen(transaction) do
transaction.updated_at |> Timex.from_now()
end
def receipt(%Transaction{receipt: receipt}) do
if Ecto.assoc_loaded?(receipt) do
receipt
else
nil
end
end
def status(transaction, receipt) do
%{
0 => %{true => :out_of_gas, false => :failed},
1 => %{true => :success, false => :success}
}
|> Map.get(receipt.status, %{true: :pending, false: :pending})
|> Map.get(receipt.gas_used == transaction.gas)
end
def to_address_hash(transaction) do
(transaction.to_address && transaction.to_address.hash) || nil
end
end

@ -1,20 +0,0 @@
defmodule ExplorerWeb.WeiConverter do
@moduledoc """
Utility module for conversion of wei to other units
"""
@wei_per_ether 1_000_000_000_000_000_000
@wei_per_gwei 1_000_000_000
@spec to_ether(Decimal.t()) :: Decimal.t()
def to_ether(wei) do
wei
|> Decimal.div(Decimal.new(@wei_per_ether))
end
@spec to_gwei(Decimal.t()) :: Decimal.t()
def to_gwei(wei) do
wei
|> Decimal.div(Decimal.new(@wei_per_gwei))
end
end

@ -13,7 +13,7 @@
<dl>
<div class="address__item">
<dt class="address__item-key"><%= gettext "Balance" %></dt>
<dd class="address__item-value address__balance" title="<%= @address.hash %>"><%= format_balance(@address.balance) %> <%= gettext "POA" %></dd>
<dd class="address__item-value address__balance" title="<%= @address.hash %>"><%= format_balance(@address.balance) %> <%= gettext "Ether" %></dd>
</div>
</dl>
</div>

@ -1,7 +1,7 @@
<section class="container__section block">
<div class="address__headline">
<h1 class="address__headline-title"><%= gettext("Address %{number}", number: @conn.params["address_id"]) %></h1>
<div class="address__pagination"><%= pagination_links @conn, @transactions, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &address_transaction_to_path/5 %></div>
<div class="address__pagination"><%= pagination_links @conn, @page, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &address_transaction_to_path/5 %></div>
</div>
<div class="address__container">
<div class="address__tabs">
@ -23,9 +23,9 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @transactions do %>
<%= for transaction <- @page.entries do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= transaction.status %>"></div></td>
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= status(transaction) %>"></div></td>
<td class="transactions__column transactions__column--hash">
<div class="transactions__hash"><%= link(transaction.hash, to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash), class: "transactions__link transactions__link--truncated transactions__link--long-hash") %></div>
</td>
@ -41,7 +41,7 @@
<td class="transactions__column transactions__column--to transactions__column--optional">
<div class="transactions__hash"><%= link(transaction.to_address.hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %></div>
</td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>

@ -1,7 +1,7 @@
<section class="container__section block">
<div class="address__headline">
<h1 class="address__headline-title"><%= gettext("Address %{number}", number: @conn.params["address_id"]) %></h1>
<div class="address__pagination"><%= pagination_links @conn, @transactions, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &address_transaction_to_path/5 %></div>
<div class="address__pagination"><%= pagination_links @conn, @page, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &address_transaction_to_path/5 %></div>
</div>
<div class="address__container">
<div class="address__tabs">
@ -23,9 +23,9 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @transactions do %>
<%= for transaction <- @page.entries do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= transaction.status %>"></div></td>
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= status(transaction) %>"></div></td>
<td class="transactions__column transactions__column--hash">
<div class="transactions__hash"><%= link(transaction.hash, to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash), class: "transactions__link transactions__link--truncated transactions__link--long-hash") %></div>
</td>
@ -41,7 +41,7 @@
<td class="transactions__column transactions__column--to transactions__column--optional">
<div class="transactions__hash"><%= link(transaction.to_address.hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %></div>
</td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>

@ -11,8 +11,8 @@
<th class="blocks__column-header"><%= gettext "Height" %></th>
<th class="blocks__column-header"><%= gettext "Age" %></th>
<th class="blocks__column-header"><%= gettext "Transactions" %></th>
<th class="blocks__column-header blocks__column-header--optional"><%= gettext "Gas Used" %></th>
<th class="blocks__column-header blocks__column-header--optional"><%= gettext "Gas Limit" %></th>
<th class="blocks__column-header blocks__column-header--optional"><%= gettext "Gas Used" %> (<%= gettext("Gas") %>)</th>
<th class="blocks__column-header blocks__column-header--optional"><%= gettext "Gas Limit" %> (<%= gettext("Gas") %>)</th>
<th class="blocks__column-header blocks__column-header--optional"><%= gettext "Gas Price" %></th>
</tr>
</thead>

@ -16,11 +16,11 @@
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Timestamp" %></dt>
<dd class="block__item-value"><%= @block.age %> (<%= @block.formatted_timestamp %>)</dd>
<dd class="block__item-value"><%= age(@block) %> (<%= formatted_timestamp(@block) %>)</dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Transactions" %></dt>
<dd class="block__item-value"><%= gettext "%{count} transactions in this block", count: @block.transactions_count %></dd>
<dd class="block__item-value"><%= gettext "%{count} transactions in this block", count: @block_transaction_count %></dd>
</div>
<div class="block__item">
<dt class="block__item-key"><%= gettext "Hash" %></dt>

@ -1,7 +1,7 @@
<section class="container__section block">
<div class="blocks__headline">
<h1 class="blocks__headline-title"><%= gettext("Showing #%{number}", number: @conn.params["block_id"]) %></h1>
<div class="blocks__pagination"><%= pagination_links @conn, @transactions, ["en", @conn.params["block_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &block_transaction_path/5 %></div>
<div class="blocks__pagination"><%= pagination_links @conn, @page, ["en", @conn.params["block_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("&lsaquo;"), next: Phoenix.HTML.raw("&rsaquo;"), path: &block_transaction_path/5 %></div>
</div>
<div class="block__container">
<div class="block__tabs">
@ -22,9 +22,9 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @transactions do %>
<%= for transaction <- @page.entries do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= transaction.status %>"></div></td>
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= status(transaction) %>"></div></td>
<td class="transactions__column transactions__column--hash">
<div class="transactions__hash"><%= link(transaction.hash, to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash), class: "transactions__link transactions__link--truncated transactions__link--long-hash") %></div>
</td>
@ -40,7 +40,7 @@
<td class="transactions__column transactions__column--to transactions__column--optional">
<div class="transactions__hash"><%= link(transaction.to_address.hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %></div>
</td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>

@ -100,7 +100,7 @@
</td>
<td class="transactions__column transactions__column--block"><%= link(transaction.block.number, to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number), class: "transactions__link") %></td>
<td class="transactions__column transactions__column--age transactions__column--optional"><%= transaction.block.timestamp |> Timex.from_now() %></td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>

@ -1,7 +1,7 @@
<section class="container__section">
<section class="container__subsection">
<div class="transactions__headline">
<h1 class="transactions__headline-title"><%= gettext("Showing %{count} Pending Transactions", count: @transactions.total_entries) %></h1>
<h1 class="transactions__headline-title"><%= gettext("Showing %{count} Pending Transactions", count: @transaction_count) %></h1>
</div>
<div class="transactions">
<div class="transactions__tabs">
@ -21,33 +21,35 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @transactions.entries do %>
<%= for transaction <- @transactions do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= transaction.status %>"></div></td>
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--pending"></div></td>
<td class="transactions__column transactions__column--hash">
<%= link(transaction.hash, to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash), class: "transactions__link transactions__link--truncated transactions__link--long-hash") %>
</td>
<td class="transactions__column transactions__column--last-seen"><%= transaction.last_seen %></td>
<td class="transactions__column transactions__column--last-seen"><%= last_seen(transaction) %></td>
<td class="transactions__column transactions__column--optional transactions__column--from-address">
<%= if transaction.to_address_hash do %>
<%= link(transaction.to_address_hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address_hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %>
<% to_address_hash = to_address_hash(transaction) %>
<%= if to_address_hash do %>
<%= link(to_address_hash, to: address_path(@conn, :show, @conn.assigns.locale, to_address_hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %>
<% else %>
<%= gettext "Pending" %>
<% end %>
</td>
<td class="transactions__column transactions__column--optional transactions__column--to-address">
<%= if transaction.from_address_hash do %>
<%= link(transaction.from_address_hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address_hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %>
<% from_address_hash = from_address_hash(transaction) %>
<%= if from_address_hash do %>
<%= link(from_address_hash, to: address_path(@conn, :show, @conn.assigns.locale, from_address_hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %>
<% else %>
<%= gettext "Pending" %>
<% end %>
</td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>
</table>
<%= link(gettext("Next Page"), to: pending_transaction_path(@conn, :index, @conn.assigns.locale, %{"last_seen" => @transactions.last_seen}), class: "transactions__link transactions__link--next-page") %>
<%= link(gettext("Next Page"), to: pending_transaction_path(@conn, :index, @conn.assigns.locale, %{"last_seen" => @last_seen_transaction_id}), class: "transactions__link transactions__link--next-page") %>
</div>
</div>
</section>

@ -24,7 +24,7 @@
<tbody>
<%= for transaction <- @transactions.entries do %>
<tr class="transactions__row">
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= transaction.status %>"></div></td>
<td class="transactions__column transactions__column--status"><div class="transactions__dot transactions__dot--<%= status(transaction) %>"></div></td>
<td class="transactions__column transactions__column--hash">
<div class="transactions__hash"><%= link(transaction.hash, to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash), class: "transactions__link transactions__link--truncated transactions__link--long-hash") %></div>
</td>
@ -40,7 +40,7 @@
<td class="transactions__column transactions__column--to transactions__column--optional">
<div class="transactions__hash"><%= link(transaction.to_address.hash, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), class: "transactions__link transactions__link--truncated transactions__link--hash") %></div>
</td>
<td class="transactions__column transactions__column--value"><%= Decimal.div(Decimal.new(transaction.value), Decimal.new(1_000_000_000_000_000_000)) |> Decimal.to_string(:normal) %> <%= gettext "POA" %></td>
<td class="transactions__column transactions__column--value"><%= value(transaction) %> <%= gettext "Ether" %></td>
</tr>
<% end %>
</tbody>

@ -10,8 +10,8 @@
<dt class="transaction__item-key"><%= gettext "Transaction Status" %></dt>
<dd class="transaction__item-value transaction__item-value--status">
<div class="transaction__status">
<%= @transaction.formatted_status %>
<div class="transaction__dot transaction__dot--<%= @transaction.status %>"></div>
<%= formatted_status(@transaction) %>
<div class="transaction__dot transaction__dot--<%= status(@transaction) %>"></div>
</div>
</dd>
</div>
@ -19,20 +19,23 @@
<dt class="transaction__item-key"><%= gettext "Block Number" %></dt>
<dd class="transaction__item-value">
<span class="transaction__item--primary">
<%= link(@transaction.block_number, to: block_path(@conn, :show, @conn.assigns.locale, @transaction.block_number), class: "transaction__link") %>
<% block = @transaction.block %>
<%= if block do %>
<%= link(block.number, to: block_path(@conn, :show, @conn.assigns.locale, block.number), class: "transaction__link") %>
<% end %>
</span>
<span class="transaction__item--secondary">
(<%= gettext "%{confirmations} block confirmations", confirmations: @transaction.confirmations %>)
(<%= gettext "%{confirmations} block confirmations", confirmations: confirmations(@transaction, max_block_number: @max_block_number) %>)
</span>
</dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Age" %></dt>
<dd class="transaction__item-value" title="<%= @transaction.formatted_timestamp %>"><%= @transaction.formatted_age %></dd>
<dd class="transaction__item-value" title="<%= formatted_timestamp(@transaction) %>"><%= formatted_age(@transaction) %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Value" %></dt>
<dd class="transaction__item-value"><%= @transaction.value |> ExplorerWeb.WeiConverter.to_ether() |> Decimal.to_string(:normal) %> <%= gettext "POA" %></dd>
<dd class="transaction__item-value"><%= value(@transaction) %> <%= gettext "Ether" %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "From" %></dt>
@ -64,23 +67,25 @@
<dl>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "First Seen" %></dt>
<dd class="transaction__item-value" title="<%= @transaction.first_seen %>"><%= @transaction.first_seen %></dd>
<% first_seen = first_seen(@transaction) %>
<dd class="transaction__item-value" title="<%= first_seen %>"><%= first_seen %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Last Seen" %></dt>
<dd class="transaction__item-value" title="<%= @transaction.last_seen %>"><%= @transaction.last_seen %></dd>
<% last_seen = last_seen(@transaction) %>
<dd class="transaction__item-value" title="<%= last_seen %>"><%= last_seen %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Gas Limit" %></dt>
<dd class="transaction__item-value"><%= format_gas_limit(@transaction.gas) %></dd>
<dd class="transaction__item-value"><%= format_gas_limit(@transaction.gas) %> <%= gettext("Gas") %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Gas Price" %></dt>
<dd class="transaction__item-value"><%= @transaction.gas_price |> Cldr.Number.to_string! %> (<%= @transaction.gas_price |> ExplorerWeb.WeiConverter.to_gwei() |> Decimal.to_string(:normal) %> <%= gettext "Gwei" %>)</dd>
<dd class="transaction__item-value"><%= gas_price(@transaction, :wei) %> <%= gettext("Wei") %> (<%= gas_price(@transaction, :gwei) %> <%= gettext "Gwei" %>)</dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Cumulative Gas Used" %></dt>
<dd class="transaction__item-value"><%= @transaction.cumulative_gas_used %></dd>
<dd class="transaction__item-value"><%= cumulative_gas_used(@transaction) %> <%= gettext("Wei") %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Input" %></dt>

@ -13,8 +13,8 @@
<th class="internal-transaction__column-header"><%= gettext "Type" %></th>
<th class="internal-transaction__column-header"><%= gettext "From" %></th>
<th class="internal-transaction__column-header"><%= gettext "To" %></th>
<th class="internal-transaction__column-header"><%= gettext "Value" %></th>
<th class="internal-transaction__column-header"><%= gettext "Gas Limit" %></th>
<th class="internal-transaction__column-header"><%= gettext "Value" %> (<%= gettext "Ether" %>)</th>
<th class="internal-transaction__column-header"><%= gettext "Gas Limit" %> (<%= gettext "Gas" %>)</th>
</thead>
<%= for transaction <- @internal_transactions do %>
<tgroup>
@ -30,8 +30,8 @@
to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash),
class: "transaction-log__link") %>
</td>
<td><%= transaction.value %></td>
<td><%= ExplorerWeb.TransactionView.format_gas_limit(transaction.gas) %></td>
<td><%= value(transaction) %></td>
<td><%= gas(transaction) %></td>
</tr>
</tgroup>
<% end %>

@ -1,4 +1,8 @@
defmodule ExplorerWeb.AddressTransactionFromView do
use ExplorerWeb, :view
@dialyzer :no_match
alias ExplorerWeb.TransactionView
defdelegate status(transacton), to: TransactionView
defdelegate value(transaction), to: TransactionView
end

@ -1,4 +1,8 @@
defmodule ExplorerWeb.AddressTransactionToView do
use ExplorerWeb, :view
@dialyzer :no_match
alias ExplorerWeb.TransactionView
defdelegate status(transacton), to: TransactionView
defdelegate value(transaction), to: TransactionView
end

@ -1,4 +1,10 @@
defmodule ExplorerWeb.BlockTransactionView do
use ExplorerWeb, :view
@dialyzer :no_match
alias ExplorerWeb.TransactionView
# Functions
defdelegate status(transacton), to: TransactionView
defdelegate value(transaction), to: TransactionView
end

@ -1,4 +1,17 @@
defmodule ExplorerWeb.BlockView do
use ExplorerWeb, :view
alias Explorer.Chain.Block
@dialyzer :no_match
# Functions
def age(%Block{timestamp: timestamp}) do
Timex.from_now(timestamp)
end
def formatted_timestamp(%Block{timestamp: timestamp}) do
Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime)
end
end

@ -1,4 +1,7 @@
defmodule ExplorerWeb.ChainView do
use ExplorerWeb, :view
@dialyzer :no_match
alias ExplorerWeb.TransactionView
defdelegate value(transaction), to: TransactionView
end

@ -1,4 +1,28 @@
defmodule ExplorerWeb.PendingTransactionView do
use ExplorerWeb, :view
alias Explorer.Chain.{Address, Transaction}
alias ExplorerWeb.TransactionView
@dialyzer :no_match
# Functions
def from_address_hash(%Transaction{from_address: from_address}) do
case from_address do
%Address{hash: hash} -> hash
_ -> nil
end
end
defdelegate last_seen(transaction), to: TransactionView
def to_address_hash(%Transaction{to_address: to_address}) do
case to_address do
%Address{hash: hash} -> hash
_ -> nil
end
end
defdelegate value(transaction), to: TransactionView
end

@ -2,10 +2,80 @@ defmodule ExplorerWeb.TransactionView do
use ExplorerWeb, :view
alias Cldr.Number
@dialyzer :no_match
alias Explorer.Chain
alias Explorer.Chain.{Block, InternalTransaction, Transaction}
alias ExplorerWeb.BlockView
# Functions
def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do
case block do
nil -> 0
_ -> Chain.confirmations(block, named_arguments)
end
end
def cumulative_gas_used(%Transaction{block: block}) do
case block do
nil -> gettext("Pending")
%Block{gas_used: gas_used} -> Number.to_string!(gas_used)
end
end
def first_seen(%Transaction{inserted_at: inserted_at}) do
Timex.from_now(inserted_at)
end
def format_gas_limit(gas) do
gas
|> Number.to_string!()
Number.to_string!(gas)
end
def formatted_age(%Transaction{block: block}) do
case block do
nil -> gettext("Pending")
_ -> "#{BlockView.age(block)} (#{BlockView.formatted_timestamp(block)})"
end
end
def formatted_timestamp(%Transaction{block: block}) do
case block do
nil -> gettext("Pending")
_ -> BlockView.formatted_timestamp(block)
end
end
def gas(%type{gas: gas}) when type in [InternalTransaction, Transaction] do
Cldr.Number.to_string!(gas)
end
def gas_price(transaction, unit) do
transaction
|> Chain.gas_price(unit)
|> Cldr.Number.to_string!()
end
def last_seen(%Transaction{updated_at: updated_at}) do
Timex.from_now(updated_at)
end
def status(transaction) do
Chain.transaction_to_status(transaction)
end
def formatted_status(transaction) do
transaction
|> Chain.transaction_to_status()
|> case do
:failed -> gettext("Failed")
:out_of_gas -> gettext("Out of Gas")
:pending -> gettext("Pending")
:success -> gettext("Success")
end
end
def value(transaction) do
transaction
|> Chain.value(:ether)
|> Cldr.Number.to_string!()
end
end

@ -83,6 +83,8 @@ defmodule ExplorerWeb.Mixfile do
{:excoveralls, "~> 0.8.1", only: [:test]},
{:explorer, in_umbrella: true},
{:exvcr, "~> 0.10", only: :test},
# HTML CSS selectors for Phoenix controller tests
{:floki, "~> 0.20.1", only: :test},
{:flow, "~> 0.12"},
{:gettext, "~> 0.11"},
{:httpoison, "~> 1.0", override: true},

@ -1,14 +1,16 @@
#: lib/explorer_web/templates/address_transaction/index.html.eex:18
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:19
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:19
#: lib/explorer_web/templates/block/index.html.eex:12
#: lib/explorer_web/templates/block_transaction/index.html.eex:18
#: lib/explorer_web/templates/chain/show.html.eex:61
#: lib/explorer_web/templates/chain/show.html.eex:89
#: lib/explorer_web/templates/transaction/index.html.eex:18
#: lib/explorer_web/templates/transaction/show.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:33
msgid "Age"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:17
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:18
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:18
#: lib/explorer_web/templates/block_transaction/index.html.eex:17
#: lib/explorer_web/templates/chain/show.html.eex:28
#: lib/explorer_web/templates/chain/show.html.eex:88
@ -17,7 +19,7 @@ msgid "Block"
msgstr ""
#: lib/explorer_web/templates/chain/show.html.eex:54
#: lib/explorer_web/templates/layout/_header.html.eex:16
#: lib/explorer_web/templates/layout/_header.html.eex:18
msgid "Blocks"
msgstr ""
@ -31,7 +33,8 @@ msgstr ""
msgid "Gas Used"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:16
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:17
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:17
#: lib/explorer_web/templates/block/show.html.eex:26
#: lib/explorer_web/templates/block_transaction/index.html.eex:16
#: lib/explorer_web/templates/chain/show.html.eex:87
@ -50,8 +53,6 @@ msgstr ""
msgid "POA Network Explorer"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:9
#: lib/explorer_web/templates/address_transaction/index.html.eex:9
#: lib/explorer_web/templates/block/index.html.eex:13
#: lib/explorer_web/templates/block/show.html.eex:8
#: lib/explorer_web/templates/block/show.html.eex:22
@ -59,18 +60,19 @@ msgstr ""
#: lib/explorer_web/templates/chain/show.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:62
#: lib/explorer_web/templates/chain/show.html.eex:81
#: lib/explorer_web/templates/layout/_header.html.eex:12
#: lib/explorer_web/templates/pending_transaction/index.html.eex:8
#: lib/explorer_web/templates/transaction/index.html.eex:8
msgid "Transactions"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:21
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:22
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
#: lib/explorer_web/templates/chain/show.html.eex:90
#: lib/explorer_web/templates/pending_transaction/index.html.eex:20
#: lib/explorer_web/templates/transaction/index.html.eex:21
#: lib/explorer_web/templates/transaction/show.html.eex:43
#: lib/explorer_web/templates/transaction/overview.html.eex:37
#: lib/explorer_web/templates/transaction/show.html.eex:16
msgid "Value"
msgstr ""
@ -84,7 +86,8 @@ msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:15
#: lib/explorer_web/templates/block/show.html.eex:58
#: lib/explorer_web/templates/transaction/show.html.eex:79
#: lib/explorer_web/templates/transaction/overview.html.eex:79
#: lib/explorer_web/templates/transaction/show.html.eex:17
msgid "Gas Limit"
msgstr ""
@ -93,7 +96,7 @@ msgid "Miner"
msgstr ""
#: lib/explorer_web/templates/block/show.html.eex:62
#: lib/explorer_web/templates/transaction/show.html.eex:91
#: lib/explorer_web/templates/transaction/overview.html.eex:61
msgid "Nonce"
msgstr ""
@ -117,32 +120,35 @@ msgstr ""
msgid "Total Difficulty"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:28
#: lib/explorer_web/templates/transaction/overview.html.eex:19
msgid "Block Number"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:3
#: lib/explorer_web/templates/transaction/overview.html.eex:2
msgid "Transaction Details"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:87
#: lib/explorer_web/templates/transaction/overview.html.eex:87
msgid "Cumulative Gas Used"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:37
#: lib/explorer_web/templates/block/index.html.eex:14
#: lib/explorer_web/templates/block/index.html.eex:15
#: lib/explorer_web/templates/transaction/overview.html.eex:80
#: lib/explorer_web/templates/transaction/show.html.eex:17
msgid "Gas"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:16
#: lib/explorer_web/templates/transaction/show.html.eex:83
#: lib/explorer_web/templates/transaction/overview.html.eex:83
msgid "Gas Price"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:95
#: lib/explorer_web/templates/transaction/overview.html.eex:91
msgid "Input"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:34
#: lib/explorer_web/templates/transaction/overview.html.eex:28
msgid "%{confirmations} block confirmations"
msgstr ""
@ -155,44 +161,48 @@ msgstr ""
msgid "Address"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:19
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:20
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:20
#: lib/explorer_web/templates/block_transaction/index.html.eex:19
#: lib/explorer_web/templates/pending_transaction/index.html.eex:18
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/show.html.eex:47
#: lib/explorer_web/templates/transaction/overview.html.eex:41
#: lib/explorer_web/templates/transaction/show.html.eex:14
msgid "From"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:8
#: lib/explorer_web/templates/address_transaction/index.html.eex:8
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:8
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:8
#: lib/explorer_web/templates/block/show.html.eex:7
#: lib/explorer_web/templates/block_transaction/index.html.eex:8
#: lib/explorer_web/templates/transaction/show.html.eex:8
#: lib/explorer_web/templates/transaction_log/index.html.eex:8
msgid "Overview"
msgstr ""
#: lib/explorer/forms/transaction_form.ex:83
#: lib/explorer_web/views/transaction_view.ex:72
msgid "Success"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:20
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:21
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:21
#: lib/explorer_web/templates/block_transaction/index.html.eex:20
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:20
#: lib/explorer_web/templates/transaction/show.html.eex:57
#: lib/explorer_web/templates/transaction/overview.html.eex:51
#: lib/explorer_web/templates/transaction/show.html.eex:15
msgid "To"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:15
#: lib/explorer_web/templates/transaction/overview.html.eex:10
#: lib/explorer_web/templates/transaction/overview.html.eex:10
msgid "Transaction Hash"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:10
msgid "Transaction Status"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:14
#: lib/explorer_web/templates/address/show.html.eex:15
msgid "Balance"
msgstr ""
@ -218,32 +228,30 @@ msgstr ""
msgid "Showing %{count} Transactions"
msgstr ""
#: lib/explorer/forms/pending_transaction_form.ex:13
#: lib/explorer/forms/transaction_form.ex:42
#: lib/explorer/forms/transaction_form.ex:46
#: lib/explorer/forms/transaction_form.ex:50
#: lib/explorer/forms/transaction_form.ex:54
#: lib/explorer/forms/transaction_form.ex:84
#: lib/explorer_web/templates/pending_transaction/index.html.eex:9
#: lib/explorer_web/templates/pending_transaction/index.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:42
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/pending_transaction/index.html.eex:44
#: lib/explorer_web/templates/transaction/index.html.eex:9
#: lib/explorer_web/templates/transaction/show.html.eex:52
#: lib/explorer_web/templates/transaction/show.html.eex:62
#: lib/explorer_web/templates/transaction/overview.html.eex:46
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/views/transaction_view.ex:20
#: lib/explorer_web/views/transaction_view.ex:35
#: lib/explorer_web/views/transaction_view.ex:42
#: lib/explorer_web/views/transaction_view.ex:71
msgid "Pending"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:71
#: lib/explorer_web/templates/transaction/overview.html.eex:69
msgid "First Seen"
msgstr ""
#: lib/explorer_web/templates/pending_transaction/index.html.eex:17
#: lib/explorer_web/templates/transaction/show.html.eex:75
#: lib/explorer_web/templates/transaction/overview.html.eex:74
msgid "Last Seen"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:9
#: lib/explorer_web/templates/transaction_log/index.html.eex:9
#: lib/explorer_web/templates/transaction/show.html.eex:7
#: lib/explorer_web/templates/transaction_log/index.html.eex:7
msgid "Logs"
msgstr ""
@ -287,32 +295,86 @@ msgstr ""
msgid "TPM"
msgstr ""
#: lib/explorer_web/templates/pending_transaction/index.html.eex:50
#: lib/explorer_web/templates/pending_transaction/index.html.eex:52
#: lib/explorer_web/templates/transaction/index.html.eex:48
msgid "Next Page"
msgstr ""
#: lib/explorer/forms/transaction_form.ex:82
#: lib/explorer_web/views/transaction_view.ex:69
msgid "Failed"
msgstr ""
#: lib/explorer/forms/transaction_form.ex:81
#: lib/explorer_web/views/transaction_view.ex:70
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:15
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:16
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:16
#: lib/explorer_web/templates/block_transaction/index.html.eex:15
#: lib/explorer_web/templates/pending_transaction/index.html.eex:15
#: lib/explorer_web/templates/transaction/index.html.eex:15
msgid "Status"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
msgid "Address %{number}"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/block_transaction/index.html.eex:3
msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:16
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:44
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:44
#: lib/explorer_web/templates/block_transaction/index.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:103
#: lib/explorer_web/templates/pending_transaction/index.html.eex:47
#: lib/explorer_web/templates/transaction/index.html.eex:43
#: lib/explorer_web/templates/transaction/overview.html.eex:38
#: lib/explorer_web/templates/transaction/show.html.eex:16
msgid "Ether"
msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:84
msgid "Gwei"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:6
#: lib/explorer_web/templates/transaction_log/index.html.eex:6
msgid "Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/layout/_header.html.eex:12
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:40
msgid "There are no Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/transaction_log/index.html.eex:45
msgid "There are no logs currently."
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:10
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:10
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:10
msgid "Transactions From"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:9
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:9
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:9
msgid "Transactions To"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:13
msgid "Type"
msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:88
msgid "Wei"
msgstr ""

@ -10,17 +10,19 @@ msgid ""
msgstr ""
"Language: en\n"
#: lib/explorer_web/templates/address_transaction/index.html.eex:18
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:19
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:19
#: lib/explorer_web/templates/block/index.html.eex:12
#: lib/explorer_web/templates/block_transaction/index.html.eex:18
#: lib/explorer_web/templates/chain/show.html.eex:61
#: lib/explorer_web/templates/chain/show.html.eex:89
#: lib/explorer_web/templates/transaction/index.html.eex:18
#: lib/explorer_web/templates/transaction/show.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:33
msgid "Age"
msgstr "Age"
#: lib/explorer_web/templates/address_transaction/index.html.eex:17
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:18
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:18
#: lib/explorer_web/templates/block_transaction/index.html.eex:17
#: lib/explorer_web/templates/chain/show.html.eex:28
#: lib/explorer_web/templates/chain/show.html.eex:88
@ -29,7 +31,7 @@ msgid "Block"
msgstr "Block"
#: lib/explorer_web/templates/chain/show.html.eex:54
#: lib/explorer_web/templates/layout/_header.html.eex:16
#: lib/explorer_web/templates/layout/_header.html.eex:18
msgid "Blocks"
msgstr "Blocks"
@ -43,7 +45,8 @@ msgstr "%{year} POA Network Ltd. All rights reserved"
msgid "Gas Used"
msgstr "Gas Used"
#: lib/explorer_web/templates/address_transaction/index.html.eex:16
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:17
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:17
#: lib/explorer_web/templates/block/show.html.eex:26
#: lib/explorer_web/templates/block_transaction/index.html.eex:16
#: lib/explorer_web/templates/chain/show.html.eex:87
@ -62,8 +65,6 @@ msgstr "Height"
msgid "POA Network Explorer"
msgstr "POA Network Explorer"
#: lib/explorer_web/templates/address/show.html.eex:9
#: lib/explorer_web/templates/address_transaction/index.html.eex:9
#: lib/explorer_web/templates/block/index.html.eex:13
#: lib/explorer_web/templates/block/show.html.eex:8
#: lib/explorer_web/templates/block/show.html.eex:22
@ -71,18 +72,19 @@ msgstr "POA Network Explorer"
#: lib/explorer_web/templates/chain/show.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:62
#: lib/explorer_web/templates/chain/show.html.eex:81
#: lib/explorer_web/templates/layout/_header.html.eex:12
#: lib/explorer_web/templates/pending_transaction/index.html.eex:8
#: lib/explorer_web/templates/transaction/index.html.eex:8
msgid "Transactions"
msgstr "Transactions"
#: lib/explorer_web/templates/address_transaction/index.html.eex:21
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:22
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
#: lib/explorer_web/templates/chain/show.html.eex:90
#: lib/explorer_web/templates/pending_transaction/index.html.eex:20
#: lib/explorer_web/templates/transaction/index.html.eex:21
#: lib/explorer_web/templates/transaction/show.html.eex:43
#: lib/explorer_web/templates/transaction/overview.html.eex:37
#: lib/explorer_web/templates/transaction/show.html.eex:16
msgid "Value"
msgstr "Value"
@ -96,7 +98,8 @@ msgstr "Difficulty"
#: lib/explorer_web/templates/block/index.html.eex:15
#: lib/explorer_web/templates/block/show.html.eex:58
#: lib/explorer_web/templates/transaction/show.html.eex:79
#: lib/explorer_web/templates/transaction/overview.html.eex:79
#: lib/explorer_web/templates/transaction/show.html.eex:17
msgid "Gas Limit"
msgstr "Gas Limit"
@ -105,7 +108,7 @@ msgid "Miner"
msgstr "Validator"
#: lib/explorer_web/templates/block/show.html.eex:62
#: lib/explorer_web/templates/transaction/show.html.eex:91
#: lib/explorer_web/templates/transaction/overview.html.eex:61
msgid "Nonce"
msgstr "Nonce"
@ -129,32 +132,35 @@ msgstr "Timestamp"
msgid "Total Difficulty"
msgstr "Total Difficulty"
#: lib/explorer_web/templates/transaction/show.html.eex:28
#: lib/explorer_web/templates/transaction/overview.html.eex:19
msgid "Block Number"
msgstr "Block Height"
#: lib/explorer_web/templates/transaction/show.html.eex:3
#: lib/explorer_web/templates/transaction/overview.html.eex:2
msgid "Transaction Details"
msgstr "Transaction Details"
#: lib/explorer_web/templates/transaction/show.html.eex:87
#: lib/explorer_web/templates/transaction/overview.html.eex:87
msgid "Cumulative Gas Used"
msgstr "Cumulative Gas Used"
#: lib/explorer_web/templates/transaction/show.html.eex:37
#: lib/explorer_web/templates/block/index.html.eex:14
#: lib/explorer_web/templates/block/index.html.eex:15
#: lib/explorer_web/templates/transaction/overview.html.eex:80
#: lib/explorer_web/templates/transaction/show.html.eex:17
msgid "Gas"
msgstr "Gas"
#: lib/explorer_web/templates/block/index.html.eex:16
#: lib/explorer_web/templates/transaction/show.html.eex:83
#: lib/explorer_web/templates/transaction/overview.html.eex:83
msgid "Gas Price"
msgstr "Gas Price"
#: lib/explorer_web/templates/transaction/show.html.eex:95
#: lib/explorer_web/templates/transaction/overview.html.eex:91
msgid "Input"
msgstr "Input"
#: lib/explorer_web/templates/transaction/show.html.eex:34
#: lib/explorer_web/templates/transaction/overview.html.eex:28
msgid "%{confirmations} block confirmations"
msgstr "%{confirmations} block confirmations"
@ -167,44 +173,48 @@ msgstr "%{count} transactions in this block"
msgid "Address"
msgstr "Address"
#: lib/explorer_web/templates/address_transaction/index.html.eex:19
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:20
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:20
#: lib/explorer_web/templates/block_transaction/index.html.eex:19
#: lib/explorer_web/templates/pending_transaction/index.html.eex:18
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/show.html.eex:47
#: lib/explorer_web/templates/transaction/overview.html.eex:41
#: lib/explorer_web/templates/transaction/show.html.eex:14
msgid "From"
msgstr "From"
#: lib/explorer_web/templates/address/show.html.eex:8
#: lib/explorer_web/templates/address_transaction/index.html.eex:8
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:8
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:8
#: lib/explorer_web/templates/block/show.html.eex:7
#: lib/explorer_web/templates/block_transaction/index.html.eex:8
#: lib/explorer_web/templates/transaction/show.html.eex:8
#: lib/explorer_web/templates/transaction_log/index.html.eex:8
msgid "Overview"
msgstr "Overview"
#: lib/explorer/forms/transaction_form.ex:83
#: lib/explorer_web/views/transaction_view.ex:72
msgid "Success"
msgstr "Success"
#: lib/explorer_web/templates/address_transaction/index.html.eex:20
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:21
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:21
#: lib/explorer_web/templates/block_transaction/index.html.eex:20
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:20
#: lib/explorer_web/templates/transaction/show.html.eex:57
#: lib/explorer_web/templates/transaction/overview.html.eex:51
#: lib/explorer_web/templates/transaction/show.html.eex:15
msgid "To"
msgstr "To"
#: lib/explorer_web/templates/transaction/show.html.eex:15
#: lib/explorer_web/templates/transaction/overview.html.eex:10
#: lib/explorer_web/templates/transaction/overview.html.eex:10
msgid "Transaction Hash"
msgstr "Transaction Hash"
#: lib/explorer_web/templates/transaction/show.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:10
msgid "Transaction Status"
msgstr "Transaction Status"
#: lib/explorer_web/templates/address/show.html.eex:14
#: lib/explorer_web/templates/address/show.html.eex:15
msgid "Balance"
msgstr "Balance"
@ -230,32 +240,30 @@ msgstr "Showing #%{start_block} to #%{end_block}"
msgid "Showing %{count} Transactions"
msgstr "Showing %{count} Transactions"
#: lib/explorer/forms/pending_transaction_form.ex:13
#: lib/explorer/forms/transaction_form.ex:42
#: lib/explorer/forms/transaction_form.ex:46
#: lib/explorer/forms/transaction_form.ex:50
#: lib/explorer/forms/transaction_form.ex:54
#: lib/explorer/forms/transaction_form.ex:84
#: lib/explorer_web/templates/pending_transaction/index.html.eex:9
#: lib/explorer_web/templates/pending_transaction/index.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:42
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/pending_transaction/index.html.eex:44
#: lib/explorer_web/templates/transaction/index.html.eex:9
#: lib/explorer_web/templates/transaction/show.html.eex:52
#: lib/explorer_web/templates/transaction/show.html.eex:62
#: lib/explorer_web/templates/transaction/overview.html.eex:46
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/views/transaction_view.ex:20
#: lib/explorer_web/views/transaction_view.ex:35
#: lib/explorer_web/views/transaction_view.ex:42
#: lib/explorer_web/views/transaction_view.ex:71
msgid "Pending"
msgstr "Pending"
#: lib/explorer_web/templates/transaction/show.html.eex:71
#: lib/explorer_web/templates/transaction/overview.html.eex:69
msgid "First Seen"
msgstr ""
#: lib/explorer_web/templates/pending_transaction/index.html.eex:17
#: lib/explorer_web/templates/transaction/show.html.eex:75
#: lib/explorer_web/templates/transaction/overview.html.eex:74
msgid "Last Seen"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:9
#: lib/explorer_web/templates/transaction_log/index.html.eex:9
#: lib/explorer_web/templates/transaction/show.html.eex:7
#: lib/explorer_web/templates/transaction_log/index.html.eex:7
msgid "Logs"
msgstr ""
@ -299,32 +307,86 @@ msgstr ""
msgid "TPM"
msgstr ""
#: lib/explorer_web/templates/pending_transaction/index.html.eex:50
#: lib/explorer_web/templates/pending_transaction/index.html.eex:52
#: lib/explorer_web/templates/transaction/index.html.eex:48
msgid "Next Page"
msgstr ""
#: lib/explorer/forms/transaction_form.ex:82
#: lib/explorer_web/views/transaction_view.ex:69
msgid "Failed"
msgstr ""
#: lib/explorer/forms/transaction_form.ex:81
#: lib/explorer_web/views/transaction_view.ex:70
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:15
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:16
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:16
#: lib/explorer_web/templates/block_transaction/index.html.eex:15
#: lib/explorer_web/templates/pending_transaction/index.html.eex:15
#: lib/explorer_web/templates/transaction/index.html.eex:15
msgid "Status"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
msgid "Address %{number}"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/block_transaction/index.html.eex:3
msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:16
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:44
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:44
#: lib/explorer_web/templates/block_transaction/index.html.eex:43
#: lib/explorer_web/templates/chain/show.html.eex:103
#: lib/explorer_web/templates/pending_transaction/index.html.eex:47
#: lib/explorer_web/templates/transaction/index.html.eex:43
#: lib/explorer_web/templates/transaction/overview.html.eex:38
#: lib/explorer_web/templates/transaction/show.html.eex:16
msgid "Ether"
msgstr "POA"
#: lib/explorer_web/templates/transaction/overview.html.eex:84
msgid "Gwei"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:6
#: lib/explorer_web/templates/transaction_log/index.html.eex:6
msgid "Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/layout/_header.html.eex:12
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:40
msgid "There are no Internal Transactions"
msgstr ""
#: lib/explorer_web/templates/transaction_log/index.html.eex:45
msgid "There are no logs currently."
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:10
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:10
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:10
msgid "Transactions From"
msgstr ""
#: lib/explorer_web/templates/address/show.html.eex:9
#: lib/explorer_web/templates/address_transaction_from/index.html.eex:9
#: lib/explorer_web/templates/address_transaction_to/index.html.eex:9
msgid "Transactions To"
msgstr ""
#: lib/explorer_web/templates/transaction/show.html.eex:13
msgid "Type"
msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:88
msgid "Wei"
msgstr ""

@ -12,7 +12,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
test "returns transactions from this address", %{conn: conn} do
address = insert(:address)
transaction = insert(:transaction, hash: "0xsnacks", from_address_id: address.id)
hash = "0xsnacks"
transaction = insert(:transaction, hash: hash, from_address_id: address.id)
insert(:receipt, transaction: transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
@ -20,8 +21,16 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
conn =
get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 1
assert List.first(conn.assigns.transactions.entries).hash == "0xsnacks"
assert html = html_response(conn, 200)
transaction_hash_divs =
Floki.find(html, "td.transactions__column--hash div.transactions__hash a")
assert length(transaction_hash_divs) == 1
assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [
"/en/transactions/#{hash}"
]
end
test "does not return transactions to this address", %{conn: conn} do
@ -37,7 +46,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
conn =
get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a receipt", %{conn: conn} do
@ -51,7 +61,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
conn =
get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a from address", %{conn: conn} do
@ -65,7 +76,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
conn =
get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a to address", %{conn: conn} do
@ -79,7 +91,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do
conn =
get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
end
end

@ -12,7 +12,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
test "returns transactions to this address", %{conn: conn} do
address = insert(:address)
transaction = insert(:transaction, hash: "0xsnacks", to_address_id: address.id)
hash = "0xsnacks"
transaction = insert(:transaction, hash: hash, to_address_id: address.id)
insert(:receipt, transaction: transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
@ -20,8 +21,17 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
conn =
get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 1
assert List.first(conn.assigns.transactions.entries).hash == "0xsnacks"
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 1
transaction_hash_divs =
Floki.find(html, "td.transactions__column--hash div.transactions__hash a")
assert length(transaction_hash_divs) == 1
assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [
"/en/transactions/#{hash}"
]
end
test "does not return transactions from this address", %{conn: conn} do
@ -37,7 +47,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
conn =
get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a receipt", %{conn: conn} do
@ -51,7 +62,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
conn =
get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a from address", %{conn: conn} do
@ -65,7 +77,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
conn =
get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
test "does not return related transactions without a to address", %{conn: conn} do
@ -79,7 +92,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
conn =
get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert conn.assigns.transactions.total_entries == 0
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
end
end
end

@ -17,31 +17,46 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
end
test "returns transactions for the block", %{conn: conn} do
transaction = insert(:transaction, hash: "0xsnacks")
hash = "0xsnacks"
transaction = insert(:transaction, hash: hash)
insert(:receipt, transaction: transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
address = insert(:address)
insert(:to_address, transaction: transaction, address: address)
insert(:from_address, transaction: transaction, address: address)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert conn.assigns.transactions.total_entries == 1
assert List.first(conn.assigns.transactions.entries).hash == "0xsnacks"
assert html = html_response(conn, 200)
transaction_hash_divs =
Floki.find(html, "td.transactions__column--hash div.transactions__hash a")
assert length(transaction_hash_divs) == 1
assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [
"/en/transactions/#{hash}"
]
end
test "does not return unrelated transactions", %{conn: conn} do
insert(:transaction)
block = insert(:block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert conn.assigns.transactions.total_entries == 0
refute html_response(conn, 200) =~ ~r/transactions__row/
end
test "does not return related transactions without a receipt", %{conn: conn} do
transaction = insert(:transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert conn.assigns.transactions.total_entries == 0
refute html_response(conn, 200) =~ ~r/transactions__row/
end
test "does not return related transactions without a from address", %{conn: conn} do
@ -49,8 +64,10 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
insert(:receipt, transaction: transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert conn.assigns.transactions.total_entries == 0
refute html_response(conn, 200) =~ ~r/transactions__row/
end
test "does not return related transactions without a to address", %{conn: conn} do
@ -58,8 +75,10 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
insert(:receipt, transaction: transaction)
block = insert(:block)
insert(:block_transaction, transaction: transaction, block: block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert conn.assigns.transactions.total_entries == 0
refute html_response(conn, 200) =~ ~r/transactions__row/
end
end
end

@ -12,8 +12,13 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
insert(:block_transaction, transaction: transaction, block: block)
insert(:to_address, transaction: transaction, address: address)
insert(:from_address, transaction: transaction, address: address)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert conn.assigns.transactions.entries == []
assert html = html_response(conn, 200)
assert html =~ ~r/Showing 0 Pending Transactions/
refute html =~ ~r/transactions__row/
end
test "does not count transactions that have a receipt", %{conn: conn} do
@ -24,8 +29,13 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
insert(:block_transaction, transaction: transaction, block: block)
insert(:to_address, transaction: transaction, address: address)
insert(:from_address, transaction: transaction, address: address)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert conn.assigns.transactions.total_entries === 0
assert html = html_response(conn, 200)
assert html =~ ~r/Showing 0 Pending Transactions/
refute html =~ ~r/transactions__row/
end
test "returns pending transactions", %{conn: conn} do
@ -33,8 +43,10 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
transaction = insert(:transaction)
insert(:to_address, transaction: transaction, address: address)
insert(:from_address, transaction: transaction, address: address)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert List.first(conn.assigns.transactions.entries).id == transaction.id
assert html_response(conn, 200) =~ transaction.hash
end
test "returns a count of pending transactions", %{conn: conn} do
@ -42,8 +54,10 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
transaction = insert(:transaction)
insert(:to_address, transaction: transaction, address: address)
insert(:from_address, transaction: transaction, address: address)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert length(conn.assigns.transactions.entries) === 1
assert html_response(conn, 200) =~ ~r/Showing 1 Pending Transactions/
end
test "paginates transactions using the last seen transaction", %{conn: conn} do
@ -59,19 +73,23 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
last_seen: transaction.id
)
assert conn.assigns.transactions.entries == []
refute html_response(conn, 200) =~ ~r/transactions__row/
end
test "sends back an estimate of the number of transactions", %{conn: conn} do
insert(:transaction)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
refute conn.assigns.transactions.total_entries == nil
assert html_response(conn, 200) =~ ~r/Showing 1 Pending Transactions/
end
test "works when there are no transactions", %{conn: conn} do
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert conn.assigns.transactions.total_entries == 0
assert conn.assigns.transactions.entries == []
assert html = html_response(conn, 200)
assert html =~ ~r/Showing 0 Pending Transactions/
refute html =~ ~r/transactions__row/
end
end
end

@ -18,7 +18,9 @@ defmodule ExplorerWeb.TransactionControllerTest do
block = insert(:block)
insert(:receipt, transaction: transaction)
insert(:block_transaction, transaction: transaction, block: block)
conn = get(conn, "/en/transactions")
assert length(conn.assigns.transactions.entries) === 1
end
@ -68,16 +70,26 @@ defmodule ExplorerWeb.TransactionControllerTest do
} do
block = insert(:block, %{number: 777})
transaction = insert(:transaction, hash: "0x8") |> with_block(block)
conn = get(conn, "/en/transactions/0x8")
assert conn.assigns.transaction.id == transaction.id
assert conn.assigns.transaction.block_number == block.number
assert html = html_response(conn, 200)
assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == transaction.hash
assert html |> Floki.find("span.transaction__item--primary a") |> Floki.text() ==
to_string(block.number)
end
test "returns a transaction without associated block data", %{conn: conn} do
transaction = insert(:transaction, hash: "0x8")
conn = get(conn, "/en/transactions/0x8")
assert conn.assigns.transaction.id == transaction.id
assert conn.assigns.transaction.block_number == ""
assert html = html_response(conn, 200)
assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == transaction.hash
assert html |> Floki.find("span.transaction__item--primary a") |> Floki.text() == ""
end
test "returns internal transactions for the transaction", %{conn: conn} do

@ -15,9 +15,8 @@ defmodule ExplorerWeb.TransactionLogControllerTest do
receipt = insert(:receipt, transaction: transaction)
address = insert(:address)
insert(:log, receipt: receipt, address_id: address.id)
path = transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash)
conn = get(conn, path)
conn = get(conn, transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash))
first_log = List.first(conn.assigns.logs.entries)
assert first_log.receipt_id == receipt.id

@ -123,9 +123,9 @@ defmodule ExplorerWeb.UserListTest do
insert(
:transaction,
hash: "0xSk8",
value: 5656,
gas: 1_230_000_000_000_123_123,
gas_price: 7_890_000_000_898_912_300_045,
value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether),
gas: Decimal.new(1_230_000_000_000_123_123),
gas_price: Decimal.new(7_890_000_000_898_912_300_045),
input: "0x00012",
nonce: 99045,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
@ -172,10 +172,15 @@ defmodule ExplorerWeb.UserListTest do
|> click(link("0xSk8"))
|> assert_has(css(".transaction__subheading", text: "0xSk8"))
|> assert_has(css(".transaction__item", text: "123,987"))
|> assert_has(css(".transaction__item", text: "5656 POA"))
|> assert_has(css(".transaction__item", text: "5,656 POA"))
|> assert_has(css(".transaction__item", text: "Success"))
|> assert_has(css(".transaction__item", text: "7,890,000,000,898,912,300,045"))
|> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123"))
|> assert_has(
css(
".transaction__item",
text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)"
)
)
|> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas"))
|> assert_has(css(".transaction__item", text: "0x00012"))
|> assert_has(css(".transaction__item", text: "99045"))
|> assert_has(css(".transaction__item", text: "123,987"))

@ -1,30 +0,0 @@
defmodule ExplorerWeb.BlockFormTest do
use Explorer.DataCase
alias ExplorerWeb.BlockForm
describe "build/1" do
test "that it has a number" do
block = insert(:block, number: 311)
insert_list(2, :transaction) |> list_with_block(block)
assert BlockForm.build(block).number == 311
end
test "that it returns a count of transactions" do
block = insert(:block, number: 311)
insert_list(2, :transaction) |> list_with_block(block)
assert BlockForm.build(block).transactions_count == 2
end
test "that it returns a block's age" do
block = insert(:block, timestamp: Timex.now() |> Timex.shift(hours: -1))
assert BlockForm.build(block).age == "1 hour ago"
end
test "formats a timestamp" do
date = "Jan-23-2018 10:48:56 AM Etc/UTC"
block = insert(:block, timestamp: Timex.parse!(date, "%b-%d-%Y %H:%M:%S %p %Z", :strftime))
assert BlockForm.build(block).formatted_timestamp == date
end
end
end

@ -1,37 +0,0 @@
defmodule ExplorerWeb.PendingTransactionFormTest do
use Explorer.DataCase
alias ExplorerWeb.PendingTransactionForm
describe "build/1" do
test "returns a successful transaction when there is a successful receipt" do
time = DateTime.utc_now()
to_address = insert(:address, hash: "0xcafe")
from_address = insert(:address, hash: "0xbee5")
transaction =
insert(
:transaction,
inserted_at: time,
updated_at: time,
to_address_id: to_address.id,
from_address_id: from_address.id
)
form =
PendingTransactionForm.build(transaction |> Repo.preload([:to_address, :from_address]))
assert(
form ==
Map.merge(transaction |> Repo.preload([:to_address, :from_address]), %{
to_address_hash: "0xcafe",
from_address_hash: "0xbee5",
first_seen: time |> Timex.from_now(),
last_seen: time |> Timex.from_now(),
status: :pending,
formatted_status: "Pending"
})
)
end
end
end

@ -1,308 +0,0 @@
defmodule ExplorerWeb.TransactionFormTest do
use Explorer.DataCase
alias ExplorerWeb.TransactionForm
describe "build/1" do
test "returns a successful transaction when there is a successful receipt" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address, hash: "0xsleepypuppy")
from_address = insert(:address, hash: "0xilovefrogs")
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id
)
|> with_block(block)
insert(:receipt, status: 1, transaction: transaction)
form =
transaction
|> Repo.preload([:block, :to_address, :from_address, :receipt])
|> TransactionForm.build()
formatted_timestamp = block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)
assert(
form == %{
block_number: 1,
age: "2 hours ago",
formatted_age: "2 hours ago (#{formatted_timestamp})",
formatted_timestamp: formatted_timestamp,
cumulative_gas_used: "99,523",
to_address_hash: "0xsleepypuppy",
from_address_hash: "0xilovefrogs",
confirmations: 23,
status: :success,
formatted_status: "Success",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
test "returns a failed transaction when there is a failed receipt" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address, hash: "0xsleepypuppy")
from_address = insert(:address, hash: "0xilovefrogs")
transaction =
insert(
:transaction,
gas: 155,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id
)
|> with_block(block)
insert(:receipt, status: 0, gas_used: 100, transaction: transaction)
form =
transaction
|> Repo.preload([:block, :to_address, :from_address, :receipt])
|> TransactionForm.build()
formatted_timestamp = block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)
assert(
form == %{
block_number: 1,
age: "2 hours ago",
formatted_age: "2 hours ago (#{formatted_timestamp})",
formatted_timestamp: formatted_timestamp,
cumulative_gas_used: "99,523",
to_address_hash: "0xsleepypuppy",
from_address_hash: "0xilovefrogs",
confirmations: 23,
status: :failed,
formatted_status: "Failed",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
test "returns a out of gas transaction when the gas matches the gas used" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address, hash: "0xsleepypuppy")
from_address = insert(:address, hash: "0xilovefrogs")
transaction =
insert(
:transaction,
gas: 555,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id
)
|> with_block(block)
insert(:receipt, status: 0, gas_used: 555, transaction: transaction)
form =
transaction
|> Repo.preload([:block, :to_address, :from_address, :receipt])
|> TransactionForm.build()
formatted_timestamp = block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)
assert(
form == %{
block_number: 1,
age: "2 hours ago",
formatted_age: "2 hours ago (#{formatted_timestamp})",
formatted_timestamp: formatted_timestamp,
cumulative_gas_used: "99,523",
to_address_hash: "0xsleepypuppy",
from_address_hash: "0xilovefrogs",
confirmations: 23,
status: :out_of_gas,
formatted_status: "Out of Gas",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
test "returns a pending transaction when there is no receipt" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address, hash: "0xsleepypuppy")
from_address = insert(:address, hash: "0xilovefrogs")
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id
)
|> with_block(block)
|> Repo.preload([:block, :to_address, :from_address, :receipt])
form = TransactionForm.build(transaction)
formatted_timestamp = block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime)
assert(
form == %{
block_number: 1,
age: "2 hours ago",
formatted_age: "2 hours ago (#{formatted_timestamp})",
formatted_timestamp: formatted_timestamp,
cumulative_gas_used: "99,523",
to_address_hash: "0xsleepypuppy",
from_address_hash: "0xilovefrogs",
confirmations: 23,
status: :pending,
formatted_status: "Pending",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
test "returns a pending transaction when there is no block" do
to_address = insert(:address, hash: "0xchadmuska")
from_address = insert(:address, hash: "0xtonyhawk")
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id
)
|> Repo.preload([:to_address, :from_address])
form = TransactionForm.build(transaction)
assert(
form == %{
block_number: "",
age: "Pending",
formatted_age: "Pending",
formatted_timestamp: "Pending",
cumulative_gas_used: "Pending",
to_address_hash: "0xchadmuska",
from_address_hash: "0xtonyhawk",
confirmations: 0,
status: :pending,
formatted_status: "Pending",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
test "works when there are no addresses" do
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: nil,
from_address_id: nil
)
|> Repo.preload([:block, :to_address, :from_address])
form = TransactionForm.build(transaction)
assert(
form == %{
block_number: "",
age: "Pending",
formatted_age: "Pending",
formatted_timestamp: "Pending",
cumulative_gas_used: "Pending",
to_address_hash: nil,
from_address_hash: nil,
confirmations: 0,
status: :pending,
formatted_status: "Pending",
first_seen: "48 years ago",
last_seen: "38 years ago"
}
)
end
end
describe "build_and_merge/1" do
test "it returns a merged map of a transaction and its built data" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
transaction =
insert(
:transaction,
hash: "0xkittenpower",
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
gas: 555
)
|> with_block(block)
insert(:receipt, status: 0, gas_used: 555, transaction: transaction)
form =
transaction
|> Repo.preload([:block, :to_address, :from_address, :receipt])
|> TransactionForm.build_and_merge()
assert form.hash == "0xkittenpower"
assert form.block_number == 1
assert form.formatted_status == "Out of Gas"
end
end
end

@ -1,19 +0,0 @@
defmodule ExplorerWeb.WeiConverterTest do
use ExUnit.Case
alias ExplorerWeb.WeiConverter
test "it converts wei to ether correctly" do
wei = Decimal.new(239_047_000_000_000)
expected_value = Decimal.new(0.000239047)
assert WeiConverter.to_ether(wei) == expected_value
end
test "it converts wei to Gwei correctly" do
wei = Decimal.new(239_047_123_000_000)
expected_value = Decimal.new(239_047.123)
assert WeiConverter.to_gwei(wei) == expected_value
end
end

@ -0,0 +1,57 @@
defmodule ExplorerWeb.TransactionViewTest do
use ExplorerWeb.ConnCase, async: true
alias Explorer.Chain.Transaction
alias Explorer.Repo
alias ExplorerWeb.TransactionView
describe "formatted_status/1" do
test "without receipt" do
transaction =
:transaction
|> insert()
|> Repo.preload(:receipt)
assert TransactionView.formatted_status(transaction) == "Pending"
end
test "with receipt with status 0 with gas_used < gas" do
gas = 2
%Transaction{id: id} = insert(:transaction, gas: gas)
insert(:receipt, gas_used: gas - 1, status: 0, transaction_id: id)
transaction =
Transaction
|> Repo.get!(id)
|> Repo.preload(:receipt)
assert TransactionView.formatted_status(transaction) == "Failed"
end
test "with receipt with status 0 with gas <= gas_used" do
gas = 2
%Transaction{id: id} = insert(:transaction, gas: gas)
insert(:receipt, gas_used: gas, status: 0, transaction_id: id)
transaction =
Transaction
|> Repo.get!(id)
|> Repo.preload(:receipt)
assert TransactionView.formatted_status(transaction) == "Out of Gas"
end
test "with receipt with status 1" do
gas = 2
%Transaction{id: id} = insert(:transaction, gas: gas)
insert(:receipt, gas_used: gas - 1, status: 1, transaction_id: id)
transaction =
Transaction
|> Repo.get!(id)
|> Repo.preload(:receipt)
assert TransactionView.formatted_status(transaction) == "Success"
end
end
end

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

@ -28,10 +28,12 @@
"exq_ui": {:hex, :exq_ui, "0.9.0", "e97e9fa9009f30d2926b51a166e40a3a521e83f61f3f333fede8335b2aa57f09", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:exq, "~> 0.9", [hex: :exq, repo: "hexpm", optional: false]}, {:plug, ">= 0.8.1 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"exvcr": {:hex, :exvcr, "0.10.0", "5150808404d9f48dbda636f70f7f8fefd93e2433cd39f695f810e73b3a9d1736", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 0.13", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], [], "hexpm"},
"floki": {:hex, :floki, "0.20.1", "4ee125a81605f5920189ac6afbee8dbf4de7808319e726f0d57781e660185980", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"flow": {:hex, :flow, "0.12.0", "32c5a5f3ff6693e004b6c17a8c64dce2f8cdaf9564912d79427176013a586ab6", [:mix], [{:gen_stage, "~> 0.12.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.12.2", "e0e347cbb1ceb5f4e68a526aec4d64b54ad721f0a8b30aa9d28e0ad749419cbb", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.14.0", "1a019a2e51d5ad3d126efe166dcdf6563768e5d06c32a99ad2281a1fa94b4c72", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"httpoison": {:hex, :httpoison, "1.1.0", "497949fb62924432f64a45269d20e6f61ecf35084ffa270917afcdb7cd4d8061", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jiffy": {:hex, :jiffy, "0.15.1", "be83b09388da1a6c7e798207c9d6a1c4d71bb95fcc387d37d35861788f49ab97", [:rebar3], [], "hexpm"},
@ -42,6 +44,7 @@
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mox": {:hex, :mox, "0.3.2", "3b9b8364fd4f28628139de701d97c636b27a8f925f57a8d5a1b85fbd620dad3a", [:mix], [], "hexpm"},
"new_relixir": {:hex, :new_relixir, "0.4.1", "55907ef2b968cf9c9278c58e43d1d68f756d21101fa552639c566c5536280915", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},

Loading…
Cancel
Save