parent
f77ff79a36
commit
fe78cbfe39
@ -0,0 +1,99 @@ |
||||
import $ from 'jquery' |
||||
import { walletEnabled, getCurrentAccount } from './write.js' |
||||
import { openErrorModal, openWarningModal, openSuccessModal } from '../modals.js' |
||||
|
||||
const loadFunctions = (element) => { |
||||
const $element = $(element) |
||||
const url = $element.data('url') |
||||
const hash = $element.data('hash') |
||||
const type = $element.data('type') |
||||
const action = $element.data('action') |
||||
|
||||
$.get( |
||||
url, |
||||
{ hash: hash, type: type, action: action }, |
||||
response => $element.html(response) |
||||
) |
||||
.done(function () { |
||||
$('[data-function]').each((_, element) => { |
||||
readWriteFunction(element) |
||||
}) |
||||
}) |
||||
.fail(function (response) { |
||||
$element.html(response.statusText) |
||||
}) |
||||
} |
||||
|
||||
const readWriteFunction = (element) => { |
||||
const $element = $(element) |
||||
const $form = $element.find('[data-function-form]') |
||||
|
||||
const $responseContainer = $element.find('[data-function-response]') |
||||
|
||||
$form.on('submit', (event) => { |
||||
const action = $form.data('action') |
||||
event.preventDefault() |
||||
|
||||
if (action === 'read') { |
||||
const url = $form.data('url') |
||||
const $functionName = $form.find('input[name=function_name]') |
||||
const $functionInputs = $form.find('input[name=function_input]') |
||||
|
||||
const args = $.map($functionInputs, element => { |
||||
return $(element).val() |
||||
}) |
||||
|
||||
const data = { |
||||
function_name: $functionName.val(), |
||||
args |
||||
} |
||||
|
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
} else { |
||||
walletEnabled() |
||||
.then((isWalletEnabled) => { |
||||
if (isWalletEnabled) { |
||||
const functionName = $form.find('input[name=function_name]').val() |
||||
|
||||
const $functionInputs = $form.find('input[name=function_input]') |
||||
const args = $.map($functionInputs, element => { |
||||
return $(element).val() |
||||
}) |
||||
|
||||
const contractAddress = $form.data('contract-address') |
||||
const contractAbi = $form.data('contract-abi') |
||||
|
||||
getCurrentAccount() |
||||
.then(currentAccount => { |
||||
const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) |
||||
|
||||
TargetContract.methods[functionName](...args).send({ from: currentAccount }) |
||||
.on('error', function (error) { |
||||
openErrorModal(`Error in sending transaction for method "${functionName}"`, error, false) |
||||
}) |
||||
.on('transactionHash', function (txHash) { |
||||
const getTxReceipt = (txHash) => { |
||||
window.web3.eth.getTransactionReceipt(txHash) |
||||
.then(txReceipt => { |
||||
if (txReceipt) { |
||||
openSuccessModal('Success', `Successfully sent <a href="/tx/${txHash}">transaction</a> for method "${functionName}"`) |
||||
clearInterval(txReceiptPollingIntervalId) |
||||
} |
||||
}) |
||||
} |
||||
const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) |
||||
}) |
||||
}) |
||||
} else { |
||||
openWarningModal('Unauthorized', 'You haven\'t approved the reading of account list from your MetaMask/Nifty wallet or MetaMask/Nifty wallet is not installed.') |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const container = $('[data-smart-contract-functions]') |
||||
|
||||
if (container.length) { |
||||
loadFunctions(container) |
||||
} |
@ -1,3 +1,3 @@ |
||||
import './read_only_functions' |
||||
import './functions' |
||||
import './wei_ether_converter' |
||||
import '../../app' |
||||
|
@ -1,54 +0,0 @@ |
||||
import $ from 'jquery' |
||||
|
||||
const loadFunctions = (element) => { |
||||
const $element = $(element) |
||||
const url = $element.data('url') |
||||
const hash = $element.data('hash') |
||||
const type = $element.data('type') |
||||
|
||||
$.get( |
||||
url, |
||||
{ hash: hash, type: type }, |
||||
response => $element.html(response) |
||||
) |
||||
.done(function () { |
||||
$('[data-function]').each((_, element) => { |
||||
readFunction(element) |
||||
}) |
||||
}) |
||||
.fail(function (response) { |
||||
$element.html(response.statusText) |
||||
}) |
||||
} |
||||
|
||||
const readFunction = (element) => { |
||||
const $element = $(element) |
||||
const $form = $element.find('[data-function-form]') |
||||
|
||||
const $responseContainer = $element.find('[data-function-response]') |
||||
|
||||
$form.on('submit', (event) => { |
||||
event.preventDefault() |
||||
|
||||
const url = $form.data('url') |
||||
const $functionName = $form.find('input[name=function_name]') |
||||
const $functionInputs = $form.find('input[name=function_input]') |
||||
|
||||
const args = $.map($functionInputs, element => { |
||||
return $(element).val() |
||||
}) |
||||
|
||||
const data = { |
||||
function_name: $functionName.val(), |
||||
args |
||||
} |
||||
|
||||
$.get(url, data, response => $responseContainer.html(response)) |
||||
}) |
||||
} |
||||
|
||||
const container = $('[data-smart-contract-functions]') |
||||
|
||||
if (container.length) { |
||||
loadFunctions(container) |
||||
} |
@ -0,0 +1,29 @@ |
||||
import Web3 from 'web3' |
||||
|
||||
export const walletEnabled = () => { |
||||
if (window.ethereum) { |
||||
window.web3 = new Web3(window.ethereum) |
||||
if (window.ethereum._state && window.ethereum._state.isUnlocked) { // Nifty Wallet
|
||||
window.web3 = new Web3(window.web3.currentProvider) |
||||
return Promise.resolve(true) |
||||
} else if (window.ethereum._state && window.ethereum._state.isUnlocked === false) { // Nifty Wallet
|
||||
return Promise.resolve(false) |
||||
} else { |
||||
window.ethereum.enable() |
||||
window.web3 = new Web3(window.web3.currentProvider) |
||||
return Promise.resolve(true) |
||||
} |
||||
} else if (window.web3) { |
||||
window.web3 = new Web3(window.web3.currentProvider) |
||||
return Promise.resolve(true) |
||||
} else { |
||||
return Promise.resolve(false) |
||||
} |
||||
} |
||||
|
||||
export const getCurrentAccount = async () => { |
||||
const accounts = await window.web3.eth.getAccounts() |
||||
const account = accounts[0] ? accounts[0].toLowerCase() : null |
||||
|
||||
return account |
||||
} |
@ -0,0 +1 @@ |
||||
import '../lib/smart_contract/write' |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,45 @@ |
||||
# credo:disable-for-this-file |
||||
# |
||||
# When moving the calls to ajax, this controller became very similar to the |
||||
# `address_contract_controller`, but both are necessary until we are able to |
||||
# address a better way to organize the controllers. |
||||
# |
||||
# So, for now, I'm adding this comment to disable the credo check for this file. |
||||
defmodule BlockScoutWeb.AddressWriteContractController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias Explorer.{Chain, Market} |
||||
alias Explorer.Chain.Address |
||||
alias Explorer.ExchangeRates.Token |
||||
alias Indexer.Fetcher.CoinBalanceOnDemand |
||||
|
||||
def index(conn, %{"address_id" => address_hash_string}) do |
||||
address_options = [ |
||||
necessity_by_association: %{ |
||||
:contracts_creation_internal_transaction => :optional, |
||||
:names => :optional, |
||||
:smart_contract => :optional, |
||||
:token => :optional, |
||||
:contracts_creation_transaction => :optional |
||||
} |
||||
] |
||||
|
||||
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) 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)}) |
||||
) |
||||
else |
||||
_ -> |
||||
not_found(conn) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1 @@ |
||||
<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %> |
@ -0,0 +1,20 @@ |
||||
<section class="container"> |
||||
|
||||
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> |
||||
|
||||
<div class="card"> |
||||
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> |
||||
<!-- 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> |
||||
<span class="loading-spinner-small mr-2"> |
||||
<span class="loading-spinner-block-1"></span> |
||||
<span class="loading-spinner-block-2"></span> |
||||
</span> |
||||
<%= gettext("Loading...") %> |
||||
</div> |
||||
</div> |
||||
</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/write_contract.js") %>"></script> |
||||
</section> |
@ -1,7 +1,13 @@ |
||||
defmodule BlockScoutWeb.AddressReadContractView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
def queryable?(inputs), do: Enum.any?(inputs) |
||||
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) |
||||
|
||||
def queryable?(inputs) when is_nil(inputs), do: false |
||||
|
||||
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) |
||||
|
||||
def outputs?(outputs) when is_nil(outputs), do: false |
||||
|
||||
def address?(type), do: type == "address" |
||||
end |
||||
|
@ -1,7 +1,13 @@ |
||||
defmodule BlockScoutWeb.AddressReadProxyView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
def queryable?(inputs), do: Enum.any?(inputs) |
||||
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) |
||||
|
||||
def queryable?(inputs) when is_nil(inputs), do: false |
||||
|
||||
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) |
||||
|
||||
def outputs?(outputs) when is_nil(outputs), do: false |
||||
|
||||
def address?(type), do: type == "address" |
||||
end |
||||
|
@ -0,0 +1,13 @@ |
||||
defmodule BlockScoutWeb.AddressWriteContractView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) |
||||
|
||||
def queryable?(inputs) when is_nil(inputs), do: false |
||||
|
||||
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) |
||||
|
||||
def outputs?(outputs) when is_nil(outputs), do: false |
||||
|
||||
def address?(type), do: type == "address" |
||||
end |
@ -0,0 +1,28 @@ |
||||
defmodule Explorer.SmartContract.Writer do |
||||
@moduledoc """ |
||||
Generates smart-contract transactions |
||||
""" |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
@spec write_functions(Hash.t()) :: [%{}] |
||||
def write_functions(contract_address_hash) do |
||||
abi = |
||||
contract_address_hash |
||||
|> Chain.address_hash_to_smart_contract() |
||||
|> Map.get(:abi) |
||||
|
||||
case abi do |
||||
nil -> |
||||
[] |
||||
|
||||
_ -> |
||||
abi |
||||
|> Enum.filter( |
||||
&(&1["type"] !== "event" && |
||||
(&1["stateMutability"] == "nonpayable" || &1["stateMutability"] == "payable" || &1["payable"] || |
||||
(!&1["payable"] && !&1["constant"] && !&1["stateMutability"]))) |
||||
) |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue