defmodule Ethers do
@moduledoc """
high-level module providing a convenient and efficient interface for interacting
with the Ethereum blockchain using Elixir.
This module offers a simple API for common Ethereum operations such as deploying contracts,
estimating gas, fetching current gas prices, and querying event logs.
"""
alias Ethers.Types
alias Ethers.{Event, RPC, Utils}
@doc """
Returns the current gas price from the RPC API
"""
@spec current_gas_price() :: {:ok, non_neg_integer()}
def current_gas_price do
with {:ok, price_hex} <- RPC.eth_gas_price() do
Ethers.Utils.hex_to_integer(price_hex)
end
end
@doc """
Deploys a contract to the blockchain.
This will return the transaction hash for the deployment transaction.
To get the address of your deployed contract, use `Ethers.deployed_address/2`.
To deploy a cotract you must have the binary related to it. It can either be a part of the ABI
File you have or as a separate file.
## Parameters
- contract_module_or_binary: Either the contract module which was already loaded or the compiled binary of the contract.
- contract_init: Constructor value for contract deployment. Use `CONTRACT_MODULE.constructor` function's output. If your contract does not have a constructor, you can pass an empty binary here.
- params: Parameters for the transaction creating the contract.
- opts: RPC and account options.
"""
@spec deploy(atom() | binary(), binary(), Keyword.t(), Keyword.t()) ::
{:ok, Types.t_hash()} | {:error, atom()}
def deploy(contract_module_or_binary, contract_init, params, opts \\ [])
def deploy(contract_module, contract_init, params, opts) when is_atom(contract_module) do
cond do
not function_exported?(contract_module, :__contract_binary__, 0) ->
{:error, :invalid_contract_module}
bin = contract_module.__contract_binary__() ->
deploy(bin, contract_init, params, opts)
true ->
{:error, :no_contract_binary}
end
end
def deploy(contract_binary, contract_init, params, opts) when is_binary(contract_binary) do
params =
Enum.into(params, %{
data: "0x#{contract_binary}#{contract_init}",
to: nil
})
with {:ok, params} <- Utils.maybe_add_gas_limit(params, opts) do
RPC.eth_send_transaction(params, opts)
end
end
@doc """
Returns the address of the deployed contract if the deployment is finished and successful
## Parameters
- tx_hash: Hash of the Transaction which created a contract.
- opts: RPC and account options.
"""
@spec deployed_address(binary, Keyword.t()) ::
{:ok, Types.t_address()}
| {:error, :no_contract_address | :transaction_not_found | atom()}
def deployed_address(tx_hash, opts \\ []) when is_binary(tx_hash) do
case RPC.eth_get_transaction_receipt(tx_hash, opts) do
{:ok, %{"contractAddress" => contract_address}} ->
{:ok, contract_address}
{:ok, nil} ->
{:error, :transaction_not_found}
{:ok, _} ->
{:error, :no_contract_address}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Estimates gas for a eth_call
## Parameters
- params
- opts: RPC and account options.
"""
@spec estimate_gas(map(), Keyword.t()) ::
{:ok, non_neg_integer()} | {:error, :gas_estimation_failed}
def estimate_gas(params, opts \\ []) do
RPC.estimate_gas(params, [], opts)
end
@doc """
Returns the event logs with the given filter
"""
@spec get_logs(map(), Keyword.t(), Keyword.t()) :: {:ok, [Event.t()]} | {:error, atom()}
def get_logs(%{topics: _, selector: selector} = params, overrides \\ [], opts \\ []) do
params =
overrides
|> Enum.into(params)
|> Map.drop([:selector])
with {:ok, resp} when is_list(resp) <- RPC.eth_get_logs(params, opts) do
logs = Enum.map(resp, &Event.decode(&1, selector))
{:ok, logs}
end
end
@doc false
def keccak_module, do: Application.get_env(:ethers, :keccak_module, ExKeccak)
@doc false
def json_module, do: Application.get_env(:ethers, :json_module, Jason)
@doc false
def rpc_client, do: Application.get_env(:ethers, :rpc_client, Ethereumex.HttpClient)
end