defmodule Web3MoveEx.Aptos.RPC do
@moduledoc """
Copy From: https://github.com/myastrallabs/stormstout
Api Docs: https://aptos.dev/nodes/aptos-api-spec/#/
"""
alias Utils.ExHttp
defstruct [:endpoint, :client, :chain_id]
@endpoint %{
mainnet: "https://fullnode.mainnet.aptoslabs.com/v1",
testnet: "https://fullnode.testnet.aptoslabs.com/v1",
devnet: "https://fullnode.devnet.aptoslabs.com/v1"
}
@faucet %{
testnet: "https://faucet.testnet.aptoslabs.com",
devnet: "https://faucet.devnet.aptoslabs.com"
}
@doc """
{:ok, client} = Aptos.RPC.connect()
{:ok, result} = Aptos.RPC.get_token_data(
client,
"0xdc4e806913a006d86da8327a079d794435e2e3117fd418062ddf43943d663490",
"DummyDog",
"DummyDog 1"
)
"""
def get_token_data(client, creator, collection_name, token_name) do
with {:ok, result} <- get_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
@doc """
{:ok, client} = AptosRPC.connect()
{:ok, res} = Aptos.RPC.get_collection_data(
client,
"0xdc4e806913a006d86da8327a079d794435e2e3117fd418062ddf43943d663490",
"DummyDog"
)
"""
def get_collection_data(client, account, collection_name) do
with {:ok, result} <- get_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
def connect() do
connect(@endpoint.testnet)
end
def connect(:testnet) do
connect(@endpoint.testnet)
end
def connect(:devnet) do
connect(@endpoint.devnet)
end
def connect(:mainnet) do
connect(@endpoint.mainnet)
end
def connect(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
def connect(:faucet, network_type) do
endpoint = Map.get(@faucet, network_type)
client =
Tesla.client([
{Tesla.Middleware.BaseUrl, endpoint},
# {Tesla.Middleware.Headers, [{"content-type", "application/json"}]},
{Tesla.Middleware.JSON, engine_opts: [keys: :atoms]}
])
{:ok, %__MODULE__{client: client, endpoint: endpoint}}
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_resources(client, address, query \\ []) do
get(client, "/accounts/#{address}/resources", query: query)
end
def get_resource(client, address, resource_type) do
path = build_resource(client, address, resource_type)
ExHttp.http_get(path)
end
def build_resource(%{endpoint: endpoint}, address, resource_type) do
"#{endpoint}/accounts/#{address}/resource/#{resource_type}"
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
def build_event_path(%{endpoint: endpoint}, address, event_handle, field) do
"#{endpoint}/accounts/#{address}/events/#{event_handle}/#{field}"
end
@doc """
> https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/get_table_item
Example by curl:
```curl --request POST \
--url https://fullnode.testnet.aptoslabs.com/v1/tables/0x55faf86aea81d23c0f8ec9bb5fa6ec8fed920a3482ea75d5bf27474a00d42198/item \
--header 'Content-Type: application/json' \
--data '{
"key_type": "0x1::string::String",
"value_type": "0xb923303d20c38a120669ad0ed751a105f254b049e75a350111d566009df9ba11::addr_info::AddrInfo",
"key": "0x73c7448760517E3E6e416b2c130E3c6dB2026A1d"
}'
{"addr":"0x73c7448760517E3E6e416b2c130E3c6dB2026A1d","addr_type":"0","chains":["Ethereum"],"created_at":"1668826549","description":"Cool Addr","expired_at":"1700362549","id":"1","msg":"33344091.1.nonce_geek","pubkey":"","signature":"0x","updated_at":"0"}%
```
Example to call by func:
> Web3MoveEx.Aptos.RPC.get_table_item(client, "0x55faf86aea81d23c0f8ec9bb5fa6ec8fed920a3482ea75d5bf27474a00d42198", "0x1::string::String", "0xb923303d20c38a120669ad0ed751a105f254b049e75a350111d566009df9ba11::addr_info::AddrInfo", "0x73c7448760517E3E6e416b2c130E3c6dB2026A1d")
"""
def get_table_item(client, table_handle, key_type, value_type, key) do
payload = %{key_type: key_type, value_type: value_type, key: key}
get_table_item(client, table_handle, payload)
end
def get_table_item(client, table_handle, table_key) do
post(client, "/tables/#{table_handle}/item", table_key)
end
def submit_bcs_transaction(client, signed_transaction_in_bcs) do
post(client, "/transactions", signed_transaction_in_bcs,
headers: [{"content-type", "application/x.aptos.signed_transaction+bcs"}]
)
end
def get_faucet(client, address, amount \\ 100000000) do
post(client, "/mint?amount=#{amount}&address=#{address}", "")
end
end