GajuDesk/src/gd_grids.erl
Craig Everett f92c5fbde0 First-run wallet creatorator (v0.7.0) (#24)
Adds a first-run wallet and account creator option for noobs.
If they select the "create a default wallet" option, they are jumped to the name + password screen for wallet creation, and a new account is generated for them named "Account 1".

This leapfrogs the problem of users having to know what is going on with the blockchain and wallet at all before getting started.
#18
#19

Reviewed-on: #24
Reviewed-by: Ulf Wiger <ulfwiger@qpq.swiss>
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-08-07 15:47:05 +09:00

119 lines
5.1 KiB
Erlang

%%% @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.7.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>").
-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(string:trim(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.