Skip to content

dev_query_arweave.erl - Arweave GraphQL Query Device

Overview

Purpose: Implement Arweave GraphQL API queries within the ~query@1.0 device
Module: dev_query_arweave
Parent Device: query@1.0
Query Types: Transactions, Blocks, and their fields

This module provides GraphQL-style query capabilities for Arweave data stored in the local cache. It supports querying transactions and blocks by various criteria including IDs, tags, owners, recipients, and block heights.

Supported Query Arguments

-define(SUPPORTED_QUERY_ARGS,
    [
        <<"height">>,      % Block height range
        <<"id">>,          % Single transaction/block ID
        <<"ids">>,         % Multiple IDs
        <<"tags">>,        % Tag filters
        <<"owners">>,      % Owner address filters
        <<"recipients">>   % Recipient address filters
    ]
).

Dependencies

  • HyperBEAM: hb_ao, hb_cache, hb_store, hb_opts, hb_message, hb_util, hb_maps, hb_crypto
  • Query: dev_query_graphql
  • Arweave: dev_arweave_block_cache, dev_codec_httpsig_keyid
  • Testing: eunit
  • Includes: include/hb.hrl

Public Functions Overview

%% Query Handler
-spec query(Object, Field, Args, Opts) -> {ok, Result}.

Public Functions

query/4

-spec query(Object, Field, Args, Opts) -> {ok, Result}
    when
        Object :: map() | list() | term(),
        Field :: binary(),
        Args :: map(),
        Opts :: map(),
        Result :: term().

Description: Handle Arweave GraphQL queries for transactions, blocks, and their fields. Supports nested field resolution following GraphQL patterns.

Test Code:
-module(dev_query_arweave_test).
-include_lib("eunit/include/eunit.hrl").
 
query_edges_test() ->
    List = [#{<<"a">> => 1}, #{<<"b">> => 2}],
    {ok, Edges} = dev_query_arweave:query(List, <<"edges">>, #{}, #{}),
    ?assertEqual([{ok, #{<<"a">> => 1}}, {ok, #{<<"b">> => 2}}], Edges).
 
query_node_test() ->
    Msg = #{<<"data">> => <<"test">>},
    {ok, Result} = dev_query_arweave:query(Msg, <<"node">>, #{}, #{}),
    ?assertEqual(Msg, Result).
 
query_block_fields_test() ->
    Block = #{
        <<"previous_block">> => <<"prev-hash">>,
        <<"height">> => 12345,
        <<"timestamp">> => 1234567890
    },
    {ok, Prev} = dev_query_arweave:query(Block, <<"previous">>, #{}, #{}),
    ?assertEqual(<<"prev-hash">>, Prev),
    {ok, Height} = dev_query_arweave:query(Block, <<"height">>, #{}, #{}),
    ?assertEqual(12345, Height),
    {ok, Timestamp} = dev_query_arweave:query(Block, <<"timestamp">>, #{}, #{}),
    ?assertEqual(1234567890, Timestamp).
 
query_owner_fields_test() ->
    Owner = #{ <<"key">> => <<"pubkey">>, <<"address">> => <<"addr">> },
    {ok, Key} = dev_query_arweave:query(Owner, <<"key">>, #{}, #{}),
    ?assertEqual(<<"pubkey">>, Key),
    {ok, Addr} = dev_query_arweave:query(Owner, <<"address">>, #{}, #{}),
    ?assertEqual(<<"addr">>, Addr).
 
query_winston_test() ->
    {ok, Amount} = dev_query_arweave:query(12345, <<"winston">>, #{}, #{}),
    ?assertEqual(12345, Amount).
 
query_data_fields_test() ->
    DataObj = #{ <<"data">> => <<"hello world">>, <<"type">> => <<"text/plain">> },
    {ok, Size} = dev_query_arweave:query(DataObj, <<"size">>, #{}, #{}),
    ?assertEqual(11, Size),
    {ok, Type} = dev_query_arweave:query(DataObj, <<"type">>, #{}, #{}),
    ?assertEqual(<<"text/plain">>, Type).

Supported Fields

Collection Queries

transactions / transaction

Query for transactions matching specified criteria.

query(Obj, <<"transactions">>, Args, Opts) -> {ok, [Message]}.
query(Obj, <<"transaction">>, Args, Opts) -> {ok, Message | null}.

blocks / block

Query for blocks matching specified criteria.

query(Obj, <<"blocks">>, Args, Opts) -> {ok, [Block]}.
query(Obj, <<"block">>, Args, Opts) -> {ok, Block | null}.

edges / node

Navigate GraphQL edge/node structure.

query(List, <<"edges">>, Args, Opts) -> {ok, [{ok, Msg} || Msg <- List]}.
query(Msg, <<"node">>, Args, Opts) -> {ok, Msg}.

Block Fields

previous

Get the previous block hash.

query(Block, <<"previous">>, _Args, Opts) -> {ok, PreviousBlockHash | null}.

height

Get the block height.

query(Block, <<"height">>, _Args, Opts) -> {ok, Height | null}.

timestamp

Get the block timestamp.

query(Block, <<"timestamp">>, _Args, Opts) -> {ok, Timestamp | null}.

Transaction Fields

signature

Get the transaction signature from commitments.

query(Msg, <<"signature">>, _Args, Opts) -> {ok, Signature | null}.

owner

Get the owner address and public key.

query(Msg, <<"owner">>, _Args, Opts) -> {ok, #{ <<"address">> => Addr, <<"key">> => Key } | null}.

key / address

Extract key or address from owner object.

query(#{ <<"key">> := Key }, <<"key">>, _Args, _Opts) -> {ok, Key}.
query(#{ <<"address">> := Address }, <<"address">>, _Args, _Opts) -> {ok, Address}.

fee / quantity

Get transaction fee or quantity (in Winston).

query(Msg, <<"fee">>, _Args, Opts) -> {ok, Fee}.
query(Msg, <<"quantity">>, _Args, Opts) -> {ok, Quantity}.
query(Number, <<"winston">>, _Args, _Opts) -> {ok, Number}.

recipient

Get the transaction recipient.

query(Msg, <<"recipient">>, _Args, Opts) -> {ok, Recipient | <<"">>}.

anchor

Get the transaction anchor (last transaction hash).

query(Msg, <<"anchor">>, _Args, Opts) -> {ok, Anchor | <<"">>}.

data

Get transaction data with content type.

query(Msg, <<"data">>, _Args, Opts) -> {ok, #{ <<"data">> => Data, <<"type">> => ContentType }}.

size / type

Get data size or content type from data object.

query(#{ <<"data">> := Data }, <<"size">>, _Args, _Opts) -> {ok, byte_size(Data)}.
query(#{ <<"type">> := Type }, <<"type">>, _Args, _Opts) -> {ok, Type}.

Query Matching

match/3

Generates matches for each supported query argument.

ArgumentDescriptionMatch Method
heightBlock height rangeEnumerate heights, check store
idSingle IDDirect lookup
idsMultiple IDsDirect lookup list
tagsTag key-value pairsCache template match
ownersOwner addressesCommitment field match
recipientsRecipient addressesCommitment field match

Common Patterns

%% Query transactions by tag
Args = #{
    <<"tags">> => [
        #{ <<"name">> => <<"App-Name">>, <<"values">> => [<<"MyApp">>] },
        #{ <<"name">> => <<"Type">>, <<"values">> => [<<"Message">>] }
    ]
},
{ok, Transactions} = dev_query_arweave:query(#{}, <<"transactions">>, Args, Opts).
 
%% Query single transaction by ID
{ok, TX} = dev_query_arweave:query(#{}, <<"transaction">>, #{ <<"id">> => TXID }, Opts).
 
%% Query blocks by height range
{ok, Blocks} = dev_query_arweave:query(
    #{},
    <<"blocks">>,
    #{ <<"height">> => #{ <<"min">> => 1000000, <<"max">> => 1000010 } },
    Opts
).
 
%% Query by owner address
{ok, OwnerTXs} = dev_query_arweave:query(
    #{},
    <<"transactions">>,
    #{ <<"owners">> => [OwnerAddress] },
    Opts
).
 
%% Navigate GraphQL-style response
{ok, Transactions} = dev_query_arweave:query(#{}, <<"transactions">>, Args, Opts),
{ok, Edges} = dev_query_arweave:query(Transactions, <<"edges">>, #{}, Opts),
lists:foreach(
    fun({ok, Edge}) ->
        {ok, Node} = dev_query_arweave:query(Edge, <<"node">>, #{}, Opts),
        {ok, Owner} = dev_query_arweave:query(Node, <<"owner">>, #{}, Opts),
        {ok, Address} = dev_query_arweave:query(Owner, <<"address">>, #{}, Opts),
        io:format("TX from: ~s~n", [Address])
    end,
    Edges
).
 
%% Get transaction data with metadata
{ok, TX} = dev_query_arweave:query(#{}, <<"transaction">>, #{ <<"id">> => ID }, Opts),
{ok, DataObj} = dev_query_arweave:query(TX, <<"data">>, #{}, Opts),
{ok, Size} = dev_query_arweave:query(DataObj, <<"size">>, #{}, Opts),
{ok, Type} = dev_query_arweave:query(DataObj, <<"type">>, #{}, Opts).

Store Scoping

The query scope can be configured to limit which stores are searched:

%% Default: local store only
Opts = #{ query_arweave_scope => [local] }.
 
%% Include remote stores
Opts = #{ query_arweave_scope => [local, remote] }.

ID Resolution

The module handles multiple ID types for messages:

  1. Base ID: Unsigned message ID
  2. Commitment IDs: IDs derived from signatures
  3. Store Paths: Resolved filesystem paths
%% Find all IDs for a message
all_ids(ID, Opts) ->
    Store = hb_opts:get(store, no_store, Opts),
    case hb_store:list(Store, << ID/binary, "/commitments">>) of
        {ok, []} -> [ID];
        {ok, CommitmentIDs} -> CommitmentIDs;
        _ -> []
    end.

References

  • GraphQL Device - dev_query_graphql.erl
  • Query Device - dev_query.erl
  • Block Cache - dev_arweave_block_cache.erl
  • Cache System - hb_cache.erl
  • Message Handling - hb_message.erl

Notes

  1. GraphQL Compatibility: Follows Arweave GraphQL API patterns
  2. Local Cache: Queries local cache, not Arweave network directly
  3. Commitment Fields: Owner/recipient extracted from message commitments
  4. Null Handling: Returns null for missing optional fields
  5. Height Range: Block height queries use min/max range
  6. Tag Matching: Uses hb_cache:match/2 with template patterns
  7. ID Intersection: Multiple query args produce intersection of results
  8. Edge/Node Pattern: Supports GraphQL-style connection pagination
  9. Data Fallback: Checks both data and body fields for content
  10. Store Scoping: Configurable store scope for queries