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, 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 """ Returns the current block number of the blockchain. """ @spec current_block_number(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()} def current_block_number(opts \\ []) do with {:ok, block_number} <- RPC.eth_block_number(opts) do Ethers.Utils.hex_to_integer(block_number) 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. The binary MUST be hex encoded. - 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 with true <- function_exported?(contract_module, :__contract_binary__, 0), bin when not is_nil(bin) <- contract_module.__contract_binary__() do deploy(bin, contract_init, params, opts) else _error -> {:error, :binary_not_found} end end def deploy("0x" <> contract_binary, contract_init, params, opts) do deploy(contract_binary, contract_init, params, opts) 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}} when not is_nil(contract_address) -> {:ok, contract_address} {:ok, nil} -> {:error, :transaction_not_found} {:ok, _} -> {:error, :no_contract_address} {:error, reason} -> {:error, reason} end 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