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) | |
``` | |