defmodule Web3 do @moduledoc """ TODO [ ] add web3 moduledoc """ use Web3.Utils alias Web3.{HTTP, Dispatcher} defmacro __using__(opts) do quote do require Logger import unquote(__MODULE__) @before_compile unquote(__MODULE__) Module.register_attribute(__MODULE__, :registered_middleware, accumulate: true) Module.register_attribute(__MODULE__, :registered_methods, accumulate: true) Module.register_attribute(__MODULE__, :registered_contracts, accumulate: true) @default_middleware [ Web3.Middleware.Parser, Web3.Middleware.RequestInspector, Web3.Middleware.ResponseFormatter ] @default_methods [ {:eth_blockNumber, return_fn: :hex}, {:eth_getBalance, args: 2, return_fn: :hex}, {:eth_gasPrice, return_fn: :hex}, {:eth_getTransactionReceipt, args: 1}, {:eth_getBlockByHash, args: 2}, {:eth_getBlockByNumber, args: 2}, {:eth_getTransactionCount, args: 2, return_fn: :hex}, {:eth_getLogs, args: 1}, {:eth_sendRawTransaction, args: 1}, {:eth_getCode, args: 2}, {:net_version, return_fn: :integer}, {:eth_getTransactionByHash, args: 1}, {:eth_getUncleByBlockHashAndIndex, args: 2}, {:eth_getTransactionByBlockHashAndIndex, args: 2}, {:eth_getTransactionByBlockNumberAndIndex, args: 2}, {:eth_getBlockTransactionCountByHash, args: 1}, {:eth_getBlockTransactionCountByNumber, args: 1} ] @default_config [ http: Web3.HTTP.HTTPoison, http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :web3]] ] @config compile_config(__MODULE__, @default_config, unquote(opts)) end end defmacro __before_compile__(env) do # middleware default_middleware = env.module |> Module.get_attribute(:default_middleware, []) |> Enum.reverse() registered_middleware = env.module |> Module.get_attribute(:registered_middleware, []) |> Enum.reverse() middleware = Enum.reduce(default_middleware, registered_middleware, fn middleware, acc -> [middleware | acc] end) # methods default_methods = env.module |> Module.get_attribute(:default_methods, []) registered_methods = env.module |> Module.get_attribute(:registered_methods, []) methods = default_methods ++ registered_methods # contracts registered_contracts = env.module |> Module.get_attribute(:registered_contracts, []) global_config = env.module |> Module.get_attribute(:config) |> Keyword.put(:middleware, middleware) dispatch_defs = for {method, opts} <- methods do new_opts = global_config |> Keyword.merge(opts) defdispatch(method, new_opts) end contract_defs = for {contract_name, opts} <- registered_contracts do contract_name = Module.concat(__CALLER__.module, contract_name) new_opts = global_config |> Keyword.merge(opts) defcontract(contract_name, new_opts) end quote generated: true do # dispatch unquote(dispatch_defs) # contract unquote(contract_defs) def config(), do: unquote(global_config) @doc """ Execute Contract TODO [ ] add contract doc """ def execute_contract(requests, abi) do Web3.Contract.execute(requests, abi, unquote(global_config)) end end end def json_rpc(payload, json_rpc_arguments), do: HTTP.json_rpc(payload, json_rpc_arguments) defp defdispatch(method, opts \\ []) do # validate method # :ok = parse_method(method) arg_number = Keyword.get(opts, :args, 0) method_name = Keyword.get(opts, :name, method) return_fn = Keyword.get(opts, :return_fn, :raw) middleware = Keyword.get(opts, :middleware, []) json_rpc_arguments = [ http: Keyword.get(opts, :http), http_options: Keyword.get(opts, :http_options, []), rpc_endpoint: Keyword.get(opts, :rpc_endpoint) ] args = Macro.generate_arguments(arg_number, __MODULE__) quote do def unquote(method_name)(unquote_splicing(args)) do payload = %Dispatcher.Payload{ json_rpc_arguments: unquote(json_rpc_arguments), args: unquote(args), method_name: unquote(method_name), method: unquote(method), return_fn: unquote(return_fn), middleware: unquote(middleware) } Dispatcher.dispatch(payload) end end end defp defcontract(contract_name, opts \\ []) do quote do defmodule unquote(contract_name) do use Web3.ABI.Compiler, unquote(opts) end end end defmacro middleware(middleware_module) do quote do @registered_middleware unquote(middleware_module) end end defmacro contract(contract_name, opts) do quote do @registered_contracts {unquote(contract_name), unquote(opts)} end end defmacro dispatch(method, opts) do # :ok = parse_method(method) opts = parse_opts(opts, []) quote do @registered_methods {unquote(method), unquote(opts)} end end def compile_config(module_name, default_config, opts) do config = Application.get_env(:web3, module_name, []) default_config |> Keyword.merge(config) |> Keyword.merge(opts) end @register_methods [ :eth_blockNumber, :eth_getBalance, :eth_gasPrice, :eth_getTransactionReceipt ] def parse_method(method) do unless method in @register_methods do raise """ unexpected dispatch parameter "#{method}" available params are: #{Enum.map_join(@register_methods, ", ", &to_string/1)} """ else :ok end end @register_params [ :name, :args, :return_fn ] defp parse_opts([{:name, alias_name} | opts], result) when is_binary(alias_name) do parse_opts(opts, [{:name, String.to_atom(alias_name)} | result]) end defp parse_opts([{param, value} | opts], result) when param in @register_params do parse_opts(opts, [{param, value} | result]) end defp parse_opts([{param, _value} | _opts], _result) do raise """ unexpected dispatch parameter "#{param}" available params are: #{Enum.map_join(@register_params, ", ", &to_string/1)} """ end defp parse_opts([], result), do: result end