Full Code of hswick/exw3 for AI

master 17074b213e8a cached
37 files
92.2 KB
26.6k tokens
121 symbols
1 requests
Download .txt
Repository: hswick/exw3
Branch: master
Commit: 17074b213e8a
Files: 37
Total size: 92.2 KB

Directory structure:
gitextract_a2glllkx/

├── .formatter.exs
├── .github/
│   ├── dependabot.yml
│   ├── install_parity.sh
│   └── workflows/
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── config/
│   └── config.exs
├── docker-compose.yml
├── lib/
│   ├── exw3/
│   │   ├── abi.ex
│   │   ├── address.ex
│   │   ├── client.ex
│   │   ├── contract.ex
│   │   ├── normalize.ex
│   │   ├── rpc.ex
│   │   └── utils.ex
│   └── exw3.ex
├── mix.exs
├── parity.sh
└── test/
    ├── examples/
    │   ├── build/
    │   │   ├── AddressTester.abi
    │   │   ├── ArrayTester.abi
    │   │   ├── Complex.abi
    │   │   ├── EventTester.abi
    │   │   └── SimpleStorage.abi
    │   └── contracts/
    │       ├── AddressTester.sol
    │       ├── ArrayTester.sol
    │       ├── Complex.sol
    │       ├── EventTester.sol
    │       └── SimpleStorage.sol
    ├── exw3/
    │   ├── abi_test.exs
    │   ├── address_test.exs
    │   ├── client_test.exs
    │   ├── contract_test.exs
    │   ├── rpc_test.exs
    │   └── utils_test.exs
    ├── exw3_test.exs
    └── test_helper.exs

================================================
FILE CONTENTS
================================================

================================================
FILE: .formatter.exs
================================================
# Used by "mix format"
[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: mix
  directory: "/"
  schedule:
    interval: monthly
  open-pull-requests-limit: 10
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: monthly
  open-pull-requests-limit: 10


================================================
FILE: .github/install_parity.sh
================================================
# Install Parity blockchain tests on Github action
echo > passfile # just to be safe

wget https://releases.parity.io/ethereum/v2.7.2/x86_64-unknown-linux-gnu/parity

chmod 755 ./parity
echo > passfile

./parity --chain dev 2>&1 &

PARITY_PID=$!
sleep 10
kill -9 $(lsof -t -i:8545) # cleanup old zombie instances


================================================
FILE: .github/workflows/test.yml
================================================
name: test

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - '*'

jobs:
  test:
    runs-on: ubuntu-latest
    name: Test OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}

    strategy:
      matrix:
        otp: [22.x, 23.x, 24.x]
        elixir: [1.10.x, 1.11.x, 1.12.x]

    steps:
      - uses: actions/checkout@v3

      - uses: erlef/setup-beam@v1.11
        with:
          otp-version: ${{matrix.otp}}
          elixir-version: ${{matrix.elixir}}

      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - name: Cache Dependencies
        uses: actions/cache@v3.0.8
        with:
          path: |
            deps
            _build/dev
            _build/test
          key: elixir-cache-${{secrets.CACHE_VERSION}}-${{matrix.elixir}}-otp-${{matrix.otp}}-${{runner.os}}-${{hashFiles('mix.lock')}}-${{github.ref}}
          restore-keys: |
            elixir-cache-${{secrets.CACHE_VERSION}}-${{matrix.elixir}}-otp-${{matrix.otp}}-${{runner.os}}-${{hashFiles('mix.lock')}}-
            elixir-cache-${{secrets.CACHE_VERSION}}-${{matrix.elixir}}-otp-${{matrix.otp}}-${{runner.os}}-

      - name: Install Dependencies
        run: mix deps.get

      - name: Install Parity Blockchain
        run: .github/install_parity.sh

      - name: Run Parity Blockchain
        run:  ./parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --reseal-min-period 0 --password passfile &

      - name: Test
        run: mix test

      - name: Dialyzer
        run: mix dialyzer --halt-exit-status


================================================
FILE: .gitignore
================================================
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
exw3-*.tar

# Temporary files, for example, from tests.
/tmp/

# Misc.
.env*
passfile
docker/openethereum


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS


================================================
FILE: README.md
================================================
# ExW3

[![Build Status](https://github.com/hswick/exw3/workflows/test/badge.svg?branch=master)](https://github.com/hswick/exw3/actions?query=workflow%3Atest)
[![Module Version](https://img.shields.io/hexpm/v/exw3.svg?style=flat)](https://hex.pm/packages/exw3)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg?style=flat)](https://hexdocs.pm/exw3/)
[![Total Download](https://img.shields.io/hexpm/dt/exw3.svg?style=flat)](https://hex.pm/packages/exw3)
[![License](https://img.shields.io/hexpm/l/exw3.svg?style=flat)](https://github.com/hswick/exw3/blob/master/LICENSE)
[![Last Updated](https://img.shields.io/github/last-commit/hswick/exw3.svg?style=flat)](https://github.com/hswick/exw3/commits/master)

<p align="center">
  <img src="./assets/exw3_logo.jpg"/>
</p>

## Installation

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

```elixir
def deps do
  [
    {:exw3, "~> 0.6"}
  ]
end
```

## Overview

ExW3 is a wrapper around ethereumex to provide a high level, user friendly json rpc api. This library is focused on providing a handy abstraction for working with smart contracts, and any other relevant utilities.

## Usage

Ensure you have an ethereum node to connect to at the specified url in your config. An easy local testnet to use is ganache-cli:

```bash
$ ganache-cli
```

Or you can use parity:
Install Parity, then run it with

```bash
$ echo > passfile
parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --reseal-min-period 0 --password passfile
```

If Parity complains about password or missing account, try

```bash
$ parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72
```

### HTTP

To use Ethereumex's HttpClient simply set your config like this:

```elixir
config :ethereumex,
  client_type: :http,
  url: "http://localhost:8545"
```

### IPC

If you want to use IpcClient set your config to something like this:

```elixir
config :ethereumex,
  client_type: :ipc,
  ipc_path: "/.local/share/io.parity.ethereum/jsonrpc.ipc"
```

Provide an absolute path to the ipc socket provided by whatever Ethereum client you are running. You don't need to include the home directory, as that will be prepended to the path provided.

**NOTE:** Use of IPC is recommended, as it is more secure and significantly faster.

Currently, ExW3 supports a handful of JSON RPC commands. Primarily the ones that get used the most. If ExW3 doesn't provide a specific command, you can always use the [Ethereumex](https://github.com/exthereum/ethereumex) commands.

Check out the [documentation](https://hexdocs.pm/exw3/ExW3.html) for more details of the API.

### Example

```elixir
iex(1)> accounts = ExW3.accounts()
["0x00a329c0648769a73afac7f9381e08fb43dbea72"]
iex(2)> ExW3.balance(Enum.at(accounts, 0))
1606938044258990275541962092341162602522200978938292835291376
iex(3)> ExW3.block_number()
1252
iex(4)> simple_storage_abi = ExW3.Abi.load_abi("test/examples/build/SimpleStorage.abi")
%{
  "get" => %{
    "constant" => true,
    "inputs" => [],
    "name" => "get",
    "outputs" => [%{"name" => "", "type" => "uint256"}],
    "payable" => false,
    "stateMutability" => "view",
    "type" => "function"
  },
  "set" => %{
    "constant" => false,
    "inputs" => [%{"name" => "_data", "type" => "uint256"}],
    "name" => "set",
    "outputs" => [],
    "payable" => false,
    "stateMutability" => "nonpayable",
    "type" => "function"
  }
}
iex(5)> ExW3.Contract.start_link
{:ok, #PID<0.265.0>}
iex(6)> ExW3.Contract.register(:SimpleStorage, abi: simple_storage_abi)
:ok
iex(7)> {:ok, address, tx_hash} = ExW3.Contract.deploy(:SimpleStorage, bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"), options: %{gas: 300_000, from: Enum.at(accounts, 0)})
{:ok, "0x22018c2bb98387a39e864cf784e76cb8971889a5",
 "0x4ea539048c01194476004ef69f407a10628bed64e88ee8f8b17b4d030d0e7cb7"}
iex(8)> ExW3.Contract.at(:SimpleStorage, address)
:ok
iex(9)> ExW3.Contract.call(:SimpleStorage, :get)
{:ok, 0}
iex(10)> ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(accounts, 0), gas: 50_000})
{:ok, "0x88838e84a401a1d6162290a1a765507c4a83f5e050658a83992a912f42149ca5"}
iex(11)> ExW3.Contract.call(:SimpleStorage, :get)
{:ok, 1}
```

## Address Type

If you are familiar with web3.js you may find the way ExW3 handles addresses unintuitive. ExW3's ABI encoder interprets the address type as an uint160. If you are using an address as an option to a transaction like `:from` or `:to` this will work as expected. However, if one of your smart contracts is expecting an address type for an input parameter then you will need to do this:

```elixir
a = ExW3.Utils.hex_to_integer("0x88838e84a401a1d6162290a1a765507c4a83f5e050658a83992a912f42149ca5")
```

## Events

ExW3 allows the retrieval of event logs using filters or transaction receipts. In this example we will demonstrate a filter. Assume we have already deployed and registered a contract called EventTester.

```elixir
# We can optionally specify extra parameters like `:fromBlock`, and `:toBlock`
{:ok, filter_id} = ExW3.Contract.filter(:EventTester, "Simple", %{fromBlock: 42, toBlock: "latest"})

# After some point that we think there are some new changes
{:ok, changes} = ExW3.Contract.get_filter_changes(filter_id)

# We can then uninstall the filter after we are done using it
ExW3.Contract.uninstall_filter(filter_id)
```

## Indexed Events

Ethereum allows a user to add topics to filters. This means the filter will only return events with the specific index parameters. For all of the extra options see [here](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter)

If you have written your event in Solidity like this:

```
event SimpleIndex(uint256 indexed num, bytes32 indexed data, uint256 otherNum);
```

You can add a filter on which logs will be returned back to the RPC client based on the indexed fields.

ExW3 allows for 2 ways of specifying these parameters (`:topics`) in two ways. The first, and probably more preferred way, is with a map:

```elixir
indexed_filter_id = ExW3.Contract.filter(
  :EventTester,
  "SimpleIndex",
  %{
    topics: %{num: 46, data: "Hello, World!"},
  }
)
```

The other option is a list (mapped version is an abstraction over this). The downside here is this is order dependent. Any values you don't want to specify must be represented with a `nil`. This approach has been included because it is the implementation of the JSON RPC spec.

```elixir
indexed_filter_id = ExW3.Contract.filter(
  :EventTester,
  "SimpleIndex",
  %{
    topics: [nil, "Hello, World!"]
  }
)
```

Here we are skipping the `num` topic, and only filtering on the `data` parameter.

NOTE!!! These two approaches are mutually exclusive, and for almost all cases you should prefer the map.

## Continuous Event Handling

In many cases, you will want some process to continuously listen for events. We can implement this functionality using a recursive function. Since Elixir uses tail call optimization, we won't have to worry about blowing up the stack.

```elixir
def listen_for_event do
  {:ok, changes} = ExW3.Contract.get_filter_changes(filter_id) # Get our changes from the blockchain
  handle_changes(changes) # Some function to deal with the data. Good place to use pattern matching.
  :timer.sleep(1000) # Some delay in milliseconds. Recommended to save bandwidth, and not spam.
  listen_for_event() # Recurse
end
```

# Compiling Solidity

To compile the test solidity contracts after making a change run this command:

```bash
$ solc --abi --bin --overwrite -o test/examples/build test/examples/contracts/*.sol
```

# Contributing

## Test

The full test suite requires a running blockchain. You can run your own or start `openethereum` with `docker-compose`.

```bash
$ docker-compose up
$ mix test
```

## Copyright and License

Copyright (c) 2018 Harley Swick

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: config/config.exs
================================================
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for third-
# party users, it should be done in your mix.exs file.

# Sample configuration:
#
#     config :logger,
#       level: :info
#
#     config :logger, :console,
#       format: "$date $time [$level] $metadata$message\n",
#       metadata: [:user_id]

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
#     import_config "#{Mix.env}.exs"
config :ethereumex,
  client_type: :ipc,
  url: "http://localhost:8545",
  ipc_path:
    System.get_env(
      "IPC_PATH",
      "#{System.user_home!()}/.local/share/io.parity.ethereum/jsonrpc.ipc"
    )


================================================
FILE: docker-compose.yml
================================================
version: '3.8'

services:
  openethereum:
    image: openethereum/openethereum:v3.3.0
    command: '--chain=dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --password=/home/openethereum/.local/share/openethereum/passfile --jsonrpc-interface=0.0.0.0'
    ports:
      - '8545:8545'
      - '8546:8546'
    volumes:
      - ./docker:/home/openethereum/.local/share


================================================
FILE: lib/exw3/abi.ex
================================================
defmodule ExW3.Abi do
  @doc "Decodes event based on given data and provided signature"
  @spec decode_event(binary(), binary()) :: any()
  def decode_event(data, signature) do
    formatted_data =
      data
      |> String.slice(2..-1)
      |> Base.decode16!(case: :lower)

    fs = ABI.FunctionSelector.decode(signature)

    ABI.TypeDecoder.decode(formatted_data, fs)
  end

  @doc "Loads the abi at the file path and reformats it to a map"
  @spec load_abi(binary()) :: list() | {:error, atom()}
  def load_abi(file_path) do
    with {:ok, cwd} <- File.cwd(),
         {:ok, abi} <- File.read(Path.join([cwd, file_path])) do
      reformat_abi(Jason.decode!(abi))
    end
  end

  @doc "Loads the bin ar the file path"
  @spec load_bin(binary()) :: binary()
  def load_bin(file_path) do
    with {:ok, cwd} <- File.cwd(),
         {:ok, bin} <- File.read(Path.join([cwd, file_path])) do
      bin
    end
  end

  @doc "Decodes data based on given type signature"
  @spec decode_data(binary(), binary()) :: any()
  def decode_data(types_signature, data) do
    {:ok, trim_data} = String.slice(data, 2..String.length(data)) |> Base.decode16(case: :lower)

    ABI.decode(types_signature, trim_data) |> List.first()
  end

  @doc "Decodes output based on specified functions return signature"
  @spec decode_output(map(), binary(), binary()) :: list()
  def decode_output(abi, name, output) do
    {:ok, trim_output} =
      String.slice(output, 2..String.length(output)) |> Base.decode16(case: :lower)

    output_types = Enum.map(abi[name]["outputs"], fn x -> x["type"] end)
    types_signature = Enum.join(["(", Enum.join(output_types, ","), ")"])
    output_signature = "#{name}(#{types_signature})"

    outputs =
      ABI.decode(output_signature, trim_output)
      |> List.first()
      |> Tuple.to_list()

    outputs
  end

  @doc "Returns the type signature of a given function"
  @spec types_signature(map(), binary()) :: binary()
  def types_signature(abi, name) do
    input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end)
    types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])
    types_signature
  end

  @doc "Returns the 4 character method id based on the hash of the method signature"
  @spec method_signature(map(), binary()) :: binary()
  def method_signature(abi, name) do
    if abi[name] do
      input_signature = ExKeccak.hash_256("#{name}#{types_signature(abi, name)}")

      # Take first four bytes
      <<init::binary-size(4), _rest::binary>> = input_signature
      init
    else
      raise "#{name} method not found in the given abi"
    end
  end

  @doc "Encodes data into Ethereum hex string based on types signature"
  @spec encode_data(binary(), list()) :: binary()
  def encode_data(types_signature, data) do
    ABI.TypeEncoder.encode_raw(
      [List.to_tuple(data)],
      ABI.FunctionSelector.decode_raw(types_signature)
    )
  end

  @doc "Encodes list of options and returns them as a map"
  @spec encode_options(map(), list()) :: map()
  def encode_options(options, keys) do
    keys
    |> Enum.filter(fn option ->
      Map.has_key?(options, option)
    end)
    |> Enum.map(fn option ->
      {option, encode_option(options[option])}
    end)
    |> Enum.into(%{})
  end

  @doc "Encodes options into Ethereum JSON RPC hex string"
  @spec encode_option(integer()) :: binary()
  def encode_option(0), do: "0x0"

  def encode_option(nil), do: nil

  def encode_option(value) do
    "0x" <>
      (value
       |> :binary.encode_unsigned()
       |> Base.encode16(case: :lower)
       |> String.trim_leading("0"))
  end

  @doc "Encodes data and appends it to the encoded method id"
  @spec encode_method_call(map(), binary(), list()) :: binary()
  def encode_method_call(abi, name, input) do
    encoded_method_call =
      method_signature(abi, name) <> encode_data(types_signature(abi, name), input)

    encoded_method_call |> Base.encode16(case: :lower)
  end

  @doc "Encodes input from a method call based on function signature"
  @spec encode_input(map(), binary(), list()) :: binary()
  def encode_input(abi, name, input) do
    if abi[name]["inputs"] do
      input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end)
      types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])
      input_signature = ExKeccak.hash_256("#{name}#{types_signature}")

      # Take first four bytes
      <<init::binary-size(4), _rest::binary>> = input_signature

      encoded_input =
        init <>
          ABI.TypeEncoder.encode_raw(
            [List.to_tuple(input)],
            ABI.FunctionSelector.decode_raw(types_signature)
          )

      encoded_input |> Base.encode16(case: :lower)
    else
      raise "#{name} method not found with the given abi"
    end
  end

  defp reformat_abi(abi) do
    abi
    |> Enum.map(&map_abi/1)
    |> Map.new()
  end

  defp map_abi(x) do
    case {x["name"], x["type"]} do
      {nil, "constructor"} -> {:constructor, x}
      {nil, "fallback"} -> {:fallback, x}
      {name, _} -> {name, x}
    end
  end
end


================================================
FILE: lib/exw3/address.ex
================================================
defmodule ExW3.Address do
  @type t :: %__MODULE__{bytes: binary}

  defstruct ~w[bytes]a

  @spec from_bytes(binary) :: t
  def from_bytes(bytes) do
    %__MODULE__{bytes: bytes}
  end

  @spec from_hex(String.t()) :: t
  def from_hex(address) do
    case address do
      "0x" <> a ->
        from_hex(a)

      a ->
        bytes = a |> String.downcase() |> Base.decode16!(case: :lower)
        %__MODULE__{bytes: bytes}
    end
  end

  @spec to_bytes(t) :: binary
  def to_bytes(%__MODULE__{bytes: bytes}) do
    bytes
  end

  @spec to_string(t) :: String.t()
  def to_string(%__MODULE__{bytes: bytes}) do
    Base.encode16(bytes, case: :lower)
  end

  @spec to_hex(t) :: String.t()
  def to_hex(%__MODULE__{} = address) do
    "0x#{__MODULE__.to_string(address)}"
  end

  @spec to_checksum(t) :: String.t()
  def to_checksum(%__MODULE__{} = address) do
    address = address |> __MODULE__.to_string()
    address_hash = address |> ExKeccak.hash_256() |> Base.encode16(case: :lower)

    keccak_hash_list =
      address_hash
      |> String.split("", trim: true)
      |> Enum.map(fn x -> elem(Integer.parse(x, 16), 0) end)

    list_arr =
      for n <- 0..(String.length(address) - 1) do
        number = Enum.at(keccak_hash_list, n)

        cond do
          number >= 8 -> String.upcase(String.at(address, n))
          true -> String.downcase(String.at(address, n))
        end
      end

    "0x" <> List.to_string(list_arr)
  end

  @spec is_valid_checksum?(String.t()) :: boolean
  def is_valid_checksum?(hex_address) do
    address = hex_address |> __MODULE__.from_hex()
    __MODULE__.to_checksum(address) == hex_address
  end
end



================================================
FILE: lib/exw3/client.ex
================================================
defmodule ExW3.Client do
  @type argument :: term
  @type request_error :: Ethereumex.Client.Behaviour.error()
  @type error :: {:error, :invalid_client_type} | request_error

  @spec call_client(atom) :: {:ok, term} | error
  @spec call_client(atom, [argument]) :: {:ok, term} | error
  def call_client(method_name, arguments \\ []) do
    url_opt = extract_url_opt(arguments)

    case client_type(url_opt) do
      :http -> apply(Ethereumex.HttpClient, method_name, arguments)
      :ipc -> apply(Ethereumex.IpcClient, method_name, arguments)
      _ -> {:error, :invalid_client_type}
    end
  end

  defp extract_url_opt(arguments) do
    arguments
    |> List.last()
    |> case do
      last when is_list(last) -> Keyword.get(last, :url)
      _ -> nil
    end
  end

  defp client_type(nil), do: Application.get_env(:ethereumex, :client_type, :http)
  defp client_type("http://" <> _), do: :http
  defp client_type("https://" <> _), do: :http
  defp client_type(_), do: :invalid
end


================================================
FILE: lib/exw3/contract.ex
================================================
defmodule ExW3.Contract do
  use GenServer

  @doc "Begins the Contract process to manage all interactions with smart contracts"
  @spec start_link() :: {:ok, pid()}
  def start_link(_ \\ :ok) do
    GenServer.start_link(__MODULE__, %{filters: %{}}, name: ContractManager)
  end

  @doc "Deploys contracts with given arguments"
  @spec deploy(atom(), list()) :: {:ok, binary(), binary()}
  def deploy(name, args) do
    GenServer.call(ContractManager, {:deploy, {name, args}})
  end

  @doc "Registers the contract with the ContractManager process. Only :abi is required field."
  @spec register(atom(), list()) :: :ok
  def register(name, contract_info) do
    GenServer.cast(ContractManager, {:register, {name, contract_info}})
  end

  @doc "Uninstalls the filter, and deletes the data associated with the filter id"
  @spec uninstall_filter(binary()) :: :ok
  def uninstall_filter(filter_id) do
    GenServer.cast(ContractManager, {:uninstall_filter, filter_id})
  end

  @doc "Sets the address for the contract specified by the name argument"
  @spec at(atom(), binary()) :: :ok
  def at(name, address) do
    GenServer.cast(ContractManager, {:at, {name, address}})
  end

  @doc "Returns the current Contract GenServer's address"
  @spec address(atom()) :: {:ok, binary()}
  def address(name) do
    GenServer.call(ContractManager, {:address, name})
  end

  @doc "Use a Contract's method with an eth_call"
  @spec call(atom(), atom(), list(), any()) :: {:ok, any()}
  def call(contract_name, method_name, args \\ [], timeout \\ :infinity) do
    GenServer.call(ContractManager, {:call, {contract_name, method_name, args}}, timeout)
  end

  @doc "Use a Contract's method with an eth_sendTransaction"
  @spec send(atom(), atom(), list(), map()) :: {:ok, binary()}
  def send(contract_name, method_name, args, options) do
    GenServer.call(ContractManager, {:send, {contract_name, method_name, args, options}})
  end

  @doc "Returns a formatted transaction receipt for the given transaction hash(id)"
  @spec tx_receipt(atom(), binary()) :: map()
  def tx_receipt(contract_name, tx_hash) do
    GenServer.call(ContractManager, {:tx_receipt, {contract_name, tx_hash}})
  end

  @doc "Installs a filter on the Ethereum node. This also formats the parameters, and saves relevant information to format event logs."
  @spec filter(atom(), binary(), map()) :: {:ok, binary()}
  def filter(contract_name, event_name, event_data \\ %{}) do
    GenServer.call(
      ContractManager,
      {:filter, {contract_name, event_name, event_data}}
    )
  end

  @doc "Using saved information related to the filter id, event logs are formatted properly"
  @spec get_filter_changes(binary()) :: {:ok, list()}
  def get_filter_changes(filter_id) do
    GenServer.call(
      ContractManager,
      {:get_filter_changes, filter_id}
    )
  end

  def init(state) do
    {:ok, state}
  end

  defp data_signature_helper(name, fields) do
    non_indexed_types = Enum.map(fields, &Map.get(&1, "type"))
    Enum.join([name, "(", Enum.join(non_indexed_types, ","), ")"])
  end

  defp topic_types_helper(fields) do
    if length(fields) > 0 do
      Enum.map(fields, fn field ->
        "(#{field["type"]})"
      end)
    else
      []
    end
  end

  defp init_events(abi) do
    events =
      Enum.filter(abi, fn {_, v} ->
        v["type"] == "event"
      end)

    names_and_signature_types_map =
      Enum.map(events, fn {name, v} ->
        types = Enum.map(v["inputs"], &Map.get(&1, "type"))
        signature = Enum.join([name, "(", Enum.join(types, ","), ")"])
        encoded_event_signature = ExW3.Utils.keccak256(signature)

        indexed_fields =
          Enum.filter(v["inputs"], fn input ->
            input["indexed"]
          end)

        indexed_names =
          Enum.map(indexed_fields, fn field ->
            field["name"]
          end)

        non_indexed_fields =
          Enum.filter(v["inputs"], fn input ->
            !input["indexed"]
          end)

        non_indexed_names =
          Enum.map(non_indexed_fields, fn field ->
            field["name"]
          end)

        data_signature = data_signature_helper(name, non_indexed_fields)

        event_attributes = %{
          signature: data_signature,
          non_indexed_names: non_indexed_names,
          topic_types: topic_types_helper(indexed_fields),
          topic_names: indexed_names
        }

        {{encoded_event_signature, event_attributes}, {name, encoded_event_signature}}
      end)

    signature_types_map =
      Enum.map(names_and_signature_types_map, fn {signature_types, _} ->
        signature_types
      end)

    names_map =
      Enum.map(names_and_signature_types_map, fn {_, names} ->
        names
      end)

    [
      events: Enum.into(signature_types_map, %{}),
      event_names: Enum.into(names_map, %{})
    ]
  end

  def deploy_helper(bin, abi, args) do
    constructor_arg_data =
      if arguments = args[:args] do
        constructor_abi =
          Enum.find(abi, fn {_, v} ->
            v["type"] == "constructor"
          end)

        if constructor_abi do
          {_, constructor} = constructor_abi
          input_types = Enum.map(constructor["inputs"], fn x -> x["type"] end)
          types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])

          arg_count = Enum.count(arguments)
          input_types_count = Enum.count(input_types)

          if input_types_count != arg_count do
            raise "Number of provided arguments to constructor is incorrect. Was given #{
                    arg_count
                  } args, looking for #{input_types_count}."
          end

          bin <>
            (ExW3.Abi.encode_data(types_signature, arguments) |> Base.encode16(case: :lower))
        else
          # IO.warn("Could not find a constructor")
          bin
        end
      else
        bin
      end

    gas = ExW3.Abi.encode_option(args[:options][:gas])
    gasPrice = ExW3.Abi.encode_option(args[:options][:gas_price])

    tx = %{
      from: args[:options][:from],
      data: "0x#{constructor_arg_data}",
      gas: gas,
      gasPrice: gasPrice
    }

    {:ok, tx_hash} = ExW3.Rpc.eth_send([tx])
    {:ok, tx_receipt} = ExW3.Rpc.tx_receipt(tx_hash)

    {tx_receipt["contractAddress"], tx_hash}
  end

  def eth_call_helper(address, abi, method_name, args) do
    result =
      ExW3.Rpc.eth_call([
        %{
          to: address,
          data: "0x#{ExW3.Abi.encode_method_call(abi, method_name, args)}"
        }
      ])

    case result do
      {:ok, data} ->
        ([:ok] ++ ExW3.Abi.decode_output(abi, method_name, data)) |> List.to_tuple()

      {:error, err} ->
        {:error, err}
    end
  end

  def eth_send_helper(address, abi, method_name, args, options) do
    encoded_options =
      ExW3.Abi.encode_options(
        options,
        [:gas, :gasPrice, :value, :nonce]
      )

    gas = ExW3.Abi.encode_option(args[:options][:gas])
    gasPrice = ExW3.Abi.encode_option(args[:options][:gas_price])

    ExW3.Rpc.eth_send([
      Map.merge(
        %{
          to: address,
          data: "0x#{ExW3.Abi.encode_method_call(abi, method_name, args)}",
          gas: gas,
          gasPrice: gasPrice
        },
        Map.merge(options, encoded_options)
      )
    ])
  end

  defp register_helper(contract_info) do
    if contract_info[:abi] do
      contract_info ++ init_events(contract_info[:abi])
    else
      raise "ABI not provided upon initialization"
    end
  end

  # Options' checkers

  defp check_option(nil, error_atom), do: {:error, error_atom}
  defp check_option([], error_atom), do: {:error, error_atom}
  defp check_option([head | _tail], _atom) when head != nil, do: {:ok, head}
  defp check_option([_head | tail], atom), do: check_option(tail, atom)
  defp check_option(value, _atom), do: {:ok, value}

  # Casts

  def handle_cast({:at, {name, address}}, state) do
    contract_state = state[name]
    contract_state = Keyword.put(contract_state, :address, address)
    state = Map.put(state, name, contract_state)
    {:noreply, state}
  end

  def handle_cast({:register, {name, contract_info}}, state) do
    {:noreply, Map.put(state, name, register_helper(contract_info))}
  end

  def handle_cast({:uninstall_filter, filter_id}, state) do
    ExW3.uninstall_filter(filter_id)
    {:noreply, Map.put(state, :filters, Map.delete(state[:filters], filter_id))}
  end

  # Calls

  defp filter_topics_helper(event_signature, event_data, topic_types, topic_names) do
    topics =
      if is_map(event_data[:topics]) do
        Enum.map(topic_names, fn name ->
          event_data[:topics][String.to_atom(name)]
        end)
      else
        event_data[:topics]
      end

    if topics do
      formatted_topics =
        Enum.map(0..(length(topics) - 1), fn i ->
          topic = Enum.at(topics, i)

          if topic do
            if is_list(topic) do
              topic_type = Enum.at(topic_types, i)

              Enum.map(topic, fn t ->
                "0x" <> (ExW3.Abi.encode_data(topic_type, [t]) |> Base.encode16(case: :lower))
              end)
            else
              topic_type = Enum.at(topic_types, i)
              "0x" <> (ExW3.Abi.encode_data(topic_type, [topic]) |> Base.encode16(case: :lower))
            end
          else
            topic
          end
        end)

      [event_signature] ++ formatted_topics
    else
      [event_signature]
    end
  end

  def from_block_helper(event_data) do
    if event_data[:fromBlock] do
      new_from_block =
        if Enum.member?(["latest", "earliest", "pending"], event_data[:fromBlock]) do
          event_data[:fromBlock]
        else
          ExW3.Abi.encode_data("(uint256)", [event_data[:fromBlock]])
        end

      Map.put(event_data, :fromBlock, new_from_block)
    else
      event_data
    end
  end

  defp param_helper(event_data, key) do
    if event_data[key] do
      new_param =
        if Enum.member?(["latest", "earliest", "pending"], event_data[key]) do
          event_data[key]
        else
          "0x" <>
            (ExW3.Abi.encode_data("(uint256)", [event_data[key]])
             |> Base.encode16(case: :lower))
        end

      Map.put(event_data, key, new_param)
    else
      event_data
    end
  end

  defp event_data_format_helper(event_data) do
    event_data
    |> param_helper(:fromBlock)
    |> param_helper(:toBlock)
    |> Map.delete(:topics)
  end

  def get_event_attributes(state, contract_name, event_name) do
    contract_info = state[contract_name]
    contract_info[:events][contract_info[:event_names][event_name]]
  end

  defp extract_non_indexed_fields(data, names, signature) do
    Enum.zip(names, ExW3.Abi.decode_event(data, signature)) |> Enum.into(%{})
  end

  defp format_log_data(log, event_attributes) do
    non_indexed_fields =
      extract_non_indexed_fields(
        Map.get(log, "data"),
        event_attributes[:non_indexed_names],
        event_attributes[:signature]
      )

    indexed_fields =
      if length(log["topics"]) > 1 do
        [_head | tail] = log["topics"]

        decoded_topics =
          Enum.map(0..(length(tail) - 1), fn i ->
            topic_type = Enum.at(event_attributes[:topic_types], i)
            topic_data = Enum.at(tail, i)

            {decoded} = ExW3.Abi.decode_data(topic_type, topic_data)

            decoded
          end)

        Enum.zip(event_attributes[:topic_names], decoded_topics) |> Enum.into(%{})
      else
        %{}
      end

    new_data = Map.merge(indexed_fields, non_indexed_fields)

    Map.put(log, "data", new_data)
  end

  def handle_call({:filter, {contract_name, event_name, event_data}}, _from, state) do
    contract_info = state[contract_name]

    event_signature = contract_info[:event_names][event_name]
    topic_types = contract_info[:events][event_signature][:topic_types]
    topic_names = contract_info[:events][event_signature][:topic_names]

    topics = filter_topics_helper(event_signature, event_data, topic_types, topic_names)

    payload =
      Map.merge(
        %{address: contract_info[:address], topics: topics},
        event_data_format_helper(event_data)
      )

    filter_id = ExW3.Rpc.new_filter(payload)

    {:reply, {:ok, filter_id},
     Map.put(
       state,
       :filters,
       Map.put(state[:filters], filter_id, %{
         contract_name: contract_name,
         event_name: event_name
       })
     )}
  end

  def handle_call({:get_filter_changes, filter_id}, _from, state) do
    filter_info = Map.get(state[:filters], filter_id)

    event_attributes =
      get_event_attributes(state, filter_info[:contract_name], filter_info[:event_name])

    logs = ExW3.Rpc.get_filter_changes(filter_id)

    formatted_logs =
      if logs != [] do
        Enum.map(logs, fn log ->
          formatted_log =
            Enum.reduce(
              [
                ExW3.Normalize.transform_to_integer(log, [
                  "blockNumber",
                  "logIndex",
                  "transactionIndex"
                ]),
                format_log_data(log, event_attributes)
              ],
              &Map.merge/2
            )

          formatted_log
        end)
      else
        logs
      end

    {:reply, {:ok, formatted_logs}, state}
  end

  def handle_call({:deploy, {name, args}}, _from, state) do
    contract_info = state[name]

    with {:ok, _} <- check_option(args[:options][:from], :missing_sender),
         {:ok, _} <- check_option(args[:options][:gas], :missing_gas),
         {:ok, bin} <- check_option([state[:bin], args[:bin]], :missing_binary) do
      {contract_addr, tx_hash} = deploy_helper(bin, contract_info[:abi], args)
      result = {:ok, contract_addr, tx_hash}
      {:reply, result, state}
    else
      err -> {:reply, err, state}
    end
  end

  def handle_call({:address, name}, _from, state) do
    {:reply, state[name][:address], state}
  end

  def handle_call({:call, {contract_name, method_name, args}}, _from, state) do
    contract_info = state[contract_name]

    with {:ok, address} <- check_option(contract_info[:address], :missing_address) do
      result = eth_call_helper(address, contract_info[:abi], Atom.to_string(method_name), args)
      {:reply, result, state}
    else
      err -> {:reply, err, state}
    end
  end

  def handle_call({:send, {contract_name, method_name, args, options}}, _from, state) do
    contract_info = state[contract_name]

    with {:ok, address} <- check_option(contract_info[:address], :missing_address),
         {:ok, _} <- check_option(options[:from], :missing_sender),
         {:ok, _} <- check_option(options[:gas], :missing_gas) do
      result =
        eth_send_helper(
          address,
          contract_info[:abi],
          Atom.to_string(method_name),
          args,
          options
        )

      {:reply, result, state}
    else
      err -> {:reply, err, state}
    end
  end

  def handle_call({:tx_receipt, {contract_name, tx_hash}}, _from, state) do
    contract_info = state[contract_name]

    {:ok, receipt} = ExW3.tx_receipt(tx_hash)

    events = contract_info[:events]
    logs = receipt["logs"]

    formatted_logs =
      Enum.map(logs, fn log ->
        topic = Enum.at(log["topics"], 0)
        event_attributes = Map.get(events, topic)

        if event_attributes do
          non_indexed_fields =
            Enum.zip(
              event_attributes[:non_indexed_names],
              ExW3.Abi.decode_event(log["data"], event_attributes[:signature])
            )
            |> Enum.into(%{})

          if length(log["topics"]) > 1 do
            [_head | tail] = log["topics"]

            decoded_topics =
              Enum.map(0..(length(tail) - 1), fn i ->
                topic_type = Enum.at(event_attributes[:topic_types], i)
                topic_data = Enum.at(tail, i)

                {decoded} = ExW3.Abi.decode_data(topic_type, topic_data)

                decoded
              end)

            indexed_fields =
              Enum.zip(event_attributes[:topic_names], decoded_topics) |> Enum.into(%{})

            Map.merge(indexed_fields, non_indexed_fields)
          else
            non_indexed_fields
          end
        else
          nil
        end
      end)

    {:reply, {:ok, {receipt, formatted_logs}}, state}
  end
end


================================================
FILE: lib/exw3/normalize.ex
================================================
defmodule ExW3.Normalize do
  @spec transform_to_integer(map(), list()) :: map()
  def transform_to_integer(map, keys) do
    for k <- keys, into: %{} do
      {:ok, v} = map |> Map.get(k) |> ExW3.Utils.hex_to_integer()
      {k, v}
    end
  end
end


================================================
FILE: lib/exw3/rpc.ex
================================================
defmodule ExW3.Rpc do
  import ExW3.Client

  @type invalid_hex_string_error :: ExW3.Utils.invalid_hex_string_error()
  @type request_error :: Ethereumex.Client.Behaviour.error()
  @type opts :: {:url, String.t()}
  @type hex_block_number :: String.t()
  @type latest :: String.t()
  @type earliest :: String.t()
  @type pending :: String.t()

  @doc "returns all available accounts"
  @spec accounts() :: list()
  @spec accounts([opts]) :: list()
  def accounts(opts \\ []) do
    case call_client(:eth_accounts, [opts]) do
      {:ok, accounts} -> accounts
      err -> err
    end
  end

  @doc "Returns the current block number"
  @spec block_number() :: {:ok, non_neg_integer} | {:error, ExW3.Utils.invalid_hex_string()}
  @spec block_number([opts]) :: {:ok, non_neg_integer} | {:error, ExW3.Utils.invalid_hex_string()}
  def block_number(opts \\ []) do
    case call_client(:eth_block_number, [opts]) do
      {:ok, hex_block_number} -> ExW3.Utils.hex_to_integer(hex_block_number)
      err -> err
    end
  end

  @doc "Returns current balance of account"
  @spec balance(binary()) :: integer() | {:error, any()}
  @spec balance(binary(), [opts]) :: integer() | {:error, any()}
  def balance(account, opts \\ []) do
    case call_client(:eth_get_balance, [account, "latest", opts]) do
      {:ok, hex_balance} ->
        {:ok, balance} = ExW3.Utils.hex_to_integer(hex_balance)
        balance

      err ->
        err
    end
  end

  @doc "Returns transaction receipt for specified transaction hash(id)"
  @spec tx_receipt(binary()) :: {:ok, map()} | {:error, any()}
  def tx_receipt(tx_hash) do
    case call_client(:eth_get_transaction_receipt, [tx_hash]) do
      {:ok, nil} ->
        {:error, :not_mined}

      {:ok, receipt} ->
        normalized_receipt =
          ExW3.Normalize.transform_to_integer(receipt, ~w(blockNumber cumulativeGasUsed gasUsed))

        {:ok, Map.merge(receipt, normalized_receipt)}

      err ->
        {:error, err}
    end
  end

  @doc "Returns block data for specified block number"
  @spec block(integer()) :: any() | {:error, any()}
  def block(block_number) do
    case call_client(:eth_get_block_by_number, [block_number, true]) do
      {:ok, block} -> block
      err -> err
    end
  end

  @doc "Creates a new filter, returns filter id. For more sophisticated use, prefer ExW3.Contract.filter."
  @spec new_filter(map()) :: binary() | {:error, any()}
  def new_filter(map) do
    case call_client(:eth_new_filter, [map]) do
      {:ok, filter_id} -> filter_id
      err -> err
    end
  end

  @doc "Gets event changes (logs) by filter. Unlike ExW3.Contract.get_filter_changes it does not return the data in a formatted way"
  @spec get_filter_changes(binary()) :: any()
  def get_filter_changes(filter_id) do
    case call_client(:eth_get_filter_changes, [filter_id]) do
      {:ok, changes} -> changes
      err -> err
    end
  end

  @type log_filter :: %{
          optional(:address) => String.t(),
          optional(:fromBlock) => hex_block_number | latest | earliest | pending,
          optional(:toBlock) => hex_block_number | latest | earliest | pending,
          optional(:topics) => [String.t()],
          optional(:blockhash) => String.t()
        }

  @spec get_logs(log_filter, [opts]) :: {:ok, list} | {:error, term} | request_error
  def get_logs(filter, opts \\ []) do
    with {:ok, _} = result <- call_client(:eth_get_logs, [filter, opts]) do
      result
    else
      err -> err
    end
  end

  @doc "Uninstalls filter from the ethereum node"
  @spec uninstall_filter(binary()) :: boolean() | {:error, any()}
  def uninstall_filter(filter_id) do
    case call_client(:eth_uninstall_filter, [filter_id]) do
      {:ok, result} -> result
      err -> err
    end
  end

  @doc "Mines number of blocks specified. Default is 1"
  @spec mine(integer()) :: any() | {:error, any()}
  def mine(num_blocks \\ 1) do
    for _ <- 0..(num_blocks - 1) do
      call_client(:request, ["evm_mine", [], []])
    end
  end

  @doc "Using the personal api, returns list of accounts."
  @spec personal_list_accounts(list()) :: {:ok, list()} | {:error, any()}
  def personal_list_accounts(opts \\ []) do
    call_client(:request, ["personal_listAccounts", [], opts])
  end

  @doc "Using the personal api, this method creates a new account with the passphrase, and returns new account address."
  @spec personal_new_account(binary(), list()) :: {:ok, binary()} | {:error, any()}
  def personal_new_account(password, opts \\ []) do
    call_client(:request, ["personal_newAccount", [password], opts])
  end

  @doc "Using the personal api, this method unlocks account using the passphrase provided, and returns a boolean."
  @spec personal_unlock_account(binary(), list()) :: {:ok, boolean()} | {:error, any()}
  ### E.g. ExW3.personal_unlock_account(["0x1234","Password",30], [])
  def personal_unlock_account(params, opts \\ []) do
    call_client(:request, ["personal_unlockAccount", params, opts])
  end

  @doc "Using the personal api, this method sends a transaction and signs it in one call, and returns a transaction id hash."
  @spec personal_send_transaction(map(), binary(), list()) :: {:ok, binary()} | {:error, any()}
  def personal_send_transaction(param_map, passphrase, opts \\ []) do
    call_client(:request, ["personal_sendTransaction", [param_map, passphrase], opts])
  end

  @doc "Using the personal api, this method signs a transaction, and returns the signed transaction."
  @spec personal_sign_transaction(map(), binary(), list()) :: {:ok, map()} | {:error, any()}
  def personal_sign_transaction(param_map, passphrase, opts \\ []) do
    call_client(:request, ["personal_signTransaction", [param_map, passphrase], opts])
  end

  @doc "Using the personal api, this method calculates an Ethereum specific signature, and returns that signature."
  @spec personal_sign(binary(), binary(), binary(), list()) :: {:ok, binary()} | {:error, any()}
  def personal_sign(data, address, passphrase, opts \\ []) do
    call_client(:request, ["personal_sign", [data, address, passphrase], opts])
  end

  @doc "Using the personal api, this method returns the address associated with the private key that was used to calculate the signature with personal_sign."
  @spec personal_ec_recover(binary(), binary(), []) :: {:ok, binary()} | {:error, any()}
  def personal_ec_recover(data0, data1, opts \\ []) do
    call_client(:request, ["personal_ecRecover", [data0, data1], opts])
  end

  @doc "Calculates an Ethereum specific signature and signs the data provided, using the accounts private key"
  @spec eth_sign(binary(), binary(), list()) :: {:ok, binary()} | {:error, any()}
  def eth_sign(data0, data1, opts \\ []) do
    call_client(:request, ["eth_sign", [data0, data1], opts])
  end

  @doc "Simple eth_call to client. Recommended to use ExW3.Contract.call instead."
  @spec eth_call(list()) :: any()
  def eth_call(arguments) do
    call_client(:eth_call, arguments)
  end

  @doc "Simple eth_send_transaction. Recommended to use ExW3.Contract.send instead."
  @spec eth_send(list()) :: any()
  def eth_send(arguments) do
    call_client(:eth_send_transaction, arguments)
  end
end


================================================
FILE: lib/exw3/utils.ex
================================================
defmodule ExW3.Utils do
  alias ExW3.Address

  @type invalid_hex_string :: :invalid_hex_string
  @type negative_integer :: :negative_integer
  @type non_integer :: :non_integer
  @type eth_hex :: String.t()

  @doc "Convert eth hex string to integer"
  @spec hex_to_integer(eth_hex) ::
          {:ok, non_neg_integer} | {:error, invalid_hex_string}
  def hex_to_integer(hex) do
    case hex do
      "0x" <> hex -> {:ok, String.to_integer(hex, 16)}
      _ -> {:error, :invalid_hex_string}
    end
  rescue
    ArgumentError ->
      {:error, :invalid_hex_string}
  end

  @doc "Convert an integer to eth hex string"
  @spec integer_to_hex(non_neg_integer) ::
          {:ok, eth_hex} | {:error, negative_integer | non_integer}
  def integer_to_hex(i) do
    case i do
      i when i < 0 -> {:error, :negative_integer}
      i -> {:ok, "0x" <> Integer.to_string(i, 16)}
    end
  rescue
    ArgumentError ->
      {:error, :non_integer}
  end

  @doc "Returns a 0x prepended 32 byte hash of the input string"
  @spec keccak256(String.t()) :: String.t()
  def keccak256(str) do
    "0x#{str |> ExKeccak.hash_256() |> Base.encode16(case: :lower)}"
  end

  @unit_map %{
    :noether => 0,
    :wei => 1,
    :kwei => 1_000,
    :Kwei => 1_000,
    :babbage => 1_000,
    :femtoether => 1_000,
    :mwei => 1_000_000,
    :Mwei => 1_000_000,
    :lovelace => 1_000_000,
    :picoether => 1_000_000,
    :gwei => 1_000_000_000,
    :Gwei => 1_000_000_000,
    :shannon => 1_000_000_000,
    :nanoether => 1_000_000_000,
    :nano => 1_000_000_000,
    :szabo => 1_000_000_000_000,
    :microether => 1_000_000_000_000,
    :micro => 1_000_000_000_000,
    :finney => 1_000_000_000_000_000,
    :milliether => 1_000_000_000_000_000,
    :milli => 1_000_000_000_000_000,
    :ether => 1_000_000_000_000_000_000,
    :kether => 1_000_000_000_000_000_000_000,
    :grand => 1_000_000_000_000_000_000_000,
    :mether => 1_000_000_000_000_000_000_000_000,
    :gether => 1_000_000_000_000_000_000_000_000_000,
    :tether => 1_000_000_000_000_000_000_000_000_000_000
  }

  @doc "Converts the value to whatever unit key is provided. See unit map for details."
  @spec to_wei(integer, atom) :: integer
  def to_wei(num, key) do
    if @unit_map[key] do
      num * @unit_map[key]
    else
      throw("#{key} not valid unit")
    end
  end

  @doc "Converts the value to whatever unit key is provided. See unit map for details."
  @spec from_wei(integer, atom) :: integer | float | no_return
  def from_wei(num, key) do
    if @unit_map[key] do
      num / @unit_map[key]
    else
      throw("#{key} not valid unit")
    end
  end

  @deprecated "Use ExW3.Address.to_checksum/1 instead."
  @doc "Returns a checksummed address conforming to EIP-55"
  @spec to_checksum_address(String.t()) :: String.t()
  def to_checksum_address(address) do
    address
    |> Address.from_hex()
    |> Address.to_checksum()
  end

  @deprecated "Use ExW3.Address.is_valid_checksum?/1 instead."
  @doc "Checks if the address is a valid checksummed address"
  @spec is_valid_checksum_address(String.t()) :: boolean
  def is_valid_checksum_address(address) do
    Address.is_valid_checksum?(address)
  end

  @doc "converts Ethereum style bytes to string"
  @spec bytes_to_string(binary()) :: binary()
  def bytes_to_string(bytes) do
    bytes
    |> Base.encode16(case: :lower)
    |> String.replace_trailing("0", "")
    |> Base.decode16!(case: :lower)
  end

  @doc "Converts an Ethereum address into a form that can be used by the ABI encoder"
  @spec format_address(binary()) :: integer()
  def format_address(address) do
    address
    |> String.slice(2..-1)
    |> Base.decode16!(case: :lower)
    |> :binary.decode_unsigned()
  end

  @deprecated "Use ExW3.Address.to_hex/1 instead."
  @doc "Converts bytes to Ethereum address"
  @spec to_address(binary()) :: binary()
  def to_address(bytes) do
    bytes
    |> Address.from_bytes()
    |> Address.to_hex()
  end
end


================================================
FILE: lib/exw3.ex
================================================
defmodule ExW3 do
  defdelegate accounts(opts \\ []), to: ExW3.Rpc
  defdelegate block_number(opts \\ []), to: ExW3.Rpc
  defdelegate balance(account, opts \\ []), to: ExW3.Rpc
  defdelegate tx_receipt(tx_hash), to: ExW3.Rpc
  defdelegate block(block_number), to: ExW3.Rpc
  defdelegate new_filter(map), to: ExW3.Rpc
  defdelegate get_filter_changes(filter_id), to: ExW3.Rpc
  defdelegate get_logs(filter, opts \\ []), to: ExW3.Rpc
  defdelegate uninstall_filter(filter_id), to: ExW3.Rpc
  defdelegate mine(num_blocks \\ 1), to: ExW3.Rpc
  defdelegate personal_list_accounts(opts \\ []), to: ExW3.Rpc
  defdelegate personal_new_account(password, opts \\ []), to: ExW3.Rpc
  defdelegate personal_unlock_account(params, opts \\ []), to: ExW3.Rpc
  defdelegate personal_send_transaction(param_map, passphrase, opts \\ []), to: ExW3.Rpc
  defdelegate personal_sign_transaction(param_map, passphrase, opts \\ []), to: ExW3.Rpc
  defdelegate personal_sign(data, address, passphrase, opts \\ []), to: ExW3.Rpc
  defdelegate personal_ec_recover(data0, data1, opts \\ []), to: ExW3.Rpc
  defdelegate eth_sign(data0, data1, opts \\ []), to: ExW3.Rpc
  defdelegate eth_call(arguments), to: ExW3.Rpc
  defdelegate eth_send(arguments), to: ExW3.Rpc
end


================================================
FILE: mix.exs
================================================
defmodule ExW3.MixProject do
  use Mix.Project

  @source_url "https://github.com/hswick/exw3"
  @version "0.6.1"

  def project do
    [
      app: :exw3,
      version: @version,
      elixir: "~> 1.10",
      build_embedded: Mix.env() == :prod,
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      docs: docs(),
      package: package(),
      name: "exw3",
      dialyzer: [
        remove_defaults: [:unknown]
      ],
      preferred_cli_env: [
        docs: :docs,
        "hex.publish": :docs
      ]
    ]
  end

  def application do
    [applications: [:logger, :ex_abi, :ethereumex]]
  end

  defp deps do
    [
      {:ex_doc, ">= 0.0.0", only: :docs, runtime: false},
      {:ethereumex, "~> 0.7.0"},
      {:ex_keccak, "~> 0.2"},
      {:ex_abi, "~> 0.5.4"},
      {:dialyxir, "~> 1.0", only: [:dev], runtime: false},
      {:jason, "~> 1.2"}
    ]
  end

  defp package do
    [
      name: "exw3",
      description: "A high level Ethereum JSON RPC Client for Elixir",
      files: ["lib", "mix.exs", "README*", "LICENSE*"],
      maintainers: ["Harley Swick"],
      licenses: ["Apache-2.0"],
      links: %{"GitHub" => @source_url}
    ]
  end

  defp docs do
    [
      extras: [
        LICENSE: [title: "License"],
        "README.md": [title: "Overview"]
      ],
      main: "readme",
      assets: "assets",
      source_url: @source_url,
      source_ref: "v#{@version}",
      formatters: ["html"]
    ]
  end
end


================================================
FILE: parity.sh
================================================
parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --reseal-min-period 0 --password passfile --jsonrpc-apis "web3,eth,personal,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore" --ipc-apis "web3,eth,personal,pubsub,net,parity,parity_pubsub,parity_accounts,traces,rpc,secretstore"


================================================
FILE: test/examples/build/AddressTester.abi
================================================
[{"constant":true,"inputs":[{"name":"a","type":"address"}],"name":"get","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"pure","type":"function"}]

================================================
FILE: test/examples/build/ArrayTester.abi
================================================
[{"constant":true,"inputs":[{"name":"ints","type":"uint256[5]"}],"name":"staticUint","outputs":[{"name":"","type":"uint256[5]"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"ints","type":"uint256[]"}],"name":"dynamicUint","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"pure","type":"function"}]

================================================
FILE: test/examples/build/Complex.abi
================================================
[{"constant":true,"inputs":[],"name":"getBroAndBroBro","outputs":[{"name":"bro","type":"uint256"},{"name":"broBro","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBarFoo","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_foobar","type":"bytes32"}],"name":"setFooBar","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBoth","outputs":[{"name":"","type":"uint256"},{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_barFoo","type":"bool"}],"name":"setBarFoo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_fooboo","type":"uint256"}],"name":"getFooBoo","outputs":[{"name":"fooBoo","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getFooBar","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_foo","type":"uint256"}],"name":"setFoo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_foo","type":"uint256"},{"name":"_foobar","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"foo","type":"uint256"},{"indexed":false,"name":"person","type":"address"}],"name":"Bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"fooboo","type":"bool"},{"indexed":false,"name":"foo","type":"uint256"},{"indexed":false,"name":"foobar","type":"bytes32"}],"name":"FooBar","type":"event"}]

================================================
FILE: test/examples/build/EventTester.abi
================================================
[{"constant":false,"inputs":[{"name":"data","type":"bytes32"}],"name":"simpleIndex","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"data","type":"bytes32"}],"name":"simple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"num","type":"uint256"},{"indexed":false,"name":"data","type":"bytes32"}],"name":"Simple","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"num","type":"uint256"},{"indexed":true,"name":"data","type":"bytes32"},{"indexed":false,"name":"otherNum","type":"uint256"}],"name":"SimpleIndex","type":"event"}]

================================================
FILE: test/examples/build/SimpleStorage.abi
================================================
[{"constant":false,"inputs":[{"name":"_data","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]

================================================
FILE: test/examples/contracts/AddressTester.sol
================================================
pragma solidity ^0.4.18;

contract AddressTester {
    function get(address a) public pure returns (address) {
        return a;
    }
}

================================================
FILE: test/examples/contracts/ArrayTester.sol
================================================
pragma solidity ^0.4.18;

contract ArrayTester {
    function dynamicUint(uint[] ints) public pure returns (uint[]) {
        return ints;
    }

    function staticUint(uint[5] ints) public pure returns (uint[5]) {
        return ints;
    }
}

================================================
FILE: test/examples/contracts/Complex.sol
================================================
pragma solidity ^0.4.0;

contract Complex {

    uint foo;
    bytes32 foobar;
    bool barFoo;

    event Bar(uint foo, address person);
    event FooBar(bool fooboo, uint foo, bytes32 foobar);

    constructor(uint _foo, bytes32 _foobar) public {
        foo = _foo;
        foobar = _foobar;
    }

    function getBoth() public view returns (uint, bytes32) {
        return (foo, foobar);
    }

    function getBarFoo() public view returns (bool) {
        return barFoo;
    }

    function getFooBar() public view returns (bytes32) {
        return foobar;
    }

    function getFooBoo(uint _fooboo) public pure returns (uint fooBoo) {
        fooBoo = _fooboo + 42;
    }

    function getBroAndBroBro() public view returns (uint bro, bytes32 broBro) {
        return (foo + 42, foobar);
    }

    function setFoo(uint _foo) public {
        foo = _foo;
    }

    function setFooBar(bytes32 _foobar) public {
        foobar = _foobar;
    }

    function setBarFoo(bool _barFoo) public {
        barFoo = _barFoo;
    }

    function() public payable { }

}

================================================
FILE: test/examples/contracts/EventTester.sol
================================================
pragma solidity ^0.4.18;

contract EventTester {

    event Simple(uint256 num, bytes32 data);

    event SimpleIndex(uint256 indexed num, bytes32 indexed data, uint256 otherNum);

    function simple(bytes32 data) public {
        emit Simple(42, data);
    }

    function simpleIndex(bytes32 data) public {
	emit SimpleIndex(46, data, 42);
    }
    
}


================================================
FILE: test/examples/contracts/SimpleStorage.sol
================================================
pragma solidity ^0.4.0;

contract SimpleStorage {
    uint data;

    function set(uint _data) public {
        data = _data;
    }

    function get() public view returns (uint) {
        return data;
    }
}

================================================
FILE: test/exw3/abi_test.exs
================================================
defmodule ExW3.AbiTest do
  use ExUnit.Case

  test ".load_abi/1 returns a map keyed by function & event name" do
    assert ExW3.Abi.load_abi("test/examples/build/SimpleStorage.abi") == %{
             "get" => %{
               "constant" => true,
               "inputs" => [],
               "name" => "get",
               "outputs" => [%{"name" => "", "type" => "uint256"}],
               "payable" => false,
               "stateMutability" => "view",
               "type" => "function"
             },
             "set" => %{
               "constant" => false,
               "inputs" => [%{"name" => "_data", "type" => "uint256"}],
               "name" => "set",
               "outputs" => [],
               "payable" => false,
               "stateMutability" => "nonpayable",
               "type" => "function"
             }
           }
  end
end


================================================
FILE: test/exw3/address_test.exs
================================================
defmodule ExW3.AddressTest do
  use ExUnit.Case
  alias ExW3.Address

  @bytes_address <<25, 154, 209, 226, 223, 37, 243, 29, 135, 172, 137, 205, 225, 158, 82, 44, 130, 62, 90, 166, 30>>
  @string_address "199ad1e2df25f31d87ac89cde19e522c823e5aa61e"
  @hex_address "0x199ad1e2df25f31d87ac89cde19e522c823e5aa61e"
  @checksum_address "0x199ad1E2dF25f31D87AC89cdE19E522c823E5AA61e"

  test ".from_bytes/1 returns an address struct" do
    address = Address.from_bytes(@bytes_address)
    assert address.bytes == @bytes_address
  end

  test ".from_hex/1 returns an address struct with and without the checksum" do
    address = Address.from_hex(@hex_address)
    assert address.bytes == @bytes_address

    from_checksum_address = Address.from_hex(@checksum_address)
    assert from_checksum_address.bytes == @bytes_address
  end

  test ".to_bytes/1 returns the bytes of the address struct" do
    address = %Address{bytes: @bytes_address}
    assert Address.to_bytes(address) == @bytes_address
  end

  test ".to_string/1 returns the hex encoded string without a 0x prefix" do
    address = %Address{bytes: @bytes_address}
    assert Address.to_string(address) == @string_address
  end

  test ".to_hex/1 returns the hex encoded string with a 0x prefix" do
    address = %Address{bytes: @bytes_address}
    assert Address.to_hex(address) == @hex_address
  end

  test ".to_checksum/1 returns the hex encoded string with a 0x prefix conforming to EIP-55" do
    address = %Address{bytes: @bytes_address}
    assert Address.to_checksum(address) == @checksum_address
  end

  test ".is_valid_checksum?/1 is true when it conforms to EIP-55" do
    assert Address.is_valid_checksum?(@checksum_address) == true
    assert Address.is_valid_checksum?(@hex_address) == false
  end
end


================================================
FILE: test/exw3/client_test.exs
================================================
defmodule ExW3.ClientTest do
  use ExUnit.Case

  test ".call_client/1 calls the JSON-RPC method with an empty list of arguments" do
    assert {:ok, hex} = ExW3.Client.call_client(:eth_block_number)
    assert "0x" <> _ = hex
  end

  test ".call_client/2 calls the JSON-RPC method with the given arguments" do
    assert {:ok, accounts} = ExW3.Client.call_client(:eth_accounts)
    assert Enum.count(accounts) > 0
    assert ["0x" <> _ = account] = accounts

    assert {:ok, balance} = ExW3.Client.call_client(:eth_get_balance, [account])
    assert "0x" <> _ = balance
  end

  test ".call_client/2 can specifiy a http url with & without params" do
    assert {:ok, accounts} =
             ExW3.Client.call_client(:eth_accounts, [[url: Ethereumex.Config.rpc_url()]])

    account = Enum.at(accounts, 0)

    assert {:ok, "0x" <> _} =
             ExW3.Client.call_client(:eth_get_balance, [
               account,
               "latest",
               [url: Ethereumex.Config.rpc_url()]
             ])

    assert ExW3.Client.call_client(:eth_get_balance, [account, "latest", [url: "unsupported"]]) ==
             {:error, :invalid_client_type}

    assert ExW3.Client.call_client(:eth_get_balance, [
             account,
             "latest",
             [url: "http://localhost:1234"]
           ]) ==
             {:error, :econnrefused}

    assert ExW3.Client.call_client(:eth_get_balance, [
             account,
             "latest",
             [url: "https://localhost:1234"]
           ]) ==
             {:error, :econnrefused}
  end
end


================================================
FILE: test/exw3/contract_test.exs
================================================
defmodule EXW3.ContractTest do
  use ExUnit.Case
  doctest ExW3.Contract

  @simple_storage_abi ExW3.Abi.load_abi("test/examples/build/SimpleStorage.abi")

  setup_all do
    start_supervised!(ExW3.Contract)
    :ok
  end

  test ".at assigns the address to the state of the registered contract" do
    ExW3.Contract.register(:SimpleStorage, abi: @simple_storage_abi)

    assert ExW3.Contract.address(:SimpleStorage) == nil

    accounts = ExW3.accounts()

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :SimpleStorage,
        bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
        args: [],
        options: %{
          gas: 300_000,
          from: Enum.at(accounts, 0)
        }
      )

    assert ExW3.Contract.at(:SimpleStorage, address) == :ok

    state = :sys.get_state(ContractManager)
    contract_state = state[:SimpleStorage]
    assert Keyword.get(contract_state, :address) == address
    assert Keyword.get(contract_state, :abi) == @simple_storage_abi
  end

  test ".address returns the registered address for the contract" do
    ExW3.Contract.register(:SimpleStorage, abi: @simple_storage_abi)

    assert ExW3.Contract.address(:SimpleStorage) == nil

    accounts = ExW3.accounts()

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :SimpleStorage,
        bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
        args: [],
        options: %{
          gas: 300_000,
          from: Enum.at(accounts, 0)
        }
      )

    assert ExW3.Contract.at(:SimpleStorage, address) == :ok
    assert ExW3.Contract.address(:SimpleStorage) == address
  end
end


================================================
FILE: test/exw3/rpc_test.exs
================================================
defmodule ExW3.RpcTest do
  use ExUnit.Case

  describe ".accounts" do
    test "returns a list from the eth_accounts JSON-RPC endpoint" do
      assert ExW3.accounts() |> is_list
    end

    test "can override the http endpoint" do
      assert ExW3.accounts(url: Ethereumex.Config.rpc_url()) |> is_list
      assert ExW3.accounts(url: "https://localhost:1234") == {:error, :econnrefused}
    end
  end

  describe ".block_number" do
    test "returns the integer block number from the eth_blockNumber JSON-RPC endpoint" do
      assert {:ok, bn} = ExW3.block_number()
      assert bn |> is_integer
    end

    test "can override the http endpoint" do
      assert {:ok, _} = ExW3.block_number(url: Ethereumex.Config.rpc_url())
      assert ExW3.block_number(url: "https://localhost:1234") == {:error, :econnrefused}
    end
  end

  describe ".balance" do
    test "returns the latest integer balance from the eth_getBalance JSON-RPC endpoint" do
      account = ExW3.accounts() |> Enum.at(0)
      assert ExW3.balance(account) |> is_integer
    end

    test "can override the http endpoint" do
      account = ExW3.accounts() |> Enum.at(0)
      assert ExW3.balance(account, url: Ethereumex.Config.rpc_url()) |> is_integer
      assert ExW3.balance(account, url: "https://localhost:1234") == {:error, :econnrefused}
    end
  end
end


================================================
FILE: test/exw3/utils_test.exs
================================================
defmodule ExW3.UtilsTest do
  use ExUnit.Case
  doctest ExW3.Utils

  describe ".hex_to_integer/1" do
    test "parses a hex encoded string to an integer" do
      assert ExW3.Utils.hex_to_integer("0x1") == {:ok, 1}
      assert ExW3.Utils.hex_to_integer("0x2") == {:ok, 2}
      assert ExW3.Utils.hex_to_integer("0x2a") == {:ok, 42}
      assert ExW3.Utils.hex_to_integer("0x2A") == {:ok, 42}
    end

    test "returns an error when the string is not a valid hexidecimal" do
      assert ExW3.Utils.hex_to_integer("0x") == {:error, :invalid_hex_string}
      assert ExW3.Utils.hex_to_integer("0a") == {:error, :invalid_hex_string}
      assert ExW3.Utils.hex_to_integer("0xZ") == {:error, :invalid_hex_string}
    end
  end

  describe ".integer_to_hex/1" do
    test "encodes an integer to hexadecimal" do
      assert ExW3.Utils.integer_to_hex(0) == {:ok, "0x0"}
      assert ExW3.Utils.integer_to_hex(1) == {:ok, "0x1"}
      assert ExW3.Utils.integer_to_hex(42) == {:ok, "0x2A"}
    end

    test "returns an error when the integer is negative" do
      assert ExW3.Utils.integer_to_hex(-1) == {:error, :negative_integer}
    end

    test "returns an error when the value is not an integer" do
      assert ExW3.Utils.integer_to_hex(1.1) == {:error, :non_integer}
      assert ExW3.Utils.integer_to_hex(nil) == {:error, :non_integer}
    end
  end

  describe ".keccak256/1" do
    test "returns a 0x prepended 32 byte hash of the input" do
      hex_hash = ExW3.Utils.keccak256("foo")
      assert "0x" <> hash = hex_hash
      assert hash == "41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d"

      num_bytes = byte_size(hash)
      assert trunc(num_bytes / 2) == 32
    end
  end

  describe ".to_wei/2" do
    test "converts a unit to_wei" do
      assert ExW3.Utils.to_wei(1, :wei) == 1
      assert ExW3.Utils.to_wei(1, :kwei) == 1_000
      assert ExW3.Utils.to_wei(1, :Kwei) == 1_000
      assert ExW3.Utils.to_wei(1, :babbage) == 1_000
      assert ExW3.Utils.to_wei(1, :mwei) == 1_000_000
      assert ExW3.Utils.to_wei(1, :Mwei) == 1_000_000
      assert ExW3.Utils.to_wei(1, :lovelace) == 1_000_000
      assert ExW3.Utils.to_wei(1, :gwei) == 1_000_000_000
      assert ExW3.Utils.to_wei(1, :Gwei) == 1_000_000_000
      assert ExW3.Utils.to_wei(1, :shannon) == 1_000_000_000
      assert ExW3.Utils.to_wei(1, :szabo) == 1_000_000_000_000
      assert ExW3.Utils.to_wei(1, :finney) == 1_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :ether) == 1_000_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :kether) == 1_000_000_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :grand) == 1_000_000_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :mether) == 1_000_000_000_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :gether) == 1_000_000_000_000_000_000_000_000_000
      assert ExW3.Utils.to_wei(1, :tether) == 1_000_000_000_000_000_000_000_000_000_000

      assert ExW3.Utils.to_wei(1, :kwei) == ExW3.Utils.to_wei(1, :femtoether)
      assert ExW3.Utils.to_wei(1, :szabo) == ExW3.Utils.to_wei(1, :microether)
      assert ExW3.Utils.to_wei(1, :finney) == ExW3.Utils.to_wei(1, :milliether)
      assert ExW3.Utils.to_wei(1, :milli) == ExW3.Utils.to_wei(1, :milliether)
      assert ExW3.Utils.to_wei(1, :milli) == ExW3.Utils.to_wei(1000, :micro)

      {:ok, agent} = Agent.start_link(fn -> false end)

      try do
        ExW3.Utils.to_wei(1, :wei1)
      catch
        _ -> Agent.update(agent, fn _ -> true end)
      end

      assert Agent.get(agent, fn state -> state end)
    end
  end

  describe ".from_wei/2" do
    test "converts a unit from wei" do
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :wei) == 1_000_000_000_000_000_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :kwei) == 1_000_000_000_000_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :mwei) == 1_000_000_000_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :gwei) == 1_000_000_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :szabo) == 1_000_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :finney) == 1_000
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :ether) == 1
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :kether) == 0.001
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :grand) == 0.001
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :mether) == 0.000001
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :gether) == 0.000000001
      assert ExW3.Utils.from_wei(1_000_000_000_000_000_000, :tether) == 0.000000000001
    end
  end

  describe ".to_checksum_address/1" do
    test "returns checksum for all caps address" do
      assert ExW3.Utils.to_checksum_address(
               String.downcase("0x52908400098527886E0F7030069857D2E4169EE7")
             ) ==
               "0x52908400098527886E0F7030069857D2E4169EE7"

      assert ExW3.Utils.to_checksum_address(
               String.downcase("0x8617E340B3D01FA5F11F306F4090FD50E238070D")
             ) ==
               "0x8617E340B3D01FA5F11F306F4090FD50E238070D"
    end

    test "returns checksumfor all lowercase address" do
      assert ExW3.Utils.to_checksum_address(
               String.downcase("0xde709f2102306220921060314715629080e2fb77")
             ) ==
               "0xde709f2102306220921060314715629080e2fb77"

      assert ExW3.Utils.to_checksum_address(
               String.downcase("0x27b1fdb04752bbc536007a920d24acb045561c26")
             ) ==
               "0x27b1fdb04752bbc536007a920d24acb045561c26"
    end

    test "returns checksum for normal addresses" do
      assert ExW3.Utils.to_checksum_address(
               String.downcase("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
             ) ==
               "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"

      assert ExW3.Utils.to_checksum_address(
               String.downcase("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")
             ) ==
               "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"

      assert ExW3.Utils.to_checksum_address(
               String.downcase("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")
             ) ==
               "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"

      assert ExW3.Utils.to_checksum_address(
               String.downcase("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")
             ) ==
               "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
    end
  end

  describe ".is_valid_checksum_address/1" do
    test "returns valid check for is_valid_checksum_address()" do
      assert ExW3.Utils.is_valid_checksum_address("0x52908400098527886E0F7030069857D2E4169EE7") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0x27b1fdb04752bbc536007a920d24acb045561c26") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0xde709f2102306220921060314715629080e2fb77") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0x8617E340B3D01FA5F11F306F4090FD50E238070D") ==
               true

      assert ExW3.Utils.is_valid_checksum_address("0x52908400098527886E0F7030069857D2E4169EE7") ==
               true
    end

    test "returns invalid check for is_valid_checksum_address()" do
      assert ExW3.Utils.is_valid_checksum_address("0x2f015c60e0be116b1f0cd534704db9c92118fb6a") ==
               false
    end
  end
end


================================================
FILE: test/exw3_test.exs
================================================
defmodule ExW3Test do
  use ExUnit.Case
  doctest ExW3

  setup_all do
    start_supervised!(ExW3.Contract)

    %{
      simple_storage_abi: ExW3.Abi.load_abi("test/examples/build/SimpleStorage.abi"),
      array_tester_abi: ExW3.Abi.load_abi("test/examples/build/ArrayTester.abi"),
      event_tester_abi: ExW3.Abi.load_abi("test/examples/build/EventTester.abi"),
      complex_abi: ExW3.Abi.load_abi("test/examples/build/Complex.abi"),
      address_tester_abi: ExW3.Abi.load_abi("test/examples/build/AddressTester.abi"),
      accounts: ExW3.accounts()
    }
  end

  # Only works with ganache-cli

  # test "mines a block" do
  #   block_number = ExW3.block_number()
  #   ExW3.mine()
  #   assert ExW3.block_number() == block_number + 1
  # end

  # test "mines multiple blocks" do
  #   block_number = ExW3.block_number()
  #   ExW3.mine(5)
  #   assert ExW3.block_number() == block_number + 5
  # end

  test "starts a Contract GenServer for simple storage contract", context do
    ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :SimpleStorage,
        bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
        args: [],
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    ExW3.Contract.at(:SimpleStorage, address)

    assert address == ExW3.Contract.address(:SimpleStorage)

    {:ok, data} = ExW3.Contract.call(:SimpleStorage, :get)

    assert data == 0

    ExW3.Contract.send(:SimpleStorage, :set, [1], %{
      from: Enum.at(context[:accounts], 0),
      gas: 50_000
    })

    {:ok, data} = ExW3.Contract.call(:SimpleStorage, :get)

    assert data == 1
  end

  test "starts a Contract GenServer for array tester contract", context do
    ExW3.Contract.register(:ArrayTester, abi: context[:array_tester_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :ArrayTester,
        bin: ExW3.Abi.load_bin("test/examples/build/ArrayTester.bin"),
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    ExW3.Contract.at(:ArrayTester, address)

    assert address == ExW3.Contract.address(:ArrayTester)

    arr = [1, 2, 3, 4, 5]

    {:ok, result} = ExW3.Contract.call(:ArrayTester, :staticUint, [arr])

    assert result == arr

    {:ok, result} = ExW3.Contract.call(:ArrayTester, :dynamicUint, [arr])

    assert result == arr
  end

  test "starts a Contract GenServer for event tester contract", context do
    ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :EventTester,
        bin: ExW3.Abi.load_bin("test/examples/build/EventTester.bin"),
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    ExW3.Contract.at(:EventTester, address)

    assert address == ExW3.Contract.address(:EventTester)

    {:ok, tx_hash} =
      ExW3.Contract.send(:EventTester, :simple, ["Hello, World!"], %{
        from: Enum.at(context[:accounts], 0),
        gas: 30_000
      })

    {:ok, {receipt, logs}} = ExW3.Contract.tx_receipt(:EventTester, tx_hash)

    assert receipt |> is_map

    data =
      logs
      |> Enum.at(0)
      |> Map.get("data")
      |> ExW3.Utils.bytes_to_string()

    assert data == "Hello, World!"

    {:ok, tx_hash2} =
      ExW3.Contract.send(:EventTester, :simpleIndex, ["Hello, World!"], %{
        from: Enum.at(context[:accounts], 0),
        gas: 30_000
      })

    {:ok, {_receipt, logs}} = ExW3.Contract.tx_receipt(:EventTester, tx_hash2)

    otherNum =
      logs
      |> Enum.at(0)
      |> Map.get("otherNum")

    assert otherNum == 42

    num =
      logs
      |> Enum.at(0)
      |> Map.get("num")

    assert num == 46

    data =
      logs
      |> Enum.at(0)
      |> Map.get("data")
      |> ExW3.Utils.bytes_to_string()

    assert data == "Hello, World!"
  end

  test "Testing formatted get filter changes", context do
    ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :EventTester,
        bin: ExW3.Abi.load_bin("test/examples/build/EventTester.bin"),
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    ExW3.Contract.at(:EventTester, address)

    # Test non indexed events

    {:ok, filter_id} = ExW3.Contract.filter(:EventTester, "Simple")

    {:ok, _tx_hash} =
      ExW3.Contract.send(
        :EventTester,
        :simple,
        ["Hello, World!"],
        %{from: Enum.at(context[:accounts], 0), gas: 30_000}
      )

    {:ok, change_logs} = ExW3.Contract.get_filter_changes(filter_id)

    event_log = Enum.at(change_logs, 0)

    assert event_log |> is_map
    log_data = Map.get(event_log, "data")
    assert log_data |> is_map
    assert Map.get(log_data, "num") == 42
    assert ExW3.Utils.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"

    ExW3.Contract.uninstall_filter(filter_id)

    # Test indexed events

    {:ok, indexed_filter_id} = ExW3.Contract.filter(:EventTester, "SimpleIndex")

    {:ok, _tx_hash} =
      ExW3.Contract.send(
        :EventTester,
        :simpleIndex,
        ["Hello, World!"],
        %{from: Enum.at(context[:accounts], 0), gas: 30_000}
      )

    {:ok, change_logs} = ExW3.Contract.get_filter_changes(indexed_filter_id)

    event_log = Enum.at(change_logs, 0)

    assert event_log |> is_map
    log_data = Map.get(event_log, "data")
    assert log_data |> is_map
    assert Map.get(log_data, "num") == 46
    assert ExW3.Utils.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"
    assert Map.get(log_data, "otherNum") == 42
    ExW3.Contract.uninstall_filter(indexed_filter_id)

    # Test Indexing Indexed Events

    {:ok, indexed_filter_id} =
      ExW3.Contract.filter(
        :EventTester,
        "SimpleIndex",
        %{
          topics: [nil, ["Hello, World", "Hello, World!"]],
          fromBlock: 1,
          toBlock: "latest"
        }
      )

    {:ok, _tx_hash} =
      ExW3.Contract.send(
        :EventTester,
        :simpleIndex,
        ["Hello, World!"],
        %{from: Enum.at(context[:accounts], 0), gas: 30_000}
      )

    {:ok, change_logs} = ExW3.Contract.get_filter_changes(indexed_filter_id)

    event_log = Enum.at(change_logs, 0)
    assert event_log |> is_map
    log_data = Map.get(event_log, "data")
    assert log_data |> is_map
    assert Map.get(log_data, "num") == 46
    assert ExW3.Utils.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"
    assert Map.get(log_data, "otherNum") == 42

    ExW3.Contract.uninstall_filter(indexed_filter_id)

    # Tests filter with map params

    {:ok, indexed_filter_id} =
      ExW3.Contract.filter(
        :EventTester,
        "SimpleIndex",
        %{
          topics: %{num: 46, data: "Hello, World!"}
        }
      )

    {:ok, _tx_hash} =
      ExW3.Contract.send(
        :EventTester,
        :simpleIndex,
        ["Hello, World!"],
        %{from: Enum.at(context[:accounts], 0), gas: 30_000}
      )

    # Demonstrating the delay capability
    {:ok, change_logs} = ExW3.Contract.get_filter_changes(indexed_filter_id)

    event_log = Enum.at(change_logs, 0)
    assert event_log |> is_map
    log_data = Map.get(event_log, "data")
    assert log_data |> is_map
    assert Map.get(log_data, "num") == 46
    assert ExW3.Utils.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"
    assert Map.get(log_data, "otherNum") == 42

    ExW3.Contract.uninstall_filter(indexed_filter_id)
  end

  test "starts a Contract GenServer for Complex contract", context do
    ExW3.Contract.register(:Complex, abi: context[:complex_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :Complex,
        bin: ExW3.Abi.load_bin("test/examples/build/Complex.bin"),
        args: [42, "Hello, world!"],
        options: %{
          from: Enum.at(context[:accounts], 0),
          gas: 300_000
        }
      )

    ExW3.Contract.at(:Complex, address)

    assert address == ExW3.Contract.address(:Complex)

    {:ok, foo, foobar} = ExW3.Contract.call(:Complex, :getBoth)

    assert foo == 42

    assert ExW3.Utils.bytes_to_string(foobar) == "Hello, world!"
  end

  test "starts a Contract GenServer for AddressTester contract", context do
    ExW3.Contract.register(:AddressTester, abi: context[:address_tester_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :AddressTester,
        bin: ExW3.Abi.load_bin("test/examples/build/AddressTester.bin"),
        options: %{
          from: Enum.at(context[:accounts], 0),
          gas: 300_000
        }
      )

    ExW3.Contract.at(:AddressTester, address)

    assert address == ExW3.Contract.address(:AddressTester)

    formatted_address =
      Enum.at(context[:accounts], 0)
      |> ExW3.Utils.format_address()

    {:ok, same_address} = ExW3.Contract.call(:AddressTester, :get, [formatted_address])

    assert %ExW3.Address{bytes: same_address} |> ExW3.Address.to_hex() == Enum.at(context[:accounts], 0)
  end

  test "returns proper error messages at contract deployment", context do
    ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])

    assert {:error, :missing_gas} ==
             ExW3.Contract.deploy(
               :SimpleStorage,
               bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
               args: [],
               options: %{
                 from: Enum.at(context[:accounts], 0)
               }
             )

    assert {:error, :missing_sender} ==
             ExW3.Contract.deploy(
               :SimpleStorage,
               bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
               args: [],
               options: %{
                 gas: 300_000
               }
             )

    assert {:error, :missing_binary} ==
             ExW3.Contract.deploy(
               :SimpleStorage,
               args: [],
               options: %{
                 gas: 300_000,
                 from: Enum.at(context[:accounts], 0)
               }
             )
  end

  test "return proper error messages at send and call", context do
    ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :SimpleStorage,
        bin: ExW3.Abi.load_bin("test/examples/build/SimpleStorage.bin"),
        args: [],
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    assert {:error, :missing_address} == ExW3.Contract.call(:SimpleStorage, :get)

    assert {:error, :missing_address} ==
             ExW3.Contract.send(:SimpleStorage, :set, [1], %{
               from: Enum.at(context[:accounts], 0),
               gas: 50_000
             })

    ExW3.Contract.at(:SimpleStorage, address)

    assert {:error, :missing_sender} ==
             ExW3.Contract.send(:SimpleStorage, :set, [1], %{gas: 50_000})

    assert {:error, :missing_gas} ==
             ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0)})
  end

  test ".get_logs/1", context do
    ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])

    {:ok, address, _} =
      ExW3.Contract.deploy(
        :EventTester,
        bin: ExW3.Abi.load_bin("test/examples/build/EventTester.bin"),
        options: %{
          gas: 300_000,
          from: Enum.at(context[:accounts], 0)
        }
      )

    ExW3.Contract.at(:EventTester, address)

    {:ok, current_block} = ExW3.block_number()
    {:ok, from_block} = ExW3.Utils.integer_to_hex(current_block)

    {:ok, simple_tx_hash} =
      ExW3.Contract.send(:EventTester, :simple, ["Hello, World!"], %{
        from: Enum.at(context[:accounts], 0),
        gas: 30_000
      })

    {:ok, _} =
      ExW3.Contract.send(:EventTester, :simpleIndex, ["Hello, World!"], %{
        from: Enum.at(context[:accounts], 0),
        gas: 30_000
      })

    filter = %{
      fromBlock: from_block,
      toBlock: "latest",
      topics: [ExW3.Utils.keccak256("Simple(uint256,bytes32)")]
    }

    assert {:ok, logs} = ExW3.get_logs(filter)
    assert Enum.count(logs) == 1

    log = Enum.at(logs, 0)
    assert log["transactionHash"] == simple_tx_hash
  end
end


================================================
FILE: test/test_helper.exs
================================================
ExUnit.start()
Download .txt
gitextract_a2glllkx/

├── .formatter.exs
├── .github/
│   ├── dependabot.yml
│   ├── install_parity.sh
│   └── workflows/
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── config/
│   └── config.exs
├── docker-compose.yml
├── lib/
│   ├── exw3/
│   │   ├── abi.ex
│   │   ├── address.ex
│   │   ├── client.ex
│   │   ├── contract.ex
│   │   ├── normalize.ex
│   │   ├── rpc.ex
│   │   └── utils.ex
│   └── exw3.ex
├── mix.exs
├── parity.sh
└── test/
    ├── examples/
    │   ├── build/
    │   │   ├── AddressTester.abi
    │   │   ├── ArrayTester.abi
    │   │   ├── Complex.abi
    │   │   ├── EventTester.abi
    │   │   └── SimpleStorage.abi
    │   └── contracts/
    │       ├── AddressTester.sol
    │       ├── ArrayTester.sol
    │       ├── Complex.sol
    │       ├── EventTester.sol
    │       └── SimpleStorage.sol
    ├── exw3/
    │   ├── abi_test.exs
    │   ├── address_test.exs
    │   ├── client_test.exs
    │   ├── contract_test.exs
    │   ├── rpc_test.exs
    │   └── utils_test.exs
    ├── exw3_test.exs
    └── test_helper.exs
Download .txt
SYMBOL INDEX (121 symbols across 16 files)

FILE: lib/exw3.ex
  class ExW3 (line 1) | defmodule ExW3

FILE: lib/exw3/abi.ex
  class ExW3.Abi (line 1) | defmodule ExW3.Abi
    method decode_event (line 4) | def decode_event(data, signature) do
    method load_abi (line 17) | def load_abi(file_path) do
    method load_bin (line 26) | def load_bin(file_path) do
    method decode_data (line 35) | def decode_data(types_signature, data) do
    method decode_output (line 43) | def decode_output(abi, name, output) do
    method types_signature (line 61) | def types_signature(abi, name) do
    method method_signature (line 69) | def method_signature(abi, name) do
    method encode_data (line 83) | def encode_data(types_signature, data) do
    method encode_options (line 92) | def encode_options(options, keys) do
    method encode_option (line 105) | def encode_option(0), do: "0x0"
    method encode_option (line 107) | def encode_option(nil), do: nil
    method encode_option (line 109) | def encode_option(value) do
    method encode_method_call (line 119) | def encode_method_call(abi, name, input) do
    method encode_input (line 128) | def encode_input(abi, name, input) do
    method reformat_abi (line 150) | defp reformat_abi(abi) do
    method map_abi (line 156) | defp map_abi(x) do

FILE: lib/exw3/address.ex
  class ExW3.Address (line 1) | defmodule ExW3.Address
    method from_bytes (line 7) | def from_bytes(bytes) do
    method from_hex (line 12) | def from_hex(address) do
    method to_bytes (line 24) | def to_bytes(%__MODULE__{bytes: bytes}) do
    method to_string (line 29) | def to_string(%__MODULE__{bytes: bytes}) do
    method to_hex (line 34) | def to_hex(%__MODULE__{} = address) do
    method to_checksum (line 39) | def to_checksum(%__MODULE__{} = address) do
    method is_valid_checksum? (line 62) | def is_valid_checksum?(hex_address) do

FILE: lib/exw3/client.ex
  class ExW3.Client (line 1) | defmodule ExW3.Client
    method call_client (line 8) | def call_client(method_name, arguments \\ []) do
    method extract_url_opt (line 18) | defp extract_url_opt(arguments) do
    method client_type (line 27) | defp client_type(nil), do: Application.get_env(:ethereumex, :client_ty...
    method client_type (line 28) | defp client_type("http://" <> _), do: :http
    method client_type (line 29) | defp client_type("https://" <> _), do: :http
    method client_type (line 30) | defp client_type(_), do: :invalid

FILE: lib/exw3/contract.ex
  class ExW3.Contract (line 1) | defmodule ExW3.Contract
    method start_link (line 6) | def start_link(_ \\ :ok) do
    method deploy (line 12) | def deploy(name, args) do
    method register (line 18) | def register(name, contract_info) do
    method uninstall_filter (line 24) | def uninstall_filter(filter_id) do
    method at (line 30) | def at(name, address) do
    method address (line 36) | def address(name) do
    method call (line 42) | def call(contract_name, method_name, args \\ [], timeout \\ :infinity) do
    method send (line 48) | def send(contract_name, method_name, args, options) do
    method tx_receipt (line 54) | def tx_receipt(contract_name, tx_hash) do
    method filter (line 60) | def filter(contract_name, event_name, event_data \\ %{}) do
    method get_filter_changes (line 69) | def get_filter_changes(filter_id) do
    method init (line 76) | def init(state) do
    method data_signature_helper (line 80) | defp data_signature_helper(name, fields) do
    method topic_types_helper (line 85) | defp topic_types_helper(fields) do
    method init_events (line 95) | defp init_events(abi) do
    method deploy_helper (line 155) | def deploy_helper(bin, abi, args) do
    method eth_call_helper (line 203) | def eth_call_helper(address, abi, method_name, args) do
    method eth_send_helper (line 221) | def eth_send_helper(address, abi, method_name, args, options) do
    method register_helper (line 244) | defp register_helper(contract_info) do
    method check_option (line 254) | defp check_option(nil, error_atom), do: {:error, error_atom}
    method check_option (line 255) | defp check_option([], error_atom), do: {:error, error_atom}
    method check_option (line 257) | defp check_option([_head | tail], atom), do: check_option(tail, atom)
    method check_option (line 258) | defp check_option(value, _atom), do: {:ok, value}
    method handle_cast (line 262) | def handle_cast({:at, {name, address}}, state) do
    method handle_cast (line 269) | def handle_cast({:register, {name, contract_info}}, state) do
    method handle_cast (line 273) | def handle_cast({:uninstall_filter, filter_id}, state) do
    method filter_topics_helper (line 280) | defp filter_topics_helper(event_signature, event_data, topic_types, to...
    method from_block_helper (line 317) | def from_block_helper(event_data) do
    method param_helper (line 332) | defp param_helper(event_data, key) do
    method event_data_format_helper (line 349) | defp event_data_format_helper(event_data) do
    method get_event_attributes (line 356) | def get_event_attributes(state, contract_name, event_name) do
    method extract_non_indexed_fields (line 361) | defp extract_non_indexed_fields(data, names, signature) do
    method format_log_data (line 365) | defp format_log_data(log, event_attributes) do
    method handle_call (line 397) | def handle_call({:filter, {contract_name, event_name, event_data}}, _f...
    method handle_call (line 425) | def handle_call({:get_filter_changes, filter_id}, _from, state) do
    method handle_call (line 458) | def handle_call({:deploy, {name, args}}, _from, state) do
    method handle_call (line 472) | def handle_call({:address, name}, _from, state) do
    method handle_call (line 476) | def handle_call({:call, {contract_name, method_name, args}}, _from, st...
    method handle_call (line 487) | def handle_call({:send, {contract_name, method_name, args, options}}, ...
    method handle_call (line 508) | def handle_call({:tx_receipt, {contract_name, tx_hash}}, _from, state) do

FILE: lib/exw3/normalize.ex
  class ExW3.Normalize (line 1) | defmodule ExW3.Normalize
    method transform_to_integer (line 3) | def transform_to_integer(map, keys) do

FILE: lib/exw3/rpc.ex
  class ExW3.Rpc (line 1) | defmodule ExW3.Rpc
    method accounts (line 15) | def accounts(opts \\ []) do
    method block_number (line 25) | def block_number(opts \\ []) do
    method balance (line 35) | def balance(account, opts \\ []) do
    method tx_receipt (line 48) | def tx_receipt(tx_hash) do
    method block (line 66) | def block(block_number) do
    method new_filter (line 75) | def new_filter(map) do
    method get_filter_changes (line 84) | def get_filter_changes(filter_id) do
    method get_logs (line 100) | def get_logs(filter, opts \\ []) do
    method uninstall_filter (line 110) | def uninstall_filter(filter_id) do
    method mine (line 119) | def mine(num_blocks \\ 1) do
    method personal_list_accounts (line 127) | def personal_list_accounts(opts \\ []) do
    method personal_new_account (line 133) | def personal_new_account(password, opts \\ []) do
    method personal_unlock_account (line 140) | def personal_unlock_account(params, opts \\ []) do
    method personal_send_transaction (line 146) | def personal_send_transaction(param_map, passphrase, opts \\ []) do
    method personal_sign_transaction (line 152) | def personal_sign_transaction(param_map, passphrase, opts \\ []) do
    method personal_sign (line 158) | def personal_sign(data, address, passphrase, opts \\ []) do
    method personal_ec_recover (line 164) | def personal_ec_recover(data0, data1, opts \\ []) do
    method eth_sign (line 170) | def eth_sign(data0, data1, opts \\ []) do
    method eth_call (line 176) | def eth_call(arguments) do
    method eth_send (line 182) | def eth_send(arguments) do

FILE: lib/exw3/utils.ex
  class ExW3.Utils (line 1) | defmodule ExW3.Utils
    method hex_to_integer (line 12) | def hex_to_integer(hex) do
    method integer_to_hex (line 25) | def integer_to_hex(i) do
    method keccak256 (line 37) | def keccak256(str) do
    method to_wei (line 73) | def to_wei(num, key) do
    method from_wei (line 83) | def from_wei(num, key) do
    method to_checksum_address (line 94) | def to_checksum_address(address) do
    method is_valid_checksum_address (line 103) | def is_valid_checksum_address(address) do
    method bytes_to_string (line 109) | def bytes_to_string(bytes) do
    method format_address (line 118) | def format_address(address) do
    method to_address (line 128) | def to_address(bytes) do

FILE: mix.exs
  class ExW3.MixProject (line 1) | defmodule ExW3.MixProject
    method project (line 7) | def project do
    method application (line 28) | def application do
    method deps (line 32) | defp deps do
    method package (line 43) | defp package do
    method docs (line 54) | defp docs do

FILE: test/exw3/abi_test.exs
  class ExW3.AbiTest (line 1) | defmodule ExW3.AbiTest

FILE: test/exw3/address_test.exs
  class ExW3.AddressTest (line 1) | defmodule ExW3.AddressTest

FILE: test/exw3/client_test.exs
  class ExW3.ClientTest (line 1) | defmodule ExW3.ClientTest

FILE: test/exw3/contract_test.exs
  class EXW3.ContractTest (line 1) | defmodule EXW3.ContractTest

FILE: test/exw3/rpc_test.exs
  class ExW3.RpcTest (line 1) | defmodule ExW3.RpcTest

FILE: test/exw3/utils_test.exs
  class ExW3.UtilsTest (line 1) | defmodule ExW3.UtilsTest

FILE: test/exw3_test.exs
  class ExW3Test (line 1) | defmodule ExW3Test
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (101K chars).
[
  {
    "path": ".formatter.exs",
    "chars": 97,
    "preview": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 245,
    "preview": "version: 2\nupdates:\n- package-ecosystem: mix\n  directory: \"/\"\n  schedule:\n    interval: monthly\n  open-pull-requests-lim"
  },
  {
    "path": ".github/install_parity.sh",
    "chars": 313,
    "preview": "# Install Parity blockchain tests on Github action\necho > passfile # just to be safe\n\nwget https://releases.parity.io/et"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1570,
    "preview": "name: test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - '*'\n\njobs:\n  test:\n    runs-o"
  },
  {
    "path": ".gitignore",
    "chars": 670,
    "preview": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up "
  },
  {
    "path": "LICENSE",
    "chars": 10173,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 8441,
    "preview": "# ExW3\n\n[![Build Status](https://github.com/hswick/exw3/workflows/test/badge.svg?branch=master)](https://github.com/hswi"
  },
  {
    "path": "config/config.exs",
    "chars": 1257,
    "preview": "# This file is responsible for configuring your application\n# and its dependencies with the aid of the Mix.Config module"
  },
  {
    "path": "docker-compose.yml",
    "chars": 371,
    "preview": "version: '3.8'\n\nservices:\n  openethereum:\n    image: openethereum/openethereum:v3.3.0\n    command: '--chain=dev --unlock"
  },
  {
    "path": "lib/exw3/abi.ex",
    "chars": 5062,
    "preview": "defmodule ExW3.Abi do\n  @doc \"Decodes event based on given data and provided signature\"\n  @spec decode_event(binary(), b"
  },
  {
    "path": "lib/exw3/address.ex",
    "chars": 1652,
    "preview": "defmodule ExW3.Address do\n  @type t :: %__MODULE__{bytes: binary}\n\n  defstruct ~w[bytes]a\n\n  @spec from_bytes(binary) ::"
  },
  {
    "path": "lib/exw3/client.ex",
    "chars": 991,
    "preview": "defmodule ExW3.Client do\n  @type argument :: term\n  @type request_error :: Ethereumex.Client.Behaviour.error()\n  @type e"
  },
  {
    "path": "lib/exw3/contract.ex",
    "chars": 16310,
    "preview": "defmodule ExW3.Contract do\n  use GenServer\n\n  @doc \"Begins the Contract process to manage all interactions with smart co"
  },
  {
    "path": "lib/exw3/normalize.ex",
    "chars": 251,
    "preview": "defmodule ExW3.Normalize do\n  @spec transform_to_integer(map(), list()) :: map()\n  def transform_to_integer(map, keys) d"
  },
  {
    "path": "lib/exw3/rpc.ex",
    "chars": 7166,
    "preview": "defmodule ExW3.Rpc do\n  import ExW3.Client\n\n  @type invalid_hex_string_error :: ExW3.Utils.invalid_hex_string_error()\n  "
  },
  {
    "path": "lib/exw3/utils.ex",
    "chars": 3950,
    "preview": "defmodule ExW3.Utils do\n  alias ExW3.Address\n\n  @type invalid_hex_string :: :invalid_hex_string\n  @type negative_integer"
  },
  {
    "path": "lib/exw3.ex",
    "chars": 1240,
    "preview": "defmodule ExW3 do\n  defdelegate accounts(opts \\\\ []), to: ExW3.Rpc\n  defdelegate block_number(opts \\\\ []), to: ExW3.Rpc\n"
  },
  {
    "path": "mix.exs",
    "chars": 1457,
    "preview": "defmodule ExW3.MixProject do\n  use Mix.Project\n\n  @source_url \"https://github.com/hswick/exw3\"\n  @version \"0.6.1\"\n\n  def"
  },
  {
    "path": "parity.sh",
    "chars": 305,
    "preview": "parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --reseal-min-period 0 --password passfile --jsonr"
  },
  {
    "path": "test/examples/build/AddressTester.abi",
    "chars": 173,
    "preview": "[{\"constant\":true,\"inputs\":[{\"name\":\"a\",\"type\":\"address\"}],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payabl"
  },
  {
    "path": "test/examples/build/ArrayTester.abi",
    "chars": 376,
    "preview": "[{\"constant\":true,\"inputs\":[{\"name\":\"ints\",\"type\":\"uint256[5]\"}],\"name\":\"staticUint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint2"
  },
  {
    "path": "test/examples/build/Complex.abi",
    "chars": 1915,
    "preview": "[{\"constant\":true,\"inputs\":[],\"name\":\"getBroAndBroBro\",\"outputs\":[{\"name\":\"bro\",\"type\":\"uint256\"},{\"name\":\"broBro\",\"type"
  },
  {
    "path": "test/examples/build/EventTester.abi",
    "chars": 694,
    "preview": "[{\"constant\":false,\"inputs\":[{\"name\":\"data\",\"type\":\"bytes32\"}],\"name\":\"simpleIndex\",\"outputs\":[],\"payable\":false,\"stateM"
  },
  {
    "path": "test/examples/build/SimpleStorage.abi",
    "chars": 299,
    "preview": "[{\"constant\":false,\"inputs\":[{\"name\":\"_data\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutabili"
  },
  {
    "path": "test/examples/contracts/AddressTester.sol",
    "chars": 136,
    "preview": "pragma solidity ^0.4.18;\n\ncontract AddressTester {\n    function get(address a) public pure returns (address) {\n        r"
  },
  {
    "path": "test/examples/contracts/ArrayTester.sol",
    "chars": 244,
    "preview": "pragma solidity ^0.4.18;\n\ncontract ArrayTester {\n    function dynamicUint(uint[] ints) public pure returns (uint[]) {\n  "
  },
  {
    "path": "test/examples/contracts/Complex.sol",
    "chars": 1068,
    "preview": "pragma solidity ^0.4.0;\n\ncontract Complex {\n\n    uint foo;\n    bytes32 foobar;\n    bool barFoo;\n\n    event Bar(uint foo,"
  },
  {
    "path": "test/examples/contracts/EventTester.sol",
    "chars": 356,
    "preview": "pragma solidity ^0.4.18;\n\ncontract EventTester {\n\n    event Simple(uint256 num, bytes32 data);\n\n    event SimpleIndex(ui"
  },
  {
    "path": "test/examples/contracts/SimpleStorage.sol",
    "chars": 209,
    "preview": "pragma solidity ^0.4.0;\n\ncontract SimpleStorage {\n    uint data;\n\n    function set(uint _data) public {\n        data = _"
  },
  {
    "path": "test/exw3/abi_test.exs",
    "chars": 868,
    "preview": "defmodule ExW3.AbiTest do\n  use ExUnit.Case\n\n  test \".load_abi/1 returns a map keyed by function & event name\" do\n    as"
  },
  {
    "path": "test/exw3/address_test.exs",
    "chars": 1775,
    "preview": "defmodule ExW3.AddressTest do\n  use ExUnit.Case\n  alias ExW3.Address\n\n  @bytes_address <<25, 154, 209, 226, 223, 37, 243"
  },
  {
    "path": "test/exw3/client_test.exs",
    "chars": 1564,
    "preview": "defmodule ExW3.ClientTest do\n  use ExUnit.Case\n\n  test \".call_client/1 calls the JSON-RPC method with an empty list of a"
  },
  {
    "path": "test/exw3/contract_test.exs",
    "chars": 1633,
    "preview": "defmodule EXW3.ContractTest do\n  use ExUnit.Case\n  doctest ExW3.Contract\n\n  @simple_storage_abi ExW3.Abi.load_abi(\"test/"
  },
  {
    "path": "test/exw3/rpc_test.exs",
    "chars": 1340,
    "preview": "defmodule ExW3.RpcTest do\n  use ExUnit.Case\n\n  describe \".accounts\" do\n    test \"returns a list from the eth_accounts JS"
  },
  {
    "path": "test/exw3/utils_test.exs",
    "chars": 7761,
    "preview": "defmodule ExW3.UtilsTest do\n  use ExUnit.Case\n  doctest ExW3.Utils\n\n  describe \".hex_to_integer/1\" do\n    test \"parses a"
  },
  {
    "path": "test/exw3_test.exs",
    "chars": 12427,
    "preview": "defmodule ExW3Test do\n  use ExUnit.Case\n  doctest ExW3\n\n  setup_all do\n    start_supervised!(ExW3.Contract)\n\n    %{\n    "
  },
  {
    "path": "test/test_helper.exs",
    "chars": 15,
    "preview": "ExUnit.start()\n"
  }
]

About this extraction

This page contains the full source code of the hswick/exw3 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (92.2 KB), approximately 26.6k tokens, and a symbol index with 121 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!