defmodule ExW3 do def reformat_abi(abi) do Map.new(Enum.map(abi, fn x -> {x["name"], x} end)) end def load_abi(file_path) do file = File.read(Path.join(System.cwd(), file_path)) case file do {:ok, abi} -> reformat_abi(Poison.Parser.parse!(abi)) err -> err end end 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) output_signature = Enum.join([name, "(", Enum.join(output_types, ")"), ")"]) ABI.decode(output_signature, trim_output) end def encode_input(abi, name, input) do if abi[name]["inputs"] do input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end) input_signature = Enum.join([name, "(", Enum.join(input_types, ","), ")"]) ABI.encode(input_signature, input) |> Base.encode16(case: :lower) else raise "#{name} method not found with the given abi" end end def bytes_to_string bytes do bytes |> Base.encode16(case: :lower) |> String.replace_trailing("0", "") |> Hexate.decode end def accounts do case Ethereumex.HttpClient.eth_accounts() do {:ok, accounts} -> accounts err -> err end end # Converts ethereum hex string to decimal number def to_decimal(hex_string) do hex_string |> String.slice(2..-1) |> String.to_integer(16) end def block_number do case Ethereumex.HttpClient.eth_block_number() do {:ok, block_number} -> block_number |> to_decimal err -> err end end def balance(account) do case Ethereumex.HttpClient.eth_get_balance(account) do {:ok, balance} -> balance |> to_decimal err -> err end end def keys_to_decimal(map, keys) do Map.new( Enum.map(keys, fn k -> {k, Map.get(map, k) |> to_decimal} end) ) end def tx_receipt(tx_hash) do case Ethereumex.HttpClient.eth_get_transaction_receipt(tx_hash) do {:ok, receipt} -> Map.merge receipt, keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"]) err -> err end end def block(block_number) do case Ethereumex.HttpClient.eth_get_block_by_number(block_number, true) do {:ok, block} -> block err -> err end end def mine(num_blocks \\ 1) do for _ <- 0..(num_blocks - 1) do Ethereumex.HttpClient.request("evm_mine", [], []) end end def encode_event(signature) do ExthCrypto.Hash.Keccak.kec(signature) |> Base.encode16(case: :lower) end 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 defmodule Contract do use Agent def at(abi, address) do {:ok, pid} = Agent.start_link(fn -> %{abi: abi, address: address, events: setup_events(abi)} end) pid end defp setup_events(abi) do events = Enum.filter abi, fn {_, v} -> v["type"] == "event" end signature_types_map = Enum.map events, fn {name, v} -> types = Enum.map v["inputs"], &Map.get(&1, "type") names = Enum.map v["inputs"], &Map.get(&1, "name") signature = Enum.join([name, "(", Enum.join(types, ","), ")"]) {"0x#{ExW3.encode_event(signature)}", %{signature: signature, names: names}} end Enum.into signature_types_map, %{} end def get(contract, key) do Agent.get(contract, &Map.get(&1, key)) end @doc """ Puts the `value` for the given `key` in the `contract`. """ def put(contract, key, value) do Agent.update(contract, &Map.put(&1, key, value)) end def deploy(bin_filename, options) do {:ok, bin} = File.read(Path.join(System.cwd(), bin_filename)) tx = %{ from: options[:from], data: bin, gas: options[:gas] } {:ok, tx_receipt_id} = Ethereumex.HttpClient.eth_send_transaction(tx) {:ok, tx_receipt} = Ethereumex.HttpClient.eth_get_transaction_receipt(tx_receipt_id) tx_receipt["contractAddress"] end def method(contract_agent, method_name, args \\ [], options \\ %{}) do method_name = case method_name |> is_atom do true -> Inflex.camelize(method_name, :lower) false -> method_name end input = ExW3.encode_input(get(contract_agent, :abi), method_name, args) if get(contract_agent, :abi)[method_name]["constant"] do {:ok, output} = Ethereumex.HttpClient.eth_call(%{ to: get(contract_agent, :address), data: input }) ([:ok] ++ ExW3.decode_output(get(contract_agent, :abi), method_name, output)) |> List.to_tuple() else Ethereumex.HttpClient.eth_send_transaction( Map.merge( %{ to: get(contract_agent, :address), data: input }, options ) ) end end def tx_receipt(contract_agent, tx_hash) do receipt = ExW3.tx_receipt tx_hash events = get(contract_agent, :events) logs = receipt["logs"] formatted_logs = Enum.map logs, fn log -> topic = Enum.at log["topics"], 0 event = Map.get events, topic if event do Enum.zip(event[:names], ExW3.decode_event(log["data"], event[:signature])) |> Enum.into(%{}) else nil end end {:ok, {receipt, formatted_logs}} end end end