Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save leeduckgo/958ae1253e048740e5f501967d2ceba6 to your computer and use it in GitHub Desktop.
Save leeduckgo/958ae1253e048740e5f501967d2ceba6 to your computer and use it in GitHub Desktop.
CodesOnChain.Contracts.Bodhi
defmodule CodesOnChain.Contracts.Bodhi do
@moduledoc """
the interact with bodhi on op chain.
> https://mainnet.optimism.io
> 10
> https://explorer.optimism.io
"""
alias Ethereumex.HttpClient
alias Components.Transaction
require Logger
@endpoint "https://mainnet.optimism.io"
@chain_id 10
@contract_addr "0x2AD82A4E39Bac43A54DdfE6f94980AAf0D1409eF"
@arweave_node "https://arweave.net"
@func %{
asset_index: "assetIndex()",
assets: "assets(uint256)",
balance_of: "balanceOf(address, uint256)",
buy: "buy(uint256, uint256)",
get_buy_price_after_fee: "getBuyPriceAfterFee(uint256, uint256)",
create: "create(string)"
}
@contract_addr_space_factory "0xA14D19387C83b56343fC2E7a8707986aF6a74d08"
@func_space_factory %{
space_index: "spaceIndex()",
spaces: "spaces(uint256)"
}
# An example space: 0xBEAFc083600efC2376648bFF353Ce8A3EcaA1463
@func_space %{
asset_to_creator: "assetToCreator(uint256)",
create: "create(string, uint256)"
}
@gas_limit 1_000_000
def get_module_doc, do: @moduledoc
# +--------------+
# | Space Reader |
# +--------------+
def get_true_creator(contract_addr, asset_id) do
data = TypeTranslator.get_data(@func_space.asset_to_creator, [asset_id])
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
[addr] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([:address])
"0x" <> Base.encode16(addr, case: :lower)
end
# +--------------+
# | Space Write |
# +--------------+
def create_and_buy_space(priv, contract_addr, ar_tx_id, amount) do
asset_id = get_asset_index()
# amount = 0.01
# batch send create and buy.
# 0. get nonce.
# gen addr from priv
acct =
priv
|> TypeTranslator.addr_to_bin()
|> EthWallet.generate_keys()
nonce = Transaction.get_nonce(acct.addr, [url: @endpoint, request_timeout: 1000])
# 1. build create tx.
# 2. build buy tx.
data_create = ABI.encode(@func_space.create, [ar_tx_id, 0])
# get gas
{:ok, gas_price} = HttpClient.eth_gas_price([url: @endpoint, request_timeout: 1000])
gas_limit = @gas_limit
tx_create = %Transaction{
from: acct.addr,
to: contract_addr,
gas_limit: gas_limit,
gas_price: TypeTranslator.hex_to_int(gas_price),
value: 0,
data: data_create
}
amount_raw =
"#{amount}"
|> Decimal.new()
|> Decimal.mult(1_000_000_000_000_000_000)
|> Decimal.to_integer()
# get data
data = ABI.encode(@func.buy, [asset_id, amount_raw])
# get the buy price.
# price_raw = 21000000000000
price_raw = get_buy_price_after_fee(asset_id, amount)
tx = %Transaction{
from: acct.addr,
to: @contract_addr,
gas_limit: gas_limit,
gas_price: TypeTranslator.hex_to_int(gas_price),
value: price_raw,
data: data
}
# 3. send & send.
tx_result = Transaction.send(
@chain_id,
acct.priv,
tx_create,
nonce,
[url: @endpoint, request_timeout: 500])
tx_result_2 = Transaction.send(
@chain_id,
acct.priv,
tx,
nonce + 1 ,
[url: @endpoint, request_timeout: 500])
%{tx_result_1: tx_result, tx_result_2: tx_result_2}
end
# +--------------+
# | Asset Reader |
# +--------------+
def get_buy_price_after_fee(asset_id, amount) do
amount_raw =
"#{amount}"
|> Decimal.new()
|> Decimal.mult(1_000_000_000_000_000_000)
|> Decimal.to_integer()
data = TypeTranslator.get_data(@func.get_buy_price_after_fee, [asset_id, amount_raw])
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[price_raw] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}])
price_raw + 1_500_000_000_000_000
end
def balance_of(addr, asset_id) do
# addr_bin
addr_bin = TypeTranslator.addr_to_bin(addr)
# get data
data = TypeTranslator.get_data(@func.balance_of, [addr_bin, asset_id])
# get raw response
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[balance] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}])
balance
|> Decimal.div(1_000_000_000_000_000_000)
|> Decimal.to_float()
end
# +------------------------+
# | RAW Data Fetcher Space |
# +------------------------+
def get_space_index() do
data = TypeTranslator.get_data(@func_space_factory.space_index, [])
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr_space_factory
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
[index] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}])
index
end
def get_spaces(begin_space_id, end_space_id) do
Enum.map(begin_space_id..end_space_id, fn space_id ->
get_space(space_id)
end)
end
# TODO: check events to get all the assets under this space.
# I think that it could be use api of opscan here.
def get_logs(contract_addr, begin_block) do
end
def get_space(space_id) do
# get data
data = TypeTranslator.get_data(@func_space_factory.spaces, [space_id])
# get raw response
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr_space_factory
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[addr] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([:address])
"0x" <> Base.encode16(addr, case: :lower)
end
# +------------------+
# | RAW Data Fetcher |
# +------------------+
def get_assets(begin_asset_id, end_asset_id) do
Enum.map(begin_asset_id..end_asset_id, fn asset_id ->
get_asset(asset_id)
end)
end
def get_asset_index() do
data = TypeTranslator.get_data(@func.asset_index, [])
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
[index] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}])
index
end
def get_asset(asset_id) do
# get data
data = TypeTranslator.get_data(@func.assets, [asset_id])
# get raw response
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[asset_id, ar_resource, addr] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}, :string, :address])
addr = "0x" <> Base.encode16(addr, case: :lower)
[asset_id, ar_resource, addr]
end
# +----------------+
# | Write Tx Funcs |
# +----------------+
def create_and_buy(priv, ar_tx_id, amount) do
asset_id = get_asset_index()
# amount = 0.01
# batch send create and buy.
# 0. get nonce.
# gen addr from priv
acct =
priv
|> TypeTranslator.addr_to_bin()
|> EthWallet.generate_keys()
nonce = Transaction.get_nonce(acct.addr, [url: @endpoint, request_timeout: 1000])
# 1. build create tx.
# 2. build buy tx.
data_create = ABI.encode(@func.create, [ar_tx_id])
# get gas
{:ok, gas_price} = HttpClient.eth_gas_price([url: @endpoint, request_timeout: 1000])
gas_limit = @gas_limit
tx_create = %Transaction{
from: acct.addr,
to: @contract_addr,
gas_limit: gas_limit,
gas_price: TypeTranslator.hex_to_int(gas_price),
value: 0,
data: data_create
}
amount_raw =
"#{amount}"
|> Decimal.new()
|> Decimal.mult(1_000_000_000_000_000_000)
|> Decimal.to_integer()
# get data
data = ABI.encode(@func.buy, [asset_id, amount_raw])
# get the buy price.
# price_raw = 21000000000000
price_raw = get_buy_price_after_fee(asset_id, amount)
tx = %Transaction{
from: acct.addr,
to: @contract_addr,
gas_limit: gas_limit,
gas_price: TypeTranslator.hex_to_int(gas_price),
value: price_raw,
data: data
}
# 3. send & send.
tx_result = Transaction.send(
@chain_id,
acct.priv,
tx_create,
nonce,
[url: @endpoint, request_timeout: 500])
Logger.info(inspect(tx_result))
tx_result_2 = Transaction.send(
@chain_id,
acct.priv,
tx,
nonce + 1 ,
[url: @endpoint, request_timeout: 500])
Logger.info(inspect(tx_result_2))
end
def buy(priv, asset_id, amount) do
# gen amount_raw
amount_raw =
"#{amount}"
|> Decimal.new()
|> Decimal.mult(1_000_000_000_000_000_000)
|> Decimal.to_integer()
# gen addr from priv
acct =
priv
|> TypeTranslator.addr_to_bin()
|> EthWallet.generate_keys()
# get the buy price.
# price_raw = 21000000000000
price_raw = get_buy_price_after_fee(asset_id, amount)
# get gas
gas_limit = @gas_limit
{:ok, gas_price} = HttpClient.eth_gas_price([url: @endpoint, request_timeout: 1000])
# get data
data = ABI.encode(@func.buy, [asset_id, amount_raw])
# buy the asset.
tx = %Transaction{
from: acct.addr,
to: @contract_addr,
gas_limit: gas_limit,
gas_price: TypeTranslator.hex_to_int(gas_price),
value: price_raw,
data: data
}
# Transaction.send(
# @chain_id,
# acct.priv,
# tx,
# [url: @endpoint, request_timeout: 1000])
tx_result = Transaction.send(
@chain_id,
acct.priv,
tx,
[url: @endpoint, request_timeout: 1000])
inspect(tx_result)
end
# +----------------------+
# | Text Data Translator |
# +----------------------+
@doc """
params:
{
"type": "INSERT",
"table": "raw_data",
"record": {
"id": 13,
"ar_tx_id": "5HpDDzIe40n3c_7CH-3XECQxTf14VeUfAgfxkc3BZDo",
"content": "fdsfsdsfdsfd",
"created_at": "2023-12-16T22:22:08.542514+00:00"
},
"schema": "public",
"old_record": null
}
"""
def supabase_raw_to_text_data(payload) do
%{
id_on_chain: id_on_chain,
ar_tx_id: ar_tx_id,
creator: creator
} = payload.record
content = get_content(ar_tx_id)
tags =
case get_tags(ar_tx_id) do
{:ok, tags} ->
tags
_ ->
%{}
end
result =
if is_map(content) do
cond do
content.type in ["text/markdown; charset=utf-8", "text/plain; charset=utf-8"] ->
%{
id_on_chain: id_on_chain,
content: content.content,
creator: creator,
tags: tags
}
true ->
"pass"
end
else
"pass"
end
result
end
def supabase_raw_to_img_data(payload) do
%{
id_on_chain: id_on_chain,
ar_tx_id: ar_tx_id,
creator: creator
} = payload.record
content = get_content(ar_tx_id)
result =
if is_map(content) do
cond do
String.contains?(content.type, "image") ->
%{
id_on_chain: id_on_chain,
content: content.content,
creator: creator
}
true ->
"pass"
end
else
"pass"
end
result
end
def supabase_get_assets(from, to) do
Enum.map(from..to, fn asset ->
supabase_get_asset(asset)
end)
end
def supabase_get_asset(asset_id) do
# get data
data = TypeTranslator.get_data(@func.assets, [asset_id])
# get raw response
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[asset_id, ar_resource, addr] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}, :string, :address])
addr = "0x" <> Base.encode16(addr, case: :lower)
%{
id_on_chain: asset_id,
ar_tx_id: ar_resource,
creator: addr
}
end
def supabase_text_data_to_vector_data(payload) do
%{
id_on_chain: id_on_chain,
content: content,
creator: creator
} = payload.record
metadata = %{id: id_on_chain, type: "text/markdown; charset=utf-8", creator: creator}
paragraphs = String.split(content, "\n")
Enum.map(paragraphs, fn paragraph ->
%{data: paragraph, metadata: metadata}
end)
|> List.flatten()
|> Enum.reject(fn %{data: data, metadata: _metadata} ->
data == "\r"
end)
end
def handle_audio(content) do
if String.contains?(content, "<audio") do
%{
has_audio: true,
link: fetch_audio_link(content)}
else
%{
has_audio: false
}
end
end
def fetch_audio_link(content) do
[_pre, payload] = String.split(content, "<audio src=\"")
[link, _aft] = String.split(payload, "\"")
link
end
def get_tags(ar_tx_id) do
try do
result = ArweaveSdkEx.get_tx(@arweave_node, ar_tx_id)
case result do
{:ok, %{decoded_tags: tags}} ->
{:ok, tags}
{:error, "404"} ->
{:error, "404"}
error ->
{:error, "Unexpected error: #{inspect(error)}"}
end
rescue
error ->
{:error, inspect(error)}
end
end
def get_content(ar_tx_id) do
try do
{:ok, content} =
ArweaveSdkEx.get_content_in_tx(@arweave_node, ar_tx_id)
content
rescue
error ->
inspect(error)
end
end
def to_text_database(asset_lists) do
asset_lists
|> Enum.map(fn [id_on_chain, ar_tx_id, creator] ->
content =
get_content(ar_tx_id)
Logger.info("-- get_content_from_arweave --")
[id_on_chain, content, creator]
end)
|> Enum.filter(fn [_id_on_chain, content, _creator] ->
is_map(content)
end)
|> Enum.filter(fn [_id_on_chain, content, _creator] ->
content.type == "text/markdown; charset=utf-8"
end)
|> Enum.map(fn [id_on_chain, content, creator] ->
[id_on_chain, content.content, creator]
end)
end
# +--------------------------+
# | VectorDB Data Translator |
# +--------------------------+
def to_vector_db(asset_lists) do
asset_lists
|> Enum.map(fn [id_on_chain, content, creator] ->
metadata = %{id: id_on_chain, type: "text/markdown; charset=utf-8", creator: creator}
paragraphs = String.split(content, "\n")
Enum.map(paragraphs, fn paragraph ->
%{data: paragraph, metadata: metadata}
end)
end)
|> List.flatten()
|> Enum.reject(fn %{data: data, metadata: _metadata} ->
data == "\r"
end)
end
end
defmodule CodesOnChain.Contracts.Bodhi.VectorTagger do
@moduledoc """
the interact with vectorTagger on op chain.
> https://mainnet.optimism.io
> 10
> https://explorer.optimism.io
"""
alias Ethereumex.HttpClient
require Logger
@endpoint "https://mainnet.optimism.io"
@contract_addr "0xbF3ED49679E75BdA9E5c99954cdFbb7a60D7CE03"
@func %{
tag_index: "tagIndex()",
tags: "tags(uint256)",
}
def get_module_doc, do: @moduledoc
# +------------------+
# | RAW Data Fetcher |
# +------------------+
def supabase_get_tags(from, to) do
Enum.map(from..to, fn tag_id ->
supabase_get_tag(tag_id)
end)
end
def supabase_get_tag(tag_id) do
# get data
data = TypeTranslator.get_data(@func.tags, [tag_id])
# get raw response
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
# decode
[asset_id, metadata, addr] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}, :string, :address])
addr = "0x" <> Base.encode16(addr, case: :lower)
%{
asset_id: asset_id,
metadata: metadata,
creator: addr
}
end
def get_tag_index() do
data = TypeTranslator.get_data(@func.tag_index, [])
{:ok, raw} =
HttpClient.eth_call(
%{
data: data,
to: @contract_addr
},
"latest",
[url: @endpoint, request_timeout: 1000]
)
[index] =
raw
|> Binary.drop(2)
|> Base.decode16!(case: :lower)
|> ABI.TypeDecoder.decode_raw([{:uint, 256}])
index
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment