Spaces:
Running
Running
| <!-- livebook:{"app_settings":{"access_type":"public","output_type":"rich","show_source":true,"slug":"helius-transaction-render"}} --> | |
| # Helius Transaction Render | |
| ```elixir | |
| Mix.install([ | |
| {:req, "~> 0.3.4"}, | |
| {:jason, "~> 1.4.0"}, | |
| {:kino, "~> 0.12.3"} | |
| ]) | |
| ``` | |
| ## 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 | |
| ```elixir | |
| # Define transaction fetching logic | |
| defmodule HeliusFetch do | |
| def fetch_transaction(signature, api_key) do | |
| transactions_url = "https://api.helius.xyz/v0/transactions" | |
| response = | |
| Req.post!( | |
| transactions_url, | |
| params: ["api-key": api_key], | |
| json: %{transactions: [signature]} | |
| ) | |
| case response do | |
| %Req.Response{status: 200, body: body} -> | |
| {:ok, List.first(body)} | |
| %Req.Response{body: body} -> | |
| {:error, body} | |
| end | |
| end | |
| end | |
| ``` | |
| ```elixir | |
| import Kino.Shorts | |
| ``` | |
| ````elixir | |
| # 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"] | |
| markdown(""" | |
| **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 | |
| markdown(""" | |
| ### #{name} | |
| ```json | |
| #{Jason.encode!(event, pretty: true)} | |
| ``` | |
| """) | |
| end | |
| defp render_events(transaction) do | |
| events = transaction["events"] | |
| case events do | |
| %{} -> | |
| text("No events") | |
| _ -> | |
| events | |
| |> Enum.map(fn {name, event} -> render_event(name, event) end) | |
| |> Kino.Layout.grid() | |
| end | |
| 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"] | |
| case native_transfers do | |
| [] -> | |
| text("No native transfers") | |
| _ -> | |
| 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} | |
| """ | |
| mermaid(diagram) | |
| end | |
| 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"] | |
| case token_transfers do | |
| [] -> | |
| text("No token transfers") | |
| _ -> | |
| diagram_lines = | |
| token_transfers | |
| |> Enum.map(fn transfer -> token_transfer_diagram_line(transfer) end) | |
| |> Enum.join("\n") | |
| diagram = """ | |
| flowchart LR | |
| #{diagram_lines} | |
| """ | |
| mermaid(diagram) | |
| end | |
| end | |
| def render(transaction) do | |
| Kino.Layout.tabs( | |
| Summary: render_summary(transaction), | |
| Tree: tree(transaction), | |
| Events: render_events(transaction), | |
| "Native Transfers": render_native_transfers(transaction), | |
| "Token Transfers": render_token_transfers(transaction) | |
| ) | |
| end | |
| end | |
| ```` | |
| ## Fetch a transaction | |
| ```elixir | |
| form = | |
| Kino.Control.form( | |
| [ | |
| signature: | |
| Kino.Input.text("Transaction Signature", | |
| default: | |
| "5r4xyeKUJkagGvNGpzKd7rE2LuxPJfhZfiub7JrqS28gFHY2Z18H557srUCPbiJQErW2XA4xBoZGQpLjDE8wyFs4" | |
| ), | |
| api_key: Kino.Input.password("Helius API Key") | |
| ], | |
| submit: "Fetch" | |
| ) | |
| form |> Kino.render() | |
| frame = frame() | |
| ``` | |
| This next code block does all the magic | |
| You just need to evaluate it :) | |
| ```elixir | |
| Kino.listen(form, fn event -> | |
| signature_length = byte_size(event.data.signature) | |
| api_key_length = byte_size(event.data.api_key) | |
| origin = event.origin | |
| case {signature_length, api_key_length} do | |
| {0, _} -> | |
| Kino.Frame.render( | |
| frame, | |
| Kino.Markdown.new("**No transaction signature given**"), | |
| to: origin | |
| ) | |
| {_, 0} -> | |
| Kino.Frame.render( | |
| frame, | |
| Kino.Markdown.new("**No Helius API key given**"), | |
| to: origin | |
| ) | |
| _ -> | |
| case HeliusFetch.fetch_transaction(event.data.signature, event.data.api_key) do | |
| {:ok, transaction} -> | |
| Kino.Frame.render(frame, TransactionRender.render(transaction), to: origin) | |
| {:error, error} -> | |
| Kino.Frame.render(frame, markdown("*Error*: #{inspect(error)}"), to: origin) | |
| end | |
| end | |
| end) | |
| ``` | |