Merge pull request #6092 from blockscout/account

Blockscout Account functionality
pull/5817/head
Victor Baranov 2 years ago committed by GitHub
commit 9944f5d532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      .github/workflows/config.yml
  2. 3
      .gitignore
  3. 2
      CHANGELOG.md
  4. 2228
      apps/block_scout_web/API blueprint.md
  5. 2227
      apps/block_scout_web/API.md
  6. 2
      apps/block_scout_web/assets/css/app.scss
  7. 143
      apps/block_scout_web/assets/css/components/_account.scss
  8. 10
      apps/block_scout_web/assets/css/components/_btn_line.scss
  9. 18
      apps/block_scout_web/assets/css/components/_card.scss
  10. 43
      apps/block_scout_web/assets/js/lib/public_tags_request_form.js
  11. 19
      apps/block_scout_web/assets/js/lib/smart_contract/functions.js
  12. 13
      apps/block_scout_web/assets/js/lib/smart_contract/interact.js
  13. 19
      apps/block_scout_web/assets/js/pages/account/delete_item_handler.js
  14. 4
      apps/block_scout_web/assets/webpack.config.js
  15. 11
      apps/block_scout_web/config/config.exs
  16. 9
      apps/block_scout_web/config/runtime/test.exs
  17. 8
      apps/block_scout_web/config/test.exs
  18. 65
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  19. 4
      apps/block_scout_web/lib/block_scout_web/chain.ex
  20. 83
      apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex
  21. 87
      apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex
  22. 476
      apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex
  23. 65
      apps/block_scout_web/lib/block_scout_web/controllers/account/api_key_controller.ex
  24. 73
      apps/block_scout_web/lib/block_scout_web/controllers/account/auth_controller.ex
  25. 87
      apps/block_scout_web/lib/block_scout_web/controllers/account/custom_abi_controller.ex
  26. 114
      apps/block_scout_web/lib/block_scout_web/controllers/account/public_tags_request_controller.ex
  27. 49
      apps/block_scout_web/lib/block_scout_web/controllers/account/tag_address_controller.ex
  28. 49
      apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex
  29. 92
      apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_address_controller.ex
  30. 26
      apps/block_scout_web/lib/block_scout_web/controllers/account/watchlist_controller.ex
  31. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  32. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  33. 10
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  34. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
  35. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  36. 7
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  37. 59
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  38. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
  39. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  40. 9
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  41. 10
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  42. 7
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  43. 51
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex
  44. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex
  45. 70
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  46. 6
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex
  47. 6
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  48. 6
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
  49. 25
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
  50. 13
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
  51. 13
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
  52. 14
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
  53. 13
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
  54. 6
      apps/block_scout_web/lib/block_scout_web/endpoint.ex
  55. 75
      apps/block_scout_web/lib/block_scout_web/models/get_address_tags.ex
  56. 41
      apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex
  57. 136
      apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex
  58. 21
      apps/block_scout_web/lib/block_scout_web/plug/check_account_api.ex
  59. 31
      apps/block_scout_web/lib/block_scout_web/plug/check_account_web.ex
  60. 223
      apps/block_scout_web/lib/block_scout_web/plug/redis_cookie.ex
  61. 34
      apps/block_scout_web/lib/block_scout_web/templates/account/api_key/form.html.eex
  62. 51
      apps/block_scout_web/lib/block_scout_web/templates/account/api_key/index.html.eex
  63. 18
      apps/block_scout_web/lib/block_scout_web/templates/account/api_key/row.html.eex
  64. 36
      apps/block_scout_web/lib/block_scout_web/templates/account/auth/profile.html.eex
  65. 25
      apps/block_scout_web/lib/block_scout_web/templates/account/common/_nav.html.eex
  66. 39
      apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/form.html.eex
  67. 51
      apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/index.html.eex
  68. 18
      apps/block_scout_web/lib/block_scout_web/templates/account/custom_abi/row.html.eex
  69. 8
      apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex
  70. 72
      apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/form.html.eex
  71. 44
      apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/index.html.eex
  72. 20
      apps/block_scout_web/lib/block_scout_web/templates/account/public_tags_request/row.html.eex
  73. 33
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/form.html.eex
  74. 41
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/index.html.eex
  75. 15
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_address/row.html.eex
  76. 33
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex
  77. 41
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex
  78. 18
      apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex
  79. 42
      apps/block_scout_web/lib/block_scout_web/templates/account/watchlist/show.html.eex
  80. 91
      apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/form.html.eex
  81. 29
      apps/block_scout_web/lib/block_scout_web/templates/account/watchlist_address/row.html.eex
  82. 16
      apps/block_scout_web/lib/block_scout_web/templates/address/_labels.html.eex
  83. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  84. 25
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  85. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex
  86. 46
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  87. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex
  88. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  89. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  90. 53
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  91. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex
  92. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  93. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  94. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  95. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
  96. 52
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex
  97. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex
  98. 4
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_minus.html.eex
  99. 3
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_pen.html.eex
  100. 4
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_svg_plus.html.eex
  101. Some files were not shown because too many files have changed in this diff Show More

@ -12,6 +12,9 @@ env:
MIX_ENV: test
OTP_VERSION: '24.3.4.1'
ELIXIR_VERSION: '1.13.4'
ACCOUNT_AUTH0_DOMAIN: 'blockscoutcom.us.auth0.com'
ACCOUNT_AUTH0_LOGOUT_URL: 'https://blockscoutcom.us.auth0.com/v2/logout'
ACCOUNT_AUTH0_LOGOUT_RETURN_URL: 'https://blockscout.com/auth/logout'
jobs:
build-and-cache:
@ -34,7 +37,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -89,12 +92,12 @@ jobs:
with:
otp-version: ${{ env.OTP_VERSION }}
elixir-version: ${{ env.ELIXIR_VERSION }}
- name: Restore Mix Deps Cache
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -118,7 +121,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -141,7 +144,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -181,7 +184,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -207,7 +210,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -235,7 +238,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -281,7 +284,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -340,7 +343,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -396,7 +399,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -463,7 +466,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -493,6 +496,11 @@ jobs:
runs-on: ubuntu-latest
needs: build-and-cache
services:
redis_db:
image: 'redis:alpine'
ports:
- 6379:6379
postgres:
image: postgres
env:
@ -524,7 +532,7 @@ jobs:
uses: actions/cache@v2
id: deps-cache
with:
path: |
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_12-${{ hashFiles('mix.lock') }}
@ -571,4 +579,6 @@ jobs:
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
CHAIN_ID: "77"
ADMIN_PANEL_ENABLED: "true"
ADMIN_PANEL_ENABLED: "true"
ACCOUNT_ENABLED: "true"
ACCOUNT_REDIS_URL: "redis://localhost:6379"

3
.gitignore vendored

@ -9,6 +9,9 @@
/*.ez
/logs
# mix dialyzer artifacts
/priv/plts
# Generated on crash by the VM
erl_crash.dump

@ -1,6 +1,8 @@
## Current
### Features
- [#6092](https://github.com/blockscout/blockscout/pull/6092) - Blockscout Account functionality
- [#6073](https://github.com/blockscout/blockscout/pull/6073) - Add vyper support for rust verifier microservice integration
### Fixes

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -124,6 +124,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/_dropzone";
@import "components/_search";
@import "components/_ad";
@import "components/_account";
// Font Awesome
@import "components/_fontawesome_icon";

@ -0,0 +1,143 @@
div.divider {
height: inherit;
width: 1px;
background: #828ba0;
margin-left: 10px;
margin-right: 10px;
}
input.profile-item {
margin-left: 10px;
color: #828ba0;
outline: none;
}
.header-account {
font-size: 18px;
}
.label-account {
font-size: 1rem;
}
.white {
color: #fff;
}
.card-body-account {
max-width: none !important;
}
.form-input {
display: flex;
margin-bottom: 1rem;
}
.form-checkbox {
margin-right: 0.5rem;
align-self: center;
}
.label-checkbox {
font-size: 14px;
margin-bottom: 0;
}
.o-flow-x {
overflow-x: auto;
}
.fs-14 {
font-size: 14px;
}
.acc-link-active {
&:hover, &:focus {
background-color: $primary !important;
color: #fff !important;
}
background-color: $primary;
color: #fff;
}
.table-watchlist {
margin: 30px;
@include media-breakpoint-down(md) {
margin: 0;
}
}
.form-error {
display: block;
font-size: 13px;
line-height: 1.2;
padding-top: 10px;
}
.navbar-account {
@include media-breakpoint-down(sm) {
max-width: 100%;
flex-grow: 1;
}
}
.nav-item.account {
@include media-breakpoint-down(sm) {
width: auto;
display: inline-grid;
}
}
.nav.account {
@include media-breakpoint-down(sm) {
display: inline;
}
}
li.public-tags-address {
list-style: none;
display: flex;
}
input.public-tags-address {
flex-basis: 557px;
width: auto;
}
.add-form-field {
background: none;
border: none;
margin-left: 0.5px;
}
.remove-form-field {
background: none;
border: none;
svg {
margin-top: 3px;
}
}
.multiple-input-fields-container {
padding-inline-start: 0;
display: inline-block;
margin-bottom: 0;
}
.line-input {
@include media-breakpoint-up(md) {
display: flex;
justify-content: space-between;
.form-group {
flex-grow: 1;
margin-right: 4rem;
}
}
}
.mr-4-rem {
margin-right: 4rem;
}

@ -4,3 +4,13 @@ $btn-line-color: $secondary !default;
.btn-line {
@include btn-line($btn-line-bg, $btn-line-color);
}
.btn-line-inversed {
@include btn-line($btn-line-color, $btn-line-bg);
}
.btn-line-inversed:hover {
background-color: $btn-line-color;
color: $btn-line-bg;
text-decoration: none;
}

@ -326,4 +326,22 @@ $card-tab-icon-color-active: #fff !default;
.function-output {
margin-left: -1rem;
}
.functions-tabs input[type="radio"] {
display: none;
}
.card-misc-container {
padding-left: $card-horizontal-padding;
padding-top: $card-horizontal-padding;
.btn-line-inversed,
.btn-line {
display: inline-flex;
}
}
.nav-pills .nav-link.active {
background-color: $primary;
}

@ -0,0 +1,43 @@
import $ from 'jquery'
const $removeButton = $('.remove-form-field')[0]
const $container = $('#' + $removeButton.dataset.container)
const index = parseInt($container[0].dataset.index)
if (index <= 1) {
$('.remove-form-field').hide()
}
$('.add-form-field').on('click', (event) => {
event.preventDefault()
console.log(event)
const $container = $('#' + event.currentTarget.dataset.container)
const index = parseInt($container[0].dataset.index)
if (index < 10) {
$container.append($.parseHTML(event.currentTarget.dataset.prototype))
$container[0].dataset.index = index + 1
}
if (index >= 9) {
$('.add-form-field').hide()
}
if (index <= 1) {
$('.remove-form-field').show()
}
})
$('[data-multiple-input-field-container]').on('click', '.remove-form-field', (event) => {
event.preventDefault()
console.log(event)
const $container = $('#' + event.currentTarget.dataset.container)
const index = parseInt($container[0].dataset.index)
if (index > 1) {
$container[0].dataset.index = index - 1
event.currentTarget.parentElement.remove()
}
if (index >= 10) {
$('.add-form-field').show()
}
if (index <= 2) {
$('.remove-form-field').hide()
}
})

@ -4,7 +4,7 @@ import { queryMethod, callMethod } from './interact'
import { walletEnabled, connectToWallet, disconnectWallet, web3ModalInit } from './connect.js'
import '../../pages/address'
const loadFunctions = (element) => {
const loadFunctions = (element, isCustomABI) => {
const $element = $(element)
const url = $element.data('url')
const hash = $element.data('hash')
@ -13,7 +13,7 @@ const loadFunctions = (element) => {
$.get(
url,
{ hash, type, action },
{ hash, type, action, is_custom_abi: isCustomABI },
response => $element.html(response)
)
.done(function () {
@ -21,7 +21,9 @@ const loadFunctions = (element) => {
document.querySelector(disconnectSelector) && document.querySelector(disconnectSelector).addEventListener('click', disconnectWallet)
web3ModalInit(connectToWallet)
$('[data-function]').each((_, element) => {
const selector = isCustomABI ? '[data-function-custom]' : '[data-function]'
$(selector).each((_, element) => {
readWriteFunction(element)
})
@ -79,9 +81,10 @@ const readWriteFunction = (element) => {
return
}
const type = $('[data-smart-contract-functions]').data('type')
const isCustomABI = $form.data('custom-abi')
walletEnabled()
.then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer))
.then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer, isCustomABI))
} else if (action === 'write') {
const explorerChainId = $form.data('chainId')
walletEnabled()
@ -93,5 +96,11 @@ const readWriteFunction = (element) => {
const container = $('[data-smart-contract-functions]')
if (container.length) {
loadFunctions(container)
loadFunctions(container, false)
}
const customABIContainer = $('[data-smart-contract-functions-custom]')
if (customABIContainer.length) {
loadFunctions(customABIContainer, true)
}

@ -3,11 +3,12 @@ import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessag
import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccountPromise, getMethodInputs, prepareMethodArgs } from './common_helpers'
import BigNumber from 'bignumber.js'
export const queryMethod = (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) => {
export const queryMethod = (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer, isCustomABI) => {
const data = {
function_name: functionName,
method_id: $methodId.val(),
type
type,
is_custom_abi: isCustomABI
}
data.args_count = args.length
@ -62,7 +63,7 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f
openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, message, false)
})
.on('transactionHash', function (txHash) {
onTransactionHash(txHash, $element, functionName)
onTransactionHash(txHash, functionName)
})
} else {
const txParams = {
@ -75,7 +76,7 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f
params: [txParams]
})
.then(function (txHash) {
onTransactionHash(txHash, $element, functionName)
onTransactionHash(txHash, functionName)
})
.catch(function (error) {
openErrorModal('Error in sending transaction for fallback method', formatError(error), false)
@ -88,8 +89,8 @@ export const callMethod = (isWalletEnabled, $functionInputs, explorerChainId, $f
})
}
function onTransactionHash (txHash, $element, functionName) {
openModalWithMessage($element.find('#pending-contract-write'), true, txHash)
function onTransactionHash (txHash, functionName) {
openModalWithMessage($('#pending-contract-write'), true, txHash)
const getTxReceipt = (txHash) => {
window.ethereum.request({
method: 'eth_getTransactionReceipt',

@ -0,0 +1,19 @@
import $ from 'jquery'
$('[data-delete-item]').on('click', (event) => {
event.preventDefault()
if (confirm('Are you sure you want to delete item?')) {
$(event.currentTarget.parentElement).find('form').trigger('submit')
}
})
$('[data-delete-request]').on('click', (event) => {
event.preventDefault()
const result = prompt('Public tags: "' + event.currentTarget.dataset.tags.replace(';', '" and "') + '" will be removed.\nWhy do you want to remove tags?')
if (result) {
$(event.currentTarget.parentElement).find('[name="remove_reason"]').val(result)
$(event.currentTarget.parentElement).find('form').trigger('submit')
}
})

@ -70,7 +70,9 @@ const appJs =
'token-overview': './js/pages/token/overview.js',
'export-csv': './css/export-csv.scss',
'csv-download': './js/lib/csv_download.js',
'dropzone': './js/lib/dropzone.js'
'dropzone': './js/lib/dropzone.js',
'delete-item-handler': './js/pages/account/delete_item_handler.js',
'public-tags-request-form': './js/lib/public_tags_request_form.js'
},
output: {
filename: '[name].js',

@ -8,7 +8,7 @@ import Config
# General application configuration
config :block_scout_web,
namespace: BlockScoutWeb,
ecto_repos: [Explorer.Repo]
ecto_repos: [Explorer.Repo, Explorer.Repo.Account]
config :block_scout_web,
admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true"
@ -78,6 +78,15 @@ config :block_scout_web, BlockScoutWeb.ApiRouter,
config :block_scout_web, BlockScoutWeb.WebRouter, enabled: System.get_env("DISABLE_WEBAPP") != "true"
# Configures Ueberauth local settings
config :ueberauth, Ueberauth,
providers: [
auth0: {
Ueberauth.Strategy.Auth0,
[callback_path: "/auth/auth0/callback"]
}
]
config :hammer,
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}

@ -4,6 +4,15 @@ config :explorer, Explorer.ExchangeRates, enabled: false, store: :none
config :explorer, Explorer.KnownTokens, enabled: false, store: :none
config :ueberauth, Ueberauth.Strategy.Auth0.OAuth,
domain: "example.com",
client_id: "clien_id",
client_secret: "secrets"
config :ueberauth, Ueberauth,
logout_url: "example.com/logout",
logout_return_to_url: "example.com/return"
variant =
if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do
"parity"

@ -23,3 +23,11 @@ config :wallaby, screenshot_on_failure: true, driver: Wallaby.Chrome, js_errors:
config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: false
config :block_scout_web, :captcha_helper, BlockScoutWeb.TestCaptchaHelper
config :ueberauth, Ueberauth,
providers: [
auth0: {
Ueberauth.Strategy.Auth0,
[callback_url: "example.com/callback"]
}
]

@ -13,11 +13,76 @@ defmodule BlockScoutWeb.ApiRouter do
Router for API
"""
use BlockScoutWeb, :router
alias BlockScoutWeb.Plug.CheckAccountAPI
pipeline :api do
plug(:accepts, ["json"])
end
pipeline :account_api do
plug(:fetch_session)
plug(:protect_from_forgery)
plug(CheckAccountAPI)
end
alias BlockScoutWeb.Account.Api.V1.{TagsController, UserController}
scope "/account/v1" do
pipe_through(:api)
pipe_through(:account_api)
get("/get_csrf", UserController, :get_csrf)
scope "/user" do
get("/info", UserController, :info)
get("/watchlist", UserController, :watchlist)
delete("/watchlist/:id", UserController, :delete_watchlist)
post("/watchlist", UserController, :create_watchlist)
put("/watchlist/:id", UserController, :update_watchlist)
get("/api_keys", UserController, :api_keys)
delete("/api_keys/:api_key", UserController, :delete_api_key)
post("/api_keys", UserController, :create_api_key)
put("/api_keys/:api_key", UserController, :update_api_key)
get("/custom_abis", UserController, :custom_abis)
delete("/custom_abis/:id", UserController, :delete_custom_abi)
post("/custom_abis", UserController, :create_custom_abi)
put("/custom_abis/:id", UserController, :update_custom_abi)
get("/public_tags", UserController, :public_tags_requests)
delete("/public_tags/:id", UserController, :delete_public_tags_request)
post("/public_tags", UserController, :create_public_tags_request)
put("/public_tags/:id", UserController, :update_public_tags_request)
scope "/tags" do
get("/address/", UserController, :tags_address)
get("/address/:id", UserController, :tags_address)
delete("/address/:id", UserController, :delete_tag_address)
post("/address/", UserController, :create_tag_address)
put("/address/:id", UserController, :update_tag_address)
get("/transaction/", UserController, :tags_transaction)
get("/transaction/:id", UserController, :tags_transaction)
delete("/transaction/:id", UserController, :delete_tag_transaction)
post("/transaction/", UserController, :create_tag_transaction)
put("/transaction/:id", UserController, :update_tag_transaction)
end
end
end
scope "/account/v1" do
pipe_through(:api)
pipe_through(:account_api)
scope "/tags" do
get("/address/:address_hash", TagsController, :tags_address)
get("/transaction/:transaction_hash", TagsController, :tags_transaction)
end
end
scope "/v1", as: :api_v1 do
pipe_through(:api)
alias BlockScoutWeb.API.{EthRPC, RPC, V1}

@ -32,10 +32,10 @@ defmodule BlockScoutWeb.Chain do
alias Explorer.PagingOptions
defimpl Poison.Encoder, for: Poison.Encoder.Decimal do
defimpl Poison.Encoder, for: Decimal do
def encode(value, _opts) do
# silence the xref warning
decimal = Poison.Encoder.Decimal
decimal = Decimal
[?\", decimal.to_string(value), ?\"]
end

@ -0,0 +1,83 @@
defmodule BlockScoutWeb.Account.Api.V1.FallbackController do
use Phoenix.Controller
alias BlockScoutWeb.Account.Api.V1.UserView
alias Ecto.Changeset
def call(conn, {:identity, _}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "User not found"})
end
def call(conn, {:watchlist, _}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Watchlist not found"})
end
def call(conn, {:error, %{reason: :item_not_found}}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Item not found"})
end
def call(conn, {:error, %Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(UserView)
|> render(:changeset_errors, changeset: changeset)
end
def call(conn, {:create_tag, {:error, message}}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(UserView)
|> render(:message, %{message: message})
end
def call(conn, {:watchlist_delete, false}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Watchlist address not found"})
end
def call(conn, {:tag_delete, false}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Tag not found"})
end
def call(conn, {:api_key_delete, false}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Api key not found"})
end
def call(conn, {:custom_abi_delete, false}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Custom ABI not found"})
end
def call(conn, {:public_tag_delete, false}) do
conn
|> put_status(:not_found)
|> put_view(UserView)
|> render(:message, %{message: "Error"})
end
def call(conn, {:auth, _}) do
conn
|> put_status(:unauthorized)
|> put_view(UserView)
|> render(:message, %{message: "Unauthorized"})
end
end

@ -0,0 +1,87 @@
defmodule BlockScoutWeb.Account.Api.V1.TagsController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
alias BlockScoutWeb.Models.{GetAddressTags, GetTransactionTags, UserFromAuth}
alias Explorer.Account.Identity
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Hash.{Address, Full}
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController)
def tags_address(conn, %{"address_hash" => address_hash}) do
personal_tags =
if is_nil(current_user(conn)) do
%{personal_tags: [], watchlist_names: []}
else
uid = current_user(conn).id
with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
{:address_hash, {:ok, address_hash}} <- {:address_hash, Address.cast(address_hash)} do
GetAddressTags.get_address_tags(address_hash, %{id: identity.id, watchlist_id: watchlist.id})
else
_ ->
%{personal_tags: [], watchlist_names: []}
end
end
public_tags =
case Address.cast(address_hash) do
{:ok, address_hash} ->
GetAddressTags.get_public_tags(address_hash)
_ ->
%{common_tags: []}
end
conn
|> put_status(200)
|> render(:address_tags, %{tags_map: Map.merge(personal_tags, public_tags)})
end
def tags_transaction(conn, %{"transaction_hash" => transaction_hash}) do
transaction =
with {:ok, transaction_hash} <- Full.cast(transaction_hash),
{:ok, transaction} <- Chain.hash_to_transaction(transaction_hash) do
transaction
else
_ ->
nil
end
personal_tags =
if is_nil(current_user(conn)) do
%{personal_tags: [], watchlist_names: [], personal_tx_tag: nil}
else
uid = current_user(conn).id
with {:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
false <- is_nil(transaction) do
GetTransactionTags.get_transaction_with_addresses_tags(transaction, %{
id: identity.id,
watchlist_id: watchlist.id
})
else
_ ->
%{personal_tags: [], watchlist_names: [], personal_tx_tag: nil}
end
end
public_tags_from =
if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.from_address_hash).common_tags
public_tags_to =
if is_nil(transaction), do: [], else: GetAddressTags.get_public_tags(transaction.to_address_hash).common_tags
public_tags = %{common_tags: public_tags_from ++ public_tags_to}
conn
|> put_status(200)
|> render(:transaction_tags, %{tags_map: Map.merge(personal_tags, public_tags)})
end
end

@ -0,0 +1,476 @@
defmodule BlockScoutWeb.Account.Api.V1.UserController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import Ecto.Query, only: [from: 2]
alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.Account.Api.Key, as: ApiKey
alias Explorer.Account.CustomABI
alias Explorer.Account.{Identity, PublicTagsRequest, TagAddress, TagTransaction, WatchlistAddress}
alias Explorer.ExchangeRates.Token
alias Explorer.{Market, Repo}
alias Plug.CSRFProtection
action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController)
@ok_message "OK"
def info(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)} do
conn
|> put_status(200)
|> render(:user_info, %{identity: identity})
end
end
def watchlist(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
watchlist_with_addresses <- preload_watchlist_address_fetched_coin_balance(watchlist) do
conn
|> put_status(200)
|> render(:watchlist_addresses, %{
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
watchlist_addresses: watchlist_with_addresses.watchlist_addresses
})
end
end
def delete_watchlist(conn, %{"id" => watchlist_address_id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
{count, _} <- WatchlistAddress.delete(watchlist_address_id, watchlist.id),
{:watchlist_delete, true} <- {:watchlist_delete, count > 0} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_watchlist(conn, %{
"address_hash" => address_hash,
"name" => name,
"notification_settings" => %{
"native" => %{
"incoming" => watch_coin_input,
"outcoming" => watch_coin_output
},
"ERC-20" => %{
"incoming" => watch_erc_20_input,
"outcoming" => watch_erc_20_output
},
"ERC-721" => %{
"incoming" => watch_erc_721_input,
"outcoming" => watch_erc_721_output
}
# ,
# "ERC-1155" => %{
# "incoming" => watch_erc_1155_input,
# "outcoming" => watch_erc_1155_output
# }
},
"notification_methods" => %{
"email" => notify_email
}
}) do
watchlist_params = %{
name: name,
watch_coin_input: watch_coin_input,
watch_coin_output: watch_coin_output,
watch_erc_20_input: watch_erc_20_input,
watch_erc_20_output: watch_erc_20_output,
watch_erc_721_input: watch_erc_721_input,
watch_erc_721_output: watch_erc_721_output,
watch_erc_1155_input: watch_erc_721_input,
watch_erc_1155_output: watch_erc_721_output,
notify_email: notify_email,
address_hash: address_hash
}
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
{:ok, watchlist_address} <-
WatchlistAddress.create(Map.put(watchlist_params, :watchlist_id, watchlist.id)),
watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do
conn
|> put_status(200)
|> render(:watchlist_address, %{
watchlist_address: watchlist_address_preloaded,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
})
end
end
def update_watchlist(conn, %{
"id" => watchlist_address_id,
"address_hash" => address_hash,
"name" => name,
"notification_settings" => %{
"native" => %{
"incoming" => watch_coin_input,
"outcoming" => watch_coin_output
},
"ERC-20" => %{
"incoming" => watch_erc_20_input,
"outcoming" => watch_erc_20_output
},
"ERC-721" => %{
"incoming" => watch_erc_721_input,
"outcoming" => watch_erc_721_output
}
# ,
# "ERC-1155" => %{
# "incoming" => watch_erc_1155_input,
# "outcoming" => watch_erc_1155_output
# }
},
"notification_methods" => %{
"email" => notify_email
}
}) do
watchlist_params = %{
id: watchlist_address_id,
name: name,
watch_coin_input: watch_coin_input,
watch_coin_output: watch_coin_output,
watch_erc_20_input: watch_erc_20_input,
watch_erc_20_output: watch_erc_20_output,
watch_erc_721_input: watch_erc_721_input,
watch_erc_721_output: watch_erc_721_output,
watch_erc_1155_input: watch_erc_721_input,
watch_erc_1155_output: watch_erc_721_output,
notify_email: notify_email,
address_hash: address_hash
}
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:watchlist, %{watchlists: [watchlist | _]}} <-
{:watchlist, Repo.account_repo().preload(identity, :watchlists)},
{:ok, watchlist_address} <-
WatchlistAddress.update(Map.put(watchlist_params, :watchlist_id, watchlist.id)),
watchlist_address_preloaded <- WatchlistAddress.preload_address_fetched_coin_balance(watchlist_address) do
conn
|> put_status(200)
|> render(:watchlist_address, %{
watchlist_address: watchlist_address_preloaded,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
})
end
end
def tags_address(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
address_tags <- TagAddress.get_tags_address_by_identity_id(identity.id) do
conn
|> put_status(200)
|> render(:address_tags, %{address_tags: address_tags})
end
end
def delete_tag_address(conn, %{"id" => tag_id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{count, _} <- TagAddress.delete(tag_id, identity.id),
{:tag_delete, true} <- {:tag_delete, count > 0} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_tag_address(conn, %{"address_hash" => address_hash, "name" => name}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, address_tag} <-
TagAddress.create(%{
name: name,
address_hash: address_hash,
identity_id: identity.id
}) do
conn
|> put_status(200)
|> render(:address_tag, %{address_tag: address_tag})
end
end
def update_tag_address(conn, %{"id" => tag_id} = attrs) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, address_tag} <-
TagAddress.update(
reject_nil_map_values(%{
id: tag_id,
name: attrs["name"],
address_hash: attrs["address_hash"],
identity_id: identity.id
})
) do
conn
|> put_status(200)
|> render(:address_tag, %{address_tag: address_tag})
end
end
def tags_transaction(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
transaction_tags <- TagTransaction.get_tags_transaction_by_identity_id(identity.id) do
conn
|> put_status(200)
|> render(:transaction_tags, %{transaction_tags: transaction_tags})
end
end
def delete_tag_transaction(conn, %{"id" => tag_id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{count, _} <- TagTransaction.delete(tag_id, identity.id),
{:tag_delete, true} <- {:tag_delete, count > 0} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_tag_transaction(conn, %{"transaction_hash" => tx_hash, "name" => name}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, transaction_tag} <-
TagTransaction.create(%{
name: name,
tx_hash: tx_hash,
identity_id: identity.id
}) do
conn
|> put_status(200)
|> render(:transaction_tag, %{transaction_tag: transaction_tag})
end
end
def update_tag_transaction(conn, %{"id" => tag_id} = attrs) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, transaction_tag} <-
TagTransaction.update(
reject_nil_map_values(%{
id: tag_id,
name: attrs["name"],
tx_hash: attrs["transaction_hash"],
identity_id: identity.id
})
) do
conn
|> put_status(200)
|> render(:transaction_tag, %{transaction_tag: transaction_tag})
end
end
def api_keys(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
api_keys <- ApiKey.get_api_keys_by_identity_id(identity.id) do
conn
|> put_status(200)
|> render(:api_keys, %{api_keys: api_keys})
end
end
def delete_api_key(conn, %{"api_key" => api_key_uuid}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{count, _} <- ApiKey.delete(api_key_uuid, identity.id),
{:api_key_delete, true} <- {:api_key_delete, count > 0} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_api_key(conn, %{"name" => api_key_name}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, api_key} <-
ApiKey.create(%{name: api_key_name, identity_id: identity.id}) do
conn
|> put_status(200)
|> render(:api_key, %{api_key: api_key})
end
end
def update_api_key(conn, %{"name" => api_key_name, "api_key" => api_key_value}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, api_key} <-
ApiKey.update(%{value: api_key_value, name: api_key_name, identity_id: identity.id}) do
conn
|> put_status(200)
|> render(:api_key, %{api_key: api_key})
end
end
def custom_abis(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
custom_abis <- CustomABI.get_custom_abis_by_identity_id(identity.id) do
conn
|> put_status(200)
|> render(:custom_abis, %{custom_abis: custom_abis})
end
end
def delete_custom_abi(conn, %{"id" => id}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{count, _} <- CustomABI.delete(id, identity.id),
{:custom_abi_delete, true} <- {:custom_abi_delete, count > 0} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_custom_abi(conn, %{"contract_address_hash" => contract_address_hash, "name" => name, "abi" => abi}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, custom_abi} <-
CustomABI.create(%{
name: name,
address_hash: contract_address_hash,
abi: abi,
identity_id: identity.id
}) do
conn
|> put_status(200)
|> render(:custom_abi, %{custom_abi: custom_abi})
end
end
def update_custom_abi(
conn,
%{
"id" => id
} = params
) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, custom_abi} <-
CustomABI.update(
reject_nil_map_values(%{
id: id,
name: params["name"],
address_hash: params["contract_address_hash"],
abi: params["abi"],
identity_id: identity.id
})
) do
conn
|> put_status(200)
|> render(:custom_abi, %{custom_abi: custom_abi})
end
end
def public_tags_requests(conn, _params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
public_tags_requests <- PublicTagsRequest.get_public_tags_requests_by_identity_id(identity.id) do
conn
|> put_status(200)
|> render(:public_tags_requests, %{public_tags_requests: public_tags_requests})
end
end
def delete_public_tags_request(conn, %{"id" => id, "remove_reason" => remove_reason}) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:public_tag_delete, true} <-
{:public_tag_delete,
PublicTagsRequest.mark_as_deleted_public_tags_request(%{
id: id,
identity_id: identity.id,
remove_reason: remove_reason
})} do
conn
|> put_status(200)
|> render(:message, %{message: @ok_message})
end
end
def create_public_tags_request(conn, params) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, public_tags_request} <-
PublicTagsRequest.create(%{
full_name: params["full_name"],
email: params["email"],
tags: params["tags"],
website: params["website"],
additional_comment: params["additional_comment"],
addresses: params["addresses"],
company: params["company"],
is_owner: params["is_owner"],
identity_id: identity.id
}) do
conn
|> put_status(200)
|> render(:public_tags_request, %{public_tags_request: public_tags_request})
end
end
def update_public_tags_request(
conn,
%{
"id" => id
} = params
) do
with {:auth, %{id: uid}} <- {:auth, current_user(conn)},
{:identity, [%Identity{} = identity]} <- {:identity, UserFromAuth.find_identity(uid)},
{:ok, public_tags_request} <-
PublicTagsRequest.update(
reject_nil_map_values(%{
id: id,
full_name: params["full_name"],
email: params["email"],
tags: params["tags"],
website: params["website"],
additional_comment: params["additional_comment"],
addresses: params["addresses"],
company: params["company"],
is_owner: params["is_owner"],
identity_id: identity.id
})
) do
conn
|> put_status(200)
|> render(:public_tags_request, %{public_tags_request: public_tags_request})
end
end
def get_csrf(conn, _) do
with {:auth, %{id: _}} <- {:auth, current_user(conn)} do
conn
|> put_resp_header("x-bs-account-csrf", CSRFProtection.get_csrf_token())
|> put_status(200)
|> render(:message, %{message: "ok"})
end
end
defp reject_nil_map_values(map) when is_map(map) do
Map.reject(map, fn {_k, v} -> is_nil(v) end)
end
defp preload_watchlist_address_fetched_coin_balance(watchlist) do
watchlist
|> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id]))
|> WatchlistAddress.preload_address_fetched_coin_balance()
end
end

@ -0,0 +1,65 @@
defmodule BlockScoutWeb.Account.ApiKeyController do
use BlockScoutWeb, :controller
alias Explorer.Account.Api.Key, as: ApiKey
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def new(conn, _params) do
authenticate!(conn)
render(conn, "form.html", method: :create, api_key: empty_api_key())
end
def create(conn, %{"key" => api_key}) do
current_user = authenticate!(conn)
case ApiKey.create(%{name: api_key["name"], identity_id: current_user.id}) do
{:ok, _} ->
redirect(conn, to: api_key_path(conn, :index))
{:error, invalid_api_key} ->
render(conn, "form.html", method: :create, api_key: invalid_api_key)
end
end
def create(conn, _) do
redirect(conn, to: api_key_path(conn, :index))
end
def index(conn, _params) do
current_user = authenticate!(conn)
render(conn, "index.html", api_keys: ApiKey.get_api_keys_by_identity_id(current_user.id))
end
def edit(conn, %{"id" => api_key}) do
current_user = authenticate!(conn)
case ApiKey.get_api_key_by_value_and_identity_id(api_key, current_user.id) do
nil ->
not_found(conn)
%ApiKey{} = api_key ->
render(conn, "form.html", method: :update, api_key: ApiKey.changeset(api_key))
end
end
def update(conn, %{"id" => api_key, "key" => %{"value" => api_key, "name" => name}}) do
current_user = authenticate!(conn)
ApiKey.update(%{value: api_key, identity_id: current_user.id, name: name})
redirect(conn, to: api_key_path(conn, :index))
end
def delete(conn, %{"id" => api_key}) do
current_user = authenticate!(conn)
ApiKey.delete(api_key, current_user.id)
redirect(conn, to: api_key_path(conn, :index))
end
defp empty_api_key, do: ApiKey.changeset()
end

@ -0,0 +1,73 @@
defmodule BlockScoutWeb.Account.AuthController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Models.UserFromAuth
alias Explorer.Account
alias Plug.CSRFProtection
plug(Ueberauth)
def request(conn, _) do
not_found(conn)
end
def logout(conn, _params) do
conn
|> configure_session(drop: true)
|> redirect(to: root())
end
def profile(conn, _params),
do: conn |> get_session(:current_user) |> do_profile(conn)
defp do_profile(nil, conn),
do: redirect(conn, to: root())
defp do_profile(%{} = user, conn),
do: render(conn, :profile, user: user)
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
conn
|> put_flash(:error, "Failed to authenticate.")
|> redirect(to: root())
end
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
case UserFromAuth.find_or_create(auth) do
{:ok, user} ->
CSRFProtection.get_csrf_token()
conn
|> put_session(:current_user, user)
|> redirect(to: root())
{:error, reason} ->
conn
|> put_flash(:error, reason)
|> redirect(to: root())
end
end
def callback(conn, _) do
not_found(conn)
end
# for importing in other controllers
def authenticate!(conn) do
current_user(conn) || redirect(conn, to: root())
end
def current_user(%{private: %{plug_session: %{"current_user" => _}}} = conn) do
if Account.enabled?() do
get_session(conn, :current_user)
else
nil
end
end
def current_user(_), do: nil
defp root do
System.get_env("NETWORK_PATH") || "/"
end
end

@ -0,0 +1,87 @@
defmodule BlockScoutWeb.Account.CustomABIController do
use BlockScoutWeb, :controller
alias Ecto.Changeset
alias Explorer.Account.CustomABI
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def new(conn, _params) do
authenticate!(conn)
render(conn, "form.html", method: :create, custom_abi: empty_custom_abi())
end
def create(conn, %{"custom_abi" => custom_abi}) do
current_user = authenticate!(conn)
case CustomABI.create(%{
name: custom_abi["name"],
address_hash: custom_abi["address_hash"],
abi: custom_abi["abi"],
identity_id: current_user.id
}) do
{:ok, _} ->
redirect(conn, to: custom_abi_path(conn, :index))
{:error, invalid_custom_abi} ->
render(conn, "form.html", method: :create, custom_abi: invalid_custom_abi)
end
end
def create(conn, _) do
redirect(conn, to: custom_abi_path(conn, :index))
end
def index(conn, _params) do
current_user = authenticate!(conn)
render(conn, "index.html", custom_abis: CustomABI.get_custom_abis_by_identity_id(current_user.id))
end
def edit(conn, %{"id" => id}) do
current_user = authenticate!(conn)
case CustomABI.get_custom_abi_by_id_and_identity_id(id, current_user.id) do
nil ->
not_found(conn)
%CustomABI{} = custom_abi ->
render(conn, "form.html", method: :update, custom_abi: CustomABI.changeset_without_constraints(custom_abi))
end
end
def update(conn, %{"id" => id, "custom_abi" => %{"abi" => abi, "name" => name, "address_hash" => address_hash}}) do
current_user = authenticate!(conn)
case CustomABI.update(%{
id: id,
name: name,
address_hash: address_hash,
abi: abi,
identity_id: current_user.id
}) do
{:error, %Changeset{} = custom_abi} ->
render(conn, "form.html", method: :update, custom_abi: custom_abi)
_ ->
redirect(conn, to: custom_abi_path(conn, :index))
end
end
def update(conn, _) do
authenticate!(conn)
redirect(conn, to: custom_abi_path(conn, :index))
end
def delete(conn, %{"id" => id}) do
current_user = authenticate!(conn)
CustomABI.delete(id, current_user.id)
redirect(conn, to: custom_abi_path(conn, :index))
end
defp empty_custom_abi, do: CustomABI.changeset_without_constraints()
end

@ -0,0 +1,114 @@
defmodule BlockScoutWeb.Account.PublicTagsRequestController do
use BlockScoutWeb, :controller
alias Ecto.Changeset
alias Explorer.Account.PublicTagsRequest
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def index(conn, _params) do
current_user = authenticate!(conn)
render(conn, "index.html",
public_tags_requests: PublicTagsRequest.get_public_tags_requests_by_identity_id(current_user.id)
)
end
def new(conn, _params) do
current_user = authenticate!(conn)
render(conn, "form.html",
method: :create,
public_tags_request:
PublicTagsRequest.changeset_without_constraints(%PublicTagsRequest{}, %{
full_name: current_user.name,
email: current_user.email
})
)
end
def create(conn, %{"public_tags_request" => public_tags_request}) do
current_user = authenticate!(conn)
case PublicTagsRequest.create(%{
full_name: public_tags_request["full_name"],
email: public_tags_request["email"],
tags: public_tags_request["tags"],
website: public_tags_request["website"],
additional_comment: public_tags_request["additional_comment"],
addresses: public_tags_request["addresses"],
company: public_tags_request["company"],
is_owner: public_tags_request["is_owner"],
identity_id: current_user.id
}) do
{:ok, _} ->
redirect(conn, to: public_tags_request_path(conn, :index))
{:error, invalid_public_tags_request} ->
render(conn, "form.html", method: :create, public_tags_request: invalid_public_tags_request)
end
end
def create(conn, _) do
redirect(conn, to: public_tags_request_path(conn, :index))
end
def edit(conn, %{"id" => id}) do
current_user = authenticate!(conn)
case PublicTagsRequest.get_public_tags_request_by_id_and_identity_id(id, current_user.id) do
nil ->
not_found(conn)
%PublicTagsRequest{} = public_tags_request ->
render(conn, "form.html",
method: :update,
public_tags_request: PublicTagsRequest.changeset_without_constraints(public_tags_request)
)
end
end
def update(conn, %{
"id" => id,
"public_tags_request" => public_tags_request
}) do
current_user = authenticate!(conn)
case PublicTagsRequest.update(%{
id: id,
full_name: public_tags_request["full_name"],
email: public_tags_request["email"],
tags: public_tags_request["tags"],
website: public_tags_request["website"],
additional_comment: public_tags_request["additional_comment"],
addresses: public_tags_request["addresses"],
company: public_tags_request["company"],
is_owner: public_tags_request["is_owner"],
identity_id: current_user.id
}) do
{:error, %Changeset{} = public_tags_request} ->
render(conn, "form.html", method: :update, public_tags_request: public_tags_request)
_ ->
redirect(conn, to: public_tags_request_path(conn, :index))
end
end
def update(conn, _) do
authenticate!(conn)
redirect(conn, to: public_tags_request_path(conn, :index))
end
def delete(conn, %{"id" => id, "remove_reason" => remove_reason}) do
current_user = authenticate!(conn)
PublicTagsRequest.mark_as_deleted_public_tags_request(%{
id: id,
identity_id: current_user.id,
remove_reason: remove_reason
})
redirect(conn, to: public_tags_request_path(conn, :index))
end
end

@ -0,0 +1,49 @@
defmodule BlockScoutWeb.Account.TagAddressController do
use BlockScoutWeb, :controller
alias Explorer.Account.TagAddress
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def index(conn, _params) do
current_user = authenticate!(conn)
render(conn, "index.html", address_tags: TagAddress.get_tags_address_by_identity_id(current_user.id))
end
def new(conn, _params) do
authenticate!(conn)
render(conn, "form.html", tag_address: new_tag())
end
def create(conn, %{"tag_address" => tag_address}) do
current_user = authenticate!(conn)
case TagAddress.create(%{
name: tag_address["name"],
address_hash: tag_address["address_hash"],
identity_id: current_user.id
}) do
{:ok, _} ->
redirect(conn, to: tag_address_path(conn, :index))
{:error, invalid_tag_address} ->
render(conn, "form.html", tag_address: invalid_tag_address)
end
end
def create(conn, _) do
redirect(conn, to: tag_address_path(conn, :index))
end
def delete(conn, %{"id" => id}) do
current_user = authenticate!(conn)
TagAddress.delete(id, current_user.id)
redirect(conn, to: tag_address_path(conn, :index))
end
defp new_tag, do: TagAddress.changeset()
end

@ -0,0 +1,49 @@
defmodule BlockScoutWeb.Account.TagTransactionController do
use BlockScoutWeb, :controller
alias Explorer.Account.TagTransaction
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def index(conn, _params) do
current_user = authenticate!(conn)
render(conn, "index.html", tx_tags: TagTransaction.get_tags_transaction_by_identity_id(current_user.id))
end
def new(conn, _params) do
authenticate!(conn)
render(conn, "form.html", tag_transaction: new_tag())
end
def create(conn, %{"tag_transaction" => tag_address}) do
current_user = authenticate!(conn)
case TagTransaction.create(%{
name: tag_address["name"],
tx_hash: tag_address["tx_hash"],
identity_id: current_user.id
}) do
{:ok, _} ->
redirect(conn, to: tag_transaction_path(conn, :index))
{:error, invalid_tag_transaction} ->
render(conn, "form.html", tag_transaction: invalid_tag_transaction)
end
end
def create(conn, _) do
redirect(conn, to: tag_transaction_path(conn, :index))
end
def delete(conn, %{"id" => id}) do
current_user = authenticate!(conn)
TagTransaction.delete(id, current_user.id)
redirect(conn, to: tag_transaction_path(conn, :index))
end
defp new_tag, do: TagTransaction.changeset()
end

@ -0,0 +1,92 @@
defmodule BlockScoutWeb.Account.WatchlistAddressController do
use BlockScoutWeb, :controller
alias Explorer.Account.WatchlistAddress
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
def new(conn, _params) do
authenticate!(conn)
render(conn, "form.html", method: :create, watchlist_address: empty_watchlist_address())
end
def create(conn, %{"watchlist_address" => wa_params}) do
current_user = authenticate!(conn)
case WatchlistAddress.create(params_to_attributes(wa_params, current_user.watchlist_id)) do
{:ok, _watchlist_address} ->
redirect(conn, to: watchlist_path(conn, :show))
{:error, changeset} ->
render(conn, "form.html", method: :create, watchlist_address: changeset)
end
end
def edit(conn, %{"id" => id}) do
current_user = authenticate!(conn)
case WatchlistAddress.get_watchlist_address_by_id_and_watchlist_id(id, current_user.watchlist_id) do
nil ->
not_found(conn)
%WatchlistAddress{} = watchlist_address ->
render(conn, "form.html", method: :update, watchlist_address: WatchlistAddress.changeset(watchlist_address))
end
end
def update(conn, %{"id" => id, "watchlist_address" => wa_params}) do
current_user = authenticate!(conn)
case wa_params
|> params_to_attributes(current_user.watchlist_id)
|> Map.put(:id, id)
|> WatchlistAddress.update() do
{:ok, _watchlist_address} ->
redirect(conn, to: watchlist_path(conn, :show))
{:error, changeset} ->
render(conn, "form.html", method: :update, watchlist_address: changeset)
end
end
def delete(conn, %{"id" => id}) do
current_user = authenticate!(conn)
WatchlistAddress.delete(id, current_user.watchlist_id)
redirect(conn, to: watchlist_path(conn, :show))
end
defp empty_watchlist_address, do: WatchlistAddress.changeset()
defp params_to_attributes(
%{
"address_hash" => address_hash,
"name" => name,
"watch_coin_input" => watch_coin_input,
"watch_coin_output" => watch_coin_output,
"watch_erc_20_input" => watch_erc_20_input,
"watch_erc_20_output" => watch_erc_20_output,
"watch_erc_721_input" => watch_nft_input,
"watch_erc_721_output" => watch_nft_output,
"notify_email" => notify_email
},
watchlist_id
) do
%{
address_hash: address_hash,
name: name,
watch_coin_input: watch_coin_input,
watch_coin_output: watch_coin_output,
watch_erc_20_input: watch_erc_20_input,
watch_erc_20_output: watch_erc_20_output,
watch_erc_721_input: watch_nft_input,
watch_erc_721_output: watch_nft_output,
watch_erc_1155_input: watch_nft_input,
watch_erc_1155_output: watch_nft_output,
notify_email: notify_email,
watchlist_id: watchlist_id
}
end
end

@ -0,0 +1,26 @@
defmodule BlockScoutWeb.Account.WatchlistController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [authenticate!: 1]
import Ecto.Query, only: [from: 2]
alias Explorer.Account.{Watchlist, WatchlistAddress}
alias Explorer.Repo
def show(conn, _params) do
current_user = authenticate!(conn)
render(
conn,
"show.html",
watchlist: watchlist_with_addresses(current_user)
)
end
defp watchlist_with_addresses(user) do
Watchlist
|> Repo.account_repo().get(user.watchlist_id)
|> Repo.account_repo().preload(watchlist_addresses: from(wa in WatchlistAddress, order_by: [desc: wa.id]))
|> WatchlistAddress.preload_address_fetched_coin_balance()
end
end

@ -5,7 +5,9 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, AddressCoinBalanceView, Controller}
alias Explorer.{Chain, Market}
@ -76,7 +78,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -2,6 +2,9 @@
defmodule BlockScoutWeb.AddressContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias BlockScoutWeb.AddressContractVerificationController, as: VerificationController
alias Explorer.{Chain, Market}
@ -29,7 +32,8 @@ defmodule BlockScoutWeb.AddressContractController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string})
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -1,8 +1,12 @@
defmodule BlockScoutWeb.AddressController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{
AccessHelpers,
AddressTransactionController,
@ -101,7 +105,8 @@ defmodule BlockScoutWeb.AddressController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn)
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
)
else
:error ->
@ -130,7 +135,8 @@ defmodule BlockScoutWeb.AddressController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn)
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
)
_ ->

@ -1,6 +1,9 @@
defmodule BlockScoutWeb.AddressDecompiledContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -16,7 +19,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string})
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -5,7 +5,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, InternalTransactionView}
alias Explorer.{Chain, Market}
@ -86,7 +88,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
current_path: Controller.current_full_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string})
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->
@ -112,7 +115,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
coin_balance_status: nil,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
current_path: Controller.current_full_path(conn)
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
)
_ ->

@ -3,8 +3,12 @@ defmodule BlockScoutWeb.AddressLogsController do
Manages events logs tab.
"""
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, AddressLogsView, Controller}
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -64,7 +68,8 @@ defmodule BlockScoutWeb.AddressLogsController do
current_path: Controller.current_full_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string})
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
_ ->

@ -8,10 +8,15 @@
defmodule BlockScoutWeb.AddressReadContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.Reader
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string} = params) do
@ -25,23 +30,63 @@ defmodule BlockScoutWeb.AddressReadContractController do
}
]
custom_abi = AddressView.fetch_custom_abi(conn, address_hash_string)
custom_abi? = AddressView.check_custom_abi_for_having_read_functions(custom_abi)
need_wallet_custom_abi? =
!is_nil(custom_abi) && Reader.read_functions_required_wallet_from_abi(custom_abi.abi) != []
base_params = [
type: :regular,
action: :read,
custom_abi: custom_abi?,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract),
need_wallet? <- Reader.read_functions_required_wallet_from_abi(address.smart_contract.abi) != [],
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
render(
conn,
"index.html",
address: address,
type: :regular,
action: :read,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
base_params ++
[
address: address,
non_custom_abi: true,
need_wallet: need_wallet? || need_wallet_custom_abi?,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
]
)
else
_ ->
not_found(conn)
if custom_abi? do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, false),
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
render(
conn,
"index.html",
base_params ++
[
address: address,
non_custom_abi: false,
need_wallet: need_wallet_custom_abi?,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
]
)
else
_ ->
not_found(conn)
end
else
not_found(conn)
end
end
end
end

@ -2,6 +2,9 @@
defmodule BlockScoutWeb.AddressReadProxyController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
@ -31,7 +34,8 @@ defmodule BlockScoutWeb.AddressReadProxyController do
action: :read,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
_ ->

@ -2,6 +2,8 @@ defmodule BlockScoutWeb.AddressTokenController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, AddressTokenView, Controller}
alias Explorer.{Chain, Market}
@ -74,7 +76,8 @@ defmodule BlockScoutWeb.AddressTokenController do
current_path: Controller.current_full_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -1,6 +1,9 @@
defmodule BlockScoutWeb.AddressTokenTransferController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView}
alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
@ -109,7 +112,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: Controller.current_full_path(conn),
token: token,
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->
@ -200,7 +204,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -5,8 +5,12 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView}
alias Explorer.{Chain, Market}
@ -122,7 +126,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn)
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
)
else
:error ->
@ -151,7 +156,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
current_path: Controller.current_full_path(conn)
current_path: Controller.current_full_path(conn),
tags: get_address_tags(address_hash, current_user(conn))
)
_ ->

@ -4,9 +4,13 @@ defmodule BlockScoutWeb.AddressValidationController do
"""
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain,
only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, BlockView, Controller}
alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
@ -79,7 +83,8 @@ defmodule BlockScoutWeb.AddressValidationController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: Controller.current_full_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -8,7 +8,11 @@
defmodule BlockScoutWeb.AddressWriteContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
@ -25,6 +29,15 @@ defmodule BlockScoutWeb.AddressWriteContractController do
}
]
custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address_hash_string)
base_params = [
type: :regular,
action: :write,
custom_abi: custom_abi?,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract),
@ -32,16 +45,40 @@ defmodule BlockScoutWeb.AddressWriteContractController do
render(
conn,
"index.html",
address: address,
type: :regular,
action: :write,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
base_params ++
[
address: address,
non_custom_abi: true,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
]
)
else
_ ->
not_found(conn)
if custom_abi? do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, false),
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
render(
conn,
"index.html",
base_params ++
[
address: address,
non_custom_abi: false,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
]
)
else
_ ->
not_found(conn)
end
else
not_found(conn)
end
end
end
end

@ -2,6 +2,9 @@
defmodule BlockScoutWeb.AddressWriteProxyController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.AccessHelpers
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
@ -31,7 +34,8 @@ defmodule BlockScoutWeb.AddressWriteProxyController do
action: :write,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
_ ->

@ -1,19 +1,25 @@
defmodule BlockScoutWeb.SmartContractController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.AddressView
alias Explorer.Chain
alias Explorer.SmartContract.{Reader, Writer}
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
@burn_address "0x0000000000000000000000000000000000000000"
def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do
def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do
address_options = [
necessity_by_association: %{
:smart_contract => :optional
}
]
is_custom_abi = parse_boolean(params["is_custom_abi"])
with true <- ajax?(conn),
{:custom_abi, false} <- {:custom_abi, is_custom_abi},
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
implementation_address_hash_string =
@ -78,6 +84,9 @@ defmodule BlockScoutWeb.SmartContractController do
action: action
)
else
{:custom_abi, true} ->
custom_abi_render(conn, params)
:error ->
unprocessable_entity(conn)
@ -91,6 +100,51 @@ defmodule BlockScoutWeb.SmartContractController do
def index(conn, _), do: not_found(conn)
defp custom_abi_render(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action}) do
with custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string),
false <- is_nil(custom_abi),
abi <- custom_abi.abi,
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do
functions =
if action == "write" do
Writer.filter_write_functions(abi)
else
Reader.read_only_functions_from_abi(abi, address_hash)
end
read_functions_required_wallet =
if action == "read" do
Reader.read_functions_required_wallet_from_abi(abi)
else
[]
end
contract_abi = Poison.encode!(abi)
conn
|> put_status(200)
|> put_layout(false)
|> render(
"_functions.html",
read_functions_required_wallet: read_functions_required_wallet,
read_only_functions: functions,
address: %{hash: address_hash},
custom_abi: true,
contract_abi: contract_abi,
implementation_address: @burn_address,
implementation_abi: [],
contract_type: contract_type,
action: action
)
else
:error ->
unprocessable_entity(conn)
_ ->
not_found(conn)
end
end
def show(conn, params) do
address_options = [
necessity_by_association: %{
@ -102,6 +156,9 @@ defmodule BlockScoutWeb.SmartContractController do
}
]
custom_abi =
if parse_boolean(params["is_custom_abi"]), do: AddressView.fetch_custom_abi(conn, params["id"]), else: nil
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
{:ok, _address} <- Chain.find_contract_address(address_hash, address_options, true) do
@ -115,18 +172,19 @@ defmodule BlockScoutWeb.SmartContractController do
else: for(x <- 0..(args_count - 1), do: params["arg_" <> to_string(x)] |> convert_map_to_array())
%{output: outputs, names: names} =
if params["from"] do
Reader.query_function_with_names(
if custom_abi do
Reader.query_function_with_names_custom_abi(
address_hash,
%{method_id: params["method_id"], args: args},
contract_type,
params["from"]
params["from"],
custom_abi.abi
)
else
Reader.query_function_with_names(
address_hash,
%{method_id: params["method_id"], args: args},
contract_type
contract_type,
params["from"]
)
end

@ -1,6 +1,9 @@
defmodule BlockScoutWeb.Tokens.ContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, TabHelpers}
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
@ -33,7 +36,8 @@ defmodule BlockScoutWeb.Tokens.ContractController do
type: type,
action: action,
token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -1,6 +1,9 @@
defmodule BlockScoutWeb.Tokens.HolderController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller}
alias BlockScoutWeb.Tokens.HolderView
alias Explorer.{Chain, Market}
@ -66,7 +69,8 @@ defmodule BlockScoutWeb.Tokens.HolderController do
"index.html",
current_path: Controller.current_full_path(conn),
token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -1,6 +1,9 @@
defmodule BlockScoutWeb.Tokens.TransferController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller}
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market}
@ -71,7 +74,8 @@ defmodule BlockScoutWeb.Tokens.TransferController do
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}),
current_path: Controller.current_full_path(conn),
token: Market.add_price(token),
token_total_supply_status: TokenTotalSupplyOnDemand.trigger_fetch(address_hash)
token_total_supply_status: TokenTotalSupplyOnDemand.trigger_fetch(address_hash),
tags: get_address_tags(address_hash, current_user(conn))
)
else
{:restricted_access, _} ->

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.TransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain,
only: [
fetch_page_number: 1,
@ -10,6 +12,9 @@ defmodule BlockScoutWeb.TransactionController do
split_list_by_page: 1
]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
alias BlockScoutWeb.{
AccessHelpers,
Controller,
@ -159,8 +164,16 @@ defmodule BlockScoutWeb.TransactionController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
block_height: Chain.block_height(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
show_token_transfers: true,
transaction: transaction
transaction: transaction,
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
else
:not_found ->
@ -188,9 +201,17 @@ defmodule BlockScoutWeb.TransactionController do
"show_internal_transactions.html",
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
block_height: Chain.block_height(),
show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash),
transaction: transaction
transaction: transaction,
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
else
:not_found ->

@ -1,7 +1,10 @@
defmodule BlockScoutWeb.TransactionInternalTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, InternalTransactionView, TransactionController}
alias Explorer.{Chain, Market}
@ -101,9 +104,17 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
"index.html",
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
block_height: Chain.block_height(),
show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash),
transaction: transaction
transaction: transaction,
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
else
{:restricted_access, _} ->

@ -1,7 +1,10 @@
defmodule BlockScoutWeb.TransactionLogController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, TransactionController, TransactionLogView}
alias Explorer.{Chain, Market}
@ -95,8 +98,16 @@ defmodule BlockScoutWeb.TransactionLogController do
block_height: Chain.block_height(),
show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
transaction: transaction,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
else
{:restricted_access, _} ->

@ -1,6 +1,10 @@
defmodule BlockScoutWeb.TransactionRawTraceController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
alias BlockScoutWeb.{AccessHelpers, TransactionController}
alias EthereumJSONRPC
alias Explorer.{Chain, Market}
@ -92,8 +96,16 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
internal_transactions: internal_transactions,
block_height: Chain.block_height(),
current_user: current_user(conn),
show_token_transfers: Chain.transaction_has_token_transfers?(hash),
transaction: transaction
transaction: transaction,
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
end
end

@ -1,7 +1,10 @@
defmodule BlockScoutWeb.TransactionTokenTransferController do
use BlockScoutWeb, :controller
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
import BlockScoutWeb.Models.GetTransactionTags, only: [get_transaction_with_addresses_tags: 2]
alias BlockScoutWeb.{AccessHelpers, Controller, TransactionController, TransactionTokenTransferView}
alias Explorer.{Chain, Market}
@ -106,8 +109,16 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
block_height: Chain.block_height(),
current_path: Controller.current_full_path(conn),
current_user: current_user(conn),
show_token_transfers: true,
transaction: transaction
transaction: transaction,
from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)),
to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)),
tx_tags:
get_transaction_with_addresses_tags(
transaction,
current_user(conn)
)
)
else
:not_found ->

@ -59,11 +59,13 @@ defmodule BlockScoutWeb.Endpoint do
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug(
Plug.Session,
store: :cookie,
store: BlockScoutWeb.Plug.RedisCookie,
key: "_explorer_key",
signing_salt: "iC2ksJHS"
signing_salt: "iC2ksJHS",
same_site: "Lax"
)
use SpandexPhoenix

@ -0,0 +1,75 @@
defmodule BlockScoutWeb.Models.GetAddressTags do
@moduledoc """
Get various types of tags associated with the address
"""
import Ecto.Query, only: [from: 2]
alias Explorer.Account.{TagAddress, WatchlistAddress}
alias Explorer.Chain.Hash
alias Explorer.Repo
alias Explorer.Tags.{AddressTag, AddressToTag}
def get_address_tags(nil, nil),
do: %{personal_tags: [], watchlist_names: []}
def get_address_tags(%Hash{} = address_hash, current_user) do
%{
# common_tags: get_tags_on_address(address_hash),
personal_tags: get_personal_tags(address_hash, current_user),
watchlist_names: get_watchlist_names_on_address(address_hash, current_user)
}
end
def get_address_tags(_, _), do: %{personal_tags: [], watchlist_names: []}
def get_public_tags(%Hash{} = address_hash) do
%{
common_tags: get_tags_on_address(address_hash)
}
end
def get_tags_on_address(%Hash{} = address_hash) do
query =
from(
tt in AddressTag,
left_join: att in AddressToTag,
on: tt.id == att.tag_id,
where: att.address_hash == ^address_hash,
where: tt.label != ^"validator",
select: %{label: tt.label, display_name: tt.display_name, address_hash: att.address_hash}
)
Repo.all(query)
end
def get_tags_on_address(_), do: []
def get_personal_tags(%Hash{} = address_hash, %{id: id}) do
query =
from(
ta in TagAddress,
where: ta.address_hash_hash == ^address_hash,
where: ta.identity_id == ^id,
select: %{label: ta.name, display_name: ta.name, address_hash: ta.address_hash}
)
Repo.account_repo().all(query)
end
def get_personal_tags(_, _), do: []
def get_watchlist_names_on_address(%Hash{} = address_hash, %{watchlist_id: watchlist_id}) do
query =
from(
wa in WatchlistAddress,
where: wa.address_hash_hash == ^address_hash,
where: wa.watchlist_id == ^watchlist_id,
select: %{label: wa.name, display_name: wa.name}
)
Repo.account_repo().all(query)
end
def get_watchlist_names_on_address(_, _), do: []
end

@ -0,0 +1,41 @@
defmodule BlockScoutWeb.Models.GetTransactionTags do
@moduledoc """
Get various types of tags associated with the transaction
"""
import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2]
alias Explorer.Account.TagTransaction
alias Explorer.Chain.Transaction
alias Explorer.Repo
def get_transaction_with_addresses_tags(
%Transaction{} = transaction,
%{id: identity_id, watchlist_id: watchlist_id}
) do
tx_tag = get_transaction_tags(transaction.hash, %{id: identity_id})
addresses_tags = get_addresses_tags_for_transaction(transaction, %{id: identity_id, watchlist_id: watchlist_id})
Map.put(addresses_tags, :personal_tx_tag, tx_tag)
end
def get_transaction_with_addresses_tags(_, _), do: %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil}
def get_transaction_tags(transaction_hash, %{id: identity_id}) do
Repo.account_repo().get_by(TagTransaction, tx_hash_hash: transaction_hash, identity_id: identity_id)
end
def get_transaction_tags(_, _), do: nil
def get_addresses_tags_for_transaction(
%Transaction{} = transaction,
%{id: identity_id, watchlist_id: watchlist_id}
) do
from_tags = get_address_tags(transaction.from_address_hash, %{id: identity_id, watchlist_id: watchlist_id})
to_tags = get_address_tags(transaction.to_address_hash, %{id: identity_id, watchlist_id: watchlist_id})
%{
personal_tags: Enum.dedup(from_tags.personal_tags ++ to_tags.personal_tags),
watchlist_names: Enum.dedup(from_tags.watchlist_names ++ to_tags.watchlist_names)
}
end
end

@ -0,0 +1,136 @@
defmodule BlockScoutWeb.Models.UserFromAuth do
@moduledoc """
Retrieve the user information from an auth request
"""
require Logger
require Poison
alias Explorer.Account.Identity
alias Explorer.Repo
alias Ueberauth.Auth
import Ecto.Query, only: [from: 2]
def find_or_create(%Auth{} = auth, api_call? \\ false) do
case find_identity(auth) do
[] ->
case create_identity(auth) do
%Identity{} = identity ->
{:ok, return_value(identity, auth, api_call?)}
{:error, changeset} ->
{:error, changeset}
end
[%{} = identity | _] ->
update_identity(identity, update_identity_map(auth))
{:ok, return_value(identity, auth, api_call?)}
end
end
defp return_value(identity, _auth, true) do
identity
end
defp return_value(identity, auth, false) do
basic_info(auth, identity)
end
defp create_identity(auth) do
with {:ok, %Identity{} = identity} <- Repo.account_repo().insert(new_identity(auth)),
{:ok, _watchlist} <- add_watchlist(identity) do
identity
end
end
defp update_identity(identity, attrs) do
identity
|> Identity.changeset(attrs)
|> Repo.account_repo().update()
end
defp new_identity(auth) do
%Identity{
uid: auth.uid,
uid_hash: auth.uid,
email: email_from_auth(auth),
name: name_from_auth(auth),
nickname: nickname_from_auth(auth),
avatar: avatar_from_auth(auth)
}
end
defp add_watchlist(identity) do
watchlist = Ecto.build_assoc(identity, :watchlists, %{})
with {:ok, _} <- Repo.account_repo().insert(watchlist),
do: {:ok, identity}
end
def find_identity(auth_or_uid) do
Repo.account_repo().all(query_identity(auth_or_uid))
end
def query_identity(%Auth{} = auth) do
from(i in Identity, where: i.uid_hash == ^auth.uid)
end
def query_identity(id) do
from(i in Identity, where: i.id == ^id)
end
defp basic_info(auth, identity) do
%{watchlists: [watchlist | _]} = Repo.account_repo().preload(identity, :watchlists)
%{
id: identity.id,
uid: auth.uid,
email: email_from_auth(auth),
name: name_from_auth(auth),
nickname: nickname_from_auth(auth),
avatar: avatar_from_auth(auth),
watchlist_id: watchlist.id
}
end
defp update_identity_map(auth) do
%{
email: email_from_auth(auth),
name: name_from_auth(auth),
nickname: nickname_from_auth(auth),
avatar: avatar_from_auth(auth)
}
end
# github does it this way
defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image
# facebook does it this way
defp avatar_from_auth(%{info: %{image: image}}), do: image
# default case if nothing matches
defp avatar_from_auth(auth) do
Logger.warn(auth.provider <> " needs to find an avatar URL!")
Logger.debug(Poison.encode!(auth))
nil
end
defp email_from_auth(%{info: %{email: email}}), do: email
defp nickname_from_auth(%{info: %{nickname: nickname}}), do: nickname
defp name_from_auth(%{info: %{name: name}})
when name != "" and not is_nil(name),
do: name
defp name_from_auth(%{info: info}) do
[info.first_name, info.last_name, info.nickname]
|> Enum.map(&(&1 |> to_string() |> String.trim()))
|> case do
["", "", nick] -> nick
["", lastname, _] -> lastname
[name, "", _] -> name
[name, lastname, _] -> name <> " " <> lastname
end
end
end

@ -0,0 +1,21 @@
defmodule BlockScoutWeb.Plug.CheckAccountAPI do
@moduledoc """
Checks if the Account functionality enabled for API level.
"""
import Plug.Conn
alias Explorer.Account
def init(opts), do: opts
def call(conn, _opts) do
if Account.enabled?() do
conn
else
conn
|> put_resp_content_type("application/json")
|> send_resp(404, Jason.encode!(%{message: "Account functionality is disabled"}))
|> halt()
end
end
end

@ -0,0 +1,31 @@
defmodule BlockScoutWeb.Plug.CheckAccountWeb do
@moduledoc """
Checks if the Account functionality enabled for web interface.
"""
import Phoenix.Controller
alias Phoenix.View
import Plug.Conn
alias Explorer.Account
def init(opts), do: opts
def call(conn, _opts) do
if Account.enabled?() do
conn
else
inner_view =
View.render(
BlockScoutWeb.PageNotFoundView,
"index.html",
token: nil
)
conn
|> put_status(404)
|> put_view(BlockScoutWeb.LayoutView)
|> render(:app, inner_content: inner_view)
|> halt()
end
end
end

@ -0,0 +1,223 @@
defmodule BlockScoutWeb.Plug.RedisCookie do
@moduledoc """
Extended version of Plug.Session.COOKIE from https://github.com/elixir-plug/plug/blob/main/lib/plug/session/cookie.ex
Added Redis to have a possibility to invalidate session
"""
require Logger
@behaviour Plug.Session.Store
alias Plug.Crypto
alias Plug.Crypto.{KeyGenerator, MessageEncryptor, MessageVerifier}
@impl true
def init(opts) do
opts
|> build_opts()
|> build_rotating_opts(opts[:rotating_options])
|> Map.delete(:secret_key_base)
end
@impl true
def get(conn, raw_cookie, opts) do
opts = Map.put(opts, :secret_key_base, conn.secret_key_base)
[opts | opts.rotating_options]
|> Enum.find_value(:error, &read_raw_cookie(raw_cookie, &1))
|> decode(opts.serializer, opts.log)
|> check_in_redis(raw_cookie)
end
@impl true
def put(conn, _sid, term, opts) do
%{serializer: serializer, key_opts: key_opts, signing_salt: signing_salt} = opts
binary = encode(term, serializer)
opts
|> case do
%{encryption_salt: nil} ->
MessageVerifier.sign(binary, derive(conn.secret_key_base, signing_salt, key_opts))
%{encryption_salt: encryption_salt} ->
MessageEncryptor.encrypt(
binary,
derive(conn.secret_key_base, encryption_salt, key_opts),
derive(conn.secret_key_base, signing_salt, key_opts)
)
end
|> store_to_redis()
end
@impl true
def delete(_conn, sid, _opts) do
remove_from_redis(sid)
:ok
end
defp encode(term, :external_term_format) do
:erlang.term_to_binary(term)
end
defp encode(term, serializer) do
{:ok, binary} = serializer.encode(term)
binary
end
defp decode({:ok, binary}, :external_term_format, log) do
{:term,
try do
Crypto.non_executable_binary_to_term(binary)
rescue
e ->
Logger.log(
log,
"Plug.Session could not decode incoming session cookie. Reason: " <>
Exception.message(e)
)
%{}
end}
end
defp decode({:ok, binary}, serializer, _log) do
case serializer.decode(binary) do
{:ok, term} -> {:custom, term}
_ -> {:custom, %{}}
end
end
defp decode(:error, _serializer, false) do
{nil, %{}}
end
defp decode(:error, _serializer, log) do
Logger.log(
log,
"Plug.Session could not verify incoming session cookie. " <>
"This may happen when the session settings change or a stale cookie is sent."
)
{nil, %{}}
end
defp prederive(secret_key_base, value, key_opts)
when is_binary(secret_key_base) and is_binary(value) do
{:prederived, derive(secret_key_base, value, Keyword.delete(key_opts, :cache))}
end
defp prederive(_secret_key_base, value, _key_opts) do
value
end
defp derive(_secret_key_base, {:prederived, value}, _key_opts) do
value
end
defp derive(secret_key_base, {module, function, args}, key_opts) do
derive(secret_key_base, apply(module, function, args), key_opts)
end
defp derive(secret_key_base, key, key_opts) do
secret_key_base
|> validate_secret_key_base()
|> KeyGenerator.generate(key, key_opts)
end
defp validate_secret_key_base(nil),
do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be set")
defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64,
do: raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes")
defp validate_secret_key_base(secret_key_base), do: secret_key_base
defp check_signing_salt(opts) do
case opts[:signing_salt] do
nil -> raise ArgumentError, "cookie store expects :signing_salt as option"
salt -> salt
end
end
defp check_serializer(serializer) when is_atom(serializer), do: serializer
defp check_serializer(_),
do: raise(ArgumentError, "cookie store expects :serializer option to be a module")
defp read_raw_cookie(raw_cookie, opts) do
signing_salt = derive(opts.secret_key_base, opts.signing_salt, opts.key_opts)
opts
|> case do
%{encryption_salt: nil} ->
MessageVerifier.verify(raw_cookie, signing_salt)
%{encryption_salt: _} ->
encryption_salt = derive(opts.secret_key_base, opts.encryption_salt, opts.key_opts)
MessageEncryptor.decrypt(raw_cookie, encryption_salt, signing_salt)
end
|> case do
:error -> nil
result -> result
end
end
defp build_opts(opts) do
encryption_salt = opts[:encryption_salt]
signing_salt = check_signing_salt(opts)
iterations = Keyword.get(opts, :key_iterations, 1000)
length = Keyword.get(opts, :key_length, 32)
digest = Keyword.get(opts, :key_digest, :sha256)
log = Keyword.get(opts, :log, :debug)
secret_key_base = Keyword.get(opts, :secret_key_base)
key_opts = [iterations: iterations, length: length, digest: digest, cache: Plug.Keys]
serializer = check_serializer(opts[:serializer] || :external_term_format)
%{
secret_key_base: secret_key_base,
encryption_salt: prederive(secret_key_base, encryption_salt, key_opts),
signing_salt: prederive(secret_key_base, signing_salt, key_opts),
key_opts: key_opts,
serializer: serializer,
log: log
}
end
defp build_rotating_opts(opts, rotating_opts) when is_list(rotating_opts) do
Map.put(opts, :rotating_options, Enum.map(rotating_opts, &build_opts/1))
end
defp build_rotating_opts(opts, _), do: Map.put(opts, :rotating_options, [])
defp store_to_redis(cookie) do
Redix.command(:redix, ["SET", hash(cookie), 1])
cookie
end
defp remove_from_redis(sid) do
Redix.command(:redix, ["DEL", sid])
end
defp check_in_redis({sid, map}, _cookie) when is_nil(sid) or map == %{}, do: {nil, %{}}
defp check_in_redis({_sid, session}, cookie) do
hash = hash(cookie)
case Redix.command(:redix, ["GET", hash]) do
{:ok, one} when one in [1, "1"] ->
{hash, session}
_ ->
{nil, %{}}
end
end
defp hash(cookie) do
:sha256
|> :crypto.hash(cookie)
|> Base.encode16()
end
end

@ -0,0 +1,34 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %>
<div class="col-sm-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "API key"%></h1>
<div class="col-sm-10 card-body-account">
<% path = if @method == :update, do: api_key_path(@conn, @method, @api_key.data.value), else: api_key_path(@conn, @method) %>
<%= form_for @api_key, path, fn f -> %>
<%= if f.data.value do %>
<div class="form-group">
<%= label f, :value, gettext("API key"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :value, class: "form-control", placeholder: gettext("API key"), readonly: true %>
<%= error_tag f, :value, class: "text-danger form-error" %>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this API key"), maxlength: 255 %>
<%= error_tag f, :name, class: "text-danger form-error" %>
</div>
<br>
<div class="form-group float-right form-input">
<a class="btn btn-line" href="<%= api_key_path(@conn, :index) %>"><%= gettext "Back to API keys (Cancel)"%></a>
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,51 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :api_keys %>
<div class="col-md-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%= gettext "API keys" %> </h1>
<br>
<%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "Create an API key to use with your RPC и EthRPC API requests." %> <a href="https://docs.blockscout.com/for-users/api"> <%= gettext "Learn more" %></a>
</div>
</div>
<% else %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You can create 3 API keys per account." %> <a href="https://docs.blockscout.com/for-users/api"> <%= gettext "Learn more" %></a>
</div>
</div>
<% end %>
<div class="col-sm">
<div class="mb-3 row o-flow-x">
<%= if @api_keys != [] do %>
<table class="table mb-3 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "API key" %></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@api_keys, fn key ->
render("row.html", api_key: key, conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@api_keys) < Key.get_max_api_keys_count() do %>
<a class="button button-primary button-sm" href="<%= api_key_path(@conn, :new) %>"><%= gettext "Add API key" %></a>
<% end %>
</div>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script>
</section>

@ -0,0 +1,18 @@
<tr>
<td><%= @api_key.name %></td>
<td>
<span><%= @api_key.value %></span>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @api_key.value, aria_label: gettext("Copy API key"), title: gettext("Copy API key"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
</td>
<td>
<form method="post" action="<%= api_key_path(@conn, :delete, @api_key.value) %>">
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>">
<input type="hidden" name="_method" value="delete">
</form>
<a href="" data-delete-item><%= gettext("Remove") %></a>
</td>
<td>
<%= link gettext("Edit"), to: api_key_path(@conn, :edit, @api_key.value) %>
</td>
</tr>

@ -0,0 +1,36 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :profile %>
<div class="col-md">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account">Profile</h1>
<br>
<div class="col-sm">
<img src="<%= @user.avatar %>" alt="<%= @user.nickname %>" size="42" height="42" width="42" >
</div>
<br>
<div class="col-sm">
<div class="mb-4 row">
<label for="static-name" class="col-sm-6 col-form-label label-account">Name</label>
<div class="col-sm">
<input type="text" readonly class="form-control-plaintext profile-item label" id="static-name" value="<%= @user.name %>">
</div>
</div>
<div class="mb-4 row">
<label for="static-nickname" class="col-sm-6 col-form-label label-account">Nickname</label>
<div class="col-sm">
<input type="text" readonly class="form-control-plaintext profile-item" id="static-nickname" value="<%= @user.nickname %>">
</div>
</div>
<div class="mb-4 row">
<label for="static-email" class="col-sm-6 col-form-label label-account">Email</label>
<div class="col-sm">
<input type="text" readonly class="form-control-plaintext profile-item" id="static-email" value="<%= @user.email %>">
</div>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,25 @@
<div class="col-2 mb-3 navbar-account">
<ul class="nav account flex-column nav-pills">
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :profile) %>" aria-current="page" href="<%= auth_path(@conn, :profile) %>"><%= gettext "Profile" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :watchlist) %>" href="<%= watchlist_path(@conn, :show) %>"><%= gettext "Watch list" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :address_tags) %>" href="<%= tag_address_path(@conn, :index) %>"><%= gettext "Address Tags" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :transaction_tags) %>" href="<%= tag_transaction_path(@conn, :index) %>"><%= gettext "Transaction Tags" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :api_keys) %>" href="<%= api_key_path(@conn, :index) %>"><%= gettext "API keys" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :custom_abis) %>" href="<%= custom_abi_path(@conn, :index) %>"><%= gettext "Custom ABI" %></a>
</li>
<li class="nav-item account">
<a class="<%= nav_class(@active_item, :public_tags) %>" href="<%= public_tags_request_path(@conn, :index) %>"><%= gettext "Public tags" %></a>
</li>
</ul>
</div>

@ -0,0 +1,39 @@
<% abi = format_abi(@custom_abi) %>
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %>
<div class="col-sm-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Update"), else: gettext("Add") %> <%= gettext "Custom ABI"%></h1>
<div class="col-sm-10 card-body-account">
<% path = if @method == :update, do: custom_abi_path(@conn, @method, @custom_abi.data.id), else: custom_abi_path(@conn, @method) %>
<%= form_for @custom_abi, path, fn f -> %>
<div class="form-group">
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this Custom ABI"), maxlength: 255 %>
<%= error_tag f, :name, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :address_hash, gettext("Contract Address"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %>
<%= error_tag f, :address_hash, class: "text-danger form-error" %>
<%= error_tag f, :identity_id, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :abi, gettext("ABI"), class: "control-label", style: "font-size: 14px" %>
<%= textarea f, :abi, class: "form-control", placeholder: "[{...}]", value: abi %>
<%= error_tag f, :abi, class: "text-danger form-error" %>
</div>
<br>
<div class="form-group float-right form-input">
<a class="btn btn-line" href="<%= custom_abi_path(@conn, :index) %>"><%= gettext "Back to Custom ABI (Cancel)"%></a>
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,51 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :custom_abis %>
<div class="col-md-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%= gettext "Custom ABI" %></h1>
<br>
<%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "Create a Custom ABI to interact with contracts." %>
</div>
</div>
<% else %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You can create up to 15 Custom ABIs per account." %>
</div>
</div>
<% end %>
<div class="col-sm">
<div class="mb-3 row o-flow-x">
<%= if @custom_abis != [] do %>
<table class="table mb-3 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Contract Address" %></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@custom_abis, fn key ->
render("row.html", custom_abi: key, conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@custom_abis) < CustomABI.get_max_custom_abis_count() do %>
<a class="button button-primary button-sm" href="<%= custom_abi_path(@conn, :new) %>"><%= gettext "Add Custom ABI" %></a>
<% end %>
</div>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script>
</section>

@ -0,0 +1,18 @@
<tr>
<td><%= @custom_abi.name %></td>
<td>
<%= link(@custom_abi.address_hash, to: address_contract_path(@conn, :index, @custom_abi.address_hash)) %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @custom_abi.address_hash, aria_label: gettext("Copy Contract Address"), title: gettext("Copy Contract Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
</td>
<td>
<form method="post" action="<%= custom_abi_path(@conn, :delete, @custom_abi.id) %>">
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>">
<input type="hidden" name="_method" value="delete">
</form>
<a href="" data-delete-item><%= gettext("Remove") %></a>
</td>
<td>
<%= link gettext("Edit"), to: custom_abi_path(@conn, :edit, @custom_abi.id) %>
</td>
</tr>

@ -0,0 +1,8 @@
<div class="form-group">
<%= label @f, :addresses, gettext("Address*"), class: "control-label", style: "font-size: 14px" %>
<div>
<%= array_input @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %>
<%= array_add_button @f, :addresses, maxlength: 42, size: 70, placeholder: gettext "Smart contract / Address (0x...)" %>
</div>
<%= error_tag @f, :addresses, class: "text-danger form-error pt-0" %>
</div>

@ -0,0 +1,72 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %>
<div class="col-sm-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Request to edit a public tag/label"), else: gettext("Request a public tag/label") %></h1>
<div class="col-sm-10 card-body-account">
<% path = if @method == :update, do: public_tags_request_path(@conn, @method, @public_tags_request.data.id), else: public_tags_request_path(@conn, @method) %>
<%= form_for @public_tags_request, path, fn f -> %>
<div>
<div class="line-input">
<div class="form-group">
<%= label f, :full_name, gettext("Your name*"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :full_name, class: "form-control", placeholder: gettext("Your name"), maxlength: 255 %>
<%= error_tag f, :full_name, class: "text-danger form-error" %>
<%= error_tag f, :identity_id, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :company, gettext("Company name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :company, class: "form-control", placeholder: "Company name", maxlength: 255 %>
<%= error_tag f, :company, class: "text-danger form-error" %>
</div>
</div>
<div class="line-input">
<div class="form-group">
<%= label f, :email, gettext("E-mail*"), class: "control-label", style: "font-size: 14px" %>
<%= email_input f, :email, class: "form-control", placeholder: "E-mail", maxlength: 42 %>
<%= error_tag f, :email, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :website, gettext("Company website"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :website, class: "form-control", placeholder: "Company website", maxlength: 255 %>
<%= error_tag f, :website, class: "text-danger form-error" %>
</div>
</div>
</div>
<div class="mb-3">
<div>
<%= radio_button(f, :is_owner, true) %>
<%= label f, :is_owner_true, "I want to add tags for my project" %>
</div>
<div>
<%= radio_button(f, :is_owner, false) %>
<%= label f, :is_owner_false, "I want to report an incorrect public tag" %>
</div>
<%= error_tag f, :is_owner, class: "text-danger form-error" %>
</div>
<div class="form-group mr-4-rem">
<%= label f, :tags, gettext("Public tags* (2 tags maximum, please use \";\" as a divider)"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :tags, class: "form-control", placeholder: "Public tags", maxlength: 71 %>
<%= error_tag f, :tags, class: "text-danger form-error" %>
</div>
<%= render "address_field.html", f: f %>
<div class="form-group mr-4-rem">
<%= label f, :additional_comment, gettext("Description*"), class: "control-label", style: "font-size: 14px" %>
<%= textarea f, :additional_comment, class: "form-control", placeholder: "Specify the reason for adding tags and color preference(s).", maxlength: 255 %>
<%= error_tag f, :additional_comment, class: "text-danger form-error" %>
</div>
<br>
<div class="form-group float-left form-input">
<a class="btn btn-line" href="<%= public_tags_request_path(@conn, :index) %>"><%= gettext "Cancel"%></a>
<%= submit gettext("Send request"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/public-tags-request-form.js") %>"></script>
</section>

@ -0,0 +1,44 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :public_tags %>
<div class="col-md-10">
<div class="card">
<div class="card-body">
<h1 class="card-title list-title-description header-account"><%= gettext "Public tags" %> </h1>
<br>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." %>
</div>
</div>
<div class="col-sm">
<div class="mb-3 row o-flow-x">
<%= if @public_tags_requests != [] do %>
<table class="table mb-3 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Public tag" %></th>
<th scope="col"><%= gettext "Smart contract / Address" %></th>
<th scope="col"><%= gettext "Submission date" %></th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@public_tags_requests, fn x ->
render("row.html", public_tags_request: x, conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@public_tags_requests) < PublicTagsRequest.get_max_public_tags_request_count() do %>
<a class="button button-primary button-sm" href="<%= public_tags_request_path(@conn, :new) %>"><%= gettext "Request to add public tag" %></a>
<% end %>
</div>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/delete-item-handler.js") %>"></script>
</section>

@ -0,0 +1,20 @@
<tr>
<td>
<%= for tag <- String.split(@public_tags_request.tags, ";") do %>
<div class="bs-label black-hole mb-1"><%= tag %></div>
<% end %>
</td>
<td><%= Enum.join(@public_tags_request.addresses, "\n") %></td>
<td><%= Calendar.strftime(@public_tags_request.inserted_at, "%b %d, %Y") %></td>
<td>
<%= link (render BlockScoutWeb.CommonComponentsView, "_svg_pen.html"), to: public_tags_request_path(@conn, :edit, @public_tags_request.id) %>
</td>
<td>
<form method="post" action="<%= public_tags_request_path(@conn, :delete, @public_tags_request.id) %>">
<input type="hidden" name="_csrf_token" value="<%= Plug.CSRFProtection.get_csrf_token() %>">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="remove_reason" value="">
</form>
<a href="" data-delete-request data-tags="<%= @public_tags_request.tags %>"><%= (render BlockScoutWeb.CommonComponentsView, "_svg_trash.html") %></a>
</td>
</tr>

@ -0,0 +1,33 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %>
<div class="col-sm-10">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%= gettext "Add address tag"%></h1>
<div class="col-sm-10 card-body-account">
<%= form_for @tag_address, tag_address_path(@conn, :create), fn f -> %>
<div class="form-group">
<%= label f, :address_hash, gettext("Address"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %>
<%= error_tag f, :address_hash, class: "text-danger form-error" %>
<%= error_tag f, :identity_id, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this address"), maxlength: 35 %>
<%= error_tag f, :name, class: "text-danger form-error" %>
</div>
<div class="form-group float-right form-input">
<a class="btn btn-line" href="<%= tag_address_path(@conn, :index) %>"><%= gettext "Back to Address Tags (Cancel)"%></a>
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,41 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :address_tags %>
<div class="col-md">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%= gettext "Address Tags" %></h1>
<br>
<div class="col-sm">
<div class="mb-3 row o-flow-x">
<%= if @address_tags == [] do %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You don't have address tags yet" %>
</div>
</div>
<h2></h2>
<% else %>
<table class="table mb-3 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Address" %></th>
<th scope="col"><%= gettext "Action" %></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@address_tags, fn at ->
render("row.html", address_tag: at, conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@address_tags) < TagAddress.get_max_tags_count() do %>
<a class="button button-primary button-sm" href="<%= tag_address_path(@conn, :new) %>"><%= gettext "Add address tag" %></a>
<% end %>
</div>
</div>
</section>

@ -0,0 +1,15 @@
<%= if @address_tag.address_hash do %>
<tr>
<td><%= @address_tag.name %></td>
<td>
<div>
<%= link(trimmed_hash(@address_tag.address_hash), to: address_path(@conn, :show, @address_tag.address_hash)) %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @address_tag.address_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
</td>
<td>
<%= link "Remove Tag", to: tag_address_path(@conn, :delete, @address_tag.id), method: :delete %>
</div>
</td>
</tr>
<% end %>

@ -0,0 +1,33 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %>
<div class="col-sm-10">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%= gettext "Add transaction tag"%></h1>
<div class="col-sm-10 card-body-account">
<%= form_for @tag_transaction, tag_transaction_path(@conn, :create), fn f -> %>
<div class="form-group">
<%= label f, :tx_hash, gettext("Transaction"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :tx_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000000000000000000000000000", maxlength: 66 %>
<%= error_tag f, :tx_hash, class: "text-danger form-error" %>
<%= error_tag f, :identity_id, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this transaction"), maxlength: 35 %>
<%= error_tag f, :name, class: "text-danger form-error" %>
</div>
<div class="form-group float-right form-input">
<a class="btn btn-line" href="<%= tag_transaction_path(@conn, :index) %>"><%= gettext "Back to Transaction Tags (Cancel)"%></a>
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,41 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :transaction_tags %>
<div class="col-md">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%= gettext "Transaction Tags" %></h1>
<br>
<div class="col-sm">
<div class="mb-3 row o-flow-x">
<%= if @tx_tags == [] do %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You don't have transaction tags yet" %>
</div>
</div>
<h2></h2>
<% else %>
<table class="table mb-3 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Transaction" %></th>
<th scope="col"><%= gettext "Action" %></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@tx_tags, fn at ->
render("row.html", tx_tag: at, conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@tx_tags) < TagTransaction.get_max_tags_count() do %>
<a class="button button-primary button-sm" href="<%= tag_transaction_path(@conn, :new) %>"><%= gettext "Add transaction tag" %></a>
<% end %>
</div>
</div>
</section>

@ -0,0 +1,18 @@
<%= if @tx_tag.tx_hash do %>
<tr>
<td><%= @tx_tag.name %></td>
<td>
<div>
<%= link(@tx_tag.tx_hash,
to: transaction_path(BlockScoutWeb.Endpoint, :show, @tx_tag.tx_hash),
"data-test": "transaction_hash_link",
class: "text-truncate") %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @tx_tag.tx_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
</div>
</td>
<td>
<%= link "Remove Tag", to: tag_transaction_path(@conn, :delete, @tx_tag.id), method: :delete %>
</td>
</tr>
<% end %>

@ -0,0 +1,42 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %>
<div class="col-md">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%= gettext "Watch list" %></h1>
<br>
<div class="col-sm">
<div class="mb-4 row o-flow-x">
<%= if @watchlist.watchlist_addresses == [] do %>
<div style="min-width: 100%;">
<div class="tile tile-muted text-center" data-selector="empty-coin-balances-list">
<%= gettext "You don't have addresses on you watchlist yet" %>
</div>
</div>
<h2></h2>
<% else %>
<table class="table mb-4 table-watchlist">
<thead style="font-size: 14px; color: #6c757d" >
<tr>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Address" %></th>
<th scope="col"><%= gettext "Balance" %></th>
<th scope="col"><%= gettext "Actions" %></th>
</tr>
</thead>
<tbody style="font-size: 15px; color: #6c757d" >
<%= Enum.map(@watchlist.watchlist_addresses, fn wa ->
render(WatchlistAddressView, "row.html", watchlist_address: wa, exchange_rate: exchange_rate(), conn: @conn)
end) %>
</tbody>
</table>
<% end %>
</div>
</div>
<%= if Enum.count(@watchlist.watchlist_addresses) < WatchlistAddress.get_max_watchlist_addresses_count() do %>
<a class="button button-primary button-sm" href="<%= watchlist_address_path(@conn, :new) %>"><%= gettext "Add address" %></a>
<% end %>
</div>
</div>
</section>

@ -0,0 +1,91 @@
<section class="container">
<div class="row">
<%= render BlockScoutWeb.Account.CommonView, "_nav.html", conn: @conn, active_item: :watchlist %>
<div class="col-sm-10">
<div class="card">
<div class="card-body" >
<h1 class="card-title list-title-description header-account"><%=if @method == :update, do: gettext("Edit Watch list address"), else: gettext "Add address to the Watch list" %></h1>
<div class="col-sm-10 card-body-account">
<% path = if @method == :update, do: watchlist_address_path(@conn, @method, @watchlist_address.data.id), else: watchlist_address_path(@conn, @method) %>
<%= form_for @watchlist_address, path, fn f -> %>
<div class="form-group">
<%= label f, :address_hash, gettext("Address"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :address_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000", maxlength: 42 %>
<%= error_tag f, :address_hash, class: "text-danger form-error" %>
<%= error_tag f, :watchlist_id, class: "text-danger form-error" %>
</div>
<div class="form-group">
<%= label f, :name, gettext("Name"), class: "control-label", style: "font-size: 14px" %>
<%= text_input f, :name, class: "form-control", placeholder: gettext("Name this address"), maxlength: 35 %>
<%= error_tag f, :name, class: "text-danger form-error" %>
</div>
<br>
<p style="font-size: 14px;"><%= gettext("Please select what types of notifications you will receive:") %></p>
<div class="row">
<div class="col-sm">
<h3><%= gettext("Ether") %></h3>
<div class="form-input">
<%= checkbox f, :watch_coin_input, class: "form-checkbox" %>
<%= label f, :watch_coin_input, gettext("Incoming"), class: "label-checkbox" %>
<%= error_tag f, :watch_coin_input, class: "text-danger form-error" %>
</div>
<div class="form-input">
<%= checkbox f, :watch_coin_output, class: "form-checkbox" %>
<%= label f, :watch_coin_output, gettext("Outgoing"), class: "label-checkbox" %>
<%= error_tag f, :watch_coin_output, class: "text-danger form-error" %>
</div>
</div>
<div class="col-sm">
<h3><%= gettext "ERC-20 tokens (beta)" %></h3>
<div class="form-input">
<%= checkbox f, :watch_erc_20_input, class: "form-checkbox" %>
<%= label f, :watch_erc_20_input, gettext("Incoming"), class: "label-checkbox" %>
<%= error_tag f, :watch_erc_20_input, class: "text-danger form-error" %>
</div>
<div class="form-input">
<%= checkbox f, :watch_erc_20_output, class: "form-checkbox" %>
<%= label f, :watch_erc_20_output, gettext("Outgoing"), class: "label-checkbox" %>
<%= error_tag f, :watch_erc_20_output, class: "text-danger form-error" %>
</div>
</div>
<div class="col-md">
<h3><%= gettext "ERC-721, ERC-1155 tokens (NFT) (beta)" %></h3>
<div class="form-input">
<%= checkbox f, :watch_erc_721_input, class: "form-checkbox" %>
<%= label f, :watch_erc_721_input, gettext("Incoming"), class: "label-checkbox" %>
<%= error_tag f, :watch_erc_721_input, class: "text-danger form-error" %>
</div>
<div class="form-input">
<%= checkbox f, :watch_erc_721_output, class: "form-checkbox" %>
<%= label f, :watch_erc_721_output, gettext("Outgoing"), class: "label-checkbox" %>
<%= error_tag f, :watch_erc_721_output, class: "text-danger form-error" %>
</div>
</div>
</div>
<br>
<p style="font-size: 14px;"><%= gettext "Please select notification methods:"%></p>
<div class="form-input">
<%= checkbox f, :notify_email, class: "form-checkbox" %>
<%= label f, :notify_email, gettext("Email notifications"), class: "label-checkbox" %>
<%= error_tag f, :notify_email, class: "text-danger form-error" %>
</div>
<br>
<%= if @method != :create do %>
<%= link gettext("Remove from Watch list"), to: watchlist_address_path(@conn, :delete, @watchlist_address.data.id), method: :delete, class: "btn btn-danger white button-sm", style: "color: #fff !important;" %>
<% end %>
<div class="form-group float-right form-input">
<a class="btn btn-line" href="<%= watchlist_path(@conn, :show) %>">
<%= gettext "Back to Watch list (Cancel)" %>
</a>
<%= submit gettext("Save"), class: "button button-primary button-sm ml-3" %>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</section>

@ -0,0 +1,29 @@
<tr>
<td><%= @watchlist_address.name %></td>
<td>
<div>
<%= link(trimmed_hash(@watchlist_address.address_hash), to: address_path(@conn, :show, @watchlist_address.address_hash)) %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @watchlist_address.address_hash, aria_label: gettext("Copy From Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
</div>
</td>
<td>
<%= balance_ether(@watchlist_address.fetched_coin_balance) %>
<br>
<span class="address-current-balance">
(<span
data-wei-value="<%= if @watchlist_address.fetched_coin_balance, do: @watchlist_address.fetched_coin_balance.value %>"
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"
data-placement="top"
data-toggle="tooltip"
data-html="true"
title='<%= "@ " <> to_string(@exchange_rate.usd_value) <> "/" <> gettext("Ether") %>'
>
</span>)
</span>
</td>
<td>
<%= link(gettext("Edit"), to: watchlist_address_path(@conn, :edit, @watchlist_address.id)) %>
</td>
</tr>

@ -0,0 +1,16 @@
<%= for personal_tag <- @tags.personal_tags do %>
<%= if personal_tag.address_hash do %>
<%= if personal_tag.label =~ "dark forest" do %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: ["df", "ml-1"] %>
<% else %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tag.display_name, additional_classes: [tag_name_to_label(personal_tag.label), "ml-1"] %>
<% end %>
<% end %>
<% end %>
<%= for watchlist_name <- @tags.watchlist_names do %>
<%= if watchlist_name.label =~ "dark forest" do %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: ["df", "ml-1"] %>
<% else %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: watchlist_name.display_name, additional_classes: [tag_name_to_label(watchlist_name.label), "ml-1"] %>
<% end %>
<% end %>

@ -76,7 +76,7 @@
<i class="fa-regular fa-check-circle"></i>
<% end %>
<% end %>
<%= if smart_contract_with_read_only_functions?(@address) do %>
<%= if smart_contract_with_read_only_functions?(@address) || has_address_custom_abi_with_read_functions?(@conn, @address.hash) do %>
<%= link(
gettext("Read Contract"),
to: AccessHelpers.get_path(@conn, :address_read_contract_path, :index, @address.hash),
@ -90,14 +90,14 @@
class: "card-tab #{tab_status("read-proxy", @conn.request_path)}")
%>
<% end %>
<%= if smart_contract_with_write_functions?(@address) do %>
<%= if smart_contract_with_write_functions?(@address) || has_address_custom_abi_with_write_functions?(@conn, @address.hash) do %>
<%= link(
gettext("Write Contract"),
to: AccessHelpers.get_path(@conn, :address_write_contract_path, :index, @address.hash),
class: "card-tab #{tab_status("write-contract", @conn.request_path)}")
%>
<% end %>
<%= if smart_contract_with_write_functions?(@address) && @is_proxy do %>
<%= if smart_contract_with_write_functions?(@address) && @is_proxy do %>
<%= link(
gettext("Write Proxy"),
to: AccessHelpers.get_path(@conn, :address_write_proxy_path, :index, @address.hash),

@ -28,6 +28,7 @@
<% end %>
<h1 class="card-title lg-card-title mb-2-desktop">
<div class="title-with-label"><%= address_title(@address) %> <%= gettext "Details" %></div>
<%= render BlockScoutWeb.AddressView, "_labels.html", address_hash: @address.hash, tags: @tags %>
<!-- buttons -->
<span class="overview-title-buttons float-right">
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
@ -37,25 +38,6 @@
aria_label: gettext("Copy Address"),
title: gettext("Copy Address") %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %>
<%= if validator_metadata = primary_validator_metadata(@address) do %>
<span
class="overview-title-item"
data-target="#validatorModal"
data-toggle="modal"
>
<span
aria-label='<%= gettext("Show Validator Info") %>'
class="btn-address-card-icon"
data-placement="top"
data-toggle="tooltip"
title='<%= gettext("Validator Info") %>'
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
<path fill-rule="evenodd" d="M19 6h-6a1 1 0 0 1 0-2h6a1 1 0 0 1 0 2zm0-4h-6a1 1 0 0 1 0-2h6a1 1 0 0 1 0 2zM9 10H1a1 1 0 0 1-1-1V7a2 2 0 0 1 2-2h.184A2.962 2.962 0 0 1 2 4V3a3 3 0 1 1 6 0v1c0 .353-.072.686-.184 1H8a2 2 0 0 1 2 2v2a1 1 0 0 1-1 1zM6 3a1 1 0 0 0-2 0v1a1 1 0 0 0 2 0V3zm2 4H2v1h6V7zm5 1h6a1 1 0 0 1 0 2h-6a1 1 0 0 1 0-2z"/>
</svg>
</span>
</span>
<% end %>
</span>
</h1>
<h3 class="address-detail-hash-title mb-4 <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
@ -306,9 +288,4 @@
<!-- Modal QR -->
<%= render BlockScoutWeb.CommonComponentsView, "_modal_qr_code.html", qr_code: qr_code(@address), title: @address %>
<!-- Modal Validator -->
<%= if validator_metadata do %>
<%= render BlockScoutWeb.AddressView, "_validator_metadata_modal.html", address_name: address_name, validator_metadata: validator_metadata %>
<% end %>
<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %>

@ -3,7 +3,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section>
<div class="card">

@ -8,7 +8,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
@ -155,20 +155,14 @@
<button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= creation_code(@address) %>" aria-label="copy contract creation code">
<%= gettext "Copy Contract Creation Code" %>
</button>
<%= if match?({:selfdestructed, _}, contract_creation_code) do %>
<div class="button button-disabled button-sm float-right ml-3">
<%= gettext("Verify & Publish") %>
</div>
<% else %>
<%= if !fully_verified do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<%= link(
gettext("Verify & Publish"),
to: path,
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
<%= if !fully_verified do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<%= link(
gettext("Verify & Publish"),
to: path,
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
</div>
</div>
@ -192,20 +186,14 @@
<button type="button" class="btn-line" style="float: left;" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code">
<%= gettext "Copy Deployed ByteCode" %>
</button>
<%= if match?({:selfdestructed, _}, contract_creation_code) and !creation_code(@address) do %>
<div class="button button-disabled button-sm float-right ml-3">
<%= gettext("Verify & Publish") %>
</div>
<% else %>
<%= if !fully_verified and !creation_code(@address) do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<%= link(
gettext("Verify & Publish"),
to: path,
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
<%= if !fully_verified and !creation_code(@address) do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<%= link(
gettext("Verify & Publish"),
to: path,
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
</div>
</div>

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<% contract = last_decompiled_contract_version(@address.decompiled_smart_contracts) %>

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="address-internal-transactions">
<div class="card">

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="address-logs">
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>

@ -2,16 +2,57 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
<%= if @need_wallet do %>
<div class="card-misc-container">
<%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %>
</div>
</div>
<% end %>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
<ul class="card-misc-container nav nav-pills" id="functions-tab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="verified-tab" data-toggle="pill" href="#verified" role="tab" aria-controls="pills-verified" aria-selected="true"><%= gettext "Verified" %></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="custom-tab" data-toggle="pill" href="#custom" role="tab" aria-controls="pills-custom" aria-selected="false"><%= gettext "Custom" %></a>
</li>
</ul>
<% else %>
<%= if assigns[:custom_abi] do %>
<h2 class="card-misc-container"><%= gettext "Custom ABI from account" %></h2>
<% end %>
<% end %>
<%=
for status <- ["error", "warning", "success", "question"] do
render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status
end
%>
<%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
<div class="tab-content" id="pills-tabContent">
<% end %>
<%= if @non_custom_abi do %>
<!-- loaded via AJAX -->
<div class="card-body tab-pane fade show active" id="verified" role="tabpanel" aria-labelledby="verified-tab" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</div>
</div>
<% end %>
<%= if assigns[:custom_abi] do %>
<!-- loaded via AJAX -->
<div class="card-body tab-pane <%= if @non_custom_abi && assigns[:custom_abi], do: "fade" %>" id="custom" role="tabpanel" aria-labelledby="custom-tab" data-smart-contract-functions-custom data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</div>
</div>
<% end %>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
</div>
<% end %>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
</section>

@ -2,7 +2,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section id="tokens">
<div class="card">

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="address-token-transfers" id="transfers">
<div class="card">

@ -2,7 +2,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="address-transactions" id="txs">
<div class="card">

@ -1,7 +1,7 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="blocks-validated">
<div class="card">

@ -2,16 +2,56 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</div>
<div class="card-misc-container">
<%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %>
</div>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
<ul class="card-misc-container nav nav-pills" id="functions-tab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="verified-tab" data-toggle="pill" href="#verified" role="tab" aria-controls="pills-verified" aria-selected="true"><%= gettext "Verified" %></a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="custom-tab" data-toggle="pill" href="#custom" role="tab" aria-controls="pills-custom" aria-selected="false"><%= gettext "Custom" %></a>
</li>
</ul>
<% else %>
<%= if assigns[:custom_abi] do %>
<h2 class="card-misc-container"><%= gettext "Custom ABI from account" %></h2>
<% end %>
<% end %>
<%=
for status <- ["error", "warning", "success", "question"] do
render BlockScoutWeb.CommonComponentsView, "_modal_status.html", status: status
end
%>
<%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
<div class="tab-content" id="pills-tabContent">
<% end %>
<%= if @non_custom_abi do %>
<!-- loaded via AJAX -->
<div class="card-body tab-pane fade show active" id="verified" role="tabpanel" aria-labelledby="verified-tab" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</div>
</div>
<% end %>
<%= if assigns[:custom_abi] do %>
<!-- loaded via AJAX -->
<div class="card-body tab-pane <%= if @non_custom_abi && assigns[:custom_abi], do: "fade" %>" id="custom" role="tabpanel" aria-labelledby="custom-tab" data-smart-contract-functions-custom data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-action="<%= @action %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</div>
</div>
<% end %>
<%= if @non_custom_abi && assigns[:custom_abi] do %>
</div>
<% end %>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/contract-read-write.js") %>"></script>
</section>

@ -2,7 +2,7 @@
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-dash-square" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>

After

Width:  |  Height:  |  Size: 548 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-plus-square" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>

After

Width:  |  Height:  |  Size: 410 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save