Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Helius Transaction Render

helius.livemd

Helius Transaction Render

Mix.install([
  {:req, "~> 0.3.4"},
  {:jason, "~> 1.4.0"},
  {:kino, "~> 0.8.0"}
])

Code Setup

This section includes all the Elixir code to fetch and render the given transaction

You don’t need to edit any of it

# Define transaction fetching logic
defmodule HeliusFetch do
  def fetch_transaction(signature, api_key) do
    transactions_url = "https://api.helius.xyz/v0/transactions"

    Req.post!(
      transactions_url,
      params: ["api-key": api_key],
      json: %{transactions: [signature]}
    ).body
    |> List.first()
  end
end

Kino.nothing()
# Define transaction rendering logic
defmodule TransactionRender do
  defp truncate(string, length) do
    start = String.slice(string, 0, length)
    last = String.slice(string, 0 - length, length)
    start <> "..." <> last
  end

  defp render_summary(transaction) do
    source = transaction["source"]
    type = transaction["type"]
    description = transaction["description"]
    fee_payer = transaction["feePayer"]

    Kino.Markdown.new("""
    **Source**: #{source}

    **Type**: #{type}

    **Description**: #{description}

    **Fee Payer**: [#{truncate(fee_payer, 8)}](https://explorer.solana.com/address/#{fee_payer})
    """)
  end

  defp render_event(name, event) do
    Kino.Markdown.new("""
    ### #{name}

    ```json
    #{Jason.encode!(event, pretty: true)}
    ```
    """)
  end

  defp render_events(transaction) do
    events =
      transaction["events"]
      |> Enum.map(fn {name, event} -> render_event(name, event) end)

    Kino.Layout.grid(events)
  end

  defp native_transfer_diagram_line(transfer) do
    from =
      case transfer["fromUserAccount"] do
        "" -> "none"
        address -> truncate(address, 4)
      end

    to =
      case transfer["toUserAccount"] do
        "" -> "none"
        address -> truncate(address, 4)
      end

    amount = (transfer["amount"] / 1_000_000_000) |> Float.round(4)
    label = "#{amount} SOL"
    # Mermaid diagram line starting with 2 spaces
    "  #{from}-...->|#{label}|#{to}"
  end

  defp render_native_transfers(transaction) do
    native_transfers = transaction["nativeTransfers"]

    diagram_lines =
      native_transfers
      |> Enum.filter(fn transfer -> transfer["amount"] > 0 end)
      |> Enum.map(fn transfer -> native_transfer_diagram_line(transfer) end)
      |> Enum.join("\n")

    diagram = """
    flowchart LR
    #{diagram_lines}
    """

    Kino.Mermaid.new(diagram)
  end

  defp token_transfer_diagram_line(transfer) do
    from =
      case transfer["fromUserAccount"] do
        "" -> "none"
        address -> truncate(address, 4)
      end

    to =
      case transfer["toUserAccount"] do
        "" -> "none"
        address -> truncate(address, 4)
      end

    token_amount = transfer["tokenAmount"]
    mint = truncate(transfer["mint"], 4)
    label = "#{token_amount} #{mint}"
    "  #{from}-...->|#{label}|#{to}"
  end

  defp render_token_transfers(transaction) do
    token_transfers = transaction["tokenTransfers"]

    diagram_lines =
      token_transfers
      |> Enum.map(fn transfer -> token_transfer_diagram_line(transfer) end)
      |> Enum.join("\n")

    diagram = """
    flowchart LR 
    #{diagram_lines}
    """

    Kino.Mermaid.new(diagram)
  end

  def render(transaction) do
    Kino.Layout.tabs(
      Summary: render_summary(transaction),
      Tree: Kino.Tree.new(transaction),
      Events: render_events(transaction),
      "Native Transfers": render_native_transfers(transaction),
      "Token Transfers": render_token_transfers(transaction)
    )
  end
end

Kino.nothing()
# Agent to hold the latest transaction state
{:ok, last_transaction} = Agent.start_link(fn -> nil end)
Kino.nothing()

Fetch a transaction

form =
  Kino.Control.form(
    [
      signature: Kino.Input.text("Transaction Signature"),
      api_key: Kino.Input.password("Helius API Key")
    ],
    submit: "Fetch"
  )

form |> Kino.render()

frame = Kino.Frame.new()

This next code block does all the magic

You just need to evaluate it :)

for event <- Kino.Control.stream(form) do
  signature_length = byte_size(event.data.signature)
  api_key_length = byte_size(event.data.api_key)

  case {signature_length, api_key_length} do
    {0, _} ->
      Kino.Frame.render(
        frame,
        Kino.Markdown.new("**No transaction signature given**")
      )

    {_, 0} ->
      Kino.Frame.render(
        frame,
        Kino.Markdown.new("**No Helius API key given**")
      )

    _ ->
      transaction = HeliusFetch.fetch_transaction(event.data.signature, event.data.api_key)
      Agent.update(last_transaction, fn _ -> transaction end)
      Kino.Frame.render(frame, TransactionRender.render(transaction))
      Kino.nothing()
  end
end

Optional: write your own code!

The latest fetched transaction can be used at any time for any independent code

You can do whatever you like here!

# get the latest fetched transaction
transaction = Agent.get(last_transaction, fn transaction -> transaction end)
Kino.nothing()
# transaction is a map
transaction
# render it as a tree
transaction
|> Kino.Tree.new()
# convert to JSON
transaction
|> Jason.encode!(pretty: true)
|> IO.puts()
# sum all native balance changes
transaction["accountData"]
|> Enum.reduce(0, fn data, acc ->
  acc + data["nativeBalanceChange"]
end)