diff --git a/README.md b/README.md index ca49030..9b67b1c 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,6 @@ Vanillae is just a knife. ## Flagship Tools -- Jaeck Russell - - A minimal browser wallet extension. Not included in this repository yet. - - [Sidekick](./sidekick/) A library to talk to a browser wallet extension from the perspective of a @@ -49,25 +45,6 @@ out. consist of what data the waellet expects from the aepp, and what data the aepp expects from the waellet. -- [Parasite](./libs/parasite/) - - **NOT FOR PRODUCTION USE** - - This is a library for talking to Aeternity HTTP nodes from the perspective - of a page script. Used in example/documentation code for things that your - backend should do. - - This may eventually be polished and repackaged as a production quality - library. - -- libjr - - This is a library to talk to a page script ("aepp") from the perspective of - the wallet ("waellet"). It essentially is sidekick from the perspective of - the wallet. - - Not yet included in this repository. - ## Utilities diff --git a/bindings/erlang/src/vanillae.erl b/bindings/erlang/src/vanillae.erl index 50d64da..6703a2f 100644 --- a/bindings/erlang/src/vanillae.erl +++ b/bindings/erlang/src/vanillae.erl @@ -40,31 +40,39 @@ % AE node JSON query interface functions -export([top_height/0, top_block/0, kb_current/0, kb_current_hash/0, kb_current_height/0, -% kb_pending/0, + kb_pending/0, kb_by_hash/1, kb_by_height/1, % kb_insert/1, mb_header/1, mb_txs/1, mb_tx_index/2, mb_tx_count/1, gen_current/0, gen_by_id/1, gen_by_height/1, acc/1, acc_at_height/2, acc_at_block_id/2, -% acc_pending_txs/1, + acc_pending_txs/1, next_nonce/1, - dry_run/1, dry_run/2, + dry_run/1, dry_run/2, dry_run/3, tx/1, tx_info/1, post_tx/1, contract/1, contract_code/1, -% contract_poi/1, + contract_poi/1, % oracle/1, oracle_queries/1, oracle_queries_by_id/2, name/1, % channel/1, peer_pubkey/0, - status/0]). -% status_chainends/0]). + status/0, + status_chainends/0]). % AE contract call and serialization interface functions -export([read_aci/1, + min_gas/0, + min_gas_price/0, + min_fee/0, + contract_create/3, + contract_create/8, prepare_contract/1, + contract_call/5, contract_call/6, - contract_call/10]). + contract_call/10, + verify_signature/3]). + % OTP Application Interface %-export([start/0, stop/0]). @@ -73,7 +81,7 @@ %%% Types --export_type([ae_node/0, network_id/0]). +-export_type([ae_node/0, network_id/0, ae_error/0]). -type ae_node() :: {inet:ip_address(), inet:port_number()}. @@ -88,9 +96,9 @@ | {headers, map()} | bad_length | gc_out_of_range. --type pubkey() :: string(). % "ak_" ++ _ +-type pubkey() :: unicode:chardata(). % "ak_" ++ _ -type account_id() :: pubkey(). --type contract_id() :: string(). % "ct_" ++ _ +-type contract_id() :: unicode:chardata(). % "ct_" ++ _ -type peer_pubkey() :: string(). % "pp_" ++ _ -type keyblock_hash() :: string(). % "kh_" ++ _ -type contract_byte_array() :: string(). % "cb_" ++ _ @@ -134,7 +142,20 @@ % "block_height" => pos_integer(), % "hash" => tx_hash(), % "signatures" => [signature()], -% "tx" => map()}. % FIXME +% "tx" => +% #{"abi_version" => pos_integer(), +% "amount" => non_neg_integer(), +% "call_data" => contract_byte_array(), +% "code" => contract_byte_array(), +% "deposit" => non_neg_integer(), +% "fee" => pos_integer(), +% "gas" => pos_integer(), +% "gas_price" => pos_integer(), +% "nonce" => pos_integer(), +% "owner_id" => account_id(), +% "type" => string(), +% "version" => pos_integer(), +% "vm_version" => pos_integer()}} -type generation() :: #{string() => term()}. % #{"key_block" => keyblock(), % "micro_blocks" => [microblock_hash()]}. @@ -321,10 +342,15 @@ kb_current_height() -> end. -%-spec kb_pending() -> -% -%kb_pending() -> -% request("/v2/key-blocks/pending"). +-spec kb_pending() -> {ok, keyblock_hash()} | {error, Reason} + when Reason :: string(). +%% @doc +%% Request the hash of the pending keyblock of a mining node's beneficiary. +%% If the node queried is not configured for mining it will return +%% `{error, "Beneficiary not configured"}' + +kb_pending() -> + result(request("/v2/key-blocks/pending")). -spec kb_by_hash(ID) -> {ok, KeyBlock} | {error, Reason} @@ -488,16 +514,15 @@ acc_at_block_id(AccountID, BlockID) -> end. -% TODO -%-spec acc_pending_txs(AccountID) -> {ok, TXs} | {error, Reason} -% when AccountID :: account_id(), -% TXs :: -% Reason :: -%%% @doc -%%% Retrieve a list of transactions pending for the given account. -% -%acc_pending_txs(AccountID) -> -% request(["/v2/accounts/", AccountID, "/transactions/pending"]). +-spec acc_pending_txs(AccountID) -> {ok, TXs} | {error, Reason} + when AccountID :: account_id(), + TXs :: [tx_hash()], + Reason :: ae_error() | string(). +%% @doc +%% Retrieve a list of transactions pending for the given account. + +acc_pending_txs(AccountID) -> + request(["/v2/accounts/", AccountID, "/transactions/pending"]). -spec next_nonce(AccountID) -> {ok, Nonce} | {error, Reason} @@ -508,8 +533,14 @@ acc_at_block_id(AccountID, BlockID) -> %% Retrieve the next nonce for the given account next_nonce(AccountID) -> - case request(["/v2/accounts/", AccountID, "/next-nonce"]) of - {ok, #{"next_nonce" := Nonce}} -> {ok, Nonce}; +% case request(["/v2/accounts/", AccountID, "/next-nonce"]) of +% {ok, #{"next_nonce" := Nonce}} -> {ok, Nonce}; +% {ok, #{"reason" := "Account not found"}} -> {ok, 1}; +% {ok, #{"reason" := Reason}} -> {error, Reason}; +% Error -> Error +% end. + case request(["/v2/accounts/", AccountID]) of + {ok, #{"nonce" := Nonce}} -> {ok, Nonce + 1}; {ok, #{"reason" := "Account not found"}} -> {ok, 1}; {ok, #{"reason" := Reason}} -> {error, Reason}; Error -> Error @@ -527,27 +558,45 @@ next_nonce(AccountID) -> %% {ok, Hash} = vanillae:kb_current_hash(), %% vanilla:dry_run(TX, Hash), %% ''' +%% NOTE: +%% For this function to work the Aeternity node you are sending the request +%% to must have its configuration set to `http: endpoints: dry-run: true' dry_run(TX) -> + dry_run(TX, []). + + +-spec dry_run(TX, Accounts) -> {ok, Result} | {error, Reason} + when TX :: binary() | string(), + Accounts :: [pubkey()], + Result :: term(), % FIXME + Reason :: term(). % FIXME + +dry_run(TX, Accounts) -> case kb_current_hash() of - {ok, Hash} -> dry_run(TX, Hash); + {ok, Hash} -> dry_run(TX, Accounts, Hash); Error -> Error end. --spec dry_run(TX, KBHash) -> {ok, Result} | {error, Reason} - when TX :: binary() | string(), - KBHash :: binary() | string(), - Result :: term(), % FIXME - Reason :: term(). % FIXME +-spec dry_run(TX, Accounts, KBHash) -> {ok, Result} | {error, Reason} + when TX :: binary() | string(), + Accounts :: [pubkey()], + KBHash :: binary() | string(), + Result :: term(), % FIXME + Reason :: term(). % FIXME %% @doc %% Execute a read-only transaction on the chain at the height indicated by the %% hash provided. -dry_run(TX, KBHash) -> +dry_run(TX, Accounts, KBHash) -> KBB = to_binary(KBHash), TXB = to_binary(TX), - JSON = zj:binary_encode(#{top => KBB, accounts => [], txs => [#{tx => TXB}]}), + DryData = #{top => KBB, + accounts => Accounts, + txs => [#{tx => TXB}], + tx_events => true}, + JSON = zj:binary_encode(DryData), request("/v2/dry-run", JSON). to_binary(S) when is_binary(S) -> S; @@ -584,7 +633,8 @@ tx_info(ID) -> %% Post a transaction to the chain. post_tx(Data) -> - request("/v2/transactions", Data). + JSON = zj:binary_encode(#{tx => Data}), + request("/v2/transactions", JSON). -spec contract(ID) -> {ok, ContractData} | {error, Reason} @@ -611,11 +661,13 @@ contract_code(ID) -> end. -% FIXME: Is this broken? Seems to just stall -% -spec conract_poi(ID) -> -% -%contract_poi(ID) -> -% request(["/v2/contracts/", ID, "/poi"]). +-spec contract_poi(ID) -> {ok, Bytecode} | {error, Reason} + when ID :: contract_id(), + Bytecode :: contract_byte_array(), + Reason :: ae_error() | string(). + +contract_poi(ID) -> + request(["/v2/contracts/", ID, "/poi"]). % TODO %oracle(ID) -> @@ -675,24 +727,23 @@ status() -> request("/v2/status"). -% TODO -%-spec status_chainends() -> {ok, ChainEnds} | {error, Reason} -% when ChainEnds :: [keyblock_hash()], -% Reason :: ae_error(). -%%% @doc -%%% Retrieve the latest keyblock hashes -% -%status_chainends() -> -% request("/v2/status/chain-ends"). +-spec status_chainends() -> {ok, ChainEnds} | {error, Reason} + when ChainEnds :: [keyblock_hash()], + Reason :: ae_error(). +%% @doc +%% Retrieve the latest keyblock hashes + +status_chainends() -> + request("/v2/status/chain-ends"). request(Path) -> - vanillae_man:request(Path). + vanillae_man:request(unicode:characters_to_list(Path)). request(Path, Payload) -> - vanillae_man:request(Path, Payload). - + vanillae_man:request(unicode:characters_to_list(Path), Payload). + result({ok, #{"reason" := Reason}}) -> {error, Reason}; result(Received) -> Received. @@ -701,6 +752,236 @@ result(Received) -> Received. %%% Contract calls +-spec contract_create(CreatorID, Path, InitArgs) -> Result + when CreatorID :: unicode:chardata(), + Path :: file:filename(), + InitArgs :: [string()], + Result :: {ok, CreateTX} | {error, Reason}, + CreateTX :: binary(), + Reason :: file:posix() | term(). +%% @doc +%% This function reads the source of a Sophia contract (an .aes file) +%% and returns the unsigned create contract call data with default values. +%% For more control over exactly what those values are, use create_contract/8. + +contract_create(CreatorID, Path, InitArgs) -> + case next_nonce(CreatorID) of + {ok, Nonce} -> + Amount = 0, + Gas = 100000, + GasPrice = min_gas_price(), + Fee = min_fee(), + contract_create(CreatorID, Nonce, + Amount, Gas, GasPrice, Fee, + Path, InitArgs); + Error -> + Error + end. + + +-spec contract_create(CreatorID, Nonce, + Amount, Gas, GasPrice, Fee, + Path, InitArgs) -> Result + when CreatorID :: unicode:chardata(), + Nonce :: pos_integer(), + Amount :: non_neg_integer(), + Gas :: pos_integer(), + GasPrice :: pos_integer(), + Fee :: non_neg_integer(), + Path :: file:filename(), + InitArgs :: [string()], + Result :: {ok, CreateTX} | {error, Reason}, + CreateTX :: binary(), + Reason :: term(). +%% @doc +%% Create a "create contract" call using the supplied values. +%% +%% Contract creation is an even more opaque process than contract calls if you're new +%% to Aeternity. +%% +%% The meaning of each argument is as follows: +%%