%%% @doc %%% GRIDS URL parsing %%% %%% GRID(S): Gajumaru Remote Instruction Dispatch (Serialization) %%% GRIDS is a Gajumaru protocol for encoding wallet instructions as URLs. %%% Version 1 of the protocol consists of two verbs with two contexts each, collapsed to %%% four symbols for brevity. %%% %%% The GRIDS schema begins with "grids://" or "grid://" %%% Which way this is interpreted can vary depending on the verb. %%% %%% The typical "host" component is either an actual hostname or address and an optional %%% port number (the defaut port being 3013), or a Gajumaru chain network IDi (in which %%% case the port number is ignored if provided). Which way this field is interpreted %%% depends on the verb. %%% %%% The first element of the path after the host component indicates the protocol version. %%% Only version 1 exists at the time of this release. %%% %%% The next element of the path after the version is a single letter that indicates which %%% action to take. The following actions are available: %%% "s": Spend on Chain %%% Constructs a spend transaction to the address indicated in the path component %%% indicated in the final path element. Two qargs are valid in the trailing arguments %%% section: "a" for amount (in Pucks, not Gajus!), and "p" for data payload. %%% In this context the "host" field in the URL is interpreted as a chain network ID. %%% "t": Transfer (spend) on Host %%% The same as "spend" above, but in this context the host field of the URL is %%% interpreted as host[:port] information and the network chain ID that will be used %%% will be derived from whatever chain the given host reports. %%% "d": Dead-drop signature request %%% This instructs the wallet to retrieve a signature data blob from an HTTP or HTTPS %%% URL that can be reconstructed by replacing "grids" with "https" or "grid" with %%% "http", omitting the "/1/d" path component and then recnstructing the URL. %%% This provides a lightweight method for services to enable contract calls from %%% wallets that are not capable of compiling contract source. %%% @end -module(hz_grids). -vsn("0.6.1"). -export([url/2, parse/1, req/2, req/3]). -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(). %% @doc %% Takes url(Instruction, HTTP) -> case uri_string:parse(HTTP) of U = #{scheme := "https"} -> url2(Instruction, U#{scheme := "grids"}); U = #{scheme := "http"} -> url2(Instruction, U#{scheme := "grid"}); Error -> Error end. url2(Instruction, URL = #{path := Path}) -> GRIDS = case Instruction of spend -> URL#{path := "/1/s" ++ Path}; transfer -> URL#{path := "/1/t" ++ Path}; sign -> URL#{path := "/1/d" ++ Path} end, {ok, uri_string:recompose(GRIDS)}. -spec parse(GRIDS) -> Result when GRIDS :: string(), Result :: {ok, Instruction} | uri_string:error(), Instruction :: {{spend, chain | node}, {Location, Recipient, Amount, Payload}} | {{sign, http | https}, URL}, Location :: Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} | Chain :: binary(), Recipient :: gajudesk:id(), Amount :: non_neg_integer(), Payload :: binary(), URL :: string(). parse(GRIDS) -> case uri_string:parse(GRIDS) of #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grids"} -> spend(R, chain, list_to_binary(H), Q); #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grid"} -> spend(R, chain, list_to_binary(H), Q); #{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grids"} -> spend(R, node, {H, P}, Q); #{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grid"} -> spend(R, node, {H, P}, Q); #{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grids"} -> spend(R, node, {H, 3013}, Q); #{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grid"} -> spend(R, node, {H, 3013}, Q); U = #{path := "/1/d/" ++ L, scheme := "grids"} -> HTTP = uri_string:recompose(U#{scheme := "https", path := L}), {ok ,{{sign, https}, HTTP}}; U = #{path := "/1/d/" ++ L, scheme := "grid"} -> HTTP = uri_string:recompose(U#{scheme := "http", path := L}), {ok, {{sign, http}, HTTP}}; Error -> Error end. spend(Recipient, Context, Location, Qwargs) -> case dissect_query(Qwargs) of {ok, Amount, Payload} -> {ok, {{spend, Context}, {Location, Recipient, Amount, Payload}}}; Error -> Error end. dissect_query(Qwargs) -> case uri_string:dissect_query(Qwargs) of {error, Reason, Info} -> {error, Reason, Info}; ArgList -> case l_to_i(proplists:get_value("a", ArgList, "0")) of {ok, Amount} -> Payload = list_to_binary(proplists:get_value("p", ArgList, "")), {ok, Amount, Payload}; Error -> Error end end. l_to_i(S) -> try {ok, list_to_integer(S)} catch error:badarg -> {error, bad_url} end. req(Type, Message) -> req(Type, Message, false). req(sign, Message, ID) -> #{"grids" => 1, "chain" => "gajumaru", "network_id" => hz:network_id(), "type" => "message", "public_id" => ID, "payload" => Message}; req(tx, Data, ID) -> #{"grids" => 1, "chain" => "gajumaru", "network_id" => hz:network_id(), "type" => "tx", "public_id" => ID, "payload" => Data}; req(ack, Message, ID) -> #{"grids" => 1, "chain" => "gajumaru", "network_id" => hz:network_id(), "type" => "ack", "public_id" => ID, "payload" => Message}.