Compare commits

...

24 Commits

Author SHA1 Message Date
a2a9da0d98 Fix UI problem where key interactions could be requests without any wallet being selected 2025-06-13 17:47:37 +09:00
d595beb08d Minor display changes 2025-06-12 19:43:02 +09:00
cf6f16e3db Updates 2025-06-12 19:25:36 +09:00
88b2e986b7 Default walletses 2025-05-20 17:31:09 +09:00
09d31e74e5 Add refresh tic 2025-05-09 21:17:20 +09:00
86e9ff3e44 Fix runtime shutdown problem with GajuDesk 2025-05-08 13:06:27 +09:00
57a81e7f3f Issue proper explorer URLs 2025-04-30 17:39:26 +09:00
58a58c693f Minor usability tweaks 2025-04-25 22:52:25 +09:00
28b0b2c6f3 Update dependencies and verup 2025-04-16 16:13:42 +09:00
cbc823aba1 args (#10)
More complete types.

Reviewed-on: #10
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-04-16 16:03:09 +09:00
2bef1fd323 Fix app name 2025-04-03 15:01:16 +09:00
509f36c403 Merge pull request 'First stab at supporting syntax highlighting via wxStyledTextCtrl' (#8) from uw-syntax-highlighting into master
Reviewed-on: #8
Reviewed-by: Craig Everett <craigeverett@qpq.swiss>
2025-04-02 16:34:57 +09:00
1f17d36c42 Merge pull request 'style_massage' (#9) from style_massage into uw-syntax-highlighting
Reviewed-on: #9
Reviewed-by: Ulf Wiger <ulfwiger@qpq.swiss>
2025-03-31 20:11:53 +09:00
ea6667c05f Remove keymaster; update dep 2025-03-31 15:58:56 +09:00
020d3f554b Remove dead file 2025-03-31 15:39:25 +09:00
dea44561ee Restyle whole buffer on event to fix style breaks
TODO: Make this more precise... some day
2025-03-31 15:36:31 +09:00
9d70f98ed0 Make light/dark mode work 2025-03-31 14:41:03 +09:00
9aa2c2f79d Give the main frame visibility on events 2025-03-31 13:58:48 +09:00
Ulf Wiger
d4f2b3f2b0 Set monospace font 2025-03-30 22:15:02 +02:00
Ulf Wiger
27b78f8623 First stab at supporting syntax highlighting via wxStyledTextCtrl 2025-03-30 21:16:21 +02:00
8cb3065510 Dep update. Verup. 2025-03-06 16:40:45 +09:00
9a99d83014 Fix typo. Verup. 2025-03-06 06:53:55 +09:00
9ec748d469 Merge branch 'master' of ssh://git.qpq.swiss:21203/QPQ-AG/GajuDesk 2025-03-05 23:01:08 +09:00
4b6f761ec4 history (#6)
Ugly merge

Reviewed-on: #6
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-03-04 20:51:36 +09:00
16 changed files with 706 additions and 4509 deletions

View File

@ -3,7 +3,8 @@
{registered,[]}, {registered,[]},
{included_applications,[]}, {included_applications,[]},
{applications,[stdlib,kernel,sasl,ssl]}, {applications,[stdlib,kernel,sasl,ssl]},
{vsn,"0.5.2"}, {vsn,"0.6.6"},
{modules,[gajudesk,gd_con,gd_grids,gd_gui,gd_jt,gd_key_master, {modules,[gajudesk,gd_con,gd_grids,gd_gui,gd_jt,
gd_sup,gd_v,gd_v_devman,gd_v_netman,gd_v_wallman]}, gd_sophia_editor,gd_sup,gd_v,gd_v_devman,gd_v_netman,
gd_v_wallman]},
{mod,{gajudesk,[]}}]}. {mod,{gajudesk,[]}}]}.

View File

@ -1,23 +1,23 @@
%% Node, Chain and Net represent the physical network. %% Node, Chain and Net represent the physical network.
-record(node, -record(node,
{ip = {161,97,102,143} :: string() | inet:ip_address(), {ip = "groot.testnet.gajumaru.io" :: string() | inet:ip_address(),
external = 3013 :: none | inet:port_number(), % 3013 external = 3013 :: none | inet:port_number(), % 3013
internal = none :: none | inet:port_number(), % 3113 internal = none :: none | inet:port_number(), % 3113
rosetta = none :: none | inet:port_number(), % 8080 rosetta = none :: none | inet:port_number(), % 8080
channel = none :: none | inet:port_number(), % 3014 channel = none :: none | inet:port_number(), % 3014
mdw = none :: none | inet:port_number()}). % 4000 mdw = none :: none | inet:port_number()}). % 4000
-record(chain, -record(chain,
{id = <<"groot.devnet">> :: binary(), {id = <<"groot.testnet">> :: binary(),
coins = ["gaju"] :: [string()], coins = ["gaju"] :: [string()],
nodes = [#node{}] :: [#node{}]}). nodes = [#node{}] :: [#node{}]}).
-record(net, -record(net,
{id = <<"devnet">> :: binary(), {id = <<"testnet">> :: binary(),
chains = [#chain{}] :: [#chain{}]}). chains = [#chain{}] :: [#chain{}]}).
@ -29,17 +29,17 @@
-record(coin, -record(coin,
{id = "gaju" :: string(), {id = "gaju" :: string(),
mint = <<"groot.devnet">> :: binary(), mint = <<"groot.testnet">> :: binary(),
acs = [#ac{}] :: [#ac{}]}). acs = [#ac{}] :: [#ac{}]}).
%% Balance, POA, Key, TXs, all culminate in capturing a complete Wallet view. %% Balance, POA, Key, TXs, all culminate in capturing a complete Wallet view.
-record(balance, -record(balance,
{coin = "gaju" :: string(), {coin = "gaju" :: string(),
total = 0 :: non_neg_integer(), total = 0 :: non_neg_integer(),
dist = [{<<"groot.devnet">>, 0}] :: [{Chain :: binary(), non_neg_integer()}]}). dist = [{<<"groot.testnet">>, 0}] :: [{Chain :: binary(), non_neg_integer()}]}).
-record(poa, -record(poa,
@ -77,14 +77,14 @@
-record(wallet, -record(wallet,
{version = 2 :: integer(), {version = 2 :: integer(),
name = "" :: string(), name = "" :: string(),
poas = [] :: [#poa{}], poas = [] :: [#poa{}],
keys = [] :: [#key{}], keys = [] :: [#key{}],
chain_id = <<"groot.devnet">> :: binary(), chain_id = <<"groot.testnet">> :: binary(),
endpoint = #node{} :: #node{}, endpoint = #node{} :: #node{},
nets = [#net{}] :: [#net{}], nets = [#net{}] :: [#net{}],
txs = #{} :: gajudesk:key_txs()}). txs = #{} :: gajudesk:key_txs()}).

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
%%% @end %%% @end
-module(gajudesk). -module(gajudesk).
-vsn("0.5.2"). -vsn("0.6.6").
-behavior(application). -behavior(application).
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").

View File

@ -3,15 +3,15 @@
%%% @end %%% @end
-module(gd_con). -module(gd_con).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").
-behavior(gen_server). -behavior(gen_server).
-export([show_ui/1, -export([show_ui/1,
open_wallet/2, close_wallet/0, new_wallet/3, import_wallet/3, drop_wallet/2, open_wallet/2, close_wallet/0, new_wallet/4, import_wallet/3, drop_wallet/2,
selected/1, selected/1, network/0,
password/2, password/2,
refresh/0, refresh/0,
nonce/1, spend/1, chain/1, grids/1, sign_mess/1, sign_tx/1, sign_call/3, dry_run/2, nonce/1, spend/1, chain/1, grids/1, sign_mess/1, sign_tx/1, sign_call/3, dry_run/2,
@ -40,6 +40,7 @@
-record(s, -record(s,
{version = 1 :: integer(), {version = 1 :: integer(),
window = none :: none | wx:wx_object(), window = none :: none | wx:wx_object(),
timer = none :: none | reference(),
tasks = [] :: [#ui{}], tasks = [] :: [#ui{}],
selected = 0 :: non_neg_integer(), selected = 0 :: non_neg_integer(),
wallet = none :: none | #wallet{}, wallet = none :: none | #wallet{},
@ -79,13 +80,14 @@ close_wallet() ->
gen_server:cast(?MODULE, close_wallet). gen_server:cast(?MODULE, close_wallet).
-spec new_wallet(Name, Path, Password) -> ok -spec new_wallet(Net, Name, Path, Password) -> ok
when Name :: string(), when Net :: mainnet | testnet | devnet,
Name :: string(),
Path :: string(), Path :: string(),
Password :: string(). Password :: string().
new_wallet(Name, Path, Password) -> new_wallet(Net, Name, Path, Password) ->
gen_server:cast(?MODULE, {new_wallet, Name, Path, Password}). gen_server:cast(?MODULE, {new_wallet, Net, Name, Path, Password}).
-spec import_wallet(Name, Path, Password) -> ok -spec import_wallet(Name, Path, Password) -> ok
@ -112,6 +114,13 @@ selected(Index) ->
gen_server:cast(?MODULE, {selected, Index}). gen_server:cast(?MODULE, {selected, Index}).
-spec network() -> {ok, NetworkID} | none
when NetworkID :: binary().
network() ->
gen_server:call(?MODULE, network).
-spec password(Old, New) -> ok -spec password(Old, New) -> ok
when Old :: none | string(), when Old :: none | string(),
New :: none | string(). New :: none | string().
@ -316,7 +325,8 @@ init(none) ->
GUI_Prefs = maps:get(gd_gui, Prefs, #{}), GUI_Prefs = maps:get(gd_gui, Prefs, #{}),
Window = gd_gui:start_link(GUI_Prefs), Window = gd_gui:start_link(GUI_Prefs),
Wallets = get_prefs(wallets, Prefs, []), Wallets = get_prefs(wallets, Prefs, []),
State = #s{window = Window, wallets = Wallets, prefs = Prefs}, T = erlang:send_after(tic(), self(), tic),
State = #s{window = Window, timer = T, wallets = Wallets, prefs = Prefs},
NewState = do_show_ui(gd_v_wallman, State), NewState = do_show_ui(gd_v_wallman, State),
{ok, NewState}. {ok, NewState}.
@ -328,6 +338,10 @@ read_prefs() ->
end. end.
tic() ->
6000.
%%% gen_server Message Handling Callbacks %%% gen_server Message Handling Callbacks
@ -351,6 +365,9 @@ handle_call(list_keys, _, State) ->
handle_call({nonce, ID}, _, State) -> handle_call({nonce, ID}, _, State) ->
Response = do_nonce(ID), Response = do_nonce(ID),
{reply, Response, State}; {reply, Response, State};
handle_call(network, _, State) ->
Response = do_network(State),
{reply, Response, State};
handle_call({save, Module, Prefs}, _, State) -> handle_call({save, Module, Prefs}, _, State) ->
NewState = do_save(Module, Prefs, State), NewState = do_save(Module, Prefs, State),
{reply, ok, NewState}; {reply, ok, NewState};
@ -381,8 +398,8 @@ handle_cast(close_wallet, State) ->
ok = gd_gui:show([]), ok = gd_gui:show([]),
NewState = do_show_ui(gd_v_wallman, NextState), NewState = do_show_ui(gd_v_wallman, NextState),
{noreply, NewState}; {noreply, NewState};
handle_cast({new_wallet, Name, Path, Password}, State) -> handle_cast({new_wallet, Net, Name, Path, Password}, State) ->
NewState = do_new_wallet(Name, Path, Password, State), NewState = do_new_wallet(Net, Name, Path, Password, State),
{noreply, NewState}; {noreply, NewState};
handle_cast({import_wallet, Name, Path, Password}, State) -> handle_cast({import_wallet, Name, Path, Password}, State) ->
NewState = do_import_wallet(Name, Path, Password, State), NewState = do_import_wallet(Name, Path, Password, State),
@ -443,7 +460,6 @@ handle_cast({set_sole_node, TheOneTrueNode}, State) ->
{noreply, NewState}; {noreply, NewState};
handle_cast(stop, State) -> handle_cast(stop, State) ->
NewState = do_stop(State), NewState = do_stop(State),
ok = zx:stop(),
{noreply, NewState}; {noreply, NewState};
handle_cast(Unexpected, State) -> handle_cast(Unexpected, State) ->
ok = tell(warning, "Unexpected cast: ~tp~n", [Unexpected]), ok = tell(warning, "Unexpected cast: ~tp~n", [Unexpected]),
@ -458,6 +474,9 @@ handle_cast(Unexpected, State) ->
%% The gen_server:handle_info/2 callback. %% The gen_server:handle_info/2 callback.
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 %% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2
handle_info(tic, State) ->
NewState = do_tic(State),
{noreply, NewState};
handle_info({'DOWN', Mon, process, PID, Info}, State) -> handle_info({'DOWN', Mon, process, PID, Info}, State) ->
NewState = handle_down(Mon, PID, Info, State), NewState = handle_down(Mon, PID, Info, State),
{noreply, NewState}; {noreply, NewState};
@ -486,11 +505,15 @@ code_change(_, State, _) ->
{ok, State}. {ok, State}.
terminate(normal, _) -> terminate(Reason, _) ->
zx:stop(); ok = log(info, "Reason: ~p,", [Reason]),
terminate(Reason, State) -> case whereis(gmc_con) of
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), undefined ->
zx:stop(). zx:stop();
PID ->
ok = log(info, "gd_con found at: ~p", [PID]),
application:stop(gajumine)
end.
@ -508,6 +531,7 @@ do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) ->
Win = Name:start_link({TaskPrefs, TaskData}), Win = Name:start_link({TaskPrefs, TaskData}),
PID = wx_object:get_pid(Win), PID = wx_object:get_pid(Win),
Mon = monitor(process, PID), Mon = monitor(process, PID),
ok = Name:to_front(Win),
UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon}, UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon},
State#s{tasks = [UI | Tasks]} State#s{tasks = [UI | Tasks]}
end. end.
@ -537,12 +561,15 @@ do_add_node(New, State) ->
do_set_sole_node(TOTN = #node{external = none}, State) -> do_set_sole_node(TOTN = #node{external = none}, State) ->
do_set_sole_node(TOTN#node{external = 3013}, State); do_set_sole_node(TOTN#node{external = 3013}, State);
do_set_sole_node(New = #node{ip = IP, external = Port}, State = #s{wallet = W}) -> do_set_sole_node(New = #node{ip = IP, external = Port}, State = #s{wallet = W, wallets = Ws, pass = Pass}) ->
ok = hz:chain_nodes([{IP, Port}]), ok = hz:chain_nodes([{IP, Port}]),
case ensure_hz_set(New) of case ensure_hz_set(New) of
{ok, ChainID} -> {ok, ChainID} ->
#wallet{name = Name} = W,
RW = lists:keyfind(Name, #wr.name, Ws),
Net = #net{id = ChainID, chains = [#chain{id = ChainID, nodes = [New]}]}, Net = #net{id = ChainID, chains = [#chain{id = ChainID, nodes = [New]}]},
NewWallet = W#wallet{chain_id = ChainID, endpoint = New, nets = [Net]}, NewWallet = W#wallet{chain_id = ChainID, endpoint = New, nets = [Net]},
ok = save_wallet(RW, Pass, NewWallet),
NewState = State#s{wallet = NewWallet}, NewState = State#s{wallet = NewWallet},
do_refresh(NewState); do_refresh(NewState);
Error -> Error ->
@ -566,23 +593,26 @@ do_refresh(State = #s{wallet = #wallet{endpoint = Node}}) ->
do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) -> do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) ->
CheckBalance = CheckBalance = check_balance(ChainID),
fun(This = #poa{id = ID}) ->
Pucks =
case hz:acc(ID) of
{ok, #{"balance" := P}} -> P;
{error, "Account not found"} -> 0
end,
Dist = [{ChainID, Pucks}],
Gaju = #balance{coin = "gaju", total = Pucks, dist = Dist},
This#poa{balances = [Gaju]}
end,
NewPOAs = lists:map(CheckBalance, POAs), NewPOAs = lists:map(CheckBalance, POAs),
ok = gd_gui:show(NewPOAs), ok = gd_gui:show(NewPOAs),
NewW = W#wallet{chain_id = ChainID, poas = NewPOAs}, NewW = W#wallet{chain_id = ChainID, poas = NewPOAs},
State#s{wallet = NewW}. State#s{wallet = NewW}.
check_balance(ChainID) ->
fun(This = #poa{id = ID}) ->
Pucks =
case hz:acc(ID) of
{ok, #{"balance" := P}} -> P;
{error, "Account not found"} -> 0
end,
Dist = [{ChainID, Pucks}],
Gaju = #balance{coin = "gaju", total = Pucks, dist = Dist},
This#poa{balances = [Gaju]}
end.
ensure_hz_set(Node = #node{ip = IP, external = Port}) -> ensure_hz_set(Node = #node{ip = IP, external = Port}) ->
case hz:chain_nodes() of case hz:chain_nodes() of
[{IP, Port}] -> [{IP, Port}] ->
@ -599,7 +629,9 @@ ensure_hz_set(Node = #node{ip = IP, external = Port}) ->
_ -> _ ->
ok = hz:chain_nodes([{IP, Port}]), ok = hz:chain_nodes([{IP, Port}]),
ensure_hz_set(Node) ensure_hz_set(Node)
end. end;
ensure_hz_set(none) ->
{error, no_nodes}.
%ensure_connected(ChainID, IP, Port) -> %ensure_connected(ChainID, IP, Port) ->
@ -652,7 +684,7 @@ do_sign_mess(Request = #{"public_id" := ID, "payload" := Message},
#s{wallet = #wallet{keys = Keys}}) -> #s{wallet = #wallet{keys = Keys}}) ->
case lists:keyfind(ID, #key.id, Keys) of case lists:keyfind(ID, #key.id, Keys) of
#key{pair = #{secret := SecKey}} -> #key{pair = #{secret := SecKey}} ->
Sig = base64:encode(sign_message(list_to_binary(Message), SecKey)), Sig = base64:encode(hz:sign_message(list_to_binary(Message), SecKey)),
do_sign_mess2(Request#{"signature" => Sig}); do_sign_mess2(Request#{"signature" => Sig});
false -> false ->
gd_gui:trouble({bad_key, ID}) gd_gui:trouble({bad_key, ID})
@ -675,37 +707,6 @@ do_sign_mess2(Request = #{"url" := URL}) ->
end. end.
% TODO: Should probably be part of Hakuzaru
sign_message(Message, SecKey) ->
Prefix = <<"Gajumaru Signed Message:\n">>,
{ok, PSize} = vencode(byte_size(Prefix)),
{ok, MSize} = vencode(byte_size(Message)),
Smashed = iolist_to_binary([PSize, Prefix, MSize, Message]),
{ok, Hashed} = eblake2:blake2b(32, Smashed),
ecu_eddsa:sign_detached(Hashed, SecKey).
vencode(N) when N < 0 ->
{error, {negative_N, N}};
vencode(N) when N < 16#FD ->
{ok, <<N>>};
vencode(N) when N =< 16#FFFF ->
NBytes = eu(N, 2),
{ok, <<16#FD, NBytes/binary>>};
vencode(N) when N =< 16#FFFF_FFFF ->
NBytes = eu(N, 4),
{ok, <<16#FE, NBytes/binary>>};
vencode(N) when N < (2 bsl 64) ->
NBytes = eu(N, 8),
{ok, <<16#FF, NBytes/binary>>}.
eu(N, Size) ->
Bytes = binary:encode_unsigned(N, little),
NExtraZeros = Size - byte_size(Bytes),
ExtraZeros = << <<0>> || _ <- lists:seq(1, NExtraZeros) >>,
<<Bytes/binary, ExtraZeros/binary>>.
do_sign_tx(Request = #{"public_id" := ID, "payload" := CallData, "network_id" := NID}, do_sign_tx(Request = #{"public_id" := ID, "payload" := CallData, "network_id" := NID},
#s{wallet = #wallet{keys = Keys}}) -> #s{wallet = #wallet{keys = Keys}}) ->
BinNID = list_to_binary(NID), BinNID = list_to_binary(NID),
@ -810,6 +811,12 @@ do_nonce(ID) ->
hz:next_nonce(ID). hz:next_nonce(ID).
do_network(#s{wallet = none}) ->
none;
do_network(#s{wallet = #wallet{chain_id = ChainID}}) ->
{ok, ChainID}.
%%% State Operations %%% State Operations
@ -862,7 +869,9 @@ do_make_key2(Name, Bin, Transform,
#wallet{name = WalletName, poas = POAs, keys = Keys} = Current, #wallet{name = WalletName, poas = POAs, keys = Keys} = Current,
T = transform(Transform), T = transform(Transform),
Seed = T(Bin), Seed = T(Bin),
Key = #key{name = KeyName, id = ID} = gd_key_master:make_key(Name, Seed), {ID, Pair} = hz_key_master:make_key(Seed),
KeyName = case Name =:= "" of true -> ID; false -> Name end,
Key = #key{name = KeyName, id = ID, pair = Pair},
POA = #poa{name = KeyName, id = ID}, POA = #poa{name = KeyName, id = ID},
NewKeys = [Key | Keys], NewKeys = [Key | Keys],
NewPOAs = [POA | POAs], NewPOAs = [POA | POAs],
@ -901,7 +910,7 @@ t_xor(B, A) ->
do_recover_key(Mnemonic, State) -> do_recover_key(Mnemonic, State) ->
case gd_key_master:decode(Mnemonic) of case hz_key_master:decode(Mnemonic) of
{ok, Seed} -> {ok, Seed} ->
do_recover_key2(Seed, State); do_recover_key2(Seed, State);
Error -> Error ->
@ -911,11 +920,12 @@ do_recover_key(Mnemonic, State) ->
do_recover_key2(Seed, State = #s{wallet = Current, wallets = Wallets, pass = Pass}) -> do_recover_key2(Seed, State = #s{wallet = Current, wallets = Wallets, pass = Pass}) ->
#wallet{name = WalletName, keys = Keys, poas = POAs} = Current, #wallet{name = WalletName, keys = Keys, poas = POAs} = Current,
Recovered = #key{id = ID, name = AccName} = gd_key_master:make_key("", Seed), {ID, Pair} = hz_key_master:make_key(Seed),
Recovered = #key{id = ID, name = ID, pair = Pair},
case lists:keymember(ID, #key.id, Keys) of case lists:keymember(ID, #key.id, Keys) of
false -> false ->
NewKeys = [Recovered | Keys], NewKeys = [Recovered | Keys],
POA = #poa{name = AccName, id = ID}, POA = #poa{name = ID, id = ID},
NewPOAs = [POA | POAs], NewPOAs = [POA | POAs],
ok = gd_gui:show(NewPOAs), ok = gd_gui:show(NewPOAs),
Updated = Current#wallet{poas = NewPOAs, keys = NewKeys}, Updated = Current#wallet{poas = NewPOAs, keys = NewKeys},
@ -930,7 +940,7 @@ do_recover_key2(Seed, State = #s{wallet = Current, wallets = Wallets, pass = Pas
do_mnemonic(ID, #s{wallet = #wallet{keys = Keys}}) -> do_mnemonic(ID, #s{wallet = #wallet{keys = Keys}}) ->
case lists:keyfind(ID, #key.id, Keys) of case lists:keyfind(ID, #key.id, Keys) of
#key{pair = #{secret := <<K:32/binary, _/binary>>}} -> #key{pair = #{secret := <<K:32/binary, _/binary>>}} ->
Mnemonic = gd_key_master:encode(K), Mnemonic = hz_key_master:encode(K),
{ok, Mnemonic}; {ok, Mnemonic};
false -> false ->
{error, bad_key} {error, bad_key}
@ -975,22 +985,26 @@ do_deploy3(#{"tx_hash" := TXHash}) ->
end. end.
do_rename_key(ID, NewName, State = #s{wallet = W}) -> do_rename_key(ID, NewName, State = #s{wallet = W, wallets = Wallets, pass = Pass}) ->
#wallet{poas = POAs, keys = Keys} = W, #wallet{name = Name, poas = POAs, keys = Keys} = W,
RW = lists:keyfind(Name, #wr.name, Wallets),
A = lists:keyfind(ID, #poa.id, POAs), A = lists:keyfind(ID, #poa.id, POAs),
K = lists:keyfind(ID, #key.id, Keys), K = lists:keyfind(ID, #key.id, Keys),
NewPOAs = lists:keystore(ID, #poa.id, POAs, A#poa{name = NewName}), NewPOAs = lists:keystore(ID, #poa.id, POAs, A#poa{name = NewName}),
NewKeys = lists:keystore(ID, #key.id, Keys, K#key{name = NewName}), NewKeys = lists:keystore(ID, #key.id, Keys, K#key{name = NewName}),
NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys}, NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys},
ok = save_wallet(RW, Pass, NewWallet),
ok = gd_gui:show(NewPOAs), ok = gd_gui:show(NewPOAs),
State#s{wallet = NewWallet}. State#s{wallet = NewWallet}.
do_drop_key(ID, State = #s{wallet = W}) -> do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) ->
#wallet{poas = POAs, keys = Keys} = W, #wallet{name = Name, poas = POAs, keys = Keys} = W,
RW = lists:keyfind(Name, #wr.name, Wallets),
NewPOAs = lists:keydelete(ID, #poa.id, POAs), NewPOAs = lists:keydelete(ID, #poa.id, POAs),
NewKeys = lists:keydelete(ID, #key.id, Keys), NewKeys = lists:keydelete(ID, #key.id, Keys),
NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys}, NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys},
ok = save_wallet(RW, Pass, NewWallet),
ok = gd_gui:show(NewPOAs), ok = gd_gui:show(NewPOAs),
State#s{wallet = NewWallet}. State#s{wallet = NewWallet}.
@ -1015,12 +1029,25 @@ do_open_wallet(Path, Phrase, State) ->
default_wallet() -> default_wallet() ->
DevNet = #net{id = <<"devnet">>, chains = [#chain{}]}, default_wallet(testnet).
% TestChain1 = #chain{id = <<"groot.testnet">>,
% nodes = [#node{ip = {1,2,3,4}}, #node{ip = {5,6,7,8}}]}, default_wallet(mainnet) ->
% TestChain2 = #chain{id = <<"test_ac.testnet">>, Node = #node{ip = "groot.mainnet.gajumaru.io"},
% nodes = [#node{ip = {11,12,13,14}}, #node{ip = {15,16,17,18}}]}, Groot = #chain{id = <<"groot.mainnet">>,
% TestNet = #net{id = <<"testnet">>, chains = [TestChain1, TestChain2]}, nodes = [Node]},
MainNet = #net{id = <<"mainnet">>, chains = [Groot]},
#wallet{nets = [MainNet], endpoint = Node};
default_wallet(testnet) ->
Node = #node{ip = "groot.testnet.gajumaru.io"},
Groot = #chain{id = <<"groot.testnet">>,
nodes = [Node]},
TestNet = #net{id = <<"testnet">>, chains = [Groot]},
#wallet{nets = [TestNet], endpoint = Node};
default_wallet(devnet) ->
% TODO: This accounts for the nature of devnets by defining *no* chains.
% The GUI/CON will need to manage this properly when encountered and prompt.
DevNet = #net{id = <<"devnet">>,
chains = []},
#wallet{nets = [DevNet]}. #wallet{nets = [DevNet]}.
@ -1055,20 +1082,32 @@ do_password(Old, New, State = #s{pass = Pass}) ->
do_stop(State = #s{prefs = Prefs}) -> do_stop(State = #s{prefs = Prefs}) ->
ok = persist(Prefs), ok = persist(Prefs),
do_close_wallet(State). NewState = do_close_wallet(State),
ok =
case is_pid(whereis(gmc_con)) of
false -> zx:stop();
true -> application:stop(gajudesk)
end,
NewState.
do_new_wallet(Name, Path, Password, State = #s{wallets = Wallets, prefs = Prefs}) -> do_new_wallet(Net, Name, Path, Password, State = #s{wallets = Wallets, prefs = Prefs}) ->
case lists:keyfind(Name, #wr.name, Wallets) of case lists:keyfind(Name, #wr.name, Wallets) of
false -> false ->
NextState = do_close_wallet(State), NextState = do_close_wallet(State),
Pass = pass(Password), Pass = pass(Password),
HasPass = Pass =/= none, HasPass = Pass =/= none,
Entry = #wr{name = Name, path = Path, pass = HasPass}, Entry = #wr{name = Name, path = Path, pass = HasPass},
New = #wallet{name = Name}, DW = #wallet{endpoint = Node} = default_wallet(Net),
New = DW#wallet{name = Name},
ok = save_wallet(Entry, Pass, New), ok = save_wallet(Entry, Pass, New),
ok = gd_gui:show([]), ok = gd_gui:show([]),
ok = gd_gui:wallet(Name), ok = gd_gui:wallet(Name),
ok =
case ensure_hz_set(Node) of
{ok, ChainID} -> gd_gui:chain(ChainID, Node);
Error -> gd_gui:trouble(Error)
end,
NewWallets = [Entry | Wallets], NewWallets = [Entry | Wallets],
NewPrefs = put_prefs(wallets, NewWallets, Prefs), NewPrefs = put_prefs(wallets, NewWallets, Prefs),
ok = persist(NewPrefs), ok = persist(NewPrefs),
@ -1226,6 +1265,28 @@ read3(T) ->
end. end.
do_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, selected = Selected}) when Selected > 0 ->
NewState =
case ensure_hz_set(Node) of
{ok, ChainID} ->
POA = #poa{id = ID} = lists:nth(Selected, POAs),
CheckBalance = check_balance(ChainID),
NewPOA = CheckBalance(POA),
NewPOAs = lists:keystore(ID, #poa.id, POAs, NewPOA),
ok = gd_gui:show(NewPOAs),
State#s{wallet = This#wallet{poas = POAs}};
Error ->
ok = log(info, "Balance update on tic failed with: ~p", [Error]),
State
end,
T = erlang:send_after(tic(), self(), tic),
NewState#s{timer = T};
do_tic(State) ->
T = erlang:send_after(tic(), self(), tic),
State#s{timer = T}.
persist(Prefs) -> persist(Prefs) ->
Path = prefs_path(), Path = prefs_path(),
ok = filelib:ensure_dir(Path), ok = filelib:ensure_dir(Path),

View File

@ -37,7 +37,7 @@
%%% @end %%% @end
-module(gd_grids). -module(gd_grids).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").

View File

@ -3,7 +3,7 @@
%%% @end %%% @end
-module(gd_gui). -module(gd_gui).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").
@ -32,7 +32,7 @@
{wx = none :: none | wx:wx_object(), {wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(),
sizer = none :: none | wx:wx_object(), sizer = none :: none | wx:wx_object(),
lang = en :: en | jp, lang = en_US :: en_US | ja_JP,
j = none :: none | fun(), j = none :: none | fun(),
prefs = #{} :: #{atom() := term()}, prefs = #{} :: #{atom() := term()},
accounts = [] :: [gajudesk:poa()], accounts = [] :: [gajudesk:poa()],
@ -84,18 +84,21 @@ start_link(Accounts) ->
init(Prefs) -> init(Prefs) ->
ok = log(info, "GUI starting..."), ok = log(info, "GUI starting..."),
Lang = maps:get(lang, Prefs, en_us), Lang = maps:get(lang, Prefs, en_US),
Trans = gd_jt:read_translations(?MODULE), Trans = gd_jt:read_translations(?MODULE),
J = gd_jt:j(Lang, Trans), J = gd_jt:j(Lang, Trans),
AppName = J("GajuDesk"),
VSN = proplists:get_value(vsn, ?MODULE:module_info(attributes)),
Wx = wx:new(), Wx = wx:new(),
Frame = wxFrame:new(Wx, ?wxID_ANY, J("GajuDesk")), Frame = wxFrame:new(Wx, ?wxID_ANY, AppName ++ " v" ++ VSN),
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}]),
WallB = wxButton:new(Frame, ?wxID_ANY, [{label, "[none]"}, {style, ?wxBORDER_NONE}]), WallB = wxButton:new(Frame, ?wxID_ANY, [{label, "[none]"}, {style, ?wxBORDER_NONE}]),
WallW = #w{name = wallet, id = wxButton:getId(WallB), wx = WallB}, WallW = #w{name = wallet, id = wxButton:getId(WallB), wx = WallB},
ChainB = wxButton:new(Frame, ?wxID_ANY, [{label, "[ChainID]"}, {style, ?wxBORDER_NONE}]), ChainB = wxButton:new(Frame, ?wxID_ANY, [{label, "[ChainID]"}, {style, ?wxBORDER_NONE}]),
_ = wxButton:disable(ChainB),
ChainW = #w{name = chain, id = wxButton:getId(ChainB), wx = ChainB}, ChainW = #w{name = chain, id = wxButton:getId(ChainB), wx = ChainB},
NodeB = wxButton:new(Frame, ?wxID_ANY, [{label, "[Node]"}, {style, ?wxBORDER_NONE}]), NodeB = wxButton:new(Frame, ?wxID_ANY, [{label, "[Node]"}, {style, ?wxBORDER_NONE}]),
NodeW = #w{name = node, id = wxButton:getId(NodeB), wx = NodeB}, NodeW = #w{name = node, id = wxButton:getId(NodeB), wx = NodeB},
@ -131,7 +134,7 @@ init(Prefs) ->
{rename, J("Rename")}, {rename, J("Rename")},
{drop_key, J("Delete")}, {drop_key, J("Delete")},
{send, J("Send Money")}, {send, J("Send Money")},
{recv, J("Receive Money")}, % {recv, J("Receive Money")},
{grids, J("GRIDS URL")}, {grids, J("GRIDS URL")},
{copy, J("Copy")}, {copy, J("Copy")},
{www, J("WWW")}, {www, J("WWW")},
@ -144,6 +147,12 @@ init(Prefs) ->
end, end,
Buttons = [WallW, ChainW, NodeW, DevW | lists:map(MakeButton, ButtonTemplates)], Buttons = [WallW, ChainW, NodeW, DevW | lists:map(MakeButton, ButtonTemplates)],
Disable =
fun(Button) ->
#w{wx = W} = lists:keyfind(Button, #w.name, Buttons),
wxButton:disable(W)
end,
ok = lists:foreach(Disable, key_buttons()),
ChainSz = wxBoxSizer:new(?wxHORIZONTAL), ChainSz = wxBoxSizer:new(?wxHORIZONTAL),
AccountSz = wxBoxSizer:new(?wxHORIZONTAL), AccountSz = wxBoxSizer:new(?wxHORIZONTAL),
@ -173,18 +182,18 @@ init(Prefs) ->
_ = wxSizer:add(AccountSz, DropBn, zxw:flags(wide)), _ = wxSizer:add(AccountSz, DropBn, zxw:flags(wide)),
#w{wx = SendBn} = lists:keyfind(send, #w.name, Buttons), #w{wx = SendBn} = lists:keyfind(send, #w.name, Buttons),
#w{wx = RecvBn} = lists:keyfind(recv, #w.name, Buttons), % #w{wx = RecvBn} = lists:keyfind(recv, #w.name, Buttons),
#w{wx = GridsBn} = lists:keyfind(grids, #w.name, Buttons), #w{wx = GridsBn} = lists:keyfind(grids, #w.name, Buttons),
_ = wxSizer:add(ActionsSz, SendBn, zxw:flags(wide)), _ = wxSizer:add(ActionsSz, SendBn, zxw:flags(wide)),
_ = wxSizer:add(ActionsSz, RecvBn, zxw:flags(wide)), % _ = wxSizer:add(ActionsSz, RecvBn, zxw:flags(wide)),
_ = wxSizer:add(ActionsSz, GridsBn, zxw:flags(wide)), _ = wxSizer:add(ActionsSz, GridsBn, zxw:flags(wide)),
#w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons), #w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons),
HistoryWin = wxScrolledWindow:new(Frame), % HistoryWin = wxScrolledWindow:new(Frame),
HistorySz = wxBoxSizer:new(?wxVERTICAL), % HistorySz = wxBoxSizer:new(?wxVERTICAL),
ok = wxScrolledWindow:setSizerAndFit(HistoryWin, HistorySz), % ok = wxScrolledWindow:setSizerAndFit(HistoryWin, HistorySz),
ok = wxScrolledWindow:setScrollRate(HistoryWin, 5, 5), % ok = wxScrolledWindow:setScrollRate(HistoryWin, 5, 5),
_ = wxSizer:add(MainSz, ChainSz, zxw:flags(base)), _ = wxSizer:add(MainSz, ChainSz, zxw:flags(base)),
_ = wxSizer:add(MainSz, AccountSz, zxw:flags(base)), _ = wxSizer:add(MainSz, AccountSz, zxw:flags(base)),
@ -192,7 +201,7 @@ init(Prefs) ->
_ = wxSizer:add(MainSz, DetailsSz, zxw:flags(base)), _ = wxSizer:add(MainSz, DetailsSz, zxw:flags(base)),
_ = wxSizer:add(MainSz, ActionsSz, zxw:flags(base)), _ = wxSizer:add(MainSz, ActionsSz, zxw:flags(base)),
_ = wxSizer:add(MainSz, Refresh, zxw:flags(base)), _ = wxSizer:add(MainSz, Refresh, zxw:flags(base)),
_ = wxSizer:add(MainSz, HistoryWin, zxw:flags(wide)), % _ = wxSizer:add(MainSz, HistoryWin, zxw:flags(wide)),
ok = wxFrame:setSizer(Frame, MainSz), ok = wxFrame:setSizer(Frame, MainSz),
ok = wxSizer:layout(MainSz), ok = wxSizer:layout(MainSz),
@ -207,8 +216,8 @@ init(Prefs) ->
picker = Picker, picker = Picker,
id = ID_W, id = ID_W,
balance = Balance, balance = Balance,
buttons = Buttons, buttons = Buttons},
history = #h{win = HistoryWin, sz = HistorySz}}, % history = #h{win = HistoryWin, sz = HistorySz}},
{Frame, State}. {Frame, State}.
@ -690,15 +699,27 @@ copy_to_clipboard(String) ->
end. end.
www(State = #s{id = {_, #w{wx = ID_T}}}) -> www(State = #s{id = {_, #w{wx = ID_T}}, j = J}) ->
String = wxStaticText:getLabel(ID_T), case wxStaticText:getLabel(ID_T) of
URL = unicode:characters_to_list(["https://aescan.io/accounts/", String]), "" ->
ok = ok = handle_troubling(State, J("No key selected.")),
case wx_misc:launchDefaultBrowser(URL) of State;
true -> log(info, "Opened in browser: ~ts", [URL]); ID ->
false -> log(info, "Failed to open browser: ~ts", [URL]) ok = www2(State, ID),
end, State
State. end.
www2(State = #s{j = J}, AccountID) ->
case gd_con:network() of
{ok, ChainID} ->
URL = unicode:characters_to_list(["https://", ChainID, ".gajumaru.io/account/", AccountID]),
case wx_misc:launchDefaultBrowser(URL) of
true -> log(info, "Opened in browser: ~ts", [URL]);
false -> log(info, "Failed to open browser: ~ts", [URL])
end;
none ->
handle_troubling(State, J("No chain assigned."))
end.
spend(State = #s{accounts = []}) -> spend(State = #s{accounts = []}) ->
@ -735,7 +756,11 @@ spend2(#poa{id = ID, name = Name}, Nonce, Height, State = #s{frame = Frame, j =
AmtTx = wxTextCtrl:new(Dialog, ?wxID_ANY), AmtTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Amount")}]), AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Amount")}]),
_ = wxStaticBoxSizer:add(AmtSz, AmtTx, zxw:flags(wide)), AmtInSz = wxBoxSizer:new(?wxHORIZONTAL),
AmtLabel = wxStaticText:new(Dialog, ?wxID_ANY, ""),
_ = wxStaticBoxSizer:add(AmtInSz, AmtLabel, zxw:flags(base)),
_ = wxStaticBoxSizer:add(AmtInSz, AmtTx, zxw:flags(wide)),
_ = wxStaticBoxSizer:add(AmtSz, AmtInSz, zxw:flags(wide)),
DataTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), DataTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]),
DataSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Message (optional)")}]), DataSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Message (optional)")}]),
@ -897,12 +922,28 @@ clear_account(State = #s{balance = {_, #w{wx = B}}, id = {_, #w{wx = I}}}) ->
do_wallet(none, #s{buttons = Buttons}) -> do_wallet(none, #s{buttons = Buttons}) ->
#w{wx = WalletB} = lists:keyfind(wallet, #w.name, Buttons), #w{wx = WalletB} = lists:keyfind(wallet, #w.name, Buttons),
Disable =
fun(Button) ->
#w{wx = W} = lists:keyfind(Button, #w.name, Buttons),
wxButton:disable(W)
end,
ok = lists:foreach(Disable, key_buttons()),
ok = wxButton:setLabel(WalletB, "[no wallet]"); ok = wxButton:setLabel(WalletB, "[no wallet]");
do_wallet(Name, #s{buttons = Buttons}) -> do_wallet(Name, #s{buttons = Buttons}) ->
#w{wx = WalletB} = lists:keyfind(wallet, #w.name, Buttons), #w{wx = WalletB} = lists:keyfind(wallet, #w.name, Buttons),
Enable =
fun(Button) ->
#w{wx = W} = lists:keyfind(Button, #w.name, Buttons),
wxButton:enable(W)
end,
ok = lists:foreach(Enable, key_buttons()),
ok = wxButton:setLabel(WalletB, Name). ok = wxButton:setLabel(WalletB, Name).
key_buttons() ->
[make_key, recover, mnemonic, rename, drop_key].
do_chain(none, none, #s{buttons = Buttons}) -> do_chain(none, none, #s{buttons = Buttons}) ->
#w{wx = ChainB} = lists:keyfind(chain, #w.name, Buttons), #w{wx = ChainB} = lists:keyfind(chain, #w.name, Buttons),
#w{wx = NodeB} = lists:keyfind(node, #w.name, Buttons), #w{wx = NodeB} = lists:keyfind(node, #w.name, Buttons),

View File

@ -15,7 +15,7 @@
%%% translation library is retained). %%% translation library is retained).
-module(gd_jt). -module(gd_jt).
-vsn("0.5.2"). -vsn("0.6.6").
-export([read_translations/1, j/2, oneshot_j/2]). -export([read_translations/1, j/2, oneshot_j/2]).

View File

@ -1,164 +0,0 @@
%%% @doc
%%% Key functions go here.
%%%
%%% The main reason this is a module of its own is that in the original architecture
%%% it was a process rather than just a library of functions. Now that it exists, though,
%%% there is little motivation to cram everything here into the controller process's
%%% code.
%%% @end
-module(gd_key_master).
-vsn("0.5.2").
-export([make_key/2, encode/1, decode/1]).
-export([lcg/1]).
-include("gd.hrl").
make_key("", <<>>) ->
Pair = #{public := Public} = ecu_eddsa:sign_keypair(),
ID = gmser_api_encoder:encode(account_pubkey, Public),
Name = binary_to_list(ID),
#key{name = Name, id = ID, pair = Pair};
make_key("", Seed) ->
Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed),
ID = gmser_api_encoder:encode(account_pubkey, Public),
Name = binary_to_list(ID),
#key{name = Name, id = ID, pair = Pair};
make_key(Name, <<>>) ->
Pair = #{public := Public} = ecu_eddsa:sign_keypair(),
ID = gmser_api_encoder:encode(account_pubkey, Public),
#key{name = Name, id = ID, pair = Pair};
make_key(Name, Seed) ->
Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed),
ID = gmser_api_encoder:encode(account_pubkey, Public),
#key{name = Name, id = ID, pair = Pair}.
-spec encode(Secret) -> Phrase
when Secret :: binary(),
Phrase :: string().
%% @doc
%% The encoding and decoding procesures are written to be able to handle any
%% width of bitstring or binary and a variable size dictionary. The magic numbers
%% 32, 4096 and 12 have been dropped in because currently these are known, but that
%% will change in the future if the key size or type changes.
encode(Bin) ->
<<Number:(32 * 8)>> = Bin,
DictSize = 4096,
Words = read_words(),
% Width = chunksize(DictSize - 1, 2),
Width = 12,
Chunks = chunksize(Number, DictSize),
Binary = <<Number:(Chunks * Width)>>,
encode(Width, Binary, Words).
encode(Width, Bits, Words) ->
CheckSum = checksum(Width, Bits),
encode(Width, <<CheckSum:Width, Bits/bitstring>>, Words, []).
encode(_, <<>>, _, Acc) ->
unicode:characters_to_list(lists:join(" ", lists:reverse(Acc)));
encode(Width, Bits, Words, Acc) ->
<<I:Width, Rest/bitstring>> = Bits,
Word = lists:nth(I + 1, Words),
encode(Width, Rest, Words, [Word | Acc]).
-spec decode(Phrase) -> {ok, Secret} | {error, Reason}
when Phrase :: string(),
Secret :: binary(),
Reason :: bad_phrase | bad_word.
%% @doc
%% Reverses the encoded secret string back into its binary representation.
decode(Encoded) ->
DictSize = 4096,
Words = read_words(),
Width = chunksize(DictSize - 1, 2),
decode(Width, Words, Encoded).
decode(Width, Words, Encoded) when is_list(Encoded) ->
decode(Width, Words, list_to_binary(Encoded));
decode(Width, Words, Encoded) ->
Split = string:lexemes(Encoded, " "),
decode(Width, Words, Split, <<>>).
decode(Width, Words, [Word | Rest], Acc) ->
case find(Word, Words) of
{ok, N} -> decode(Width, Words, Rest, <<Acc/bitstring, N:Width>>);
Error -> Error
end;
decode(Width, _, [], Acc) ->
sumcheck(Width, Acc).
chunksize(N, C) ->
chunksize(N, C, 0).
chunksize(0, _, A) -> A;
chunksize(N, C, A) -> chunksize(N div C, C, A + 1).
read_words() ->
Path = filename:join([zx:get_home(), "priv", "words4096.txt"]),
{ok, Bin} = file:read_file(Path),
string:lexemes(Bin, "\n").
find(Word, Words) ->
find(Word, Words, 0).
find(Word, [Word | _], N) -> {ok, N};
find(Word, [_ | Rest], N) -> find(Word, Rest, N + 1);
find(Word, [], _) -> {error, {bad_word, Word}}.
checksum(Width, Bits) ->
checksum(Width, Bits, 0).
checksum(_, <<>>, Sum) ->
Sum;
checksum(Width, Bits, Sum) ->
<<N:Width, Rest/bitstring>> = Bits,
checksum(Width, Rest, N bxor Sum).
sumcheck(Width, Bits) ->
<<CheckSum:Width, Binary/bitstring>> = Bits,
case checksum(Width, Binary) =:= CheckSum of
true ->
<<N:(bit_size(Binary))>> = Binary,
{ok, <<N:(32 * 8)>>};
false ->
{error, bad_phrase}
end.
-spec lcg(integer()) -> integer().
%% A simple PRNG that fits into 32 bits and is easy to implement anywhere (Kotlin).
%% Specifically, it is a "linear congruential generator" of the Lehmer variety.
%% The constants used are based on recommendations from Park, Miller and Stockmeyer:
%% https://www.firstpr.com.au/dsp/rand31/p105-crawford.pdf#page=4
%%
%% The input value should be between 1 and 2^31-1.
%%
%% The purpose of this PRNG is for password-based dictionary shuffling.
lcg(N) ->
M = 16#7FFFFFFF,
A = 48271,
Q = 44488, % M div A
R = 3399, % M rem A
Div = N div Q,
Rem = N rem Q,
S = Rem * A,
T = Div * R,
Result = S - T,
case Result < 0 of
false -> Result;
true -> Result + M
end.

194
src/gd_sophia_editor.erl Normal file
View File

@ -0,0 +1,194 @@
-module(gd_sophia_editor).
-vsn("0.6.6").
-export([new/1, update/2,
get_text/1, set_text/2]).
-include("$zx_include/zx_logger.hrl").
-include_lib("wx/include/wx.hrl").
%%% Formatting Constants
%% Style labels
-define(DEFAULT, 0).
-define(KEYWORD, 1).
-define(IDENTIFIER, 2).
-define(COMMENT, 3).
-define(STRING, 4).
-define(NUMBER, 5).
-define(OPERATOR, 6).
%% Color palette
% Intensities:
-define(H, 255). % High
-define(M, 192). % Medium
-define(L, 128). % Low
-define(X, 32). % X-Low
-define(Z, 0). % Zilch
% RGB values
% R G B
-define(black, {?Z, ?Z, ?Z}).
-define(light_red, {?H, ?Z, ?Z}).
-define(light_green, {?Z, ?H, ?Z}).
-define(light_blue, {?Z, ?Z, ?H}).
-define(yellow, {?H, ?H, ?Z}).
-define(light_magenta, {?H, ?Z, ?H}).
-define(light_cyan, {?Z, ?H, ?H}).
-define(high_white, {?H, ?H, ?H}).
-define(red, {?L, ?Z, ?Z}).
-define(green, {?Z, ?L, ?Z}).
-define(blue, {?Z, ?Z, ?L}).
-define(brown, {?L, ?L, ?Z}).
-define(magenta, {?L, ?Z, ?L}).
-define(cyan, {?Z, ?L, ?L}).
-define(not_black, {?X, ?X, ?X}).
-define(grey, {?L, ?L, ?L}).
-define(white, {?M, ?M, ?M}).
styles() ->
[?DEFAULT,
?KEYWORD,
?IDENTIFIER,
?COMMENT,
?STRING,
?NUMBER,
?OPERATOR].
palette(light) ->
#{?DEFAULT => ?black,
?KEYWORD => ?blue,
?IDENTIFIER => ?cyan,
?COMMENT => ?grey,
?STRING => ?red,
?NUMBER => ?magenta,
?OPERATOR => ?brown,
bg => ?high_white};
palette(dark) ->
#{?DEFAULT => ?white,
?KEYWORD => ?light_cyan,
?IDENTIFIER => ?green,
?COMMENT => ?grey,
?STRING => ?light_red,
?NUMBER => ?light_magenta,
?OPERATOR => ?yellow,
bg => ?not_black}.
color_mode() ->
{R, G, B, _} = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW),
case (lists:sum([R, G, B]) div 3) > 128 of
true -> light;
false -> dark
end.
new(Parent) ->
STC = wxStyledTextCtrl:new(Parent),
ok = wxStyledTextCtrl:setLexer(STC, ?wxSTC_LEX_CONTAINER),
FontSize = 13,
Mono = wxFont:new(FontSize,
?wxFONTFAMILY_TELETYPE,
?wxFONTSTYLE_NORMAL,
?wxFONTWEIGHT_NORMAL,
[{face, "Monospace"}]),
SetMonospace = fun(Style) -> wxStyledTextCtrl:styleSetFont(STC, Style, Mono) end,
ok = lists:foreach(SetMonospace, styles()),
ok = wxStyledTextCtrl:styleSetFont(STC, ?wxSTC_STYLE_DEFAULT, Mono),
ok = set_colors(STC),
STC.
get_text(STC) ->
wxStyledTextCtrl:getText(STC).
set_text(STC, Text) ->
ok = wxStyledTextCtrl:setText(STC, Text),
%% Force Scintilla to request styling for the entire text
wxStyledTextCtrl:colourise(STC, 0, -1).
set_colors(STC) ->
ok = wxStyledTextCtrl:styleClearAll(STC),
Palette = #{bg := BGC} = palette(color_mode()),
Colorize =
fun(Style) ->
Color = maps:get(Style, Palette),
ok = wxStyledTextCtrl:styleSetForeground(STC, Style, Color),
ok = wxStyledTextCtrl:styleSetBackground(STC, Style, BGC)
end,
ok = wxStyledTextCtrl:styleSetBackground(STC, ?wxSTC_STYLE_DEFAULT, BGC),
lists:foreach(Colorize, styles()).
update(_Event, STC) ->
Text = wxStyledTextCtrl:getText(STC),
case so_scan:scan(Text) of
{ok, Tokens} ->
ok = wxStyledTextCtrl:startStyling(STC, 0),
apply_styles(STC, Tokens);
{error, _Reason} ->
ok
end.
apply_styles(STC, Tokens) ->
lists:foreach(fun(Token) -> style_token(STC, Token) end, Tokens).
% FIXME: 'qid' is not properly handled. If there are multi-dot qids, they will break
style_token(STC, Token) ->
{Type, Value} = type_and_value(Token),
{StartOffset, LengthOffset} = offset(Type),
{Line, Col} = element(2, Token),
Length = byte_size(to_binary(Value)) + LengthOffset,
Style = classify_style(Type, Value),
Start = wxStyledTextCtrl:positionFromLine(STC, Line - 1) + Col + StartOffset,
wxStyledTextCtrl:startStyling(STC, Start),
wxStyledTextCtrl:setStyling(STC, Length, Style).
offset(string) -> { 0, 0};
offset(qid) -> {-1, 1};
offset(_) -> {-1, 0}.
to_binary(S) when is_list(S) ->
unicode:characters_to_binary(S);
to_binary(S) when is_binary(S) ->
S;
to_binary(I) when is_integer(I) ->
integer_to_binary(I).
classify_style(Type, Value) ->
case Type of
symbol ->
case lists:member(Value, keywords()) of
true -> ?KEYWORD;
false -> ?OPERATOR
end;
id -> ?IDENTIFIER;
qid -> ?IDENTIFIER;
con -> ?IDENTIFIER;
qcon -> ?IDENTIFIER;
tvar -> ?IDENTIFIER;
string -> ?STRING;
char -> ?STRING;
int -> ?NUMBER;
hex -> ?NUMBER;
bytes -> ?NUMBER;
skip -> ?COMMENT;
_ -> ?DEFAULT
end.
type_and_value({Type, _Line, Value}) -> {Type, Value};
type_and_value({Type, _}) -> {Type, atom_to_list(Type)}.
keywords() ->
["contract", "include", "let", "switch", "type", "record", "datatype", "if",
"elif", "else", "function", "stateful", "payable", "true", "false", "mod",
"public", "entrypoint", "private", "indexed", "namespace", "interface",
"main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot"].

View File

@ -12,7 +12,7 @@
%%% @end %%% @end
-module(gd_sup). -module(gd_sup).
-vsn("0.5.2"). -vsn("0.6.6").
-behaviour(supervisor). -behaviour(supervisor).
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").

View File

@ -1,5 +1,5 @@
-module(gd_v). -module(gd_v).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").

View File

@ -1,5 +1,5 @@
-module(gd_v_devman). -module(gd_v_devman).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").
@ -15,6 +15,8 @@
-include("$zx_include/zx_logger.hrl"). -include("$zx_include/zx_logger.hrl").
-include("gd.hrl"). -include("gd.hrl").
-define(editorMode, sophia).
% Widgets % Widgets
-record(w, -record(w,
{name = none :: atom() | {FunName :: binary(), call | dryr}, {name = none :: atom() | {FunName :: binary(), call | dryr},
@ -256,6 +258,9 @@ handle_event(E = #wx{event = #wxCommand{type = command_button_clicked},
State State
end, end,
{noreply, NewState}; {noreply, NewState};
handle_event(#wx{event = Event = #wxStyledText{type = stc_styleneeded}, obj = Win}, State) ->
ok = style(State, Win, Event),
{noreply, State};
handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) -> handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) ->
Geometry = Geometry =
case wxTopLevelWindow:isMaximized(Frame) of case wxTopLevelWindow:isMaximized(Frame) of
@ -291,6 +296,14 @@ terminate(Reason, State) ->
%%% Doers %%% Doers
style(#s{code = {_, Pages}}, Win, Event) ->
case lists:keyfind(Win, #p.win, Pages) of
#p{code = STC} ->
gd_sophia_editor:update(Event, STC);
false ->
tell("Received bogus style event.~nWin: ~p~nEvent: ~p", [Win, Event])
end.
clicked(State = #s{cons = {Consbook, Contracts}}, Name) -> clicked(State = #s{cons = {Consbook, Contracts}}, Name) ->
case wxNotebook:getSelection(Consbook) of case wxNotebook:getSelection(Consbook) of
?wxNOT_FOUND -> ?wxNOT_FOUND ->
@ -416,12 +429,11 @@ call_params([{L, C} | T], A) ->
clicked4(State, clicked4(State,
#c{id = ConID, build = #{aci := ACI}, funs = {_, Funs}}, #c{id = ConID, build = #{aci := ACI}, funs = {_, Funs}},
{Name, Type}, {Name, Type},
{PK, Nonce, TTL, GasP, Gas, Amount}) -> {PK, Nonce, TTL, GasP, Gas, Amt}) ->
AACI = hz:prepare_aaci(ACI), AACI = hz:prepare_aaci(ACI),
#f{args = ArgFields} = lists:keyfind(Name, #f.name, Funs), #f{args = ArgFields} = maps:get(Name, Funs),
Args = lists:map(fun get_arg/1, ArgFields), Args = lists:map(fun get_arg/1, ArgFields),
FunName = binary_to_list(Name), case hz:contract_call(PK, Nonce, Gas, GasP, Amt, TTL, AACI, ConID, Name, Args) of
case hz:contract_call(PK, Nonce, Gas, GasP, Amount, TTL, AACI, ConID, FunName, Args) of
{ok, UnsignedTX} -> {ok, UnsignedTX} ->
case Type of case Type of
call -> do_call(State, ConID, PK, UnsignedTX); call -> do_call(State, ConID, PK, UnsignedTX);
@ -504,22 +516,13 @@ add_code_page2(State, {hash, Address}) ->
open_hash2(State, Address). open_hash2(State, Address).
add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Code) -> add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Code) ->
% FIXME: One of these days we need to define the text area as a wxStyledTextCtrl and will Color = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW),
% have to contend with system theme issues (light/dark themese, namely) tell("Color: ~p", [Color]),
% Leaving this little thing here to remind myself how any of that works later.
% The call below returns a wx_color4() type (not that we need alpha...).
% Color = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW),
% tell("Color: ~p", [Color]),
Window = wxWindow:new(Codebook, ?wxID_ANY), Window = wxWindow:new(Codebook, ?wxID_ANY),
PageSz = wxBoxSizer:new(?wxHORIZONTAL), PageSz = wxBoxSizer:new(?wxHORIZONTAL),
CodeTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_PROCESS_TAB bor ?wxTE_DONTWRAP}, CodeTx = gd_sophia_editor:new(Window),
CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), ok = gd_sophia_editor:set_text(CodeTx, Code),
TextAt = wxTextAttr:new(),
Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, [{face, "Monospace"}]),
ok = wxTextAttr:setFont(TextAt, Mono),
true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt),
ok = wxTextCtrl:setValue(CodeTx, Code),
_ = wxSizer:add(PageSz, CodeTx, zxw:flags(wide)), _ = wxSizer:add(PageSz, CodeTx, zxw:flags(wide)),
@ -531,6 +534,7 @@ add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Co
{file, Path} -> filename:basename(Path); {file, Path} -> filename:basename(Path);
{hash, Addr} -> Addr {hash, Addr} -> Addr
end, end,
ok = wxStyledTextCtrl:connect(Window, stc_styleneeded),
true = wxNotebook:addPage(Codebook, Window, FileName, [{bSelect, true}]), true = wxNotebook:addPage(Codebook, Window, FileName, [{bSelect, true}]),
Page = #p{path = Location, win = Window, code = CodeTx}, Page = #p{path = Location, win = Window, code = CodeTx},
NewPages = Pages ++ [Page], NewPages = Pages ++ [Page],
@ -594,33 +598,39 @@ deploy2(State, Source) ->
case compile(Source) of case compile(Source) of
% Options = sophia_options(), % Options = sophia_options(),
% case so_compiler:from_string(Source, Options) of % case so_compiler:from_string(Source, Options) of
% TODO: Make hz accept either the aaci or the aci, preferring the aaci if present
{ok, Build} -> {ok, Build} ->
deploy3(State, Build); ACI = maps:get(aci, Build),
RawAACI = {aaci, ContractName, Funs, NS} = hz:prepare_aaci(ACI),
{InitSpec, Callable} = maps:take("init", Funs),
AACI = setelement(3, RawAACI, Callable),
Complete = maps:put(aaci, AACI, Build),
ok = tell(info, "Deploying Contract: ~p", [ContractName]),
deploy3(State, InitSpec, Complete, NS);
Other -> Other ->
ok = tell(info, "Compilation Failed!~n~tp", [Other]), ok = tell(info, "Compilation Failed!~n~tp", [Other]),
State State
end. end.
deploy3(State, Build) -> deploy3(State, InitSpec, Build, NS) ->
case gd_con:list_keys() of case gd_con:list_keys() of
{ok, 0, []} -> {ok, 0, []} ->
handle_troubling(State, "No keys exist in the current wallet."); handle_troubling(State, "No keys exist in the current wallet.");
{ok, Selected, Keys} -> {ok, Selected, Keys} ->
deploy4(State, Build, Selected, Keys); deploy4(State, InitSpec, Build, NS, Selected, Keys);
error -> error ->
handle_troubling(State, "No wallet is selected!") handle_troubling(State, "No wallet is selected!")
end. end.
deploy4(State = #s{frame = Frame, j = J}, Build = #{aci := ACI}, Selected, Keys) -> deploy4(State = #s{frame = Frame, j = J}, InitSpec, Build, NS, Selected, Keys) ->
{#{functions := Funs}, _} = find_main(ACI), InitArgs = element(1, InitSpec),
#{arguments := As} = lom:find(name, <<"init">>, Funs),
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")), Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")),
Sizer = wxBoxSizer:new(?wxVERTICAL), Sizer = wxBoxSizer:new(?wxVERTICAL),
ScrollWin = wxScrolledWindow:new(Dialog), ScrollWin = wxScrolledWindow:new(Dialog),
ScrollSz = wxBoxSizer:new(?wxVERTICAL), ScrollSz = wxBoxSizer:new(?wxVERTICAL),
ok = wxScrolledWindow:setSizerAndFit(ScrollWin, ScrollSz), ok = wxScrolledWindow:setSizerAndFit(ScrollWin, ScrollSz),
ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5), ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5),
FunName = unicode:characters_to_list(["init/", integer_to_list(length(As))]), FunName = unicode:characters_to_list(["init/", integer_to_list(length(InitArgs))]),
FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]),
KeySz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Signature Key")}]), KeySz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Signature Key")}]),
KeyPicker = wxChoice:new(ScrollWin, ?wxID_ANY, [{choices, Keys}]), KeyPicker = wxChoice:new(ScrollWin, ?wxID_ANY, [{choices, Keys}]),
@ -635,24 +645,7 @@ deploy4(State = #s{frame = Frame, j = J}, Build = #{aci := ACI}, Selected, Keys)
GridSz = wxFlexGridSizer:new(2, 4, 4), GridSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), ok = wxFlexGridSizer:addGrowableCol(GridSz, 1),
MakeArgField = ArgFields = make_arg_fields(ScrollWin, GridSz, InitArgs, NS, J),
fun(#{name := AN, type := T}) ->
Type =
case T of
<<"address">> -> address;
<<"int">> -> integer;
<<"bool">> -> boolean;
L when is_list(L) -> list; % FIXME
% I when is_binary(I) -> iface % FIXME
I when is_binary(I) -> address % FIXME
end,
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY),
_ = wxFlexGridSizer:add(GridSz, ANT, zxw:flags(base)),
_ = wxFlexGridSizer:add(GridSz, TCT, zxw:flags(wide)),
{ANT, TCT, Type}
end,
ArgFields = lists:map(MakeArgField, As),
_ = wxStaticBoxSizer:add(FunSz, GridSz, zxw:flags(wide)), _ = wxStaticBoxSizer:add(FunSz, GridSz, zxw:flags(wide)),
_ = wxStaticBoxSizer:add(ScrollSz, FunSz, [{proportion, 1}, {flag, ?wxEXPAND}]), _ = wxStaticBoxSizer:add(ScrollSz, FunSz, [{proportion, 1}, {flag, ?wxEXPAND}]),
_ = wxStaticBoxSizer:add(ScrollSz, KeySz, [{proportion, 0}, {flag, ?wxEXPAND}]), _ = wxStaticBoxSizer:add(ScrollSz, KeySz, [{proportion, 0}, {flag, ?wxEXPAND}]),
@ -668,7 +661,7 @@ deploy4(State = #s{frame = Frame, j = J}, Build = #{aci := ACI}, Selected, Keys)
?wxID_OK -> ?wxID_OK ->
ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)),
PK = unicode:characters_to_binary(ID), PK = unicode:characters_to_binary(ID),
InitArgs = lists:map(fun get_arg/1, ArgFields), IArgs = lists:map(fun get_arg/1, ArgFields),
Controls = Controls =
[{"TTL", TTL_Tx}, [{"TTL", TTL_Tx},
{"Gas Price", GasP_Tx}, {"Gas Price", GasP_Tx},
@ -678,7 +671,7 @@ deploy4(State = #s{frame = Frame, j = J}, Build = #{aci := ACI}, Selected, Keys)
{ok, [TTL, GasP, Gas, Amount]} -> {ok, [TTL, GasP, Gas, Amount]} ->
{ok, Nonce} = hz:next_nonce(PK), {ok, Nonce} = hz:next_nonce(PK),
DeployParams = {PK, Nonce, TTL, GasP, Gas, Amount}, DeployParams = {PK, Nonce, TTL, GasP, Gas, Amount},
{ok, DeployParams, InitArgs}; {ok, DeployParams, IArgs};
E -> E ->
E E
end; end;
@ -844,9 +837,9 @@ open_hash3(State, Address, Source) ->
% TODO: Compile on load and verify the deployed hash for validity. % TODO: Compile on load and verify the deployed hash for validity.
Options = sophia_options(), Options = sophia_options(),
case so_compiler:from_string(Source, Options) of case so_compiler:from_string(Source, Options) of
{ok, Build = #{aci := ACI}} -> {ok, Build = #{aaci := AACI}} ->
{Defs = #{functions := Funs}, ConIfaces} = find_main(ACI), {Defs = #{functions := Funs}, ConIfaces} = find_main(AACI),
Callable = lom:delete(name, <<"init">>, Funs), Callable = maps:remove("init", Funs),
FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, FunDefs = {maps:put(functions, Callable, Defs), ConIfaces},
ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]),
add_code_page(State, {hash, Address}, Source); add_code_page(State, {hash, Address}, Source);
@ -910,7 +903,7 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}})
_ -> Name ++ ".aes" _ -> Name ++ ".aes"
end, end,
Path = filename:join(Dir, File), Path = filename:join(Dir, File),
Source = wxTextCtrl:getValue(Widget), Source = get_source(Widget),
case filelib:ensure_dir(Path) of case filelib:ensure_dir(Path) of
ok -> ok ->
case file:write_file(Path, Source) of case file:write_file(Path, Source) of
@ -938,6 +931,19 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}})
end end
end. end.
get_source(Widget) ->
case ?editorMode of
plain -> wxTextCtrl:getValue(Widget);
sophia -> gd_sophia_editor:get_text(Widget)
end.
set_source(Widget, Src) ->
case ?editorMode of
plain -> wxTextCtrl:setValue(Widget, Src);
sophia -> gd_sophia_editor:set_text(Widget, Src)
end.
% TODO: Break this down -- tons of things in here recur. % TODO: Break this down -- tons of things in here recur.
rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) ->
case wxNotebook:getSelection(Codebook) of case wxNotebook:getSelection(Codebook) of
@ -968,7 +974,7 @@ rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}
_ -> Name ++ ".aes" _ -> Name ++ ".aes"
end, end,
NewPath = filename:join(Dir, File), NewPath = filename:join(Dir, File),
Source = wxTextCtrl:getValue(Widget), Source = get_source(Widget),
case filelib:ensure_dir(NewPath) of case filelib:ensure_dir(NewPath) of
ok -> ok ->
case file:write_file(NewPath, Source) of case file:write_file(NewPath, Source) of
@ -1084,7 +1090,7 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j
TextAt = wxTextAttr:new(), TextAt = wxTextAttr:new(),
ok = wxTextAttr:setFont(TextAt, Mono), ok = wxTextAttr:setFont(TextAt, Mono),
true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt),
ok = wxTextCtrl:setValue(CodeTx, Source), ok = set_source(CodeTx, Source),
_ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)),
ScrollWin = wxScrolledWindow:new(Window), ScrollWin = wxScrolledWindow:new(Window),
FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]), FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]),
@ -1100,14 +1106,18 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j
_ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]), _ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]),
{Out, IFaces, Build, NewButtons} = {Out, IFaces, Build, NewButtons} =
case compile(Source) of case compile(Source) of
{ok, B = #{aci := ACI}} -> {ok, Output} ->
{#{functions := Fs}, _} = find_main(ACI), ACI = maps:get(aci, Output),
Callable = lom:delete(name, <<"init">>, Fs), AACI = {aaci, ContractName, Funs, NS} = hz:prepare_aaci(ACI),
{NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, J), Complete = maps:put(aaci, AACI, Output),
O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [B]), ok = tell(info, "Loading Contract: ~p", [ContractName]),
{O, IFs, B, NB}; Callable = maps:remove("init", Funs),
tell(info, "Callable: ~p", [Callable]),
{NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, NS, J),
O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [Complete]),
{O, IFs, Complete, NB};
Other -> Other ->
O = io_llib:format("Compilation Failed!~n~tp~n", [Other]), O = io_lib:format("Compilation Failed!~n~tp~n", [Other]),
{O, [], none, Buttons} {O, [], none, Buttons}
end, end,
ok = wxWindow:setSizer(Window, PageSz), ok = wxWindow:setSizer(Window, PageSz),
@ -1123,9 +1133,18 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j
State#s{cons = {Consbook, NewPages}, buttons = NewButtons}. State#s{cons = {Consbook, NewPages}, buttons = NewButtons}.
get_arg({_, TextCtrl, _}) -> get_arg({list, AFs, _}) ->
lists:map(fun get_arg/1, AFs);
get_arg({record, AFs}) ->
get_record(AFs);
get_arg({tuple, AFs}) ->
list_to_tuple(lists:map(fun get_arg/1, AFs));
get_arg({_, TextCtrl}) ->
wxTextCtrl:getValue(TextCtrl). wxTextCtrl:getValue(TextCtrl).
get_record([{L, A} | T]) ->
[{L, get_arg(A)} | get_record(T)].
find_main(ACI) -> find_main(ACI) ->
find_main(ACI, none, []). find_main(ACI, none, []).
@ -1141,60 +1160,166 @@ find_main([C | T], M, Is) ->
find_main([], M, Is) -> find_main([], M, Is) ->
{M, Is}. {M, Is}.
fun_interfaces(ScrollWin, FunSz, Buttons, Funs, J) -> fun_interfaces(ScrollWin, FunSz, Buttons, Funs, NS, J) ->
MakeIface = MakeIface =
fun(#{name := N, arguments := As}) -> fun(Name, {Args, _}) ->
FunName = unicode:characters_to_list([N, "/", integer_to_list(length(As))]), tell(info, "Fun: ~p, Args: ~p", [Name, Args]),
FunName = unicode:characters_to_list([Name, "/", integer_to_list(length(Args))]),
FN = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), FN = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]),
GridSz = wxFlexGridSizer:new(2, 4, 4), GridSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), ok = wxFlexGridSizer:addGrowableCol(GridSz, 1),
MakeArgField = ArgFields = make_arg_fields(ScrollWin, GridSz, Args, NS, J),
fun(#{name := AN, type := T}) ->
Type =
case T of
<<"address">> -> address;
<<"int">> -> integer;
<<"bool">> -> boolean;
L when is_list(L) -> list; % FIXME
% I when is_binary(I) -> iface % FIXME
I when is_binary(I) -> address % FIXME
end,
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY),
_ = wxFlexGridSizer:add(GridSz, ANT, zxw:flags(base)),
_ = wxFlexGridSizer:add(GridSz, TCT, zxw:flags(wide)),
{ANT, TCT, Type}
end,
ArgFields = lists:map(MakeArgField, As),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL), ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
{CallButton, DryRunButton} = CallBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Call")}]),
case N =:= <<"init">> of DryRBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Dry Run")}]),
false -> _ = wxBoxSizer:add(ButtSz, CallBn, zxw:flags(wide)),
CallBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Call")}]), _ = wxBoxSizer:add(ButtSz, DryRBn, zxw:flags(wide)),
DryRBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Dry Run")}]), CallButton = #w{name = {Name, call}, id = wxButton:getId(CallBn), wx = CallBn},
_ = wxBoxSizer:add(ButtSz, CallBn, zxw:flags(wide)), DryRButton = #w{name = {Name, dryr}, id = wxButton:getId(DryRBn), wx = DryRBn},
_ = wxBoxSizer:add(ButtSz, DryRBn, zxw:flags(wide)),
{#w{name = {N, call}, id = wxButton:getId(CallBn), wx = CallBn},
#w{name = {N, dryr}, id = wxButton:getId(DryRBn), wx = DryRBn}};
true ->
Deploy = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Deploy")}]),
_ = wxBoxSizer:add(ButtSz, Deploy, zxw:flags(wide)),
{#w{name = {N, call}, id = wxButton:getId(Deploy), wx = Deploy},
none}
end,
_ = wxStaticBoxSizer:add(FN, GridSz, zxw:flags(wide)), _ = wxStaticBoxSizer:add(FN, GridSz, zxw:flags(wide)),
_ = wxStaticBoxSizer:add(FN, ButtSz, zxw:flags(base)), _ = wxStaticBoxSizer:add(FN, ButtSz, zxw:flags(base)),
_ = wxSizer:add(FunSz, FN, zxw:flags(base)), _ = wxSizer:add(FunSz, FN, zxw:flags(base)),
#f{name = N, call = CallButton, dryrun = DryRunButton, args = ArgFields} #f{name = Name, call = CallButton, dryrun = DryRButton, args = ArgFields}
end, end,
IFaces = lists:map(MakeIface, Funs), IFaces = maps:map(MakeIface, Funs),
NewButtons = lists:foldl(fun map_iface_buttons/2, Buttons, IFaces), tell(info, "IFaces: ~p", [IFaces]),
NewButtons = maps:fold(fun map_iface_buttons/3, Buttons, IFaces),
{NewButtons, IFaces}. {NewButtons, IFaces}.
map_iface_buttons(#f{call = C = #w{id = CID}, dryrun = D = #w{id = DID}}, A) -> % FIXME: This can be simplified and needs to provide better widgets for types.
maps:put(DID, D, maps:put(CID, C, A)); % "variant" types should be wxChoice
map_iface_buttons(#f{call = C = #w{id = CID}, dryrun = none}, A) -> % Booleans should either be wxChoice or check boxes
% The sizer expansion direction for vertical elements is stupid
make_arg_fields(ScrollWin, GridSz, Args, NS, J) ->
MakeArgField =
fun
({AN, {T, already_normalized, T}}) ->
% tell(info, "~p Arg: ~p, Type: ~p", [?LINE, AN, T]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, TCT, fill()),
{T, TCT};
({T, already_normalized, T}) ->
% tell(info, "~p Type: ~p", [?LINE, T]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, atom_to_list(T)),
TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, TCT, fill()),
{T, TCT};
({AN, {_TypeName, T, T}}) ->
% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, T]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, TCT, fill()),
{T, TCT};
({AN, {_TypeName, already_normalized, {record, InnerArgs}}}) ->
% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, InnerSz, fill()),
{record, AFs};
({AN, {_TypeName, already_normalized, {tuple, InnerArgs}}}) ->
% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, InnerSz, fill()),
{tuple, AFs};
({AN, {_TypeName, already_normalized, {list, InnerArgs}}}) ->
% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
ArgSz = wxBoxSizer:new(?wxVERTICAL),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]),
AB = #w{name = {AN, add}, id = wxButton:getId(B), wx = B},
_ = wxBoxSizer:add(ArgSz, InnerSz, fill()),
_ = wxBoxSizer:add(ArgSz, B, fill()),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, ArgSz, fill()),
{list, AFs, AB};
({AN, {_TypeName, already_normalized, T}}) ->
% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, T]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, T, NS, J),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, InnerSz, fill()),
{tuple, AFs};
({AN, {{tuple, _}, already_normalized, {tuple, InnerArgs}}}) ->
% tell(info, "~p Arg: ~p, Tuple: ~p", [?LINE, AN, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, InnerSz, fill()),
{tuple, AFs};
({AN, {{list, _}, already_normalized, {list, InnerArgs}}}) ->
% tell(info, "~p Arg: ~p, List: ~p", [?LINE, AN, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN),
ArgSz = wxBoxSizer:new(?wxHORIZONTAL),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]),
AB = #w{name = {AN, add}, id = wxButton:getId(B), wx = B},
_ = wxBoxSizer:add(ArgSz, InnerSz, fill()),
_ = wxBoxSizer:add(ArgSz, B, fill()),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, ArgSz, fill()),
{list, AFs, AB};
({{tuple, _}, already_normalized, {tuple, InnerArgs}}) ->
% tell(info, "~p Tuple: ~p", [?LINE, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, "tuple"),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, InnerSz, fill()),
{tuple, AFs};
({{list, _}, already_normalized, {list, InnerArgs}}) ->
% tell(info, "~p List: ~p", [?LINE, InnerArgs]),
ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, "list"),
ArgSz = wxBoxSizer:new(?wxHORIZONTAL),
InnerSz = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1),
AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J),
B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]),
AB = #w{name = {list, add}, id = wxButton:getId(B), wx = B},
_ = wxBoxSizer:add(ArgSz, InnerSz, [{proportion, 1}, {flag, ?wxEXPAND}]),
_ = wxBoxSizer:add(ArgSz, B, fill()),
_ = wxFlexGridSizer:add(GridSz, ANT, fill()),
_ = wxFlexGridSizer:add(GridSz, ArgSz, fill()),
{list, AFs, AB}
end,
lists:map(MakeArgField, Args).
fill() ->
[{proportion, 0}, {flag, ?wxEXPAND}].
map_iface_buttons(_, #f{call = C = #w{id = CID}, dryrun = D = #w{id = DID}}, A) ->
maps:merge(#{CID => C, DID => D}, A);
map_iface_buttons(_, #f{call = C = #w{id = CID}, dryrun = none}, A) ->
maps:put(CID, C, A). maps:put(CID, C, A).

View File

@ -1,5 +1,5 @@
-module(gd_v_netman). -module(gd_v_netman).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <zxq9@zxq9.com>"). -author("Craig Everett <zxq9@zxq9.com>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").

View File

@ -1,5 +1,5 @@
-module(gd_v_wallman). -module(gd_v_wallman).
-vsn("0.5.2"). -vsn("0.6.6").
-author("Craig Everett <zxq9@zxq9.com>"). -author("Craig Everett <zxq9@zxq9.com>").
-copyright("QPQ AG <info@qpq.swiss>"). -copyright("QPQ AG <info@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").
@ -89,12 +89,34 @@ init({Prefs, Manifest}) ->
ok = wxFrame:setSizer(Frame, MainSz), ok = wxFrame:setSizer(Frame, MainSz),
ok = wxSizer:layout(MainSz), ok = wxSizer:layout(MainSz),
ok = gd_v:safe_size(Frame, Prefs), NewPrefs =
case maps:is_key(geometry, Prefs) of
true ->
Prefs;
false ->
Display = wxDisplay:new(),
{_, _, W, H} = wxDisplay:getGeometry(Display),
ok = wxDisplay:destroy(Display),
WW = 500,
WH = 350,
X = (W div 2) - (WW div 2),
Y = (H div 2) - (WH div 2),
Prefs#{geometry => {X, Y, WW, WH}}
end,
ok = gd_v:safe_size(Frame, NewPrefs),
ok = wxFrame:connect(Frame, command_button_clicked), ok = wxFrame:connect(Frame, command_button_clicked),
ok = wxFrame:connect(Frame, close_window), ok = wxFrame:connect(Frame, close_window),
true = wxFrame:show(Frame), true = wxFrame:show(Frame),
ok = wxListBox:connect(Picker, command_listbox_doubleclicked), ok = wxListBox:connect(Picker, command_listbox_doubleclicked),
ok =
case length(Manifest) =:= 0 of
false ->
ok;
true ->
self() ! new,
ok
end,
State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs,
wallets = Manifest, wallets = Manifest,
picker = Picker, picker = Picker,
@ -121,6 +143,9 @@ handle_cast(Unexpected, State) ->
{noreply, State}. {noreply, State}.
handle_info(new, State) ->
NewState = do_new(State),
{noreply, NewState};
handle_info(Unexpected, State) -> handle_info(Unexpected, State) ->
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
{noreply, State}. {noreply, State}.
@ -252,6 +277,7 @@ do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) ->
{defaultDir, DefaultDir}, {defaultDir, DefaultDir},
{defaultFile, "default.gaju"}, {defaultFile, "default.gaju"},
{wildCard, "*.gaju"}, {wildCard, "*.gaju"},
{sz, {300, 270}},
{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}],
Dialog = wxFileDialog:new(Frame, Options), Dialog = wxFileDialog:new(Frame, Options),
case wxFileDialog:showModal(Dialog) of case wxFileDialog:showModal(Dialog) of
@ -276,6 +302,7 @@ do_new2(Path, J, Frame) ->
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]), Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]),
Sizer = wxBoxSizer:new(?wxVERTICAL), Sizer = wxBoxSizer:new(?wxVERTICAL),
Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]),
NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]),
NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
_ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)), _ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)),
@ -294,12 +321,14 @@ do_new2(Path, J, Frame) ->
_ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)),
_ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, Network, zxw:flags(base)),
_ = wxSizer:add(Sizer, NameSz, zxw:flags(base)), _ = wxSizer:add(Sizer, NameSz, zxw:flags(base)),
_ = wxSizer:add(Sizer, PassSz, zxw:flags(base)), _ = wxSizer:add(Sizer, PassSz, zxw:flags(base)),
_ = wxSizer:add(Sizer, PassConSz, zxw:flags(base)), _ = wxSizer:add(Sizer, PassConSz, zxw:flags(base)),
_ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)),
ok = wxDialog:setSizer(Dialog, Sizer), ok = wxDialog:setSizer(Dialog, Sizer),
ok = wxDialog:setSize(Dialog, {300, 270}),
ok = wxBoxSizer:layout(Sizer), ok = wxBoxSizer:layout(Sizer),
ok = wxDialog:center(Dialog), ok = wxDialog:center(Dialog),
ok = wxStyledTextCtrl:setFocus(NameTx), ok = wxStyledTextCtrl:setFocus(NameTx),
@ -307,6 +336,12 @@ do_new2(Path, J, Frame) ->
Result = Result =
case wxDialog:showModal(Dialog) of case wxDialog:showModal(Dialog) of
?wxID_OK -> ?wxID_OK ->
Net =
case wxRadioBox:getSelection(Network) of
0 -> mainnet;
1 -> testnet;
_ -> mainnet
end,
Name = Name =
case wxTextCtrl:getValue(NameTx) of case wxTextCtrl:getValue(NameTx) of
"" -> Path; "" -> Path;
@ -318,17 +353,17 @@ do_new2(Path, J, Frame) ->
{P, P} -> P; {P, P} -> P;
{_, _} -> bad {_, _} -> bad
end, end,
do_new3(Name, Path, Pass); do_new3(Net, Name, Path, Pass);
?wxID_CANCEL -> ?wxID_CANCEL ->
abort abort
end, end,
ok = wxDialog:destroy(Dialog), ok = wxDialog:destroy(Dialog),
Result. Result.
do_new3(_, _, bad) -> do_new3(_, _, _, bad) ->
abort; abort;
do_new3(Name, Path, Pass) -> do_new3(Net, Name, Path, Pass) ->
gd_con:new_wallet(Name, Path, Pass). gd_con:new_wallet(Net, Name, Path, Pass).
do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) -> do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) ->

View File

@ -1,18 +1,18 @@
{name,"Clutch"}. {name,"GajuDesk"}.
{type,gui}. {type,gui}.
{modules,[]}. {modules,[]}.
{prefix,"gd"}. {prefix,"gd"}.
{author,"Craig Everett"}. {author,"Craig Everett"}.
{desc,"A desktop client for the Gajumaru network of blockchain networks"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}.
{package_id,{"otpr","gajudesk",{0,5,2}}}. {package_id,{"otpr","gajudesk",{0,6,6}}}.
{deps,[{"otpr","hakuzaru",{0,5,0}}, {deps,[{"otpr","hakuzaru",{0,6,1}},
{"otpr","eblake2",{1,0,1}},
{"otpr","base58",{0,1,1}},
{"otpr","gmserialization",{0,1,3}}, {"otpr","gmserialization",{0,1,3}},
{"otpr","sophia",{9,0,0}}, {"otpr","sophia",{9,0,0}},
{"otpr","gmbytecode",{3,4,1}}, {"otpr","gmbytecode",{3,4,1}},
{"otpr","lom",{1,0,0}}, {"otpr","lom",{1,0,0}},
{"otpr","zj",{1,1,0}}, {"otpr","zj",{1,1,0}},
{"otpr","erl_base58",{0,1,0}},
{"otpr","eblake2",{1,0,0}},
{"otpr","ec_utils",{1,0,0}}, {"otpr","ec_utils",{1,0,0}},
{"otpr","zxwidgets",{1,0,1}}]}. {"otpr","zxwidgets",{1,0,1}}]}.
{key_name,none}. {key_name,none}.