From d5ff77b2782ccb29a3d5c446e51421144d2894c7 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 12 Dec 2025 09:53:24 +0900 Subject: [PATCH 1/7] WIP --- src/hz_grids.erl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hz_grids.erl b/src/hz_grids.erl index 4fc9381..17482c7 100644 --- a/src/hz_grids.erl +++ b/src/hz_grids.erl @@ -44,8 +44,8 @@ -spec url(Instruction, HTTP) -> Result when Instruction :: spend | transfer | sign, HTTP :: uri_string:uri_string(), - GRIDS :: uri_string:uri_string(), - Result :: {ok, GRIDS} | uri_string:uri_error(). + Result :: {ok, GRIDS} | uri_string:uri_error(), + GRIDS :: uri_string:uri_string(). %% @doc %% Takes @@ -66,6 +66,19 @@ url2(Instruction, URL = #{path := Path}) -> {ok, uri_string:recompose(GRIDS)}. +-spec url(Instruction, Context, Recipient, Payload, Amount) -> Result + when Instruction :: spend | transfer, + Context :: uri_string:uri_string() | string(), % + Recipient :: string(), + Payload :: binary(), + Amount :: non_neg_integer(), + Result :: {ok, GRIDS} | error | uri_string:uri_error(). + +url(spend, Context, Recipient, Payload, Amount) -> + url(spend, +url(transfer, Context, Recipient, Payload, Amount) -> + + -spec parse(GRIDS) -> Result when GRIDS :: string(), Result :: {ok, Instruction} | uri_string:error(), From 88c6f6dcc7d31876146f4c71a3cd801a97573a75 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 18 Dec 2025 04:13:01 +0900 Subject: [PATCH 2/7] Spend/Transfer URLs --- src/hz_grids.erl | 58 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/hz_grids.erl b/src/hz_grids.erl index 17482c7..7e54aa1 100644 --- a/src/hz_grids.erl +++ b/src/hz_grids.erl @@ -38,7 +38,7 @@ -module(hz_grids). -vsn("0.7.0"). --export([url/2, parse/1, req/2, req/3]). +-export([url/2, url/3, url/4, parse/1, req/2, req/3]). -spec url(Instruction, HTTP) -> Result @@ -66,17 +66,57 @@ url2(Instruction, URL = #{path := Path}) -> {ok, uri_string:recompose(GRIDS)}. --spec url(Instruction, Context, Recipient, Payload, Amount) -> Result - when Instruction :: spend | transfer, - Context :: uri_string:uri_string() | string(), % +-spec url(Instruction, Recipient, Amount) -> GRIDS + when Instruction :: {spend, Network} | {transfer, Node}, + Network :: string(), + Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} + | uri_string:uri_string(), Recipient :: string(), - Payload :: binary(), Amount :: non_neg_integer(), - Result :: {ok, GRIDS} | error | uri_string:uri_error(). + GRIDS :: uri_string:uri_string(). +%% @doc +%% Forms a GRIDS URL for spends or transfers. +%% @equiv uri(Instruction, Recipient, Amount, ""). -url(spend, Context, Recipient, Payload, Amount) -> - url(spend, -url(transfer, Context, Recipient, Payload, Amount) -> +url(Instruction, Recipient, Amount) -> + url(Instruction, Recipient, Amount, ""). + + +-spec url(Instruction, Recipient, Amount, Payload) -> GRIDS + when Instruction :: {spend, Network} | {transfer, Node}, + Network :: string(), + Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} + | uri_string:uri_string(), % "http://..." | "https://..." + Recipient :: string(), + Amount :: non_neg_integer(), + Payload :: binary(), + GRIDS :: uri_string:uri_string(). +%% @doc +%% Forms a GRIDS URL for spends or transfers. + +url({spend, Network}, Recipient, Amount, Payload) -> + Elements = ["grids://", Network, "/1/s/", Recipient, qwargs(Amount, Payload)], + unicode:characters_to_list(Elements); +url({transfer, Node}, Recipient, Amount, Payload) -> + Prefix = + case Node of + {H, P} -> ["grid://", h_to_s(H), ":", integer_to_list(P)]; + "https://" ++ H -> ["grids://", H]; + "http://" ++ H -> ["grid://", H]; + <<"https://", H/binary>> -> ["grids://", H]; + <<"http://", H/binary>> -> ["grid://", H] + end, + unicode:characters_to_list([Prefix, "/1/t/", Recipient, qwargs(Amount, Payload)]). + +h_to_s(Host) when is_list(Host) -> Host; +h_to_s(Host) when is_binary(Host) -> Host; +h_to_s(Host) when is_tuple(Host) -> inet:ntoa(Host); +h_to_s(Host) when is_atom(Host) -> atom_to_list(Host). + +qwargs(Amount, "") -> + ["?a=", integer_to_list(Amount)]; +qwargs(Amount, Payload) -> + [$? | uri_string:compose_query([{"a", integer_to_list(Amount)}, {"p", Payload}])]. -spec parse(GRIDS) -> Result From aeb78eab3872563ec1edad7ce2ee25225f54fedc Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 18 Dec 2025 09:03:21 +0900 Subject: [PATCH 3/7] Add AACI caching --- src/hz.erl | 30 +++++++++++++++++++++++++++--- src/hz_man.erl | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index b081f5d..59509db 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -66,6 +66,7 @@ contract_create/8, prepare_contract/1, prepare_aaci/1, + cache_aaci/2, aaci_lookup_spec/2, contract_call/5, contract_call/6, @@ -1073,7 +1074,7 @@ contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs :: [string()], Result :: {ok, CreateTX} | {error, Reason}, CreateTX :: binary(), - Reason :: file:posix() | term(). + Reason :: file:posix() | bad_fun_name | aaci_not_found | term(). %% @doc %% This function takes the compiler output (instead of starting from source), %% and returns the unsigned create contract call data with default values. @@ -2207,17 +2208,35 @@ zip_record_field({Name, Type}, {Remaining, Missing}) -> {missing, {Remaining, [Name | Missing]}} end. + +-spec cache_aaci(Label, AACI) -> ok + when Label :: term(), + AACI :: aaci(). +%% @doc +%% Caches an AACI for future reference in calls that would otherwise require +%% the AACI as an argument. Once cached, a pre-built AACI can be referenced in +%% later calls by substituting the AACI argument with `{aaci, Label}'. + +cache_aaci(Label, AACI) -> + hz_man:cache_aaci(Label, AACI). + + -spec aaci_lookup_spec(AACI, Fun) -> {ok, Type} | {error, Reason} when AACI :: aaci(), Fun :: binary() | string(), Type :: {term(), term()}, % FIXME - Reason :: bad_fun_name. + Reason :: bad_fun_name | aaci_not_found. %% @doc %% Look up the type information of a given function, in the AACI provided by %% prepare_contract/1. This type information, particularly the return type, is %% useful for calling decode_bytearray/2. +aaci_lookup_spec({aaci, Label}, Fun) -> + case hz_man:lookup_aaci(Label) of + {ok, AACI} -> aaci_lookup_spec(AACI, Fun); + error -> {error, aaci_not_found} + end; aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) -> case maps:find(Fun, FunDefs) of A = {ok, _} -> A; @@ -2248,10 +2267,15 @@ min_gas() -> 200000. +encode_call_data({aaci, Label}, Fun, Args) -> + case hz_man:lookup_aaci(Label) of + {ok, AACI} -> encode_call_data(AACI, Fun, Args); + error -> {error, aaci_not_found} + end; encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) -> case maps:find(Fun, FunDefs) of {ok, {ArgDef, _ResultDef}} -> encode_call_data2(ArgDef, Fun, Args); - error -> {error, bad_fun_name} + error -> {error, bad_fun_name} end. encode_call_data2(ArgDef, Fun, Args) -> diff --git a/src/hz_man.erl b/src/hz_man.erl index 18248ff..a2b353a 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -20,6 +20,9 @@ chain_nodes/0, chain_nodes/1, timeout/0, timeout/1]). +%% Contract caching +-export([cache_aaci/2, lookup_aaci/1]). + %% The whole point of this module: -export([request_sticky/1, request_sticky/2, request/1, request/2]). @@ -44,7 +47,8 @@ chain_nodes = {[], []} :: {[hz:chain_node()], [hz:chain_node()]}, sticky = none :: none | hz:chain_node(), fetchers = [] :: [#fetcher{}], - timeout = 5000 :: pos_integer()}). + timeout = 5000 :: pos_integer(), + cache = #{} :: #{Label :: term() := AACI :: hz:aaci()}}). -type state() :: #s{}. @@ -94,6 +98,22 @@ timeout(Value) when 0 < Value, Value =< 120000 -> gen_server:cast(?MODULE, {timeout, Value}). +-spec cache_aaci(Label, AACI) -> ok + when Label :: term(), + AACI :: hz:aaci(). + +cache_aaci(Label, AACI) -> + gen_server:call(?MODULE, {cache, Label, AACI}). + + +-spec lookup_aaci(Label) -> Result + when Label :: term(), + Result :: {ok, hz:aaci()} | error. + +lookup_aaci(Label) -> + gen_server:call(?MODULE, {lookup, Label}). + + -spec request_sticky(Path) -> {ok, Value} | {error, Reason} when Path :: unicode:charlist(), Value :: map(), @@ -167,6 +187,12 @@ handle_call({request, Request}, From, State) -> handle_call({request_sticky, Request}, From, State) -> NewState = do_request_sticky(Request, From, State), {noreply, NewState}; +handle_call({lookup, Label}, _, State) -> + Result = do_lookup(Label, State), + {reply, Result, State}; +handle_call({cache, Label, AACI}, _, State) -> + NewState = do_cache_aaci(Label, AACI, State), + {reply, ok, NewState}; handle_call(tls, _, State = #s{tls = TLS}) -> {reply, TLS, State}; handle_call(chain_nodes, _, State) -> @@ -265,6 +291,15 @@ do_tls(_, State) -> State. +do_cache_aaci(Label, AACI, State = #s{cache = Cache}) -> + NewCache = maps:put(Label, AACI, Cache), + State#s{cache = NewCache}. + + +do_lookup(Label, #s{cache = Cache}) -> + maps:find(Label, Cache). + + do_request_sticky(_, From, State = #s{sticky = none}) -> ok = gen_server:reply(From, {error, no_nodes}), State; From 4ee6609111a81b10206f21224f080e450b1481a4 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 18 Dec 2025 09:06:53 +0900 Subject: [PATCH 4/7] Add lookup interface in hz --- src/hz.erl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hz.erl b/src/hz.erl index 59509db..66699c0 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -67,6 +67,7 @@ prepare_contract/1, prepare_aaci/1, cache_aaci/2, + lookup_aaci/1, aaci_lookup_spec/2, contract_call/5, contract_call/6, @@ -2221,6 +2222,16 @@ cache_aaci(Label, AACI) -> hz_man:cache_aaci(Label, AACI). +-spec lookup_aaci(Label) -> Result + when Label :: term(), + Result :: {ok, aaci()} | error. +%% @doc +%% Retrieve a previously prepared and cached AACI. + +lookup_aaci(Label) -> + hz_man:lookup_aaci(Label). + + -spec aaci_lookup_spec(AACI, Fun) -> {ok, Type} | {error, Reason} when AACI :: aaci(), Fun :: binary() | string(), From 1978ca59b36a5961a5c28985f8eaf2f882e2fc45 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 18 Dec 2025 10:37:06 +0900 Subject: [PATCH 5/7] Update specs --- src/hz.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 66699c0..a10cd9c 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1189,7 +1189,7 @@ read_aci(Path) -> -spec contract_call(CallerID, AACI, ConID, Fun, Args) -> Result when CallerID :: unicode:chardata(), - AACI :: aaci(), + AACI :: aaci() | {aaci, Label :: term()}, ConID :: unicode:chardata(), Fun :: string(), Args :: [string()], @@ -1224,7 +1224,7 @@ contract_call(CallerID, AACI, ConID, Fun, Args) -> -spec contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> Result when CallerID :: unicode:chardata(), Gas :: pos_integer(), - AACI :: aaci(), + AACI :: aaci() | {aaci, Label :: term()}, ConID :: unicode:chardata(), Fun :: string(), Args :: [string()], @@ -1262,7 +1262,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> GasPrice :: pos_integer(), Amount :: non_neg_integer(), TTL :: non_neg_integer(), - AACI :: aaci(), + AACI :: aaci() | {aaci, Label :: term()}, ConID :: unicode:chardata(), Fun :: string(), Args :: [string()], @@ -2233,7 +2233,7 @@ lookup_aaci(Label) -> -spec aaci_lookup_spec(AACI, Fun) -> {ok, Type} | {error, Reason} - when AACI :: aaci(), + when AACI :: aaci() | {aaci, Label :: term()}, Fun :: binary() | string(), Type :: {term(), term()}, % FIXME Reason :: bad_fun_name | aaci_not_found. @@ -2243,15 +2243,15 @@ lookup_aaci(Label) -> %% prepare_contract/1. This type information, particularly the return type, is %% useful for calling decode_bytearray/2. -aaci_lookup_spec({aaci, Label}, Fun) -> - case hz_man:lookup_aaci(Label) of - {ok, AACI} -> aaci_lookup_spec(AACI, Fun); - error -> {error, aaci_not_found} - end; aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) -> case maps:find(Fun, FunDefs) of A = {ok, _} -> A; error -> {error, bad_fun_name} + end; +aaci_lookup_spec({aaci, Label}, Fun) -> + case hz_man:lookup_aaci(Label) of + {ok, AACI} -> aaci_lookup_spec(AACI, Fun); + error -> {error, aaci_not_found} end. -spec min_gas_price() -> integer(). @@ -2278,15 +2278,15 @@ min_gas() -> 200000. -encode_call_data({aaci, Label}, Fun, Args) -> - case hz_man:lookup_aaci(Label) of - {ok, AACI} -> encode_call_data(AACI, Fun, Args); - error -> {error, aaci_not_found} - end; encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) -> case maps:find(Fun, FunDefs) of {ok, {ArgDef, _ResultDef}} -> encode_call_data2(ArgDef, Fun, Args); error -> {error, bad_fun_name} + end; +encode_call_data({aaci, Label}, Fun, Args) -> + case hz_man:lookup_aaci(Label) of + {ok, AACI} -> encode_call_data(AACI, Fun, Args); + error -> {error, aaci_not_found} end. encode_call_data2(ArgDef, Fun, Args) -> From 61984d15296b3c8f34c1fb3168ce8190ba0cf1e9 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Thu, 18 Dec 2025 17:48:50 -0800 Subject: [PATCH 6/7] fix ooutdated web/repo links in zomp.meta --- zomp.meta | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zomp.meta b/zomp.meta index f798ddc..69bcfaf 100644 --- a/zomp.meta +++ b/zomp.meta @@ -19,6 +19,6 @@ {copyright,"Craig Everett"}. {file_exts,[]}. {license,"MIT"}. -{repo_url,"https://gitlab.com/ioecs/hakuzaru"}. +{repo_url,"https://git.qpq.swiss/QPQ-AG/hakuzaru"}. {tags,["qpq","gajumaru","blockchain","hakuzaru","crypto","defi"]}. -{ws_url,"https://gitlab.com/ioecs/hakuzaru"}. +{ws_url,"https://git.qpq.swiss/QPQ-AG/hakuzaru"}. From f5e955b5836ab56b7dd7fd9c60bcb5af167c47cf Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 19 Dec 2025 21:34:06 +0900 Subject: [PATCH 7/7] Allow for a 'none' amount to prompt users for an amount. --- src/hz_grids.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hz_grids.erl b/src/hz_grids.erl index 7e54aa1..6a9c6de 100644 --- a/src/hz_grids.erl +++ b/src/hz_grids.erl @@ -88,7 +88,7 @@ url(Instruction, Recipient, Amount) -> Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} | uri_string:uri_string(), % "http://..." | "https://..." Recipient :: string(), - Amount :: non_neg_integer(), + Amount :: non_neg_integer() | none, Payload :: binary(), GRIDS :: uri_string:uri_string(). %% @doc @@ -113,8 +113,12 @@ h_to_s(Host) when is_binary(Host) -> Host; h_to_s(Host) when is_tuple(Host) -> inet:ntoa(Host); h_to_s(Host) when is_atom(Host) -> atom_to_list(Host). +qwargs(none, "") -> + []; qwargs(Amount, "") -> ["?a=", integer_to_list(Amount)]; +qwargs(none, Payload) -> + [$? | uri_string:compose_query([{"p", Payload}])]; qwargs(Amount, Payload) -> [$? | uri_string:compose_query([{"a", integer_to_list(Amount)}, {"p", Payload}])].