%%% @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(gd_grids). -vsn("0.6.5"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). -export([parse/1]). -include("$zx_include/zx_logger.hrl"). -spec parse(URL) -> {ok, Instruction} | {error, Reason} when URL :: string(), Instruction :: {{spend, chain | node}, {Location, Recipient, Amount, Payload}} | {{sign | mess, 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(), Reason :: bad_url. parse(URL) -> case uri_string:parse(URL) 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"} -> NewURL = uri_string:recompose(U#{scheme := "https", path := L}), {ok ,{{sign, https}, NewURL}}; U = #{path := "/1/d/" ++ L, scheme := "grid"} -> NewURL = uri_string:recompose(U#{scheme := "http", path := L}), {ok, {{sign, http}, NewURL}}; {error, Reason, Info} -> ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]), {error, bad_url}; U -> ok = tell("GRIDS cannot proceed with this result: ~p", [U]), {error, bad_url} 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} -> ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]), {error, bad_url}; 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.