dev_arweave.erl - Arweave Network Interface Device
Overview
Purpose: Provide access to Arweave network information via designated relay nodes
Module: dev_arweave
Device Name: arweave@2.9 or arweave@2.9-pre
Network: Arweave blockchain and gateway API
This device acts as a proxy to Arweave network, relaying transaction and block data from configured nodes. It handles transaction uploads, retrievals, block queries, and caches responses locally for performance. The relay nodes can be configured through the /arweave route in the node's configuration.
Supported Operations
- Transaction Upload: POST transactions to Arweave via bundler
- Transaction Retrieval: GET transaction headers and data
- Block Queries: Retrieve blocks by ID, height, or current
- Network Status: Proxy
/infoendpoint from Arweave nodes - Automatic Caching: Stores retrieved data in local cache
Dependencies
- HyperBEAM:
hb_client,hb_cache,hb_http,hb_ao,hb_message,hb_util,hb_maps - Arweave:
dev_arweave_block_cache - Includes:
include/hb.hrl - Testing:
eunit
Public Functions Overview
%% Transaction Operations
-spec tx(Base, Request, Opts) -> {ok, TX} | {error, Reason}.
%% Block Operations
-spec block(Base, Request, Opts) -> {ok, Block} | {error, Reason}.
-spec current(Base, Request, Opts) -> {ok, CurrentBlock} | {error, Reason}.
%% Network Status
-spec status(Base, Request, Opts) -> {ok, NetworkInfo} | {error, Reason}.Public Functions
1. tx/3
-spec tx(Base, Request, Opts) -> {ok, TX} | {error, Reason}
when
Base :: map(),
Request :: map(),
Opts :: map(),
TX :: map(),
Reason :: term().Description: Upload (POST) or retrieve (GET) a transaction from Arweave. For uploads, ensures the transaction is cached locally after successful upload. For retrievals, optionally includes transaction data based on the data key setting.
false- Do not retrieve data (header only)always- Error if data unavailabletrue(default) - Include data if available, header only otherwise
-module(dev_arweave_tx_test).
-include_lib("eunit/include/eunit.hrl").
post_transaction_test() ->
ServerOpts = #{ store => [hb_test_utils:test_store()] },
Server = hb_http_server:start_node(ServerOpts),
ClientOpts = #{
store => [hb_test_utils:test_store()],
priv_wallet => hb:wallet()
},
Msg = hb_message:commit(
#{
<<"variant">> => <<"ao.N.1">>,
<<"type">> => <<"Process">>,
<<"data">> => <<"test-data">>
},
ClientOpts,
#{ <<"commitment-device">> => <<"ans104@1.0">> }
),
{ok, PostRes} = hb_http:post(
Server,
Msg#{
<<"path">> => <<"/~arweave@2.9-pre/tx">>,
<<"codec-device">> => <<"ans104@1.0">>
},
ClientOpts
),
?assertMatch(#{ <<"status">> := 200 }, PostRes),
SignedID = hb_message:id(Msg, signed, ClientOpts),
{ok, GetRes} = hb_http:get(
Server,
<<"/", SignedID/binary>>,
ClientOpts
),
?assertMatch(
#{
<<"status">> := 200,
<<"variant">> := <<"ao.N.1">>,
<<"data">> := <<"test-data">>
},
GetRes
).
get_transaction_header_only_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
% Using a known valid Arweave transaction ID
Request = #{
<<"tx">> => <<"sHqUBKFeS42-CMCvNqPR31yEP63qSJG3ImshfwzJJF8">>,
<<"data">> => false,
<<"method">> => <<"GET">>
},
{ok, Result} = dev_arweave:tx(Base, Request, Opts),
% Verify we get a valid transaction header with expected fields
?assert(maps:is_key(<<"id">>, Result) orelse maps:is_key(<<"owner">>, Result)).
get_transaction_with_data_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
% Using a known valid Arweave transaction ID
Request = #{
<<"tx">> => <<"sHqUBKFeS42-CMCvNqPR31yEP63qSJG3ImshfwzJJF8">>,
<<"data">> => true,
<<"method">> => <<"GET">>
},
{ok, Result} = dev_arweave:tx(Base, Request, Opts),
?assert(maps:is_key(<<"data">>, Result)).2. block/3
-spec block(Base, Request, Opts) -> {ok, Block} | {error, Reason}
when
Base :: map(),
Request :: map(),
Opts :: map(),
Block :: map(),
Reason :: binary().Description: Retrieve block information from Arweave by ID, height, or current. Automatically caches blocks and creates pseudo-paths for height-based lookups. If no block key specified, returns current block.
<<"current">>- Current block (default)- 43-char binary - Block ID/hash
- Integer string - Block height
not_found- Defaults to current
-module(dev_arweave_block_test).
-include_lib("eunit/include/eunit.hrl").
get_current_block_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
Request = #{},
{ok, Block} = dev_arweave:block(Base, Request, Opts),
?assert(is_map(Block)),
?assert(maps:is_key(<<"height">>, Block)),
?assert(maps:is_key(<<"indep_hash">>, Block)).
get_block_by_id_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
BlockID = <<"abcdefghijklmnopqrstuvwxyz012345678901234">>, % 43 chars
Request = #{
<<"block">> => BlockID
},
Result = dev_arweave:block(Base, Request, Opts),
case Result of
{ok, Block} ->
?assertEqual(BlockID, maps:get(<<"indep_hash">>, Block));
{error, _} ->
ok % Block may not exist
end.
get_block_by_height_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
Request = #{
<<"block">> => <<"1000000">>
},
Result = dev_arweave:block(Base, Request, Opts),
case Result of
{ok, Block} ->
?assertEqual(1000000, maps:get(<<"height">>, Block));
{error, _} ->
ok % Block may not exist
end.
invalid_block_reference_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
Base = #{},
Request = #{
<<"block">> => <<"invalid-reference!@#">>
},
Result = dev_arweave:block(Base, Request, Opts),
?assertMatch({error, _}, Result).3. current/3
-spec current(Base, Request, Opts) -> {ok, CurrentBlock} | {error, Reason}
when
Base :: map(),
Request :: map(),
Opts :: map(),
CurrentBlock :: map(),
Reason :: term().Description: Retrieve the current block information from Arweave network.
Test Code:-module(dev_arweave_current_test).
-include_lib("eunit/include/eunit.hrl").
current_block_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
{ok, Block} = dev_arweave:current(#{}, #{}, Opts),
?assert(is_map(Block)),
?assert(maps:is_key(<<"height">>, Block)),
?assert(maps:is_key(<<"timestamp">>, Block)),
?assert(maps:is_key(<<"indep_hash">>, Block)),
?assert(maps:is_key(<<"hash">>, Block)).4. status/3
-spec status(Base, Request, Opts) -> {ok, NetworkInfo} | {error, Reason}
when
Base :: map(),
Request :: map(),
Opts :: map(),
NetworkInfo :: map(),
Reason :: term().Description: Proxy the /info endpoint from the Arweave node, returning network information such as version, block count, and network status.
-module(dev_arweave_status_test).
-include_lib("eunit/include/eunit.hrl").
get_network_status_test() ->
Opts = #{ store => [hb_test_utils:test_store()] },
{ok, Info} = dev_arweave:status(#{}, #{}, Opts),
?assert(is_map(Info)),
?assert(maps:is_key(<<"network">>, Info) orelse
maps:is_key(<<"version">>, Info)).Request Flow
Transaction Upload (POST)
1. Client creates signed transaction
2. Upload via hb_client:upload/2
3. Cache transaction locally on success
4. Return upload responseTransaction Retrieval (GET)
1. Extract TXID from request/base
2. Request TX header from Arweave
3. Optionally request TX data
4. Cache combined result
5. Return to clientBlock Retrieval
1. Determine block reference (ID/height/current)
2. Check local cache first
3. If not cached, request from Arweave
4. Cache block and create pseudo-paths
5. Return block dataCaching Strategy
Transaction Caching
- Uploaded transactions automatically cached
- Retrieved transactions cached for future access
- Cache keys: Transaction ID
Block Caching
- All retrieved blocks cached
- Multiple pseudo-paths created:
- Block ID (independent hash)
- Block hash (dependent hash)
- Height-based path:
~arweave@2.9/block/height/N
- Enables fast lookups by any reference type
Common Patterns
%% Upload a signed transaction
Wallet = ar_wallet:new(),
Msg = hb_message:commit(
#{
<<"variant">> => <<"ao.N.1">>,
<<"type">> => <<"Message">>,
<<"data">> => <<"Hello Arweave">>
},
#{ priv_wallet => Wallet },
#{ <<"commitment-device">> => <<"ans104@1.0">> }
),
{ok, Response} = hb_http:post(
Node,
Msg#{
<<"path">> => <<"/~arweave@2.9/tx">>,
<<"codec-device">> => <<"ans104@1.0">>
},
#{ priv_wallet => Wallet }
).
%% Get transaction with data
Request = #{
<<"tx">> => TXID,
<<"data">> => true,
<<"method">> => <<"GET">>
},
{ok, TX} = dev_arweave:tx(#{}, Request, Opts).
%% Get transaction header only
Request = #{
<<"tx">> => TXID,
<<"data">> => false,
<<"method">> => <<"GET">>
},
{ok, Header} = dev_arweave:tx(#{}, Request, Opts).
%% Get current block
{ok, Block} = dev_arweave:current(#{}, #{}, Opts).
%% Get block by height
Request = #{
<<"block">> => <<"1234567">>
},
{ok, Block} = dev_arweave:block(#{}, Request, Opts).
%% Get block by ID
Request = #{
<<"block">> => <<"valid-43-char-block-id-here-1234567890">>
},
{ok, Block} = dev_arweave:block(#{}, Request, Opts).
%% Get network status
{ok, Status} = dev_arweave:status(#{}, #{}, Opts).Configuration
Relay Node Configuration
Configure Arweave relay nodes through the node's configuration message:
NodeConfig = #{
routes => [
#{
<<"template">> => <<"/arweave/*">>,
<<"node">> => #{
<<"url">> => <<"https://arweave.net">>
}
}
]
}Response Format
Transaction Response
#{
<<"id">> => <<"transaction-id">>,
<<"owner">> => <<"public-key">>,
<<"target">> => <<"target-address">>,
<<"tags">> => [{<<"name">>, <<"value">>}],
<<"data">> => <<"transaction-data">>, % Optional
% ... other transaction fields
}Block Response
#{
<<"height">> => 1234567,
<<"indep_hash">> => <<"independent-hash-43-chars">>,
<<"hash">> => <<"dependent-hash-43-chars">>,
<<"timestamp">> => 1234567890,
<<"txs">> => [<<"tx-id-1">>, <<"tx-id-2">>],
% ... other block fields
}Network Status Response
#{
<<"network">> => <<"arweave.N.1">>,
<<"version">> => 5,
<<"blocks">> => 1234567,
<<"peers">> => 42,
% ... other status fields
}Error Handling
Common Errors
Transaction Not Found:{error, not_found}{error, <<"Invalid block reference `bad-ref`">>}always flag):
{error, data_not_available}{error, #{
<<"status">> => 500,
<<"body">> => <<"Upload failed">>
}}Internal Operations
Transaction ID Resolution
- Check
txkey in Request - Check
txkey in Base - Return
not_foundif neither present
Response Transformation
- Raw Data:
/raw/*paths return binary data directly - Block Data:
/block/*paths parsed as JSON and cached - Other Data: All other responses parsed from JSON to structured format
References
- Block Cache -
dev_arweave_block_cache.erl - Client Operations -
hb_client.erl - Cache System -
hb_cache.erl - HTTP Client -
hb_http.erl - ANS-104 -
ar_bundles.erl
Notes
- Automatic Caching: All retrieved data cached for performance
- Data Retrieval: Optional based on
datakey in request - Block Indexing: Multiple pseudo-paths for flexible lookups
- Upload Integration: Uses configured bundler for transaction uploads
- JSON Conversion: Automatic conversion between JSON and structured formats
- Relay Configuration: Nodes specified in route configuration
- Error Propagation: Network errors returned to client
- Cache-First: Checks cache before network requests
- Height Lookups: Blocks indexed by height for fast access
- ID Linkage: Block IDs linked to message IDs in cache
- POST/GET Support: Both upload and retrieval operations
- Method Detection: Uses
methodkey to distinguish operations - Content-Type Handling: Manages different response formats
- Test Integration: Designed for easy testing with local stores
- ANS-104 Support: Full support for bundled transaction uploads