WIP
This commit is contained in:
parent
c0ca2d3c91
commit
4254fe7643
@ -1,4 +1,4 @@
|
|||||||
% 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 = {161,97,102,143} :: string() | inet:ip_address(),
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
% AC and Coin represent the financial authority graph for a given coin.
|
%% AC and Coin represent the financial authority graph for a given coin.
|
||||||
|
|
||||||
-record(ac,
|
-record(ac,
|
||||||
{id = none :: string(),
|
{id = none :: string(),
|
||||||
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
% 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(),
|
||||||
@ -76,9 +76,19 @@
|
|||||||
|
|
||||||
-record(wallet,
|
-record(wallet,
|
||||||
{version = 1 :: integer(),
|
{version = 1 :: integer(),
|
||||||
|
name = "" :: string(),
|
||||||
poas = [] :: [#poa{}],
|
poas = [] :: [#poa{}],
|
||||||
keys = [] :: [#key{}],
|
keys = [] :: [#key{}],
|
||||||
pass = none :: none | binary(),
|
|
||||||
chain_id = <<"groot.devnet">> :: binary(),
|
chain_id = <<"groot.devnet">> :: binary(),
|
||||||
endpoint = #node{} :: #node{},
|
endpoint = #node{} :: #node{},
|
||||||
nets = [#net{}] :: [#net{}]}).
|
nets = [#net{}] :: [#net{}]}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% Niche registered elements
|
||||||
|
|
||||||
|
% WR: Wallet Registration
|
||||||
|
-record(wr,
|
||||||
|
{name = "" :: string(),
|
||||||
|
path = "" :: file:filename(),
|
||||||
|
pass = false :: boolean()}).
|
||||||
|
447
src/gmc_con.erl
447
src/gmc_con.erl
@ -12,12 +12,14 @@
|
|||||||
|
|
||||||
-behavior(gen_server).
|
-behavior(gen_server).
|
||||||
-export([show_ui/1,
|
-export([show_ui/1,
|
||||||
open_wallet/2, close_wallet/0, password/2, refresh/0,
|
open_wallet/2, close_wallet/0, new_wallet/3, import_wallet/3, password/2,
|
||||||
|
refresh/0,
|
||||||
nonce/1, spend/2, chain/1, grids/1,
|
nonce/1, spend/2, chain/1, grids/1,
|
||||||
make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1,
|
make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1,
|
||||||
add_node/1, set_sole_node/1]).
|
add_node/1, set_sole_node/1]).
|
||||||
-export([encrypt/2, decrypt/2]).
|
-export([encrypt/2, decrypt/2]).
|
||||||
-export([start_link/0, stop/0, save/1, save/2]).
|
-export([save/2]).
|
||||||
|
-export([start_link/0, stop/0]).
|
||||||
-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_call/3, handle_cast/2, handle_info/2]).
|
||||||
-include("$zx_include/zx_logger.hrl").
|
-include("$zx_include/zx_logger.hrl").
|
||||||
@ -35,16 +37,18 @@
|
|||||||
mon = none :: none | reference()}).
|
mon = none :: none | reference()}).
|
||||||
|
|
||||||
-record(s,
|
-record(s,
|
||||||
{version = 1 :: integer(),
|
{version = 1 :: integer(),
|
||||||
window = none :: none | wx:wx_object(),
|
window = none :: none | wx:wx_object(),
|
||||||
tasks = [] :: [#ui{}],
|
tasks = [] :: [#ui{}],
|
||||||
wallet = #wallet{} :: #wallet{},
|
wallet = none :: none | #wallet{},
|
||||||
prefs = #{} :: #{atom() := term()}}).
|
pass = none :: none | binary(),
|
||||||
|
prefs = #{} :: #{module() := term()},
|
||||||
|
wallets = [] :: [#wr{}]}).
|
||||||
|
|
||||||
|
|
||||||
-type state() :: #s{}.
|
-type state() :: #s{}.
|
||||||
-type ui_name() :: gmc_v_netman
|
-type ui_name() :: gmc_v_netman
|
||||||
| gmc_v_conman.
|
| gmc_v_wallman.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -72,6 +76,24 @@ close_wallet() ->
|
|||||||
gen_server:cast(?MODULE, close_wallet).
|
gen_server:cast(?MODULE, close_wallet).
|
||||||
|
|
||||||
|
|
||||||
|
-spec new_wallet(Name, Path, Password) -> ok
|
||||||
|
when Name :: string(),
|
||||||
|
Path :: string(),
|
||||||
|
Password :: string().
|
||||||
|
|
||||||
|
new_wallet(Name, Path, Password) ->
|
||||||
|
gen_server:cast(?MODULE, {new_wallet, Name, Path, Password}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec import_wallet(Name, Path, Password) -> ok
|
||||||
|
when Name :: string(),
|
||||||
|
Path :: string(),
|
||||||
|
Password :: string().
|
||||||
|
|
||||||
|
import_wallet(Name, Path, Password) ->
|
||||||
|
gen_server:cast(?MODULE, {import_wallet, Name, Path, Password}).
|
||||||
|
|
||||||
|
|
||||||
-spec password(Old, New) -> ok
|
-spec password(Old, New) -> ok
|
||||||
when Old :: none | string(),
|
when Old :: none | string(),
|
||||||
New :: none | string().
|
New :: none | string().
|
||||||
@ -186,20 +208,13 @@ stop() ->
|
|||||||
gen_server:cast(?MODULE, stop).
|
gen_server:cast(?MODULE, stop).
|
||||||
|
|
||||||
|
|
||||||
-spec save(Prefs) -> ok | {error, Reason}
|
-spec save(Module, Prefs) -> ok | {error, Reason}
|
||||||
when Prefs :: #{atom() := term()},
|
when Module :: module(),
|
||||||
|
Prefs :: #{atom() := term()},
|
||||||
Reason :: file:posix().
|
Reason :: file:posix().
|
||||||
|
|
||||||
save(Prefs) ->
|
save(Module, Prefs) ->
|
||||||
gen_server:call(?MODULE, {save, Prefs}).
|
gen_server:call(?MODULE, {save, Module, Prefs}).
|
||||||
|
|
||||||
|
|
||||||
-spec save(Label, Pref) -> ok
|
|
||||||
when Label :: term(),
|
|
||||||
Pref :: term().
|
|
||||||
|
|
||||||
save(Label, Pref) ->
|
|
||||||
gen_server:call(?MODULE, {save, Label, Pref}).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -224,10 +239,11 @@ start_link() ->
|
|||||||
init(none) ->
|
init(none) ->
|
||||||
ok = log(info, "Starting"),
|
ok = log(info, "Starting"),
|
||||||
Prefs = read_prefs(),
|
Prefs = read_prefs(),
|
||||||
Window = gmc_gui:start_link(Prefs),
|
GUI_Prefs = maps:get(gmc_gui, Prefs, #{}),
|
||||||
ok = gmc_gui:ask_password(),
|
Window = gmc_gui:start_link(GUI_Prefs),
|
||||||
State = #s{window = Window, prefs = Prefs},
|
State = #s{window = Window, prefs = Prefs},
|
||||||
{ok, State}.
|
NewState = do_show_ui(gmc_v_wallman, State),
|
||||||
|
{ok, NewState}.
|
||||||
|
|
||||||
|
|
||||||
read_prefs() ->
|
read_prefs() ->
|
||||||
@ -262,11 +278,8 @@ read_prefs() ->
|
|||||||
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({save, Prefs}, _, State) ->
|
handle_call({save, Module, Prefs}, _, State) ->
|
||||||
Response = do_save(State#s{prefs = Prefs}),
|
NewState = do_save(Module, Prefs, State),
|
||||||
{reply, Response, State};
|
|
||||||
handle_call({save, Label, Pref}, _, State) ->
|
|
||||||
NewState = do_save(Label, Pref, State),
|
|
||||||
{reply, ok, NewState};
|
{reply, ok, NewState};
|
||||||
handle_call({mnemonic, ID}, _, State) ->
|
handle_call({mnemonic, ID}, _, State) ->
|
||||||
Response = do_mnemonic(ID, State),
|
Response = do_mnemonic(ID, State),
|
||||||
@ -290,6 +303,12 @@ handle_cast({show_ui, Name}, State) ->
|
|||||||
handle_cast({open_wallet, Path, Phrase}, State) ->
|
handle_cast({open_wallet, Path, Phrase}, State) ->
|
||||||
NewState = do_open_wallet(Path, Phrase, State),
|
NewState = do_open_wallet(Path, Phrase, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
|
handle_cast(close_wallet, State) ->
|
||||||
|
NewState = do_close_wallet(State),
|
||||||
|
{noreply, NewState};
|
||||||
|
handle_cast({new_wallet, Name, Path, Password}, State) ->
|
||||||
|
NewState = do_new_wallet(Name, Path, Password, 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};
|
||||||
@ -324,8 +343,9 @@ handle_cast({set_sole_node, TheOneTrueNode}, State) ->
|
|||||||
NewState = do_set_sole_node(TheOneTrueNode, State),
|
NewState = do_set_sole_node(TheOneTrueNode, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
handle_cast(stop, State) ->
|
handle_cast(stop, State) ->
|
||||||
|
NewState = do_stop(State),
|
||||||
ok = zx:stop(),
|
ok = zx:stop(),
|
||||||
{noreply, State};
|
{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]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -375,29 +395,97 @@ terminate(Reason, State) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%
|
%%% GUI doers
|
||||||
|
|
||||||
|
|
||||||
do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs, wallet = Wallet}) ->
|
do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) ->
|
||||||
#wallet{nets = Nets} = Wallet,
|
|
||||||
case lists:keyfind(Name, #ui.name, Tasks) of
|
case lists:keyfind(Name, #ui.name, Tasks) of
|
||||||
#ui{wx = Win} ->
|
#ui{wx = Win} ->
|
||||||
ok = Name:to_front(Win),
|
ok = Name:to_front(Win),
|
||||||
State;
|
State;
|
||||||
false ->
|
false ->
|
||||||
Win = Name:start_link({Prefs, Nets}),
|
TaskPrefs = maps:get(Name, Prefs, #{}),
|
||||||
|
TaskData = task_data(Name, State),
|
||||||
|
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),
|
||||||
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.
|
||||||
|
|
||||||
|
task_data(gmc_v_netman, #s{wallet = #wallet{nets = Nets}}) ->
|
||||||
|
Nets;
|
||||||
|
task_data(gmc_v_netman, #s{wallet = none}) ->
|
||||||
|
[];
|
||||||
|
task_data(gmc_v_wallman, #s{wallets = Wallets}) ->
|
||||||
|
Wallets.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% Network operations
|
||||||
|
|
||||||
do_chain(_, State) ->
|
do_chain(_, State) ->
|
||||||
tell("Would be doing chain in do_chain/2 here"),
|
tell("Would be doing chain in do_chain/2 here"),
|
||||||
State.
|
State.
|
||||||
|
|
||||||
|
|
||||||
|
do_add_node(New, State) ->
|
||||||
|
tell("New node: ~p", [New]),
|
||||||
|
State.
|
||||||
|
|
||||||
|
|
||||||
|
% FIXME: This will, of course, totally explode if anything goes wrong
|
||||||
|
do_set_sole_node(TOTN = #node{external = none}, State) ->
|
||||||
|
do_set_sole_node(TOTN#node{external = 3013}, State);
|
||||||
|
do_set_sole_node(New, State = #s{wallet = W}) ->
|
||||||
|
ChainID = ensure_hz_set(New),
|
||||||
|
Net = #net{id = ChainID, chains = [#chain{id = ChainID, nodes = [New]}]},
|
||||||
|
NewWallet = W#wallet{chain_id = ChainID, endpoint = New, nets = [Net]},
|
||||||
|
NewState = State#s{wallet = NewWallet},
|
||||||
|
do_refresh(NewState).
|
||||||
|
|
||||||
|
|
||||||
|
do_refresh(State = #s{wallet = W = #wallet{poas = POAs, endpoint = Node}}) ->
|
||||||
|
ChainID = ensure_hz_set(Node),
|
||||||
|
CheckBalance =
|
||||||
|
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),
|
||||||
|
ok = gmc_gui:show(NewPOAs),
|
||||||
|
NewW = W#wallet{chain_id = ChainID, poas = NewPOAs},
|
||||||
|
State#s{wallet = NewW}.
|
||||||
|
|
||||||
|
|
||||||
|
ensure_hz_set(Node = #node{ip = IP, external = Port}) ->
|
||||||
|
case hz:status() of
|
||||||
|
{ok, #{"network_id" := ChainID}} ->
|
||||||
|
list_to_binary(ChainID);
|
||||||
|
{error, no_nodes} ->
|
||||||
|
ok = hz:chain_nodes([{IP, Port}]),
|
||||||
|
{ok, #{"network_id" := C}} = hz:status(),
|
||||||
|
ChainID = list_to_binary(C),
|
||||||
|
ok = hz:network_id(ChainID),
|
||||||
|
ok = gmc_gui:chain(ChainID, Node),
|
||||||
|
ChainID
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%ensure_connected(ChainID, IP, Port) ->
|
||||||
|
% ok = hz:chain_nodes([{IP, Port}]),
|
||||||
|
% ok = hz:network_id(ChainID).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% Chain operations
|
||||||
|
|
||||||
do_grids(String) ->
|
do_grids(String) ->
|
||||||
case gmc_grids:parse(String) of
|
case gmc_grids:parse(String) of
|
||||||
{ok, Instruction} -> do_grids2(Instruction);
|
{ok, Instruction} -> do_grids2(Instruction);
|
||||||
@ -408,6 +496,84 @@ do_grids2(Instruction) ->
|
|||||||
tell("GRIDS: ~tp", [Instruction]).
|
tell("GRIDS: ~tp", [Instruction]).
|
||||||
|
|
||||||
|
|
||||||
|
do_spend(KeyID, TX, State = #s{wallet = #wallet{keys = Keys}}) ->
|
||||||
|
case lists:keyfind(KeyID, #key.id, Keys) of
|
||||||
|
#key{pair = #{secret := PrivKey}} ->
|
||||||
|
do_spend2(PrivKey, TX, State);
|
||||||
|
false ->
|
||||||
|
log(warning, "Tried do_spend with a bad key: ~p", [KeyID])
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_spend2(PrivKey,
|
||||||
|
#spend_tx{sender_id = SenderID,
|
||||||
|
recipient_id = RecipientID,
|
||||||
|
amount = Amount,
|
||||||
|
gas_price = GasPrice,
|
||||||
|
gas = Gas,
|
||||||
|
ttl = TTL,
|
||||||
|
nonce = Nonce,
|
||||||
|
payload = Payload},
|
||||||
|
#s{wallet = #wallet{chain_id = ChainID}}) ->
|
||||||
|
Type = spend_tx,
|
||||||
|
Vsn = 1,
|
||||||
|
Fields =
|
||||||
|
[{sender_id, SenderID},
|
||||||
|
{recipient_id, RecipientID},
|
||||||
|
{amount, Amount},
|
||||||
|
{gas_price, GasPrice},
|
||||||
|
{gas, Gas},
|
||||||
|
{ttl, TTL},
|
||||||
|
{nonce, Nonce},
|
||||||
|
{payload, Payload}],
|
||||||
|
Template =
|
||||||
|
[{sender_id, id},
|
||||||
|
{recipient_id, id},
|
||||||
|
{amount, int},
|
||||||
|
{gas_price, int},
|
||||||
|
{gas, int},
|
||||||
|
{ttl, int},
|
||||||
|
{nonce, int},
|
||||||
|
{payload, binary}],
|
||||||
|
BinaryTX = aeser_chain_objects:serialize(Type, Vsn, Template, Fields),
|
||||||
|
NetworkTX = <<ChainID/binary, BinaryTX/binary>>,
|
||||||
|
Signature = ecu_eddsa:sign_detached(NetworkTX, PrivKey),
|
||||||
|
SigTxType = signed_tx,
|
||||||
|
SigTxVsn = 1,
|
||||||
|
SigTemplate =
|
||||||
|
[{signatures, [binary]},
|
||||||
|
{transaction, binary}],
|
||||||
|
TX_Data =
|
||||||
|
[{signatures, [Signature]},
|
||||||
|
{transaction, BinaryTX}],
|
||||||
|
SignedTX = aeser_chain_objects:serialize(SigTxType, SigTxVsn, SigTemplate, TX_Data),
|
||||||
|
Encoded = aeser_api_encoder:encode(transaction, SignedTX),
|
||||||
|
Outcome = hz:post_tx(Encoded),
|
||||||
|
tell("Outcome: ~p", [Outcome]).
|
||||||
|
|
||||||
|
|
||||||
|
do_nonce(ID) ->
|
||||||
|
hz:next_nonce(ID).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% State Operations
|
||||||
|
|
||||||
|
encrypt(Pass, Binary) ->
|
||||||
|
Flags = [{encrypt, true}, {padding, pkcs_padding}],
|
||||||
|
crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags).
|
||||||
|
|
||||||
|
|
||||||
|
decrypt(Pass, Binary) ->
|
||||||
|
Flags = [{encrypt, false}, {padding, pkcs_padding}],
|
||||||
|
crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags).
|
||||||
|
|
||||||
|
|
||||||
|
pass(none) ->
|
||||||
|
none;
|
||||||
|
pass(Phrase) ->
|
||||||
|
crypto:hash(sha3_256, Phrase).
|
||||||
|
|
||||||
|
|
||||||
do_make_key(Name, <<>>, _, Transform, State) ->
|
do_make_key(Name, <<>>, _, Transform, State) ->
|
||||||
Bin = crypto:strong_rand_bytes(32),
|
Bin = crypto:strong_rand_bytes(32),
|
||||||
do_make_key2(Name, Bin, Transform, State);
|
do_make_key2(Name, Bin, Transform, State);
|
||||||
@ -432,6 +598,7 @@ do_make_key(Name, Seed, base58, Transform, State) ->
|
|||||||
State
|
State
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
do_make_key2(Name, Bin, Transform, State = #s{wallet = W}) ->
|
do_make_key2(Name, Bin, Transform, State = #s{wallet = W}) ->
|
||||||
#wallet{poas = POAs, keys = Keys} = W,
|
#wallet{poas = POAs, keys = Keys} = W,
|
||||||
T = transform(Transform),
|
T = transform(Transform),
|
||||||
@ -529,74 +696,6 @@ do_drop_key(ID, State = #s{wallet = W}) ->
|
|||||||
State#s{wallet = NewWallet}.
|
State#s{wallet = NewWallet}.
|
||||||
|
|
||||||
|
|
||||||
do_add_node(New, State) ->
|
|
||||||
tell("New node: ~p", [New]),
|
|
||||||
State.
|
|
||||||
|
|
||||||
|
|
||||||
% FIXME: This will, of course, totally explode if anything goes wrong
|
|
||||||
do_set_sole_node(TOTN = #node{external = none}, State) ->
|
|
||||||
do_set_sole_node(TOTN#node{external = 3013}, State);
|
|
||||||
do_set_sole_node(New, State = #s{wallet = W}) ->
|
|
||||||
ChainID = ensure_hz_set(New),
|
|
||||||
Net = #net{id = ChainID, chains = [#chain{id = ChainID, nodes = [New]}]},
|
|
||||||
NewWallet = W#wallet{chain_id = ChainID, endpoint = New, nets = [Net]},
|
|
||||||
NewState = State#s{wallet = NewWallet},
|
|
||||||
do_refresh(NewState).
|
|
||||||
|
|
||||||
|
|
||||||
do_refresh(State = #s{wallet = W = #wallet{poas = POAs, endpoint = Node}}) ->
|
|
||||||
ChainID = ensure_hz_set(Node),
|
|
||||||
CheckBalance =
|
|
||||||
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),
|
|
||||||
ok = gmc_gui:show(NewPOAs),
|
|
||||||
NewW = W#wallet{chain_id = ChainID, poas = NewPOAs},
|
|
||||||
State#s{wallet = NewW}.
|
|
||||||
|
|
||||||
|
|
||||||
ensure_hz_set(Node = #node{ip = IP, external = Port}) ->
|
|
||||||
case hz:status() of
|
|
||||||
{ok, #{"network_id" := ChainID}} ->
|
|
||||||
list_to_binary(ChainID);
|
|
||||||
{error, no_nodes} ->
|
|
||||||
ok = hz:chain_nodes([{IP, Port}]),
|
|
||||||
{ok, #{"network_id" := C}} = hz:status(),
|
|
||||||
ChainID = list_to_binary(C),
|
|
||||||
ok = hz:network_id(ChainID),
|
|
||||||
ok = gmc_gui:chain(ChainID, Node),
|
|
||||||
ChainID
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
%ensure_connected(ChainID, IP, Port) ->
|
|
||||||
% ok = hz:chain_nodes([{IP, Port}]),
|
|
||||||
% ok = hz:network_id(ChainID).
|
|
||||||
|
|
||||||
|
|
||||||
encrypt(Pass, Binary) ->
|
|
||||||
Flags = [{encrypt, true}, {padding, pkcs_padding}],
|
|
||||||
crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags).
|
|
||||||
|
|
||||||
|
|
||||||
decrypt(Pass, Binary) ->
|
|
||||||
Flags = [{encrypt, false}, {padding, pkcs_padding}],
|
|
||||||
crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags).
|
|
||||||
|
|
||||||
|
|
||||||
pass(Phrase) ->
|
|
||||||
crypto:hash(sha3_256, Phrase).
|
|
||||||
|
|
||||||
|
|
||||||
do_open_wallet(Path, none, State) ->
|
do_open_wallet(Path, none, State) ->
|
||||||
case read(Path, none) of
|
case read(Path, none) of
|
||||||
{ok, Recovered = #wallet{poas = POAs, chain_id = ChainID, endpoint = Node}} ->
|
{ok, Recovered = #wallet{poas = POAs, chain_id = ChainID, endpoint = Node}} ->
|
||||||
@ -621,6 +720,7 @@ do_open_wallet(Path, Phrase, State) ->
|
|||||||
State#s{wallet = New}
|
State#s{wallet = New}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
default_wallet() ->
|
default_wallet() ->
|
||||||
DevNet = #net{id = <<"devnet">>, chains = [#chain{}]},
|
DevNet = #net{id = <<"devnet">>, chains = [#chain{}]},
|
||||||
TestChain1 = #chain{id = <<"groot.testnet">>,
|
TestChain1 = #chain{id = <<"groot.testnet">>,
|
||||||
@ -633,104 +733,79 @@ default_wallet() ->
|
|||||||
|
|
||||||
do_password(none, none, State) ->
|
do_password(none, none, State) ->
|
||||||
State;
|
State;
|
||||||
do_password(none, New, State = #s{wallet = #wallet{pass = none}}) ->
|
do_password(none, New, State = #s{pass = none,
|
||||||
|
wallet = #wallet{name = Name},
|
||||||
|
wallets = Wallets}) ->
|
||||||
Pass = pass(New),
|
Pass = pass(New),
|
||||||
State#s{wallet = #wallet{pass = Pass}};
|
Selected = lists:keyfind(Name, #wr.name, Wallets),
|
||||||
do_password(Old, none, State = #s{wallet = #wallet{pass = Pass}}) ->
|
Updated = Selected#wr{pass = true},
|
||||||
|
NewWallets = lists:keystore(Name, #wr.name, Wallets, Updated),
|
||||||
|
State#s{wallet = pass = Pass, wallets = NewWallets};
|
||||||
|
do_password(Old, none, State = #s{pass = Pass,
|
||||||
|
wallet = #wallet{name = Name},
|
||||||
|
wallets = Wallets}) ->
|
||||||
case pass(Old) =:= Pass of
|
case pass(Old) =:= Pass of
|
||||||
true -> State#s{wallet = #wallet{pass = none}};
|
true ->
|
||||||
false -> State
|
Selected = lists:keyfind(Name, #wr.name, Wallets),
|
||||||
end;
|
Updated = Selected#wr{pass = false},
|
||||||
do_password(Old, New, State = #s{wallet = #wallet{pass = Pass}}) ->
|
NewWallets = lists:keystore(Name, #wr.name, Wallets, Updated),
|
||||||
case pass(Old) =:= Pass of
|
State#s{pass = none, wallets = NewWallets};
|
||||||
true -> State#s{wallet = #wallet{pass = pass(New)}};
|
|
||||||
false -> State
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
do_spend(KeyID, TX, State = #s{wallet = #wallet{keys = Keys}}) ->
|
|
||||||
tell("SpendTX: ~p", [TX]),
|
|
||||||
case lists:keyfind(KeyID, #key.id, Keys) of
|
|
||||||
#key{pair = #{secret := PrivKey}} ->
|
|
||||||
do_spend2(PrivKey, TX, State);
|
|
||||||
false ->
|
false ->
|
||||||
log(warning, "Tried do_spend with a bad key: ~p", [KeyID])
|
State
|
||||||
|
end;
|
||||||
|
do_password(Old, New, State = #s{pass = Pass}) ->
|
||||||
|
case pass(Old) =:= Pass of
|
||||||
|
true -> State#s{pass = pass(New)};
|
||||||
|
false -> State
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_spend2(PrivKey,
|
|
||||||
#spend_tx{sender_id = SenderID,
|
|
||||||
recipient_id = RecipientID,
|
|
||||||
amount = Amount,
|
|
||||||
gas_price = GasPrice,
|
|
||||||
gas = Gas,
|
|
||||||
ttl = TTL,
|
|
||||||
nonce = Nonce,
|
|
||||||
payload = Payload},
|
|
||||||
#s{wallet = #wallet{chain_id = ChainID}}) ->
|
|
||||||
Type = spend_tx,
|
|
||||||
Vsn = 1,
|
|
||||||
Fields =
|
|
||||||
[{sender_id, SenderID},
|
|
||||||
{recipient_id, RecipientID},
|
|
||||||
{amount, Amount},
|
|
||||||
{gas_price, GasPrice},
|
|
||||||
{gas, Gas},
|
|
||||||
{ttl, TTL},
|
|
||||||
{nonce, Nonce},
|
|
||||||
{payload, Payload}],
|
|
||||||
Template =
|
|
||||||
[{sender_id, id},
|
|
||||||
{recipient_id, id},
|
|
||||||
{amount, int},
|
|
||||||
{gas_price, int},
|
|
||||||
{gas, int},
|
|
||||||
{ttl, int},
|
|
||||||
{nonce, int},
|
|
||||||
{payload, binary}],
|
|
||||||
BinaryTX = aeser_chain_objects:serialize(Type, Vsn, Template, Fields),
|
|
||||||
tell("BinaryTX: ~p", [BinaryTX]),
|
|
||||||
NetworkTX = <<ChainID/binary, BinaryTX/binary>>,
|
|
||||||
tell("NetworkTX: ~p", [NetworkTX]),
|
|
||||||
Signature = ecu_eddsa:sign_detached(NetworkTX, PrivKey),
|
|
||||||
SigTxType = signed_tx,
|
|
||||||
SigTxVsn = 1,
|
|
||||||
SigTemplate =
|
|
||||||
[{signatures, [binary]},
|
|
||||||
{transaction, binary}],
|
|
||||||
TX_Data =
|
|
||||||
[{signatures, [Signature]},
|
|
||||||
{transaction, BinaryTX}],
|
|
||||||
SignedTX = aeser_chain_objects:serialize(SigTxType, SigTxVsn, SigTemplate, TX_Data),
|
|
||||||
tell("SignedTX: ~p", [SignedTX]),
|
|
||||||
Encoded = aeser_api_encoder:encode(transaction, SignedTX),
|
|
||||||
tell("Encoded: ~p", [Encoded]),
|
|
||||||
Outcome = hz:post_tx(Encoded),
|
|
||||||
tell("Outcome: ~p", [Outcome]).
|
|
||||||
|
|
||||||
|
do_stop(State = #s{prefs = Prefs}) ->
|
||||||
do_nonce(ID) ->
|
|
||||||
hz:next_nonce(ID).
|
|
||||||
|
|
||||||
|
|
||||||
do_save(Label, Pref, State = #s{prefs = Prefs}) ->
|
|
||||||
NewPrefs = maps:put(Label, Pref, Prefs),
|
|
||||||
ok = persist(Prefs),
|
ok = persist(Prefs),
|
||||||
State#s{prefs = NewPrefs}.
|
do_close_wallet(State).
|
||||||
|
|
||||||
|
|
||||||
do_save(State = #s{prefs = Prefs}) ->
|
do_new_wallet(Name, Path, Password, State = #s{wallets = Wallets}) ->
|
||||||
ok = persist(Prefs),
|
case lists:keyfind(Name, #wr.name, Wallets) of
|
||||||
do_save2(State).
|
false ->
|
||||||
|
NextState = do_close_wallet(State),
|
||||||
|
Pass = pass(Password),
|
||||||
|
HasPass = Pass =/= none,
|
||||||
|
Entry = #wr{name = Name, path = Path, pass = HasPass},
|
||||||
|
New = #wallet{},
|
||||||
|
ok = save_wallet(Entry, Pass, New),
|
||||||
|
ok = gmc_gui:show([]),
|
||||||
|
NextState#s{wallet = New, pass = Pass, wallets = [Entry | Wallets]};
|
||||||
|
#wr{} ->
|
||||||
|
% FIXME
|
||||||
|
% Need to provide feedback based on where this came from
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
do_save2(#s{wallet = W = #wallet{pass = none}}) ->
|
do_save(Module, Prefs, State = #s{prefs = Cached}) ->
|
||||||
Path = save_path(),
|
Updated = maps:put(Module, Prefs, Cached),
|
||||||
|
ok = persist(Updated),
|
||||||
|
State#s{prefs = Updated}.
|
||||||
|
|
||||||
|
|
||||||
|
do_close_wallet(State = #s{wallet = none}) ->
|
||||||
|
State;
|
||||||
|
do_close_wallet(State = #s{wallet = Current, wallets = Wallets, pass = Pass}) ->
|
||||||
|
#wallet{name = Name} = Current,
|
||||||
|
RW = lists:keyfind(Name, #wr.name, Wallets),
|
||||||
|
ok = save_wallet(RW, Pass, Current),
|
||||||
|
ok = gmc_gui:show([]),
|
||||||
|
ok = do_show_ui(gmc_v_wallman, State),
|
||||||
|
State#s{pass = false, wallet = none}.
|
||||||
|
|
||||||
|
|
||||||
|
save_wallet(#wr{path = Path, pass = false}, none, Wallet) ->
|
||||||
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(Wallet));
|
||||||
do_save2(#s{wallet = W = #wallet{pass = Pass}}) ->
|
save_wallet(#wr{path = Path, pass = true}, Pass, Wallet) ->
|
||||||
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(Wallet)),
|
||||||
file:write_file(Path, Cipher).
|
file:write_file(Path, Cipher).
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +82,8 @@ 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}]),
|
||||||
|
|
||||||
|
WallB = wxButton:new(Frame, ?wxID_ANY, [{label, "[none]"}, {style, ?wxBORDER_NONE}]),
|
||||||
|
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}]),
|
||||||
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}]),
|
||||||
@ -128,7 +130,7 @@ init(Prefs) ->
|
|||||||
#w{name = Name, id = wxButton:getId(B), wx = B}
|
#w{name = Name, id = wxButton:getId(B), wx = B}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Buttons = [ChainW, NodeW | lists:map(MakeButton, ButtonTemplates)],
|
Buttons = [WallW, ChainW, NodeW | lists:map(MakeButton, ButtonTemplates)],
|
||||||
|
|
||||||
ChainSz = wxBoxSizer:new(?wxHORIZONTAL),
|
ChainSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
AccountSz = wxBoxSizer:new(?wxHORIZONTAL),
|
AccountSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
@ -136,6 +138,7 @@ init(Prefs) ->
|
|||||||
ActionsSz = wxBoxSizer:new(?wxHORIZONTAL),
|
ActionsSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
HistorySz = wxBoxSizer:new(?wxVERTICAL),
|
HistorySz = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
|
||||||
|
_ = wxSizer:add(ChainSz, WallB, zxw:flags(wide)),
|
||||||
_ = wxSizer:add(ChainSz, ChainB, zxw:flags(wide)),
|
_ = wxSizer:add(ChainSz, ChainB, zxw:flags(wide)),
|
||||||
_ = wxSizer:add(ChainSz, NodeB, zxw:flags(wide)),
|
_ = wxSizer:add(ChainSz, NodeB, zxw:flags(wide)),
|
||||||
|
|
||||||
@ -199,6 +202,7 @@ init(Prefs) ->
|
|||||||
{Frame, State}.
|
{Frame, State}.
|
||||||
|
|
||||||
|
|
||||||
|
% Put this in a gmc bevhavior
|
||||||
safe_size(Prefs) ->
|
safe_size(Prefs) ->
|
||||||
Display = wxDisplay:new(),
|
Display = wxDisplay:new(),
|
||||||
GSize = wxDisplay:getGeometry(Display),
|
GSize = wxDisplay:getGeometry(Display),
|
||||||
@ -290,6 +294,7 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked},
|
|||||||
State = #s{buttons = Buttons}) ->
|
State = #s{buttons = Buttons}) ->
|
||||||
NewState =
|
NewState =
|
||||||
case lists:keyfind(ID, #w.id, Buttons) of
|
case lists:keyfind(ID, #w.id, Buttons) of
|
||||||
|
#w{name = wallet} -> wallman(State);
|
||||||
#w{name = chain} -> netman(State);
|
#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 = make_key} -> make_key(State);
|
||||||
@ -322,7 +327,7 @@ handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs})
|
|||||||
{X, Y, W, H}
|
{X, Y, W, H}
|
||||||
end,
|
end,
|
||||||
NewPrefs = maps:put(geometry, Geometry, Prefs),
|
NewPrefs = maps:put(geometry, Geometry, Prefs),
|
||||||
ok = gmc_con:save(NewPrefs),
|
ok = gmc_con:save(?MODULE, NewPrefs),
|
||||||
ok = gmc_con:stop(),
|
ok = gmc_con:stop(),
|
||||||
ok = wxWindow:destroy(Frame),
|
ok = wxWindow:destroy(Frame),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
@ -350,6 +355,11 @@ refresh(State) ->
|
|||||||
State.
|
State.
|
||||||
|
|
||||||
|
|
||||||
|
wallman(State) ->
|
||||||
|
ok = gmc_con:show_ui(gmc_v_wallman),
|
||||||
|
State.
|
||||||
|
|
||||||
|
|
||||||
netman(State) ->
|
netman(State) ->
|
||||||
ok = gmc_con:show_ui(gmc_v_netman),
|
ok = gmc_con:show_ui(gmc_v_netman),
|
||||||
State.
|
State.
|
||||||
@ -688,13 +698,14 @@ spend(Selected, State = #s{accounts = Accounts}) ->
|
|||||||
POA = #poa{id = ID} = lists:nth(Selected, Accounts),
|
POA = #poa{id = ID} = lists:nth(Selected, Accounts),
|
||||||
case gmc_con:nonce(ID) of
|
case gmc_con:nonce(ID) of
|
||||||
{ok, Nonce} ->
|
{ok, Nonce} ->
|
||||||
spend2(POA, Nonce, State);
|
{ok, #{"top_block_height" := Height}} = hz:status(),
|
||||||
|
spend2(POA, Nonce, Height, State);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
tell("spend/2 failed to get nonce with ~tp", [Reason]),
|
tell("spend/2 failed to get nonce with ~tp", [Reason]),
|
||||||
State
|
State
|
||||||
end.
|
end.
|
||||||
|
|
||||||
spend2(#poa{id = ID, name = Name}, Nonce, State = #s{frame = Frame, j = J}) ->
|
spend2(#poa{id = ID, name = Name}, Nonce, Height, State = #s{frame = Frame, j = J}) ->
|
||||||
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Transfer"), [{size, {500, 400}}]),
|
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Transfer"), [{size, {500, 400}}]),
|
||||||
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
|
||||||
@ -716,10 +727,14 @@ spend2(#poa{id = ID, name = Name}, Nonce, State = #s{frame = Frame, j = J}) ->
|
|||||||
_ = wxStaticBoxSizer:add(DataSz, DataTx, zxw:flags(wide)),
|
_ = wxStaticBoxSizer:add(DataSz, DataTx, zxw:flags(wide)),
|
||||||
|
|
||||||
Style = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}],
|
Style = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}],
|
||||||
Preset = 1_000,
|
|
||||||
|
TTL_Sl = wxSlider:new(Dialog, ?wxID_ANY, 100, 10, 1000, Style),
|
||||||
|
TTL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("TTL")}]),
|
||||||
|
_ = wxStaticBoxSizer:add(TTL_Sz, TTL_Sl, zxw:flags(wide)),
|
||||||
|
|
||||||
Min = hz:min_gas_price(),
|
Min = hz:min_gas_price(),
|
||||||
Max = Min * 2,
|
Max = Min * 2,
|
||||||
GasSl = wxSlider:new(Dialog, ?wxID_ANY, Preset, Min, Max, Style),
|
GasSl = wxSlider:new(Dialog, ?wxID_ANY, Min, Min, Max, Style),
|
||||||
GasSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Gas Price")}]),
|
GasSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Gas Price")}]),
|
||||||
_ = wxStaticBoxSizer:add(GasSz, GasSl, zxw:flags(wide)),
|
_ = wxStaticBoxSizer:add(GasSz, GasSl, zxw:flags(wide)),
|
||||||
|
|
||||||
@ -733,10 +748,12 @@ spend2(#poa{id = ID, name = Name}, Nonce, State = #s{frame = Frame, j = J}) ->
|
|||||||
_ = wxBoxSizer:add(Sizer, ToSz, zxw:flags(base)),
|
_ = wxBoxSizer:add(Sizer, ToSz, zxw:flags(base)),
|
||||||
_ = wxBoxSizer:add(Sizer, AmtSz, zxw:flags(base)),
|
_ = wxBoxSizer:add(Sizer, AmtSz, zxw:flags(base)),
|
||||||
_ = wxBoxSizer:add(Sizer, DataSz, zxw:flags(wide)),
|
_ = wxBoxSizer:add(Sizer, DataSz, zxw:flags(wide)),
|
||||||
|
_ = wxBoxSizer:add(Sizer, TTL_Sz, zxw:flags(wide)),
|
||||||
_ = wxBoxSizer:add(Sizer, GasSz, zxw:flags(base)),
|
_ = wxBoxSizer:add(Sizer, GasSz, zxw:flags(base)),
|
||||||
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)),
|
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)),
|
||||||
ok = wxDialog:setSizer(Dialog, Sizer),
|
ok = wxDialog:setSizer(Dialog, Sizer),
|
||||||
ok = wxBoxSizer:layout(Sizer),
|
ok = wxBoxSizer:layout(Sizer),
|
||||||
|
ok = wxFrame:setSize(Dialog, {500, 450}),
|
||||||
ok = wxFrame:center(Dialog),
|
ok = wxFrame:center(Dialog),
|
||||||
ok =
|
ok =
|
||||||
case wxDialog:showModal(Dialog) of
|
case wxDialog:showModal(Dialog) of
|
||||||
@ -748,7 +765,7 @@ spend2(#poa{id = ID, name = Name}, Nonce, State = #s{frame = Frame, j = J}) ->
|
|||||||
amount = wxTextCtrl:getValue(AmtTx),
|
amount = wxTextCtrl:getValue(AmtTx),
|
||||||
gas_price = wxSlider:getValue(GasSl),
|
gas_price = wxSlider:getValue(GasSl),
|
||||||
gas = 20000,
|
gas = 20000,
|
||||||
ttl = 1,
|
ttl = Height + wxSlider:getValue(TTL_Sl),
|
||||||
nonce = Nonce,
|
nonce = Nonce,
|
||||||
payload = wxTextCtrl:getValue(DataTx)},
|
payload = wxTextCtrl:getValue(DataTx)},
|
||||||
clean_spend(ID, TX);
|
clean_spend(ID, TX);
|
||||||
@ -879,7 +896,7 @@ 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 = J("Password (leave blank for no password)"),
|
Label = J("Password (leave blank for no password)"),
|
||||||
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]),
|
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label}]),
|
||||||
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
_ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)),
|
_ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)),
|
||||||
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
|
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
@ -915,7 +932,7 @@ do_ask_password(#s{frame = Frame, prefs = Prefs, j = J}) ->
|
|||||||
|
|
||||||
|
|
||||||
price_to_string(Pucks) ->
|
price_to_string(Pucks) ->
|
||||||
Gaju = 1000000000000000000,
|
Gaju = 1_000_000_000_000_000_000,
|
||||||
H = integer_to_list(Pucks div Gaju),
|
H = integer_to_list(Pucks div Gaju),
|
||||||
R = Pucks rem Gaju,
|
R = Pucks rem Gaju,
|
||||||
case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of
|
case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
-behavior(wx_object).
|
-behavior(wx_object).
|
||||||
-include_lib("wx/include/wx.hrl").
|
-include_lib("wx/include/wx.hrl").
|
||||||
-export([to_front/1, set_manifest/1]).
|
-export([to_front/1]).
|
||||||
|
-export([set_manifest/1]).
|
||||||
-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]).
|
||||||
@ -209,6 +210,9 @@ handle_call(Unexpected, From, State) ->
|
|||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_cast(to_front, State = #s{frame = Frame}) ->
|
||||||
|
ok = wxFrame:raise(Frame),
|
||||||
|
{noreply, State};
|
||||||
handle_cast(Unexpected, State) ->
|
handle_cast(Unexpected, State) ->
|
||||||
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
|
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -231,7 +235,7 @@ handle_event(E = #wx{event = #wxCommand{type = command_button_clicked},
|
|||||||
State
|
State
|
||||||
end,
|
end,
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) ->
|
handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) ->
|
||||||
Geometry =
|
Geometry =
|
||||||
case wxTopLevelWindow:isMaximized(Frame) of
|
case wxTopLevelWindow:isMaximized(Frame) of
|
||||||
true ->
|
true ->
|
||||||
@ -241,7 +245,8 @@ handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) ->
|
|||||||
{W, H} = wxWindow:getSize(Frame),
|
{W, H} = wxWindow:getSize(Frame),
|
||||||
{X, Y, W, H}
|
{X, Y, W, H}
|
||||||
end,
|
end,
|
||||||
ok = gmc_con:save({?MODULE, geometry}, Geometry),
|
NewPrefs = maps:put(geometry, Geometry, Prefs),
|
||||||
|
ok = gmc_con:save(?MODULE, NewPrefs),
|
||||||
ok = wxWindow:destroy(Frame),
|
ok = wxWindow:destroy(Frame),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_event(Event, State) ->
|
handle_event(Event, State) ->
|
||||||
|
424
src/gmc_v_wallman.erl
Normal file
424
src/gmc_v_wallman.erl
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
-module(gmc_v_wallman).
|
||||||
|
-vsn("0.1.0").
|
||||||
|
-author("Craig Everett <zxq9@zxq9.com>").
|
||||||
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
|
-license("GPL-3.0-or-later").
|
||||||
|
|
||||||
|
-behavior(wx_object).
|
||||||
|
-include_lib("wx/include/wx.hrl").
|
||||||
|
-export([to_front/1]).
|
||||||
|
-export([start_link/1]).
|
||||||
|
-export([init/1, terminate/2, code_change/3,
|
||||||
|
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
|
||||||
|
-include("$zx_include/zx_logger.hrl").
|
||||||
|
-include("gmc.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
-record(w,
|
||||||
|
{name = none :: atom(),
|
||||||
|
id = 0 :: integer(),
|
||||||
|
wx = none :: none | wx:wx_object()}).
|
||||||
|
|
||||||
|
-record(s,
|
||||||
|
{wx = none :: none | wx:wx_object(),
|
||||||
|
frame = none :: none | wx:wx_object(),
|
||||||
|
lang = en :: en | jp,
|
||||||
|
j = none :: none | fun(),
|
||||||
|
prefs = #{} :: map(),
|
||||||
|
wallets = [] :: [#wr{}],
|
||||||
|
picker = none :: none | wx:wx_object(),
|
||||||
|
buttons = [] :: [#w{}]}).
|
||||||
|
|
||||||
|
|
||||||
|
%%% Interface
|
||||||
|
|
||||||
|
-spec to_front(Win) -> ok
|
||||||
|
when Win :: wx:wx_object().
|
||||||
|
|
||||||
|
to_front(Win) ->
|
||||||
|
wx_object:cast(Win, to_front).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% Startup
|
||||||
|
|
||||||
|
start_link(Args) ->
|
||||||
|
wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []).
|
||||||
|
|
||||||
|
|
||||||
|
init({Prefs, Manifest}) ->
|
||||||
|
Lang = maps:get(lang, Prefs, en_us),
|
||||||
|
Trans = gmc_jt:read_translations(?MODULE),
|
||||||
|
J = gmc_jt:j(Lang, Trans),
|
||||||
|
Wx = wx:new(),
|
||||||
|
Frame = wxFrame:new(Wx, ?wxID_ANY, J("Wallets")),
|
||||||
|
|
||||||
|
MainSz = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
|
||||||
|
Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]),
|
||||||
|
Names = [Name || #wr{name = Name} <- Manifest],
|
||||||
|
ok = wxListBox:set(Picker, Names),
|
||||||
|
|
||||||
|
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
|
ButtonTemplates =
|
||||||
|
[{open, J("Open")},
|
||||||
|
{new, J("New")},
|
||||||
|
{move, J("Move")},
|
||||||
|
{import, J("Import")},
|
||||||
|
{drop, J("Drop")}],
|
||||||
|
MakeButton =
|
||||||
|
fun({Name, Label}) ->
|
||||||
|
B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]),
|
||||||
|
_ = wxSizer:add(ButtSz, B, zxw:flags(wide)),
|
||||||
|
#w{name = Name, id = wxButton:getId(B), wx = B}
|
||||||
|
end,
|
||||||
|
Buttons = lists:map(MakeButton, ButtonTemplates),
|
||||||
|
|
||||||
|
_ = wxSizer:add(MainSz, Picker, zxw:flags(wide)),
|
||||||
|
_ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)),
|
||||||
|
|
||||||
|
ok = wxFrame:setSizer(Frame, MainSz),
|
||||||
|
ok = wxSizer:layout(MainSz),
|
||||||
|
|
||||||
|
ok =
|
||||||
|
case safe_size(Prefs) of
|
||||||
|
{pref, max} ->
|
||||||
|
wxTopLevelWindow:maximize(Frame);
|
||||||
|
{pref, WSize} ->
|
||||||
|
wxFrame:setSize(Frame, WSize);
|
||||||
|
{center, WSize} ->
|
||||||
|
ok = wxFrame:setSize(Frame, WSize),
|
||||||
|
wxFrame:center(Frame)
|
||||||
|
end,
|
||||||
|
|
||||||
|
ok = wxFrame:connect(Frame, command_button_clicked),
|
||||||
|
ok = wxFrame:connect(Frame, close_window),
|
||||||
|
true = wxFrame:show(Frame),
|
||||||
|
ok = wxListBox:connect(Picker, command_listbox_selected),
|
||||||
|
ok = wxListBox:connect(Picker, command_listbox_doubleclicked),
|
||||||
|
State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs,
|
||||||
|
wallets = Manifest,
|
||||||
|
picker = Picker,
|
||||||
|
buttons = Buttons},
|
||||||
|
{Frame, State}.
|
||||||
|
|
||||||
|
|
||||||
|
safe_size(Prefs) ->
|
||||||
|
Display = wxDisplay:new(),
|
||||||
|
GSize = wxDisplay:getGeometry(Display),
|
||||||
|
CSize = wxDisplay:getClientArea(Display),
|
||||||
|
PPI =
|
||||||
|
try
|
||||||
|
wxDisplay:getPPI(Display)
|
||||||
|
catch
|
||||||
|
Class:Exception -> {Class, Exception}
|
||||||
|
end,
|
||||||
|
ok = log(info, "Geometry: ~p", [GSize]),
|
||||||
|
ok = log(info, "ClientArea: ~p", [CSize]),
|
||||||
|
ok = log(info, "PPI: ~p", [PPI]),
|
||||||
|
Geometry =
|
||||||
|
case maps:find(geometry, Prefs) of
|
||||||
|
{ok, none} ->
|
||||||
|
{X, Y, _, _} = GSize,
|
||||||
|
{center, {X, Y, 420, 520}};
|
||||||
|
{ok, G} ->
|
||||||
|
{pref, G};
|
||||||
|
error ->
|
||||||
|
{X, Y, _, _} = GSize,
|
||||||
|
{center, {X, Y, 420, 520}}
|
||||||
|
end,
|
||||||
|
ok = wxDisplay:destroy(Display),
|
||||||
|
Geometry.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% wx_object
|
||||||
|
|
||||||
|
handle_call(Unexpected, From, State) ->
|
||||||
|
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_cast(to_front, State = #s{frame = Frame}) ->
|
||||||
|
ok = wxFrame:raise(Frame),
|
||||||
|
{noreply, State};
|
||||||
|
handle_cast(Unexpected, State) ->
|
||||||
|
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_info(Unexpected, State) ->
|
||||||
|
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_event(#wx{event = #wxCommand{type = command_button_clicked},
|
||||||
|
id = ID},
|
||||||
|
State = #s{buttons = Buttons}) ->
|
||||||
|
NewState =
|
||||||
|
case lists:keyfind(ID, #w.id, Buttons) of
|
||||||
|
#w{name = open} -> do_open(State);
|
||||||
|
#w{name = new} -> do_new(State);
|
||||||
|
#w{name = import} -> do_import(State);
|
||||||
|
#w{name = Name} -> handle_button(Name, State);
|
||||||
|
false -> State
|
||||||
|
end,
|
||||||
|
{noreply, NewState};
|
||||||
|
handle_event(#wx{event = #wxClose{}}, State) ->
|
||||||
|
ok = do_close(State),
|
||||||
|
{noreply, State};
|
||||||
|
handle_event(Event, State) ->
|
||||||
|
ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
code_change(_, State, _) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
|
||||||
|
terminate(wx_deleted, _) ->
|
||||||
|
wx:destroy();
|
||||||
|
terminate(Reason, State) ->
|
||||||
|
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]),
|
||||||
|
wx:destroy().
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%% doers
|
||||||
|
|
||||||
|
do_close(#s{frame = Frame, prefs = Prefs}) ->
|
||||||
|
Geometry =
|
||||||
|
case wxTopLevelWindow:isMaximized(Frame) of
|
||||||
|
true ->
|
||||||
|
max;
|
||||||
|
false ->
|
||||||
|
{X, Y} = wxWindow:getPosition(Frame),
|
||||||
|
{W, H} = wxWindow:getSize(Frame),
|
||||||
|
{X, Y, W, H}
|
||||||
|
end,
|
||||||
|
NewPrefs = maps:put(geometry, Geometry, Prefs),
|
||||||
|
ok = gmc_con:save(?MODULE, NewPrefs),
|
||||||
|
ok = wxWindow:destroy(Frame).
|
||||||
|
|
||||||
|
|
||||||
|
handle_button(Name, State) ->
|
||||||
|
ok = tell("Button Click: ~p", [Name]),
|
||||||
|
State.
|
||||||
|
|
||||||
|
|
||||||
|
do_open(State = #s{wallets = []}) ->
|
||||||
|
State;
|
||||||
|
do_open(State = #s{picker = Picker}) ->
|
||||||
|
case wxListBox:getSelection(Picker) of
|
||||||
|
-1 -> State;
|
||||||
|
Selected -> do_open2(Selected + 1, State)
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_open2(Selected, State = #s{wallets = Wallets}) ->
|
||||||
|
case lists:nth(Selected, Wallets) of
|
||||||
|
#wr{pass = true, path = Path} ->
|
||||||
|
do_open3(Path, State);
|
||||||
|
#wr{pass = false, path = Path} ->
|
||||||
|
ok = gmc_con:open_wallet(Path, none),
|
||||||
|
ok = do_close(State),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_open3(Path, State = #s{frame = Frame, j = J}) ->
|
||||||
|
Label = J("Password"),
|
||||||
|
Dialog = wxDialog:new(Frame, ?wxID_ANY, Label),
|
||||||
|
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label}]),
|
||||||
|
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)),
|
||||||
|
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
|
||||||
|
Affirm = wxButton:new(Dialog, ?wxID_OK),
|
||||||
|
_ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)),
|
||||||
|
_ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)),
|
||||||
|
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)),
|
||||||
|
ok = wxDialog:setSizer(Dialog, Sizer),
|
||||||
|
ok = wxBoxSizer:layout(Sizer),
|
||||||
|
ok = wxFrame:center(Dialog),
|
||||||
|
ok = wxStyledTextCtrl:setFocus(PassTx),
|
||||||
|
case wxDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
case wxTextCtrl:getValue(PassTx) of
|
||||||
|
"" ->
|
||||||
|
State;
|
||||||
|
Password ->
|
||||||
|
ok = gmc_con:open_wallet(Path, Password),
|
||||||
|
ok = do_close(State),
|
||||||
|
State
|
||||||
|
end;
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) ->
|
||||||
|
DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "clutch")),
|
||||||
|
Options =
|
||||||
|
[{message, J("Save Location")},
|
||||||
|
{defaultDir, DefaultDir},
|
||||||
|
{defaultFile, "default.gaju"},
|
||||||
|
{wildCard, "*.gaju"},
|
||||||
|
{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}],
|
||||||
|
Dialog = wxFileDialog:new(Frame, Options),
|
||||||
|
case wxFileDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
Dir = wxFileDialog:getDirectory(Dialog),
|
||||||
|
File = wxFileDialog:getFilename(Dialog),
|
||||||
|
Path = filename:join(Dir, File),
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
case do_new2(Path, J, Frame) of
|
||||||
|
ok ->
|
||||||
|
NewPrefs = maps:put(dir, Dir, Prefs),
|
||||||
|
do_close(State#s{prefs = NewPrefs});
|
||||||
|
abort ->
|
||||||
|
State
|
||||||
|
end;
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_new2(Path, J, Frame) ->
|
||||||
|
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Set Node")),
|
||||||
|
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
|
||||||
|
NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]),
|
||||||
|
NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)),
|
||||||
|
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Passphrase")}]),
|
||||||
|
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxSizer:add(PassSz, PassTx, zxw:flags(wide)),
|
||||||
|
Label = "Passphrase Confirmation",
|
||||||
|
PassConSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]),
|
||||||
|
PassConTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxSizer:add(PassConSz, PassConTx, 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)),
|
||||||
|
|
||||||
|
_ = wxSizer:add(Sizer, NameSz, zxw:flags(base)),
|
||||||
|
_ = wxSizer:add(Sizer, PassSz, zxw:flags(base)),
|
||||||
|
_ = wxSizer:add(Sizer, PassConSz, 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(NameTx),
|
||||||
|
|
||||||
|
Result =
|
||||||
|
case wxDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
Name =
|
||||||
|
case wxTextCtrl:getValue(NameTx) of
|
||||||
|
"" -> Path;
|
||||||
|
N -> N
|
||||||
|
end,
|
||||||
|
Pass =
|
||||||
|
case {wxTextCtrl:getValue(PassTx), wxTextCtrl:getValue(PassConTx)} of
|
||||||
|
{"", ""} -> none;
|
||||||
|
{P, P} -> P;
|
||||||
|
{_, _} -> bad
|
||||||
|
end,
|
||||||
|
do_new3(Name, Path, Pass);
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
abort
|
||||||
|
end,
|
||||||
|
ok = wxDialog:destroy(Dialog),
|
||||||
|
Result.
|
||||||
|
|
||||||
|
do_new3(_, _, bad) ->
|
||||||
|
abort;
|
||||||
|
do_new3(Name, Path, Pass) ->
|
||||||
|
gmc_con:new_wallet(Name, Path, Pass).
|
||||||
|
|
||||||
|
|
||||||
|
do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) ->
|
||||||
|
DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "clutch")),
|
||||||
|
Options =
|
||||||
|
[{message, J("Select Wallet File")},
|
||||||
|
{defaultDir, DefaultDir},
|
||||||
|
{wildCard, "*.gaju"},
|
||||||
|
{style, ?wxFD_OPEN}],
|
||||||
|
Dialog = wxFileDialog:new(Frame, Options),
|
||||||
|
case wxFileDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
Dir = wxFileDialog:getDirectory(Dialog),
|
||||||
|
File = wxFileDialog:getFilename(Dialog),
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
case do_import2(Dir, File, J, Frame) of
|
||||||
|
ok ->
|
||||||
|
NewPrefs = maps:put(dir, Dir, Prefs),
|
||||||
|
do_close(State#s{prefs = NewPrefs});
|
||||||
|
abort ->
|
||||||
|
State
|
||||||
|
end;
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
ok = wxFileDialog:destroy(Dialog),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_import2(_, "", _, _) ->
|
||||||
|
abort;
|
||||||
|
do_import2(Dir, File, J, Frame) ->
|
||||||
|
Path = filename:join(Dir, File),
|
||||||
|
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Set Node")),
|
||||||
|
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
|
||||||
|
NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]),
|
||||||
|
NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)),
|
||||||
|
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Passphrase")}]),
|
||||||
|
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY),
|
||||||
|
_ = wxSizer:add(PassSz, PassTx, 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)),
|
||||||
|
|
||||||
|
_ = wxSizer:add(Sizer, NameSz, zxw:flags(base)),
|
||||||
|
_ = wxSizer:add(Sizer, PassSz, 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(NameTx),
|
||||||
|
|
||||||
|
Result =
|
||||||
|
case wxDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
Name =
|
||||||
|
case wxTextCtrl:getValue(NameTx) of
|
||||||
|
"" -> Path;
|
||||||
|
N -> N
|
||||||
|
end,
|
||||||
|
Pass =
|
||||||
|
case wxTextCtrl:getValue(PassTx) of
|
||||||
|
"" -> none;
|
||||||
|
P -> P
|
||||||
|
end,
|
||||||
|
gmc_con:import_wallet(Name, Path, Pass);
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
abort
|
||||||
|
end,
|
||||||
|
ok = wxDialog:destroy(Dialog),
|
||||||
|
Result.
|
||||||
|
|
||||||
|
|
||||||
|
do_selection(Selected, State = #s{prefs = Prefs}) ->
|
||||||
|
NewPrefs = maps:put(selected, Selected, Prefs),
|
||||||
|
State#s{prefs = NewPrefs}.
|
Loading…
x
Reference in New Issue
Block a user