Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explorer #1

Merged
merged 1 commit into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

config :noncegeek, AptosEx, rpc_endpoint: "https://testnet.aptoslabs.com/v1"

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"
3 changes: 2 additions & 1 deletion lib/noncegeek/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ defmodule Noncegeek.Application do
# Start the PubSub system
{Phoenix.PubSub, name: Noncegeek.PubSub},
# Start the Endpoint (http/https)
NoncegeekWeb.Endpoint
NoncegeekWeb.Endpoint,
{AptosEx, Application.fetch_env!(:noncegeek, AptosEx)}
# Start a worker by calling: Noncegeek.Worker.start_link(arg)
# {Noncegeek.Worker, arg}
]
Expand Down
150 changes: 150 additions & 0 deletions lib/noncegeek/aptos_ex/aptos_ex.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
defmodule AptosEx do
@moduledoc """
The AptosEx module is a wrapper around the Aptos API.
"""

use GenServer

alias AptosEx.RPC

defmodule State do
defstruct ~w(
rpc_endpoint
client
)a
end

def start_link(options) do
rpc_endpoint = Keyword.get(options, :rpc_endpoint)

unless rpc_endpoint do
raise ArgumentError,
":rpc_endpoint must be provided to `#{__MODULE__}`" <>
"to allow for json_rpc calls when running"
end

GenServer.start_link(__MODULE__, rpc_endpoint, name: __MODULE__)
end

@doc """
Get Aptos client
"""
def get_aptos_client() do
with {:ok, client} <- GenServer.call(__MODULE__, :client) do
client
else
_ ->
raise RuntimeError, "connect aptos rpc failed, please check your config"
end
end

@impl GenServer
def init(rpc_endpoint) do
state = %State{
rpc_endpoint: rpc_endpoint
}

{:ok, state, {:continue, :start}}
end

@impl GenServer
def handle_continue(:start, %State{rpc_endpoint: rpc_endpoint} = state) do
client = RPC.connect(rpc_endpoint)
{:noreply, %{state | client: client}}
end

@impl GenServer
def handle_call(:client, _, %{client: client} = state) do
{:reply, client, state}
end

@doc """
Get Aptos account
"""
def get_account(address) do
client = get_aptos_client()
RPC.get_account(client, address)
end

@doc """
Get account resources
"""
def get_account_resources(address, query \\ []) do
client = get_aptos_client()
RPC.get_account_resources(client, address, query)
end

@doc """
Get account resource
"""
def get_account_resource(address, resource_type, query \\ []) do
client = get_aptos_client()
RPC.get_account_resource(client, address, resource_type, query)
end

@doc """
Get transaction by hash.
"""
def get_transaction_by_hash(hash) do
client = get_aptos_client()
RPC.get_transaction_by_hash(client, hash)
end

@doc """
Check transaction result.
"""
def check_transaction_by_hash(hash, times \\ 3) do
client = get_aptos_client()
RPC.check_transaction_by_hash(client, hash, times)
end

@doc """
Get events by event_key
"""
def get_events(event_key) do
client = get_aptos_client()
RPC.get_events(client, event_key)
end

@doc """
Get events by address event_handle
"""
def get_events(address, event_handle, field, query \\ [limit: 10]) do
client = get_aptos_client()
RPC.get_events(client, address, event_handle, field, query)
end

@doc """
Get table item
"""
def get_table_item(table_handle, table_key) do
client = get_aptos_client()
RPC.get_table_item(client, table_handle, table_key)
end

@doc """
Get token detail

## Examples

iex> AptosEx.get_token_data("0xe19430a2498ff6800666d41cfd4b64d6d2a53574ef7457f700f96f4a61703d07", "DummyNames", "dummy1")

"""
def get_token_data(creator, collection_name, token_name) do
client = get_aptos_client()
RPC.get_token_data(client, creator, collection_name, token_name)
end

@doc """
Get collection data

## Examples

iex> AptosEx.get_collection_data("0xe19430a2498ff6800666d41cfd4b64d6d2a53574ef7457f700f96f4a61703d07", "DummyNames")

"""
def get_collection_data(account, collection_name) do
client = get_aptos_client()
RPC.get_collection_data(client, account, collection_name)
end
end
144 changes: 144 additions & 0 deletions lib/noncegeek/aptos_ex/rpc.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
defmodule AptosEx.RPC do
@moduledoc false

defstruct [:endpoint, :client, :chain_id]

# @endpoint "https://fullnode.devnet.aptoslabs.com/v1"
@endpoint "https://testnet.aptoslabs.com/v1"

def connect(endpoint \\ @endpoint) do
client =
Tesla.client([
# TODO: convert input/output type
{Tesla.Middleware.BaseUrl, endpoint},
# {Tesla.Middleware.Headers, [{"content-type", "application/json"}]},
{Tesla.Middleware.JSON, engine_opts: [keys: :atoms]}
])

rpc = %__MODULE__{client: client, endpoint: endpoint}

with {:ok, %{chain_id: chain_id}} <- ledger_information(rpc) do
{:ok, %{rpc | endpoint: endpoint, chain_id: chain_id}}
end
end

defp get(%{client: client}, path, options \\ []) do
with {:ok, %{status: 200, body: resp_body}} <- Tesla.get(client, path, options) do
{:ok, resp_body}
else
{:ok, %{body: resp_body}} -> {:error, resp_body}
{:error, error} -> {:error, error}
end
end

defp post(%{client: client}, path, body, options \\ []) do
with {:ok, %{body: resp_body}} <- Tesla.post(client, path, body, options) do
case resp_body do
%{code: _, message: message} -> {:error, message}
_ -> {:ok, resp_body}
end
else
{:error, error} -> {:error, error}
end
end

# Chain
def ledger_information(client) do
get(client, "/")
end

# Accounts
def get_account(client, address) do
get(client, "/accounts/#{address}")
end

def get_account_resources(client, address, query \\ []) do
get(client, "/accounts/#{address}/resources", query: query)
end

def get_account_resource(client, address, resource_type, query \\ []) do
get(client, "/accounts/#{address}/resource/#{resource_type}", query: query)
end

# Transactions
def get_transaction_by_hash(client, hash) do
get(client, "/transactions/by_hash/#{hash}")
end

def check_transaction_by_hash(client, hash, times \\ 3) do
case get_transaction_by_hash(client, hash) do
{:ok, result} ->
result.success

{:error, _} ->
if times > 0 do
Process.sleep(1000)
check_transaction_by_hash(client, hash, times - 1)
else
false
end
end
end

# Events
def get_events(client, event_key) do
case get(client, "/events/#{event_key}") do
{:ok, event_list} -> {:ok, event_list}
{:error, %{error_code: "resource_not_found"}} -> {:ok, []}
end
end

def get_events(client, address, event_handle, field, query \\ [limit: 10]) do
case get(client, "/accounts/#{address}/events/#{event_handle}/#{field}", query: query) do
{:ok, event_list} -> {:ok, event_list}
{:error, %{error_code: "resource_not_found"}} -> {:ok, []}
end
end

# Table
def get_table_item(client, table_handle, table_key) do
post(client, "/tables/#{table_handle}/item", table_key)
end

# Tokens
def get_token_data(client, creator, collection_name, token_name) do
with {:ok, result} <- get_account_resource(client, creator, "0x3::token::Collections") do
%{handle: handle} = result.data.token_data

token_data_id = %{
creator: creator,
collection: collection_name,
name: token_name
}

table_key = %{
key_type: "0x3::token::TokenDataId",
value_type: "0x3::token::TokenData",
key: token_data_id
}

get_table_item(client, handle, table_key)
end
end

def get_collection_data(client, account, collection_name) do
with {:ok, result} <- get_account_resource(client, account, "0x3::token::Collections") do
%{handle: handle} = result.data.collection_data

table_key = %{
key_type: "0x1::string::String",
value_type: "0x3::token::CollectionData",
key: collection_name
}

{:ok, result} = get_table_item(client, handle, table_key)

case result do
%{error_code: _} -> {:error, result}
_ -> {:ok, result}
end
else
_ -> {:error, "Token data not found"}
end
end
end
47 changes: 47 additions & 0 deletions lib/noncegeek/explorer/models/event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule Noncegeek.Explorer.Model.Event do
@moduledoc false

use Ecto.Schema
import Ecto.Changeset

@timestamps_opts [type: :utc_datetime_usec]
schema "events" do
field :sequence_number, :integer
field :type, :string

field :account_address, :string

field :creation_number, :integer
field :version, :integer

# field :token_id, :map
# field :collection_id, :map
field :amount, :decimal
field :data, :map

belongs_to :token, Lotus.Explorer.Model.Token,
foreign_key: :token_id,
references: :token_id,
type: :map

timestamps()
end

@doc false
def changeset(event, attrs) do
required_fields = ~w(
account_address
sequence_number
token_id
type
data
version
)a

optional_fields = ~w(amount)a

event
|> cast(attrs, required_fields ++ optional_fields)
|> validate_required(required_fields)
end
end
Loading