This commit is contained in:
Craig Everett 2024-10-09 17:42:56 +09:00
parent a286c00783
commit 1f10f7c6f9
3 changed files with 100 additions and 51 deletions

View File

@ -10,13 +10,13 @@
-record(chain, -record(chain,
{id = "mint.devnet" :: string(), {id = <<"mint.devnet">> :: binary(),
coins = ["gaju"] :: [string()], coins = ["gaju"] :: [string()],
nodes = [#node{}] :: [#node{}]}). nodes = [#node{}] :: [#node{}]}).
-record(net, -record(net,
{id = "devnet" :: string(), {id = <<"devnet">> :: binary(),
chains = [#chain{}] :: [#chain{}]}). chains = [#chain{}] :: [#chain{}]}).
@ -30,7 +30,7 @@
-record(coin, -record(coin,
{id = "gaju" :: string(), {id = "gaju" :: string(),
mint = "mint.devnet" :: string(), mint = <<"mint.devnet">> :: binary(),
acs = [#ac{}] :: [#ac{}]}). acs = [#ac{}] :: [#ac{}]}).
@ -39,7 +39,7 @@
-record(balance, -record(balance,
{coin = "gaju" :: string(), {coin = "gaju" :: string(),
total = 0 :: non_neg_integer(), total = 0 :: non_neg_integer(),
dist = [{"mint.devnet", 0}] :: [{Chain :: string(), non_neg_integer()}]}). dist = [{<<"mint.devnet">>, 0}] :: [{Chain :: binary(), non_neg_integer()}]}).
-record(poa, -record(poa,
@ -79,5 +79,6 @@
poas = [] :: [#poa{}], poas = [] :: [#poa{}],
keys = [] :: [#key{}], keys = [] :: [#key{}],
pass = none :: none | binary(), pass = none :: none | binary(),
network_id = <<"mint.devnet">> :: binary(), chain_id = <<"mint.devnet">> :: binary(),
endpoint = #node{} :: #node{},
chains = [#chain{}] :: [#chain{}]}). chains = [#chain{}] :: [#chain{}]}).

View File

@ -39,14 +39,12 @@
%% Interface %% Interface
-spec open_wallet(Path, Password) -> {ok, Wallet} | {error, Reason} -spec open_wallet(Path, Password) -> ok
when Path :: file:filename(), when Path :: file:filename(),
Password :: string(), Password :: string().
Wallet :: {Accounts :: [clutch:ak()], Selected :: integer()},
Reason :: bad_password | file:posix().
open_wallet(Path, Password) -> open_wallet(Path, Password) ->
gen_server:call(?MODULE, {open_wallet, Path, Password}). gen_server:cast(?MODULE, {open_wallet, Path, Password}).
-spec close_wallet() -> ok. -spec close_wallet() -> ok.
@ -160,6 +158,7 @@ read_prefs() ->
proplists:to_map(Prefs); proplists:to_map(Prefs);
_ -> _ ->
#{selected => 0, #{selected => 0,
last => save_path(),
lang => en_us, lang => en_us,
geometry => none} geometry => none}
end. end.
@ -182,9 +181,6 @@ read_prefs() ->
%% The gen_server:handle_call/3 callback. %% The gen_server:handle_call/3 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 %% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3
handle_call({open_wallet, Path, Phrase}, _, State) ->
{Response, NewState} = do_open_wallet(Path, Phrase, State),
{reply, Response, NewState};
handle_call({save, Prefs}, _, State) -> handle_call({save, Prefs}, _, State) ->
Response = do_save(State#s{prefs = Prefs}), Response = do_save(State#s{prefs = Prefs}),
{reply, Response, State}; {reply, Response, State};
@ -216,6 +212,9 @@ handle_cast({rename_key, ID, NewName}, State) ->
handle_cast({drop_key, ID}, State) -> handle_cast({drop_key, ID}, State) ->
NewState = do_drop_key(ID, State), NewState = do_drop_key(ID, State),
{noreply, NewState}; {noreply, NewState};
handle_cast({open_wallet, Path, Phrase}, State) ->
NewState = do_open_wallet(Path, Phrase, State),
{noreply, NewState};
handle_cast({password, Old, New}, State) -> handle_cast({password, Old, New}, State) ->
NewState = do_password(Old, New, State), NewState = do_password(Old, New, State),
{noreply, NewState}; {noreply, NewState};
@ -270,7 +269,7 @@ do_make_key(Name, Seed, base64, Transform, State) ->
{ok, Bin} -> {ok, Bin} ->
do_make_key2(Name, Bin, Transform, State); do_make_key2(Name, Bin, Transform, State);
{error, Reason} -> {error, Reason} ->
ok = gmc_gui:notify({error, {base64, Reason}}), ok = gmc_gui:trouble({error, {base64, Reason}}),
State State
end; end;
do_make_key(Name, Seed, base58, Transform, State) -> do_make_key(Name, Seed, base58, Transform, State) ->
@ -279,7 +278,7 @@ do_make_key(Name, Seed, base58, Transform, State) ->
Bin = base58:base58_to_binary(Seed), Bin = base58:base58_to_binary(Seed),
do_make_key2(Name, Bin, Transform, State); do_make_key2(Name, Bin, Transform, State);
false -> false ->
ok = gmc_gui:notify({error, {base58, badarg}}), ok = gmc_gui:trouble({error, {base58, badarg}}),
State State
end. end.
@ -330,7 +329,7 @@ do_recover_key(Mnemonic, State) ->
{ok, Seed} -> {ok, Seed} ->
do_recover_key2(Seed, State); do_recover_key2(Seed, State);
Error -> Error ->
ok = gmc_gui:notify(Error), ok = gmc_gui:trouble(Error),
State State
end. end.
@ -395,15 +394,25 @@ pass(Phrase) ->
do_open_wallet(Path, none, State) -> do_open_wallet(Path, none, State) ->
case read(none) of case read(Path, none) of
{ok, Recovered = #wallet{poas = POAs}} -> {POAs, State#s{wallet = Recovered}}; {ok, Recovered = #wallet{poas = POAs, chain_id = ChainID, endpoint = Node}} ->
Error -> {Error, State} ok = gmc_gui:show(POAs),
ok = gmc_gui:chain(ChainID, Node),
State#s{wallet = Recovered};
Error ->
ok = gmc_gui:trouble(Error),
State
end; end;
do_open_wallet(Path, Phrase, State) -> do_open_wallet(Path, Phrase, State) ->
Pass = pass(Phrase), Pass = pass(Phrase),
case read(Pass) of case read(Path, Pass) of
{ok, Recovered = #wallet{poas = POAs}} -> {POAs, State#s{wallet = Recovered}}; {ok, Recovered = #wallet{poas = POAs, chain_id = ChainID, endpoint = Node}} ->
Error -> {Error, State} ok = gmc_gui:show(POAs),
ok = gmc_gui:chain(ChainID, Node),
State#s{wallet = Recovered};
Error ->
ok = gmc_gui:trouble(Error),
State
end. end.
@ -441,7 +450,7 @@ do_spend2(PrivKey,
ttl = TTL, ttl = TTL,
nonce = Nonce, nonce = Nonce,
payload = Payload}, payload = Payload},
#s{wallet = #wallet{network_id = NetworkID}}) -> #s{wallet = #wallet{chain_id = ChainID}}) ->
Type = spend_tx, Type = spend_tx,
Vsn = 1, Vsn = 1,
Fields = Fields =
@ -463,7 +472,7 @@ do_spend2(PrivKey,
{nonce, int}, {nonce, int},
{payload, binary}], {payload, binary}],
BinaryTX = aeser_chain_objects:serialize(Type, Vsn, Template, Fields), BinaryTX = aeser_chain_objects:serialize(Type, Vsn, Template, Fields),
NetworkTX = <<NetworkID/binary, BinaryTX/binary>>, NetworkTX = <<ChainID/binary, BinaryTX/binary>>,
Signature = ecu_eddsa:sign_detached(NetworkTX, PrivKey), Signature = ecu_eddsa:sign_detached(NetworkTX, PrivKey),
SigTxType = signed_tx, SigTxType = signed_tx,
SigTxVsn = 1, SigTxVsn = 1,
@ -482,30 +491,35 @@ do_save(State = #s{prefs = Prefs}) ->
do_save2(State). do_save2(State).
do_save2(State = #s{wallet = W = #wallet{pass = none}}) -> do_save2(#s{wallet = W = #wallet{pass = none}}) ->
Path = save_path(), Path = save_path(),
ok = filelib:ensure_dir(Path), ok = filelib:ensure_dir(Path),
file:write_file(Path, term_to_binary(W)); file:write_file(Path, term_to_binary(W));
do_save2(State = #s{wallet = W = #wallet{pass = Pass}}) -> do_save2(#s{wallet = W = #wallet{pass = Pass}}) ->
Path = save_path(), Path = save_path(),
ok = filelib:ensure_dir(Path), ok = filelib:ensure_dir(Path),
Cipher = encrypt(Pass, term_to_binary(W)), Cipher = encrypt(Pass, term_to_binary(W)),
file:write_file(Path, Cipher). file:write_file(Path, Cipher).
read(none) -> read(Path, none) ->
case file:read_file(save_path()) of case file:read_file(Path) of
{ok, Bin} -> read2(Bin); {ok, Bin} -> read2(Bin);
Error -> Error Error -> Error
end; end;
read(Pass) -> read(Path, Pass) ->
case file:read_file(save_path()) of case file:read_file(Path) of
{ok, Cipher} -> {ok, Cipher} ->
try try
Bin = decrypt(Pass, Cipher), Bin = decrypt(Pass, Cipher),
read2(Bin) read2(Bin)
catch catch
E:R -> {E, R} error:{error, L, "Can't finalize"} ->
ok = log(info, "Decrypt failed at ~p", [L]),
{error, bad_password};
E:R ->
tell("Here: ~p", [{E, R}]),
{E, R}
end; end;
Error -> Error ->
Error Error
@ -519,7 +533,7 @@ read2(Bin) ->
save_path() -> save_path() ->
filename:join(zx_lib:path(var, "otpr", "clutch"), "opaque.data"). filename:join(zx_lib:path(var, "otpr", "clutch"), "default.gaju").
persist(Prefs) -> persist(Prefs) ->

View File

@ -14,7 +14,7 @@
-behavior(wx_object). -behavior(wx_object).
-include_lib("wx/include/wx.hrl"). -include_lib("wx/include/wx.hrl").
-export([show/1, notify/1, ask_password/0]). -export([show/1, chain/2, trouble/1, ask_password/0]).
-export([start_link/1]). -export([start_link/1]).
-export([init/1, terminate/2, code_change/3, -export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
@ -52,8 +52,12 @@ show(Accounts) ->
wx_object:cast(?MODULE, {show, Accounts}). wx_object:cast(?MODULE, {show, Accounts}).
notify(Message) -> chain(ChainID, Node) ->
wx_object:cast(?MODULE, {notify, Message}). wx_object:cast(?MODULE, {chain, ChainID, Node}).
trouble(Message) ->
wx_object:cast(?MODULE, {trouble, Message}).
ask_password() -> ask_password() ->
@ -78,6 +82,11 @@ init(Prefs) ->
MainSz = wxBoxSizer:new(?wxVERTICAL), MainSz = wxBoxSizer:new(?wxVERTICAL),
Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]),
ChainB = wxButton:new(Frame, ?wxID_ANY, [{label, "[ChainID]"}, {style, ?wxBORDER_NONE}]),
ChainW = #w{name = chain, id = wxButton:getId(ChainB), wx = ChainB},
NodeB = wxButton:new(Frame, ?wxID_ANY, [{label, "[Node]"}, {style, ?wxBORDER_NONE}]),
NodeW = #w{name = node, id = wxButton:getId(NodeB), wx = NodeB},
ID_L = wxStaticText:new(Frame, ?wxID_ANY, J("Account ID: ")), ID_L = wxStaticText:new(Frame, ?wxID_ANY, J("Account ID: ")),
ID_T = wxStaticText:new(Frame, ?wxID_ANY, ""), ID_T = wxStaticText:new(Frame, ?wxID_ANY, ""),
ID_W = ID_W =
@ -119,13 +128,17 @@ init(Prefs) ->
#w{name = Name, id = wxButton:getId(B), wx = B} #w{name = Name, id = wxButton:getId(B), wx = B}
end, end,
Buttons = lists:map(MakeButton, ButtonTemplates), Buttons = [ChainW, NodeW | lists:map(MakeButton, ButtonTemplates)],
ChainSz = wxBoxSizer:new(?wxHORIZONTAL),
AccountSz = wxBoxSizer:new(?wxHORIZONTAL), AccountSz = wxBoxSizer:new(?wxHORIZONTAL),
DetailsSz = wxBoxSizer:new(?wxHORIZONTAL), DetailsSz = wxBoxSizer:new(?wxHORIZONTAL),
ActionsSz = wxBoxSizer:new(?wxHORIZONTAL), ActionsSz = wxBoxSizer:new(?wxHORIZONTAL),
HistorySz = wxBoxSizer:new(?wxVERTICAL), HistorySz = wxBoxSizer:new(?wxVERTICAL),
_ = wxSizer:add(ChainSz, ChainB, zxw:flags(wide)),
_ = wxSizer:add(ChainSz, NodeB, zxw:flags(wide)),
#w{wx = CopyBn} = lists:keyfind(copy, #w.name, Buttons), #w{wx = CopyBn} = lists:keyfind(copy, #w.name, Buttons),
#w{wx = WWW_Bn} = lists:keyfind(www, #w.name, Buttons), #w{wx = WWW_Bn} = lists:keyfind(www, #w.name, Buttons),
_ = wxSizer:add(DetailsSz, NumbersSz, zxw:flags(wide)), _ = wxSizer:add(DetailsSz, NumbersSz, zxw:flags(wide)),
@ -153,6 +166,7 @@ init(Prefs) ->
#w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons), #w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons),
_ = wxSizer:add(HistorySz, Refresh, zxw:flags(base)), _ = wxSizer:add(HistorySz, Refresh, zxw:flags(base)),
_ = wxSizer:add(MainSz, ChainSz, zxw:flags(base)),
_ = wxSizer:add(MainSz, AccountSz, zxw:flags(base)), _ = wxSizer:add(MainSz, AccountSz, zxw:flags(base)),
_ = wxSizer:add(MainSz, Picker, zxw:flags(wide)), _ = wxSizer:add(MainSz, Picker, zxw:flags(wide)),
_ = wxSizer:add(MainSz, DetailsSz, zxw:flags(base)), _ = wxSizer:add(MainSz, DetailsSz, zxw:flags(base)),
@ -239,6 +253,9 @@ handle_call(Unexpected, From, State) ->
handle_cast({show, Accounts}, State) -> handle_cast({show, Accounts}, State) ->
NewState = do_show(Accounts, State), NewState = do_show(Accounts, State),
{noreply, NewState}; {noreply, NewState};
handle_cast({chain, ChainID, Node}, State) ->
ok = do_chain(ChainID, Node, State),
{noreply, State};
handle_cast(password, State) -> handle_cast(password, State) ->
ok = do_ask_password(State), ok = do_ask_password(State),
{noreply, State}; {noreply, State};
@ -601,7 +618,8 @@ spend(State) ->
% State State. % State State.
grids_dialogue(State = #s{frame = Frame, j = J}) -> grids_dialogue(State) ->
%grids_dialogue(State = #s{frame = Frame, j = J}) ->
tell("Handle GRIDS URL"), tell("Handle GRIDS URL"),
% ok = % ok =
% case zxw:modal_text_input(Frame, J("GRIDS"), J("ZA GRIDS"), [J("URL")]) of % case zxw:modal_text_input(Frame, J("GRIDS"), J("ZA GRIDS"), [J("URL")]) of
@ -645,7 +663,20 @@ do_show(Accounts, State = #s{prefs = Prefs, picker = Picker}) ->
end. end.
do_ask_password(#s{frame = Frame, j = J}) -> do_chain(ChainID, #node{ip = IP}, #s{buttons = Buttons}) ->
#w{wx = ChainB} = lists:keyfind(chain, #w.name, Buttons),
#w{wx = NodeB} = lists:keyfind(node, #w.name, Buttons),
Address =
case inet:is_ip_address(IP) of
true -> inet:ntoa(IP);
false -> IP
end,
Label = unicode:characters_to_list(ChainID),
ok = wxButton:setLabel(ChainB, Label),
ok = wxButton:setLabel(NodeB, Address).
do_ask_password(#s{frame = Frame, prefs = Prefs, j = J}) ->
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")),
Sizer = wxBoxSizer:new(?wxVERTICAL), Sizer = wxBoxSizer:new(?wxVERTICAL),
Label = "Password (leave blank for no password)", Label = "Password (leave blank for no password)",
@ -661,7 +692,10 @@ do_ask_password(#s{frame = Frame, j = J}) ->
ok = wxBoxSizer:layout(Sizer), ok = wxBoxSizer:layout(Sizer),
ok = wxFrame:center(Dialog), ok = wxFrame:center(Dialog),
ok = wxStyledTextCtrl:setFocus(PassTx), ok = wxStyledTextCtrl:setFocus(PassTx),
Path = filename:join(zx_lib:path(var, "otpr", "clutch"), "opaque.data"), Path =
maps:get(last,
Prefs,
filename:join(zx_lib:path(var, "otpr", "clutch"), "default.gaju")),
ok = ok =
case wxDialog:showModal(Dialog) of case wxDialog:showModal(Dialog) of
?wxID_OK -> ?wxID_OK ->