README.md

# Ethex
====================

Ethereum Contract interaction via json-rpc for multi-chain based on ex_abi, focused on Smart Contract interaction, with reading contract, writing contract, synchronizing contract events.

**NOTE: version `1.x.x` is incompatible with `0.x.x`**

## Installation

The package can be installed by adding `ethex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ethex, "~> 1.1.0"}
  ]
end
```

## Usage

You can get eth_block_number directly:

```elixir
iex(1)> Ethex.block_number "https://binance.llamarpc.com"
{:ok, 46792582}
```

### Wallet

Create or import wallet by private key, examples below,

```elixir
iex(1)> Ethex.create_wallet
%Ethex.Web3.Wallet{
  private_key: "58d4475ecd42f9be425b28866374741db82dd6b71cfb0eb54bb1a09ff85ca87b",
  public_key: "04d179509d453e1d401850c1dc4ba16541487dd22565747ccde722312802e05c3b4f39375891a711f05aa93f56da130eed164efa10620a9a45390b66046862653b",
  eth_address: "0x2dc3c3ce6901ab9be01379d374d58c1eb0fc7a85",
  mnemonic_phrase: "flee peasant stumble once convince tennis annual govern major brick brown derive lizard twice symbol panda attitude prevent unaware donkey zebra comic peanut lazy"
}

iex(2)> Ethex.create_wallet "58d4475ecd42f9be425b28866374741db82dd6b71cfb0eb54bb1a09ff85ca87b"
%Ethex.Web3.Wallet{
  private_key: "58d4475ecd42f9be425b28866374741db82dd6b71cfb0eb54bb1a09ff85ca87b",
  public_key: "04d179509d453e1d401850c1dc4ba16541487dd22565747ccde722312802e05c3b4f39375891a711f05aa93f56da130eed164efa10620a9a45390b66046862653b",
  eth_address: "0x2dc3c3ce6901ab9be01379d374d58c1eb0fc7a85",
  mnemonic_phrase: "flee peasant stumble once convince tennis annual govern major brick brown derive lizard twice symbol panda attitude prevent unaware donkey zebra comic peanut lazy"
}
```

### Interact with contract

First write a module to parse abi, known which contract address interact with and rpc endpoint request to.

```elixir
defmodule Test.USDT do
  @moduledoc false
  use Ethex.Abi,
    rpc: "https://binance.llamarpc.com",
    abi_path: "./priv/abi/usdt.abi.json",
    contract_address: "0x55d398326f99059fF775485246999027B3197955"
end
```

**Read the Contract**

```elixir
iex(1)> Test.USDT.symbol
{:ok, "USDT"}

iex(3)> Test.USDT.name
{:ok, "Tether USD"}

iex(5)> Test.USDT.decimals
{:ok, 18}

iex(7)> Test.USDT.get_owner
{:ok, "0xf68a4b64162906eff0ff6ae34e2bb1cd42fef62d"}

iex(9)> Test.USDT.balance_of "0x8CcF629e123D83112423c283998443829A291334"
{:ok, 4011000000000000}
```

**Get logs and decode**

By invoking `eth_getLogs` method, it can fetch contract logs from chain with block range.
Then, decode logs to events as result.

```elixir
iex(1)> block_range = %Ethex.Web3.Structs.BlockRange{from_block: 46798863, to_block: 46798863}

iex(2)> {:ok, logs} = Test.USDT.get_logs_and_decode block_range
{:ok,
 [
   %Ethex.Web3.Structs.Event{
     address: "0x55d398326f99059ff775485246999027b3197955",
     block_hash: "0x943b3daa9119d8d4314f816a0f00cd824c9fe73a6d1a3076d16fe1ce91fc173d",
     block_number: 46798863,
     block_timestamp: 1739978103,
     log_index: "0x1c",
     removed: false,
     transaction_hash: "0x2c3795501857b8d6e1ccd00b4132373b25b76ccd399f1719aadbfec8d688c238",
     transaction_index: "0x6",
     returns: [
       %{name: "from", value: "0x47a90a2d92a8367a91efa1906bfc8c1e05bf10c4"},
       %{name: "to", value: "0x2d3b5ca3e5ff50b12cd9d58216abaaa6b3836443"},
       %{name: "value", value: 296241844581231922050}
     ],
     event_name: "Transfer"
   },
   ...
 ]
```

For polling logs, you need to maintaining block range to avoid `excceed max block` error. 

So here is a util for generating block range, it need `latest` or the block number you sync last time. 
The reason why not use `eth_getFilterChanges` is that some chain not implement this method.

NOTE: the max block range in Polygon is 1000, in BSC is 5000.

```elixir
iex(6)> Test.USDT.gen_block_range "latest"
{:ok, 46799328,
 %Ethex.Web3.Structs.BlockRange{from_block: 46799308, to_block: 46799328}}
 
iex(7)> Test.USDT.gen_block_range 46798000
{:ok, 46798800,
 %Ethex.Web3.Structs.BlockRange{from_block: 46798000, to_block: 46798800}}
```

If wanna sync logs automatically, using GenServer with loop, and maintaining block range. Here is an example,

```elixir
defmodule Test.USDT do
  @moduledoc false
  use GenServer

  use Ethex.Abi,
    # rpc: "https://data-seed-prebsc-1-s1.bnbchain.org:8545",
    rpc: "https://binance.llamarpc.com",
    abi_path: "./priv/abi/usdt.abi.json",
    contract_address: "0x55d398326f99059fF775485246999027B3197955"

  @loop_interval 10

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    Process.send_after(self(), :loop, :timer.seconds(@loop_interval))
    {:ok, %{last_sync_block: "latest"}}
  end

  @impl true
  def handle_info(:loop, %{last_sync_block: last_sync_block} = _state) do
    {:ok, cur_sync_block, block_range} = gen_block_range(last_sync_block)
    {:ok, _evts} = get_logs_and_decode(block_range)

    # do some other work, such as save events into database
    # Repo.insert evts

    Process.send_after(self(), :loop, :timer.seconds(@loop_interval))
    {:noreply, %{last_sync_block: cur_sync_block}}
  end
end
```