From a13bec7fccee278392320f8fbc4c98d3bc324a4a Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 15 Oct 2024 20:32:26 +0900 Subject: [PATCH] WIP --- src/gmc_grids.erl | 72 +++++++++++++++++++++++++--- src/gmc_gui.erl | 112 +++++++++++++++++++++++++++++++------------ src/gmc_v_netman.erl | 1 - zomp.meta | 8 +++- 4 files changed, 154 insertions(+), 39 deletions(-) diff --git a/src/gmc_grids.erl b/src/gmc_grids.erl index c1c311d..cd4463f 100644 --- a/src/gmc_grids.erl +++ b/src/gmc_grids.erl @@ -1,3 +1,48 @@ +%%% @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 "grids://" +%%% 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. +%%% "v": Verify +%%% Similar to the contract call signature request above, this requests a message +%%% signature over arbitrary data. The normal use case for this action is public +%%% key based identity verification by signing time-stamped, random data. This verb +%%% is required because message signature requests are specially salted to prevent +%%% a situation where sneaky types might request signature of contract call data, +%%% but present it as a random message signature. +%%% @end + -module(gmc_grids). -vsn("0.1.0"). -author("Craig Everett "). @@ -11,7 +56,7 @@ -spec parse(URL) -> {ok, Instruction} | {error, Reason} when URL :: string(), Instruction :: {{spend, chain | node}, {Location, Recipient, Amount, Payload}} - | {{sign, http | https}, URL}, + | {{sign | mess, http | https}, URL}, Location :: Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} | Chain :: binary(), Recipient :: clutch:id(), @@ -40,6 +85,12 @@ parse(URL) -> U = #{path := "/1/d/" ++ L, scheme := "grid"} -> NewURL = uri_string:recompose(U#{scheme := "http", path := L}), {ok, {{sign, http}, NewURL}}; + U = #{path := "/1/v/" ++ L, scheme := "grids"} -> + NewURL = uri_string:recompose(U#{scheme := "https", path := L}), + {ok ,{{mess, https}, NewURL}}; + U = #{path := "/1/v/" ++ L, scheme := "grid"} -> + NewURL = uri_string:recompose(U#{scheme := "http", path := L}), + {ok, {{mess, http}, NewURL}}; {error, Reason, Info} -> ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]), {error, bad_url}; @@ -51,7 +102,6 @@ parse(URL) -> spend(Recipient, Context, Location, Qwargs) -> case dissect_query(Qwargs) of {ok, Amount, Payload} -> - {ok, {{spend, Context}, {Location, Recipient, Amount, Payload}}}; Error -> Error @@ -64,8 +114,18 @@ dissect_query(Qwargs) -> ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]), {error, bad_url}; ArgList -> - Amount = proplists:get_value("a", ArgList, 0), - Payload = proplists:get_value("p", ArgList, <<>>), - {ok, Amount, Payload} + 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. - diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl index 7f5109d..6e3e3e2 100644 --- a/src/gmc_gui.erl +++ b/src/gmc_gui.erl @@ -291,7 +291,7 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, NewState = case lists:keyfind(ID, #w.id, Buttons) of #w{name = chain} -> netman(State); -% #w{name = node} -> set_node(State); + #w{name = node} -> set_node(State); #w{name = make_key} -> make_key(State); #w{name = recover} -> recover_key(State); #w{name = mnemonic} -> show_mnemonic(State); @@ -344,39 +344,91 @@ terminate(Reason, State) -> %%% Doers -% TODO: Make a network/chain management activity netman(State) -> ok = gmc_con:show_ui(gmc_v_netman), State. -%set_chain(State = #s{frame = Frame, j = J, buttons = Buttons}) -> -% Label = "Node", -% Dialog = wxDialog:new(Frame, ?wxID_ANY, J(Label)), -% Sizer = wxBoxSizer:new(?wxVERTICAL), -% NodeSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), -% NodeTx = wxTextCtrl:new(Dialog, ?wxID_ANY), -% _ = wxStaticBoxSizer:add(NodeSz, NodeTx, zxw:flags(wide)), -% ButtSz = wxBoxSizer:new(?wxHORIZONTAL), -% Affirm = wxButton:new(Dialog, ?wxID_OK), -% Cancel = wxButton:new(Dialog, ?wxID_CANCEL), -% _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), -% _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), -% _ = wxBoxSizer:add(Sizer, NodeSz, zxw:flags(base)), -% _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), -% ok = -% case wxDialog:showModal(Dialog) of -% ?wxID_OK -> -% String = wxTextCtrl:getValue(NodeTx), -% set_node2(String); -% ?wxID_CANCEL -> -% ok -% end, -% State. -% -%set_node2("") -> -% ok; -%set_node2(String) -> -% case lists: +set_node(State = #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Set Node")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + + AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address")}]), + AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), + _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), + + PortSz = wxBoxSizer:new(?wxHORIZONTAL), + Labels = + [J("External"), + J("Internal"), + J("Rosetta"), + J("Channel"), + J("Middleware")], + MakePortCtrl = + fun(L) -> + Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, L}]), + Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), + _ = wxSizer:add(Sz, Tx, zxw:flags(wide)), + _ = wxSizer:add(PortSz, Sz, zxw:flags(wide)), + Tx + end, + PortCtrls = lists:map(MakePortCtrl, Labels), + + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + + _ = wxSizer:add(Sizer, AddressSz, zxw:flags(base)), + _ = wxSizer:add(Sizer, PortSz, zxw:flags(base)), + _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), + + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:setSize(Dialog, {500, 200}), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(AddressTx), + + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + case wxTextCtrl:getValue(AddressTx) of + "" -> ok; + Address -> add_node2(Address, PortCtrls) + end; + ?wxID_CANCEL -> + ok + end, + ok = wxDialog:destroy(Dialog), + State. + +add_node2(Address, PortCtrls) -> + IP = + case inet:parse_address(Address) of + {ok, N} -> N; + {error, einval} -> Address + end, + [E, I, R, C, M] = lists:map(fun numerify_port/1, PortCtrls), + New = #node{ip = IP, external = E, internal = I, rosetta = R, channel = C, mdw = M}, + gmc_con:set_sole_node(New). + +numerify_port(Ctrl) -> + case wxTextCtrl:getValue(Ctrl) of + "" -> none; + String -> + case s_to_i(String) of + {ok, PortNum} -> PortNum; + error -> none + end + end. + +s_to_i(S) -> + try + I = list_to_integer(S), + {ok, I} + catch error:badarg -> + error + end. make_key(State = #s{frame = Frame, j = J}) -> diff --git a/src/gmc_v_netman.erl b/src/gmc_v_netman.erl index 90aa143..dab337d 100644 --- a/src/gmc_v_netman.erl +++ b/src/gmc_v_netman.erl @@ -296,7 +296,6 @@ add_node(State = #s{frame = Frame, j = J}) -> _ = wxSizer:add(Sizer, PortSz, zxw:flags(base)), _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), ok = wxBoxSizer:layout(Sizer), ok = wxFrame:setSize(Dialog, {500, 200}), diff --git a/zomp.meta b/zomp.meta index ed390df..57ba654 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,10 +2,14 @@ {type,gui}. {modules,[]}. {prefix,"gmc"}. -{author,"Craig Everett"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}. +{author,"Craig Everett"}. {package_id,{"otpr","clutch",{0,1,0}}}. -{deps,[{"otpr","erl_base58",{0,1,0}}, +{deps,[{"otpr","zj",{1,1,0}}, + {"otpr","aesophia",{7,1,2}}, + {"otpr","aebytecode",{3,2,1}}, + {"otpr","hakuzaru",{0,1,0}}, + {"otpr","erl_base58",{0,1,0}}, {"otpr","aeserialization",{0,1,0}}, {"otpr","eblake2",{1,0,0}}, {"otpr","ec_utils",{1,0,0}},