From be0607f7c17bf4956bc41006e5435e4719780331 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 29 Oct 2025 15:52:36 +0900 Subject: [PATCH] Remove dinkus file --- src/: | 1332 --------------------------------------------------------- 1 file changed, 1332 deletions(-) delete mode 100644 src/: diff --git a/src/: b/src/: deleted file mode 100644 index 138f7f2..0000000 --- a/src/: +++ /dev/null @@ -1,1332 +0,0 @@ -%%% @doc -%%% GajuDesk Controller -%%% @end - --module(gd_con). --vsn("0.7.0"). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - --behavior(gen_server). --export([show_ui/1, - open_wallet/2, close_wallet/0, new_wallet/4, import_wallet/3, drop_wallet/2, - selected/1, network/0, - password/2, - refresh/0, - nonce/1, spend/1, chain/1, grids/1, - sign_mess/1, sign_binary/1, sign_tx/1, sign_call/3, dry_run/2, - deploy/3, - make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1, list_keys/0, - add_node/1, set_sole_node/1]). --export([encrypt/2, decrypt/2]). --export([save/2]). --export([start_link/0, stop/0]). --export([init/1, terminate/2, code_change/3, - handle_call/3, handle_cast/2, handle_info/2]). --include("$zx_include/zx_logger.hrl"). - --include("gd.hrl"). - - -%%% Type and Record Definitions - - --record(ui, - {name = none :: none | ui_name(), - pid = none :: none | pid(), - wx = none :: none | wx:wx_object(), - mon = none :: none | reference()}). - --record(s, - {version = 1 :: integer(), - window = none :: none | wx:wx_object(), - timer = none :: none | reference(), - tasks = [] :: [#ui{}], - selected = 0 :: non_neg_integer(), - wallet = none :: none | #wallet{}, - pass = none :: none | binary(), - prefs = #{} :: #{module() := term()}, - wallets = [] :: [#wr{}]}). - - --type state() :: #s{}. --type ui_name() :: gd_v_netman - | gd_v_wallman - | gd_v_devman. - - - -%% Interface - - --spec show_ui(Name) -> ok - when Name :: ui_name(). - -show_ui(Name) -> - gen_server:call(?MODULE, {show_ui, Name}). - - --spec open_wallet(Path, Phrase) -> Result - when Path :: file:filename(), - Phrase :: string(), - Result :: ok | {error, Reason :: term()}. - -open_wallet(Path, Phrase) -> - gen_server:call(?MODULE, {open_wallet, Path, Phrase}, 6000). - - --spec close_wallet() -> ok. - -close_wallet() -> - gen_server:cast(?MODULE, close_wallet). - - --spec new_wallet(Net, Name, Path, Password) -> ok - when Net :: mainnet | testnet | devnet, - Name :: string(), - Path :: string(), - Password :: string(). - -new_wallet(Net, Name, Path, Password) -> - gen_server:cast(?MODULE, {new_wallet, Net, 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 drop_wallet(Path, Delete) -> ok - when Path :: file:filename(), - Delete :: boolean(). - -drop_wallet(Path, Delete) -> - gen_server:cast(?MODULE, {drop_wallet, Path, Delete}). - - --spec selected(Index) -> ok - when Index :: pos_integer() | none. - -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 - when Old :: none | string(), - New :: none | string(). - -password(Old, New) -> - gen_server:cast(?MODULE, {password, Old, New}). - - --spec refresh() -> ok. - -refresh() -> - gen_server:cast(?MODULE, refresh). - - --spec nonce(ID) -> {ok, Nonce} | {error, Reason} - when ID :: gajudesk:id(), - Nonce :: integer(), - Reason :: term(). % FIXME - -nonce(ID) -> - gen_server:call(?MODULE, {nonce, ID}). - - --spec spend(TX) -> ok - when TX :: #spend_tx{}. - -spend(TX) -> - gen_server:cast(?MODULE, {spend, TX}). - - --spec chain(ID) -> ok - when ID :: string(). - -chain(ID) -> - gen_server:cast(?MODULE, {chain, ID}). - - --spec grids(string()) -> ok. - -grids(String) -> - gen_server:cast(?MODULE, {grids, String}). - - --spec sign_mess(Request) -> ok - when Request :: map(). - -sign_mess(Request) -> - gen_server:cast(?MODULE, {sign_mess, Request}). - - --spec sign_binary(Request) -> ok - when Request :: map(). - -sign_binary(Request) -> - gen_server:cast(?MODULE, {sign_binary, Request}). - - --spec sign_tx(Request) -> ok - when Request :: map(). - -sign_tx(Request) -> - gen_server:cast(?MODULE, {sign_tx, Request}). - - --spec sign_call(ConID, PubKey, TX) -> ok - when ConID :: gajudesk:id(), - PubKey :: gajudesk:id(), - TX :: binary(). - -sign_call(ConID, PubKey, TX) -> - gen_server:cast(?MODULE, {sign_call, ConID, PubKey, TX}). - - --spec dry_run(ConID, TX) -> ok - when ConID :: gajudesk:id(), - TX :: binary(). - -dry_run(ConID, TX) -> - gen_server:cast(?MODULE, {dry_run, ConID, TX}). - - --spec deploy(Build, Params, InitArgs) -> Result - when Build :: map(), - Params :: {PK :: gajudesk:id(), - Nonce :: non_neg_integer(), - TTL :: pos_integer(), - GasP :: pos_integer(), - Gas :: pos_integer(), - Amount :: pos_integer()}, - InitArgs :: [Arg :: string()], - Result :: {ok, TX_Hash :: gajudesk:id()} - | {error, Reason}, - Reason :: term(). % FIXME - -deploy(Build, Params, InitArgs) -> - gen_server:cast(?MODULE, {deploy, Build, Params, InitArgs}). - - --spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok - when Type :: {eddsa, ed25519}, - Size :: 256, - Name :: string(), - Seed :: string(), - Encoding :: utf8 | base64 | base58 | none, - Transform :: {Algo, Yugeness}, - Algo :: sha3 | sha2 | x_or | pbkdf2, - Yugeness :: rand | non_neg_integer(). -%% @doc -%% Generate a new key. -%% The magic first two args are here because `ak_*' basically has no option other than -%% to mean a 256 bit key based on Curve 25519. When more key varieties are added to the -%% system this will change quite a lot. - -make_key({eddsa, ed25519}, 256, Name, Seed, Encoding, Transform) -> - gen_server:cast(?MODULE, {make_key, Name, Seed, Encoding, Transform}). - - --spec recover_key(Mnemonic) -> ok - when Mnemonic :: string(). - -recover_key(Mnemonic) -> - gen_server:cast(?MODULE, {recover_key, Mnemonic}). - - --spec mnemonic(ID) -> {ok, Mnemonic} | error - when ID :: gajudesk:id(), - Mnemonic :: string(). - -mnemonic(ID) -> - gen_server:call(?MODULE, {mnemonic, ID}). - - --spec rename_key(ID, NewName) -> ok - when ID :: gajudesk:id(), - NewName :: string(). - -rename_key(ID, NewName) -> - gen_server:cast(?MODULE, {rename_key, ID, NewName}). - - --spec drop_key(ID) -> ok - when ID :: gajudesk:id(). - -drop_key(ID) -> - gen_server:cast(?MODULE, {drop_key, ID}). - - --spec list_keys() -> Result - when Result :: {ok, Selected :: non_neg_integer(), Keys :: [gajudesk:id()]} - | error. - -list_keys() -> - gen_server:call(?MODULE, list_keys). - - --spec add_node(New) -> ok - when New :: #node{}. - -add_node(New) -> - gen_server:cast(?MODULE, {add_node, New}). - - --spec set_sole_node(TheOneTrueNode) -> ok - when TheOneTrueNode :: #node{}. - -set_sole_node(TheOneTrueNode) -> - gen_server:cast(?MODULE, {set_sole_node, TheOneTrueNode}). - - -%%% Lifecycle functions --spec stop() -> ok. - -stop() -> - gen_server:cast(?MODULE, stop). - - --spec save(Module, Prefs) -> ok | {error, Reason} - when Module :: module(), - Prefs :: #{atom() := term()}, - Reason :: file:posix(). - -save(Module, Prefs) -> - gen_server:call(?MODULE, {save, Module, Prefs}). - - - -%%% Startup Functions - - --spec start_link() -> Result - when Result :: {ok, pid()} - | {error, Reason}, - Reason :: {already_started, pid()} - | {shutdown, term()} - | term(). -%% @private -%% Called by gd_sup. - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). - - --spec init(none) -> {ok, state()}. - -init(none) -> - ok = log(info, "Starting"), - process_flag(sensitive, true), - Prefs = read_prefs(), - GUI_Prefs = maps:get(gd_gui, Prefs, #{}), - Window = gd_gui:start_link(GUI_Prefs), - Wallets = get_prefs(wallets, 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), - ok = gd_v_wallman:first_run(), - {ok, NewState}. - - -read_prefs() -> - case file:consult(prefs_path()) of - {ok, Prefs} -> proplists:to_map(Prefs); - _ -> #{} - end. - - -tic() -> - 6000. - - - -%%% gen_server Message Handling Callbacks - - --spec handle_call(Message, From, State) -> Result - when Message :: term(), - From :: {pid(), reference()}, - State :: state(), - Result :: {reply, Response, NewState} - | {noreply, State}, - Response :: ok - | {error, {listening, inet:port_number()}}, - NewState :: state(). -%% @private -%% The gen_server:handle_call/3 callback. -%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 - -handle_call(list_keys, _, State) -> - Response = do_list_keys(State), - {reply, Response, State}; -handle_call({nonce, ID}, _, State) -> - Response = do_nonce(ID), - {reply, Response, State}; -handle_call({open_wallet, Path, Phrase}, _, State) -> - {Response, NewState} = do_open_wallet(Path, Phrase, State), - {reply, Response, NewState}; -handle_call(network, _, State) -> - Response = do_network(State), - {reply, Response, State}; -handle_call({save, Module, Prefs}, _, State) -> - NewState = do_save(Module, Prefs, State), - {reply, ok, NewState}; -handle_call({mnemonic, ID}, _, State) -> - Response = do_mnemonic(ID, State), - {reply, Response, State}; -handle_call({show_ui, Name}, _, State) -> - NewState = do_show_ui(Name, State), - {reply, ok, NewState}; -handle_call(Unexpected, From, State) -> - ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), - {noreply, State}. - - --spec handle_cast(Message, State) -> {noreply, NewState} - when Message :: term(), - State :: state(), - NewState :: state(). -%% @private -%% The gen_server:handle_cast/2 callback. -%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 - -handle_cast(close_wallet, State) -> - NextState = do_close_wallet(State), - ok = gd_gui:show([]), - NewState = do_show_ui(gd_v_wallman, NextState), - ok = gd_v_wallman:to_front(), - {noreply, NewState}; -handle_cast({new_wallet, Net, Name, Path, Password}, State) -> - NewState = do_new_wallet(Net, Name, Path, Password, State), - {noreply, NewState}; -handle_cast({import_wallet, Name, Path, Password}, State) -> - NewState = do_import_wallet(Name, Path, Password, State), - {noreply, NewState}; -handle_cast({drop_wallet, Path, Delete}, State) -> - NewState = do_drop_wallet(Path, Delete, State), - {noreply, NewState}; -handle_cast({selected, Index}, State) -> - NewState = State#s{selected = Index}, - {noreply, NewState}; -handle_cast({password, Old, New}, State) -> - NewState = do_password(Old, New, State), - {noreply, NewState}; -handle_cast(refresh, State) -> - NewState = do_refresh(State), - {noreply, NewState}; -handle_cast({spend, TX}, State) -> - ok = do_spend(TX, State), - {noreply, State}; -handle_cast({chain, ID}, State) -> - NewState = do_chain(ID, State), - {noreply, NewState}; -handle_cast({grids, String}, State) -> - ok = do_grids(String), - {noreply, State}; -handle_cast({sign_mess, Request}, State) -> - ok = do_sign_mess(Request, State), - {noreply, State}; -handle_cast({sign_binary, Request}, State) -> - ok = do_sign_binary(Request, State), - {noreply, State}; -handle_cast({sign_tx, Request}, State) -> - ok = do_sign_tx(Request, State), - {noreply, State}; -handle_cast({sign_call, ConID, PubKey, TX}, State) -> - ok = do_sign_call(State, ConID, PubKey, TX), - {noreply, State}; -handle_cast({dry_run, ConID, TX}, State) -> - ok = do_dry_run(ConID, TX), - {noreply, State}; -handle_cast({deploy, Build, Params, InitArgs}, State) -> - ok = do_deploy(Build, Params, InitArgs, State), - {noreply, State}; -handle_cast({make_key, Name, Seed, Encoding, Transform}, State) -> - NewState = do_make_key(Name, Seed, Encoding, Transform, State), - {noreply, NewState}; -handle_cast({recover_key, Mnemonic}, State) -> - NewState = do_recover_key(Mnemonic, State), - {noreply, NewState}; -handle_cast({rename_key, ID, NewName}, State) -> - NewState = do_rename_key(ID, NewName, State), - {noreply, NewState}; -handle_cast({drop_key, ID}, State) -> - NewState = do_drop_key(ID, State), - {noreply, NewState}; -handle_cast({add_node, New}, State) -> - NewState = do_add_node(New, State), - {noreply, NewState}; -handle_cast({set_sole_node, TheOneTrueNode}, State) -> - NewState = do_set_sole_node(TheOneTrueNode, State), - {noreply, NewState}; -handle_cast(stop, State) -> - NewState = do_stop(State), - {noreply, NewState}; -handle_cast(Unexpected, State) -> - ok = tell(warning, "Unexpected cast: ~tp~n", [Unexpected]), - {noreply, State}. - - --spec handle_info(Message, State) -> {noreply, NewState} - when Message :: term(), - State :: state(), - NewState :: state(). -%% @private -%% The gen_server:handle_info/2 callback. -%% 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({show_ui, Name}, State) -> - NewState = do_show_ui(Name, State), - {noreply, NewState}; -handle_info({'DOWN', Mon, process, PID, Info}, State) -> - NewState = handle_down(Mon, PID, Info, State), - {noreply, NewState}; -handle_info(Unexpected, State) -> - ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), - {noreply, State}. - - -handle_down(Mon, PID, Info, State = #s{tasks = Tasks}) -> - case lists:keytake(Mon, #ui.mon, Tasks) of - {value, #ui{}, NewTasks} -> - State#s{tasks = NewTasks}; - false -> - Unexpected = {'DOWN', Mon, process, PID, Info}, - ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), - State - end. - - - -%% @private -%% gen_server callback to handle state transformations necessary for hot -%% code updates. This template performs no transformation. - -code_change(_, State, _) -> - {ok, State}. - - -terminate(Reason, _) -> - ok = log(info, "Reason: ~p,", [Reason]), - case whereis(gmc_con) of - undefined -> - zx:stop(); - PID -> - ok = log(info, "gd_con found at: ~p", [PID]), - application:stop(gajumine) - end. - - - -%%% GUI doers - - -do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) -> - case lists:keyfind(Name, #ui.name, Tasks) of - #ui{wx = Win} -> - ok = Name:to_front(Win), - State; - false -> - TaskPrefs = maps:get(Name, Prefs, #{}), - TaskData = task_data(Name, State), - Win = Name:start_link({TaskPrefs, TaskData}), - PID = wx_object:get_pid(Win), - Mon = monitor(process, PID), - UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon}, - State#s{tasks = [UI | Tasks]} - end. - -task_data(gd_v_netman, #s{wallet = #wallet{nets = Nets}}) -> - Nets; -task_data(gd_v_netman, #s{wallet = none}) -> - []; -task_data(gd_v_wallman, #s{wallets = Wallets}) -> - Wallets; -task_data(gd_v_devman, #s{}) -> - []. - - - -%%% Network operations - -do_chain(_, State) -> - tell("Would be doing chain in do_chain/2 here"), - State. - - -do_add_node(New, State) -> - tell("New node: ~p", [New]), - State. - - -do_set_sole_node(TOTN = #node{external = none}, State) -> - do_set_sole_node(TOTN#node{external = 3013}, State); -do_set_sole_node(New = #node{ip = IP, external = Port}, State = #s{wallet = W, wallets = Ws, pass = Pass}) -> - ok = hz:chain_nodes([{IP, Port}]), - case ensure_hz_set(New) of - {ok, ChainID} -> - #wallet{name = Name} = W, - RW = lists:keyfind(Name, #wr.name, Ws), - Net = #net{id = ChainID, chains = [#chain{id = ChainID, nodes = [New]}]}, - NewWallet = W#wallet{chain_id = ChainID, endpoint = New, nets = [Net]}, - ok = save_wallet(RW, Pass, NewWallet), - NewState = State#s{wallet = NewWallet}, - do_refresh(NewState); - Error -> - gd_gui:trouble(Error), - State - end. - - -do_refresh(State = #s{wallet = none}) -> - State; -do_refresh(State = #s{wallet = #wallet{endpoint = none}}) -> - State; -do_refresh(State = #s{wallet = #wallet{endpoint = Node}}) -> - case ensure_hz_set(Node) of - {ok, ChainID} -> - do_refresh2(ChainID, State); - Error -> - ok = gd_gui:trouble({do_refresh, 1, Error}), - State - end. - - -do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) -> - CheckBalance = check_balance(ChainID), - NewPOAs = lists:map(CheckBalance, POAs), - ok = gd_gui:show(NewPOAs), - NewW = W#wallet{chain_id = ChainID, poas = NewPOAs}, - 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}) -> - case hz:chain_nodes() of - [{IP, Port}] -> - case hz:status() of - {ok, #{"network_id" := ChainID}} -> - ok = gd_gui:chain(ChainID, Node), - {ok, list_to_binary(ChainID)}; - {error, no_nodes} -> - ok = hz:chain_nodes([{IP, Port}]), - ensure_hz_set(Node); - Error -> - Error - end; - _ -> - ok = hz:chain_nodes([{IP, Port}]), - ensure_hz_set(Node) - end; -ensure_hz_set(none) -> - {error, no_nodes}. - - -%ensure_connected(ChainID, IP, Port) -> -% ok = hz:chain_nodes([{IP, Port}]), -% ok = hz:network_id(ChainID). - - - -%%% Chain operations - -do_grids(String) -> - case gd_grids:parse(String) of - {ok, Instruction} -> do_grids2(Instruction); - Error -> gd_gui:trouble(Error) - end. - -do_grids2({{sign, http}, URL}) -> - ok = log(info, "Making request to ~p", [URL]), - case httpc:request(URL) of - {ok, {{_, 200, _}, _, JSON}} -> do_grids_sig(JSON, URL); - {error, socket_closed_remotely} -> log(info, "Socket closed remotely."); - Error -> gd_gui:trouble(Error) - end; -do_grids2({{sign, https}, URL}) -> - ok = log(info, "Making request to ~p", [URL]), - case httpc:request(URL) of - {ok, {{_, 200, _}, _, JSON}} -> do_grids_sig(JSON, URL); - {error, socket_closed_remotely} -> log(info, "Socket closed remotely."); - Error -> gd_gui:trouble(Error) - end; -do_grids2(Instruction) -> - tell("GRIDS: ~tp", [Instruction]). - -do_grids_sig(JSON, URL) -> - ok = log(info, "Received: ~p", [JSON]), - case zj:decode(JSON) of - {ok, GRIDS} -> do_grids_sig2(GRIDS#{"url" => URL}); - Error -> gd_gui:trouble(Error) - end. - -do_grids_sig2(Request = #{"grids" := 1, "type" := "message"}) -> - gd_gui:grids_mess_sig(Request); -do_grids_sig2(Request = #{"grids" := 1, "type" := "binary"}) -> - gd_gui:grids_mess_sig(Request); -do_grids_sig2(Request = #{"grids" := 1, "type" := "tx"}) -> - gd_gui:grids_mess_sig(Request); -do_grids_sig2(WTF) -> - gd_gui:trouble({trash, WTF}). - - -do_sign_mess(Request = #{"public_id" := ID}, #s{wallet = #wallet{keys = Keys}}) -> - case lists:keyfind(ID, #key.id, Keys) of - #key{pair = #{secret := SecKey}} -> do_sign_mess2(Request, SecKey); - false -> gd_gui:trouble({bad_key, ID}) - end. - -do_sign_mess2(Request = #{"payload" := Message}, SecKey) -> - Sig = base64:encode(hz:sign_message(list_to_binary(Message), SecKey)), - SignedRequest = maps:put("signature", Sig, Request), - ResponseKeys = - ["grids", - "chain", - "network_id", - "type", - "public_id", - "payload", - "signature"], - post_grids_response(ResponseKeys, SignedRequest). - - -do_sign_binary(Request = #{"public_id" := ID}, #s{wallet = #wallet{keys = Keys}}) -> - case lists:keyfind(ID, #key.id, Keys) of - #key{pair = #{secret := SecKey}} -> do_sign_binary2(Request, SecKey); - false -> gd_gui:trouble({bad_key, ID}) - end. - -do_sign_binary2(Request = #{"payload" := Payload}, SecKey) -> - case base64_decode(Payload) of - {ok, Binary} -> - Sig = base64:encode(hz:sign_binary(Binary, SecKey)), - SignedRequest = maps:put("signature", Sig, Request), - ResponseKeys = - ["grids", - "chain", - "network_id", - "type", - "public_id", - "payload", - "signature"], - post_grids_response(ResponseKeys, SignedRequest); - Error -> - gd_gui:trouble(Error) - end. - - -do_sign_tx(Request = #{"public_id" := ID, "payload" := CallData, "network_id" := NID}, - #s{wallet = #wallet{keys = Keys}}) -> - BinNID = list_to_binary(NID), - case lists:keyfind(ID, #key.id, Keys) of - #key{pair = #{secret := SecKey}} -> - BinaryTX = list_to_binary(CallData), - SignedTX = hz:sign_tx(BinaryTX, SecKey, BinNID), - SignedRequest = Request#{"signed" => true, "payload" := SignedTX}, - ResponseKeys = - ["grids", - "chain", - "network_id", - "type", - "public_id", - "payload", - "signed"], - post_grids_response(ResponseKeys, SignedRequest); - false -> - gd_gui:trouble({bad_key, ID}) - end. - - -post_grids_response(ResponseKeys, Request = #{"url" := URL}) -> - Response = zj:encode(maps:with(ResponseKeys, Request)), - case httpc:request(post, {URL, [], "application/json", Response}, [], []) of - {ok, {{_, 200, _}, _, JSON}} -> log(info, "Signed TX posted: ~p", [JSON]); - {error, socket_closed_remotely} -> log(info, "Socket closed remotely."); - Error -> gd_gui:trouble(Error) - end. - - -do_sign_call(#s{wallet = #wallet{keys = Keys, chain_id = ChainID}}, - ConID, - PubKey, - TX) -> - #key{pair = #{secret := SecKey}} = lists:keyfind(PubKey, #key.id, Keys), - SignedTX = hz:sign_tx(TX, SecKey, ChainID), - case hz:post_tx(SignedTX) of - {ok, Data = #{"tx_hash" := TXHash}} -> - ok = tell("TX succeded with: ~p", [TXHash]), - do_sign_call2(ConID, Data); - {ok, WTF} -> - gd_v_devman:trouble({error, WTF}); - Error -> - gd_v_devman:trouble(Error) - end; -do_sign_call(_, _, _, _) -> - gd_v_devman:trouble({error, no_chain}). - -do_sign_call2(ConID, #{"tx_hash" := TXHash}) -> - case hz:tx_info(TXHash) of - {ok, CallInfo = #{"call_info" := #{"return_type" := "ok"}}} -> - gd_v_devman:call_result(ConID, CallInfo); - {error, "Tx not mined"} -> - gd_v_devman:trouble({tx_hash, TXHash}); - {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> - gd_v_devman:trouble({error, Reason}); - Error -> - gd_v_devman:trouble(Error) - end. - - -do_dry_run(ConID, TX) -> - case hz:dry_run(TX) of - {ok, Result} -> gd_v_devman:dryrun_result(ConID, Result); - Other -> gd_v_devmam:trouble({error, ConID, Other}) - end. - - -do_spend(#spend_tx{sender_id = SenderID, - recipient_id = RecipientID, - amount = Amount, - gas_price = GasPrice, - gas = Gas, - ttl = TTL, - nonce = Nonce, - payload = Payload}, - #s{wallet = #wallet{keys = Keys, chain_id = NetworkID}}) -> - case lists:keyfind(SenderID, #key.id, Keys) of - #key{pair = #{secret := SecKey}} -> - Outcome = hz:spend(SenderID, - SecKey, - RecipientID, - Amount, - GasPrice, - Gas, - TTL, - Nonce, - Payload, - NetworkID), - tell(info, "Outcome: ~p", [Outcome]); - false -> - log(warning, "Tried do_spend with a bad key: ~p", [SenderID]) - end. - - -do_list_keys(#s{selected = Selected, wallet = #wallet{poas = POAs}}) -> - {ok, Selected, [ID || #poa{id = ID} <- POAs]}; -do_list_keys(#s{wallet = none}) -> - error. - - -do_nonce(ID) -> - hz:next_nonce(ID). - - -do_network(#s{wallet = none}) -> - none; -do_network(#s{wallet = #wallet{chain_id = ChainID}}) -> - {ok, ChainID}. - - - -%%% 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) -> - Bin = crypto:strong_rand_bytes(32), - do_make_key2(Name, Bin, Transform, State); -do_make_key(Name, Seed, utf8, Transform, State) -> - Bin = unicode:characters_to_binary(Seed), - do_make_key2(Name, Bin, Transform, State); -do_make_key(Name, Seed, base64, Transform, State) -> - case base64_decode(Seed) of - {ok, Bin} -> - do_make_key2(Name, Bin, Transform, State); - {error, Reason} -> - ok = gd_gui:trouble({error, {base64, Reason}}), - State - end; -do_make_key(Name, Seed, base58, Transform, State) -> - case base58:check_base58(Seed) of - true -> - Bin = base58:base58_to_binary(Seed), - do_make_key2(Name, Bin, Transform, State); - false -> - ok = gd_gui:trouble({error, {base58, badarg}}), - State - end. - - -do_make_key2(_, _, _, State = #s{wallet = none}) -> - ok = gd_gui:trouble("No wallet selected!"), - NewState = do_show_ui(gd_v_wallman, State), - ok = gd_v_wallman:to_front(), - NewState; -do_make_key2(Name, Bin, Transform, - State = #s{wallet = Current, wallets = Wallets, pass = Pass}) -> - #wallet{name = WalletName, poas = POAs, keys = Keys} = Current, - T = transform(Transform), - Seed = T(Bin), - {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}, - NewKeys = [Key | Keys], - NewPOAs = [POA | POAs], - Updated = Current#wallet{poas = NewPOAs, keys = NewKeys}, - RW = lists:keyfind(WalletName, #wr.name, Wallets), - ok = save_wallet(RW, Pass, Updated), - ok = gd_gui:show(NewPOAs), - State#s{wallet = Updated}. - - -base64_decode(String) -> - try - {ok, base64:decode(String)} - catch - E:R -> {E, R} - end. - - -transform({sha3, 256}) -> - fun(D) -> crypto:hash(sha3_256, D) end; -transform({sha2, 256}) -> - fun(D) -> crypto:hash(sha256, D) end; -transform({x_or, 256}) -> - fun t_xor/1. - - -t_xor(Bin) -> t_xor(Bin, <<0:256>>). - -t_xor(<>, A) -> - t_xor(T, crypto:exor(H, A)); -t_xor(<<>>, A) -> - A; -t_xor(B, A) -> - H = <<0:(256 - bit_size(B)), B/binary>>, - crypto:exor(H, A). - - -do_recover_key(Mnemonic, State) -> - case hz_key_master:decode(Mnemonic) of - {ok, Seed} -> - do_recover_key2(Seed, State); - Error -> - ok = gd_gui:trouble(Error), - State - end. - -do_recover_key2(Seed, State = #s{wallet = Current, wallets = Wallets, pass = Pass}) -> - #wallet{name = WalletName, keys = Keys, poas = POAs} = Current, - {ID, Pair} = hz_key_master:make_key(Seed), - Recovered = #key{id = ID, name = ID, pair = Pair}, - case lists:keymember(ID, #key.id, Keys) of - false -> - NewKeys = [Recovered | Keys], - POA = #poa{name = ID, id = ID}, - NewPOAs = [POA | POAs], - ok = gd_gui:show(NewPOAs), - Updated = Current#wallet{poas = NewPOAs, keys = NewKeys}, - RW = lists:keyfind(WalletName, #wr.name, Wallets), - ok = save_wallet(RW, Pass, Updated), - State#s{wallet = Updated}; - true -> - State - end. - - -do_mnemonic(ID, #s{wallet = #wallet{keys = Keys}}) -> - case lists:keyfind(ID, #key.id, Keys) of - #key{pair = #{secret := <>}} -> - Mnemonic = hz_key_master:encode(K), - {ok, Mnemonic}; - false -> - {error, bad_key} - end. - - -do_deploy(Build, - {PubKey, Nonce, TTL, GasPrice, Gas, Amount}, - InitArgs, - #s{wallet = #wallet{keys = Keys, chain_id = ChainID}}) -> - #key{pair = #{secret := SecKey}} = lists:keyfind(PubKey, #key.id, Keys), - case hz:contract_create_built(PubKey, - Nonce, Amount, TTL, Gas, GasPrice, - Build, InitArgs) of - {ok, CreateTX} -> do_deploy2(SecKey, CreateTX, ChainID); - Error -> gd_v_devman:trouble(Error) - end. - -do_deploy2(SecKey, CreateTX, ChainID) -> - SignedTX = hz:sign_tx(CreateTX, SecKey, ChainID), - tell(info, "SignedTX: ~p", [SignedTX]), - case hz:post_tx(SignedTX) of - {ok, Data = #{"tx_hash" := TXHash}} -> - ok = tell("Contract deploy TX succeded with: ~p", [TXHash]), - do_deploy3(Data); - {ok, WTF} -> - gd_v_devman:trouble({error, WTF}); - Error -> - gd_v_devman:trouble(Error) - end. - -do_deploy3(#{"tx_hash" := TXHash}) -> - case hz:tx_info(TXHash) of - {ok, #{"call_info" := #{"return_type" := "ok", "contract_id" := ConID}}} -> - gd_v_devman:open_contract(ConID); - {error, "Tx not mined"} -> - gd_v_devman:trouble({tx_hash, TXHash}); - {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> - gd_v_devman:trouble({error, Reason}); - Error -> - gd_v_devman:trouble(Error) - end. - - -do_rename_key(ID, NewName, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> - #wallet{name = Name, poas = POAs, keys = Keys} = W, - RW = lists:keyfind(Name, #wr.name, Wallets), - A = lists:keyfind(ID, #poa.id, POAs), - K = lists:keyfind(ID, #key.id, Keys), - NewPOAs = lists:keystore(ID, #poa.id, POAs, A#poa{name = NewName}), - NewKeys = lists:keystore(ID, #key.id, Keys, K#key{name = NewName}), - NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys}, - ok = save_wallet(RW, Pass, NewWallet), - ok = gd_gui:show(NewPOAs), - State#s{wallet = NewWallet}. - - -do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> - #wallet{name = Name, poas = POAs, keys = Keys} = W, - RW = lists:keyfind(Name, #wr.name, Wallets), - NewPOAs = lists:keydelete(ID, #poa.id, POAs), - NewKeys = lists:keydelete(ID, #key.id, Keys), - NewWallet = W#wallet{poas = NewPOAs, keys = NewKeys}, - ok = save_wallet(RW, Pass, NewWallet), - ok = gd_gui:show(NewPOAs), - State#s{wallet = NewWallet}. - - -do_open_wallet(Path, Phrase, State) -> - Pass = pass(Phrase), - case read(Path, Pass) of - {ok, Recovered = #wallet{name = Name, poas = POAs, endpoint = Node}} -> - ok = gd_gui:show(POAs), - 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, - {ok, State#s{pass = Pass, wallet = Recovered}}; - Error -> - {Error, State} - end. - - -default_wallet(mainnet) -> - Node = #node{ip = "groot.mainnet.gajumaru.io"}, - Groot = #chain{id = <<"groot.mainnet">>, - 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]}. - - -do_password(none, none, State) -> - State; -do_password(none, New, State = #s{pass = none, - wallet = #wallet{name = Name}, - wallets = Wallets}) -> - Pass = pass(New), - Selected = lists:keyfind(Name, #wr.name, Wallets), - 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 - true -> - Selected = lists:keyfind(Name, #wr.name, Wallets), - Updated = Selected#wr{pass = false}, - NewWallets = lists:keystore(Name, #wr.name, Wallets, Updated), - State#s{pass = none, wallets = NewWallets}; - false -> - State - end; -do_password(Old, New, State = #s{pass = Pass}) -> - case pass(Old) =:= Pass of - true -> State#s{pass = pass(New)}; - false -> State - end. - - -do_stop(State = #s{prefs = Prefs}) -> - ok = persist(Prefs), - 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(Net, Name, Path, Password, State = #s{wallets = Wallets, prefs = Prefs}) -> - case lists:keyfind(Name, #wr.name, Wallets) of - false -> - NextState = do_close_wallet(State), - Pass = pass(Password), - HasPass = Pass =/= none, - Entry = #wr{name = Name, path = Path, pass = HasPass}, - DW = #wallet{endpoint = Node} = default_wallet(Net), - New = DW#wallet{name = Name}, - ok = save_wallet(Entry, Pass, New), - ok = gd_gui:show([]), - 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], - NewPrefs = put_prefs(wallets, NewWallets, Prefs), - ok = persist(NewPrefs), - NextState#s{wallet = New, pass = Pass, wallets = NewWallets, prefs = NewPrefs}; - #wr{} -> - % FIXME - % Need to provide feedback based on where this came from - State - end. - -do_import_wallet(Name, Path, Password, State = #s{wallets = Wallets}) -> - NameExists = lists:keymember(Name, #wr.name, Wallets), - PathExists = lists:keymember(Path, #wr.path, Wallets), - case {NameExists, PathExists} of - {false, false} -> - do_import_wallet2(Name, Path, Password, State); - {true, false} -> - ok = gd_gui:trouble({error, name_exists}), - State; - {false, true} -> - ok = gd_gui:trouble({error, path_exists}), - State; - {true, true} -> - ok = gd_gui:trouble("Whoa! This exact wallet already exists!"), - State - end. - -do_import_wallet2(Name, Path, Password, State = #s{wallets = Wallets, prefs = Prefs}) -> - Pass = pass(Password), - case read(Path, Pass) of - {ok, Recovered = #wallet{poas = POAs, chain_id = ChainID, endpoint = Endpoint}} -> - Imported = Recovered#wallet{name = Name}, - HasPass = Pass =/= none, - Record = #wr{name = Name, path = Path, pass = HasPass}, - NewWallets = [Record | Wallets], - NewPrefs = put_prefs(wallets, NewWallets, Prefs), - ok = save_wallet(Record, Pass, Imported), - ok = persist(NewPrefs), - ok = gd_gui:show(POAs), - ok = gd_gui:chain(ChainID, Endpoint), - ok = gd_gui:wallet(Name), - State#s{wallet = Imported, wallets = NewWallets, prefs = NewPrefs}; - Error -> - ok = gd_gui:trouble(Error), - State - end. - - -do_drop_wallet(Path, Delete, State = #s{tasks = Tasks, - wallet = Wallet, - wallets = Wallets, - prefs = Prefs}) -> - CurrentName = - case Wallet of - #wallet{name = N} -> N; - none -> none - end, - case lists:keytake(Path, #wr.path, Wallets) of - {value, #wr{name = Name}, NewWallets} -> - ok = - case Name =:= CurrentName of - true -> - ok = gd_gui:show([]), - ok = gd_gui:wallet(none), - ok = gd_gui:chain(none, none); - false -> - ok - end, - ok = maybe_clean(Delete, Path), - NewPrefs = put_prefs(wallets, NewWallets, Prefs), - ok = persist(NewPrefs), - #ui{wx = WallMan} = lists:keyfind(gd_v_wallman, #ui.name, Tasks), - ok = gd_v_wallman:show(WallMan, NewWallets), - State#s{wallets = NewWallets, prefs = NewPrefs}; - false -> - State - end. - -maybe_clean(true, Path) -> - case file:delete(Path) of - ok -> ok; - Error -> gd_gui:trouble(Error) - end; -maybe_clean(false, _) -> - ok. - - -get_prefs(K, M, D) -> - P = maps:get(?MODULE, M, #{}), - maps:get(K, P, D). - -put_prefs(K, V, M) -> - P = maps:get(?MODULE, M, #{}), - NewP = maps:put(K, V, P), - maps:put(?MODULE, NewP, M). - - -do_save(Module, Prefs, State = #s{prefs = Cached}) -> - 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), - State#s{selected = 0, pass = none, wallet = none}. - - -save_wallet(#wr{path = Path, pass = false}, none, Wallet) -> - ok = filelib:ensure_dir(Path), - file:write_file(Path, term_to_binary(Wallet)); -save_wallet(#wr{path = Path, pass = true}, Pass, Wallet) -> - ok = filelib:ensure_dir(Path), - Cipher = encrypt(Pass, term_to_binary(Wallet)), - file:write_file(Path, Cipher). - - -read(Path, none) -> - case file:read_file(Path) of - {ok, Bin} -> read2(Bin); - Error -> Error - end; -read(Path, Pass) -> - case file:read_file(Path) of - {ok, Cipher} -> - try - Bin = decrypt(Pass, Cipher), - read2(Bin) - catch - error:{error, L, "Can't finalize"} -> - ok = log(info, "Decrypt failed at ~p", [L]), - {error, bad_password}; - E:R -> - {E, R} - end; - Error -> - Error - end. - -read2(Bin) -> - case zx_lib:b_to_t(Bin) of - {ok, T} -> read3(T); - error -> {error, badarg} - end. - -read3(T) -> - case element(2, T) of - 1 -> read3(setelement(2, erlang:append_element(T, #{}), 2)); - 2 -> {ok, T}; - _ -> {error, bad_wallet} - 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) -> - Path = prefs_path(), - ok = filelib:ensure_dir(Path), - zx_lib:write_terms(Path, proplists:from_map(Prefs)). - - -prefs_path() -> - filename:join(zx_lib:path(etc, "otpr", "gajudesk"), "prefs.eterms").