From ab2b816f88d2c160848c796c01b77853c5dace7a Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Wed, 16 Nov 2022 15:13:20 -0700 Subject: [PATCH] [wip] initial vw commit --- utils/vw/.gitignore | 15 +++ utils/vw/Emakefile | 1 + utils/vw/ebin/vw.app | 7 ++ utils/vw/src/vb58.erl | 249 ++++++++++++++++++++++++++++++++++++++++++ utils/vw/src/vd.erl | 248 +++++++++++++++++++++++++++++++++++++++++ utils/vw/src/vrlp.erl | 148 +++++++++++++++++++++++++ utils/vw/src/vw.erl | 40 +++++++ utils/vw/zomp.meta | 18 +++ 8 files changed, 726 insertions(+) create mode 100644 utils/vw/.gitignore create mode 100644 utils/vw/Emakefile create mode 100644 utils/vw/ebin/vw.app create mode 100644 utils/vw/src/vb58.erl create mode 100644 utils/vw/src/vd.erl create mode 100644 utils/vw/src/vrlp.erl create mode 100644 utils/vw/src/vw.erl create mode 100644 utils/vw/zomp.meta diff --git a/utils/vw/.gitignore b/utils/vw/.gitignore new file mode 100644 index 0000000..20177b4 --- /dev/null +++ b/utils/vw/.gitignore @@ -0,0 +1,15 @@ +.eunit +deps +*.o +*.beam +*.plt +*.swp +erl_crash.dump +ebin/*.beam +doc/*.html +doc/*.css +doc/edoc-info +doc/erlang.png +rel/example_project +.concrete/DEV_MODE +.rebar diff --git a/utils/vw/Emakefile b/utils/vw/Emakefile new file mode 100644 index 0000000..68c7b67 --- /dev/null +++ b/utils/vw/Emakefile @@ -0,0 +1 @@ +{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}. diff --git a/utils/vw/ebin/vw.app b/utils/vw/ebin/vw.app new file mode 100644 index 0000000..5c630f4 --- /dev/null +++ b/utils/vw/ebin/vw.app @@ -0,0 +1,7 @@ +{application,vw, + [{description,"Simple command line ae wallet"}, + {registered,[]}, + {included_applications,[]}, + {applications,[stdlib,kernel]}, + {vsn,"0.1.0"}, + {modules,[vw]}]}. diff --git a/utils/vw/src/vb58.erl b/utils/vw/src/vb58.erl new file mode 100644 index 0000000..37a99d8 --- /dev/null +++ b/utils/vw/src/vb58.erl @@ -0,0 +1,249 @@ +%% @doc +%% Vanillae Base58 Encoding/Decoding module +%% +%% References +%% +%% 1. https://digitalbazaar.github.io/base58-spec/#encode +%% 2. https://www.youtube.com/watch?v=GedV3S9X89c +%% @end +-module(vb58). + +-export([enc/1, dec/1]). + +%% TODO: move these cases to test or something +%% this originated from the +%%-mode(compile). +%%-spec enc(binary()) -> string(). +%%% https://digitalbazaar.github.io/base58-spec/#encode +% +%main([]) -> +% {ok, Cases} = file:consult("b58_cases_3.eterms"), +% test_cases(Cases). +% +%test_cases([{{encoded, E}, {decoded, D}} | Rest]) -> +% EncodeOk = E =:= enc(D), +% DecodeOk = D =:= dec(E), +% ok = +% case EncodeOk of +% true -> ok; +% false -> io:format("===============================~n" +% "YOU ARE A FAILURE TO ENCODE~n" +% "===============================~n" +% "decoded : ~tw~n" +% "expected : ~ts~n" +% "actual : ~ts~n~n", +% [D, E, enc(D)]) +% end, +% ok = +% case DecodeOk of +% true -> ok; +% false -> io:format("===============================~n" +% "YOU ARE A FAILURE TO DECODE~n" +% "===============================~n" +% "encoded : ~ts~n" +% "expected : ~tw~n" +% "actual : ~tw~n~n", +% [E, D, dec(E)]) +% end, +% test_cases(Rest); +%test_cases([]) -> +% ok. + +% this was much clearer: https://www.youtube.com/watch?v=GedV3S9X89c + +-spec enc(Bytes) -> Base58 + when Bytes :: binary(), + Base58 :: string(). +%% @doc +%% Encode a bytestring into base58 notation + +enc(Bytes) -> + % grab leading 0s + {NumLeadingZeros, Rest} = split_zeros(Bytes, 0), + NBitsInRest = bit_size(Rest), + <> = Rest, + ZerosBase58 = [$1 || _ <- lists:seq(1, NumLeadingZeros)], + RestBase58 = enc(RestBigNum, []), + ZerosBase58 ++ RestBase58. + + + +-spec split_zeros(Bytes, InitZeros) -> {NumLeadingZeros, Rest} + when Bytes :: binary(), + InitZeros :: integer(), + NumLeadingZeros :: binary(), + Rest :: binary(). + +split_zeros(<<0:8, Rest/binary>>, NumZerosAcc) -> + NewNumZerosAcc = NumZerosAcc + 1, + split_zeros(Rest, NewNumZerosAcc); +split_zeros(Rest, NumZerosAcc) -> + {NumZerosAcc, Rest}. + + + +-spec enc(BytesBigNum, Base58Acc) -> Base58 + when BytesBigNum :: integer(), + Base58Acc :: [0..57], + Base58 :: string(). + +enc(0, Acc) -> + lists:map(fun int2char/1, Acc); +enc(BitNum, Acc) -> + Q = BitNum div 58, + R = BitNum rem 58, + enc(Q, [R | Acc]). + + + +-spec dec(Base58) -> DecodedBytes + when Base58 :: string(), + DecodedBytes :: binary(). +%% @doc +%% Decode a Base58-encoded string into a bytestring + +dec(Str) -> + % the number of leading 1s tells us the number of leading zeros + {NumLeadingZeros, RestStr} = split_ones(Str, 0), + LeadingZeros = << <<0>> || _ <- lists:seq(1, NumLeadingZeros) >>, + RestNs = lists:map(fun char2int/1, RestStr), + RestBytes = dec(RestNs, 0), + <>. + +split_ones([$1 | Rest], NOnes) -> + split_ones(Rest, NOnes + 1); +split_ones(B58Str, NOnes) -> + {NOnes, B58Str}. + + +dec([N | Ns], Acc) -> + NewAcc = (Acc*58) + N, + dec(Ns, NewAcc); +dec([], FinalAccN) -> + bignum_to_binary_bige(FinalAccN, <<>>). + +bignum_to_binary_bige(0, Acc) -> + Acc; +bignum_to_binary_bige(N, Acc) -> + Q = N div 256, + R = N rem 256, + NewAcc = <>, + bignum_to_binary_bige(Q, NewAcc). + +int2char( 0) -> $1; +int2char( 1) -> $2; +int2char( 2) -> $3; +int2char( 3) -> $4; +int2char( 4) -> $5; +int2char( 5) -> $6; +int2char( 6) -> $7; +int2char( 7) -> $8; +int2char( 8) -> $9; +int2char( 9) -> $A; +int2char(10) -> $B; +int2char(11) -> $C; +int2char(12) -> $D; +int2char(13) -> $E; +int2char(14) -> $F; +int2char(15) -> $G; +int2char(16) -> $H; +int2char(17) -> $J; +int2char(18) -> $K; +int2char(19) -> $L; +int2char(20) -> $M; +int2char(21) -> $N; +int2char(22) -> $P; +int2char(23) -> $Q; +int2char(24) -> $R; +int2char(25) -> $S; +int2char(26) -> $T; +int2char(27) -> $U; +int2char(28) -> $V; +int2char(29) -> $W; +int2char(30) -> $X; +int2char(31) -> $Y; +int2char(32) -> $Z; +int2char(33) -> $a; +int2char(34) -> $b; +int2char(35) -> $c; +int2char(36) -> $d; +int2char(37) -> $e; +int2char(38) -> $f; +int2char(39) -> $g; +int2char(40) -> $h; +int2char(41) -> $i; +int2char(42) -> $j; +int2char(43) -> $k; +int2char(44) -> $m; +int2char(45) -> $n; +int2char(46) -> $o; +int2char(47) -> $p; +int2char(48) -> $q; +int2char(49) -> $r; +int2char(50) -> $s; +int2char(51) -> $t; +int2char(52) -> $u; +int2char(53) -> $v; +int2char(54) -> $w; +int2char(55) -> $x; +int2char(56) -> $y; +int2char(57) -> $z. + +char2int($1) -> 0; +char2int($2) -> 1; +char2int($3) -> 2; +char2int($4) -> 3; +char2int($5) -> 4; +char2int($6) -> 5; +char2int($7) -> 6; +char2int($8) -> 7; +char2int($9) -> 8; +char2int($A) -> 9; +char2int($B) -> 10; +char2int($C) -> 11; +char2int($D) -> 12; +char2int($E) -> 13; +char2int($F) -> 14; +char2int($G) -> 15; +char2int($H) -> 16; +char2int($J) -> 17; +char2int($K) -> 18; +char2int($L) -> 19; +char2int($M) -> 20; +char2int($N) -> 21; +char2int($P) -> 22; +char2int($Q) -> 23; +char2int($R) -> 24; +char2int($S) -> 25; +char2int($T) -> 26; +char2int($U) -> 27; +char2int($V) -> 28; +char2int($W) -> 29; +char2int($X) -> 30; +char2int($Y) -> 31; +char2int($Z) -> 32; +char2int($a) -> 33; +char2int($b) -> 34; +char2int($c) -> 35; +char2int($d) -> 36; +char2int($e) -> 37; +char2int($f) -> 38; +char2int($g) -> 39; +char2int($h) -> 40; +char2int($i) -> 41; +char2int($j) -> 42; +char2int($k) -> 43; +char2int($m) -> 44; +char2int($n) -> 45; +char2int($o) -> 46; +char2int($p) -> 47; +char2int($q) -> 48; +char2int($r) -> 49; +char2int($s) -> 50; +char2int($t) -> 51; +char2int($u) -> 52; +char2int($v) -> 53; +char2int($w) -> 54; +char2int($x) -> 55; +char2int($y) -> 56; +char2int($z) -> 57. diff --git a/utils/vw/src/vd.erl b/utils/vw/src/vd.erl new file mode 100644 index 0000000..e0b7ad1 --- /dev/null +++ b/utils/vw/src/vd.erl @@ -0,0 +1,248 @@ +%% @doc +%% Vanillae data composer/decomposer +%% +%% This is similar to serialization/deserialization, but not the same thing +%% +%% This code exists to work out concepts and code structure for Vanillae TS. It +%% may eventually become productized. Please do not use this right now. +%% +%% References: +%% +%% 1. https://github.com/aeternity/protocol/blob/master/serializations.md +%% 2. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md +-module(vd). + +-compile([export_all, nowarn_export_all]). + +%%% TYPES + +-type obj() :: #{type := atom(), + vsn := integer(), + fields := map()}. + + + +-spec decompose(API_String) -> MaybeObject + when API_String :: string(), + MaybeObject :: {ok, obj()} + | {error, Reason :: term()}. +%% @doc +%% Decompose API-encoded data + +decompose("tx_" ++ Base64) -> decompose_tx_b64(Base64); +decompose(X) -> {error, {nyi, {decompose, X}}}. + + + + +%% decode the base64 and check the hash thing +decompose_tx_b64(B64_str) -> + B64_Bytes = list_to_binary(B64_str), + %% This has the double sha at the end + Stupid_Bytes = base64:decode(B64_Bytes), + Stupid_Size = byte_size(Stupid_Bytes), + %% pull apart data + <> = Stupid_Bytes, + ActualDoubleSha = shasha(RLP_encoded_data), + case Check =:= ActualDoubleSha of + false -> + {error, {checksum_mismatch, Check, ActualDoubleSha}}; + true -> + decode_and_dispatch(RLP_encoded_data) + end. + + + +%% Double sha +shasha(Bytes) -> + <> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), + Result. + + + +%% decode rlp data +decode_and_dispatch(RLP_encoded_bytes) -> + {DecodedData, Remainder} = vrlp:decode(RLP_encoded_bytes), + case Remainder of + <<>> -> decom_dispatch(DecodedData); + _ -> {error, trailing_data} + end. + + + +%% at this point we have the rlp data, and based on the first field, we are +%% going to decompose the data +decom_dispatch([Tag_Bytes, Vsn_Bytes | Fields]) -> + Tag = binary:decode_unsigned(Tag_Bytes), + Vsn = binary:decode_unsigned(Vsn_Bytes), + dd2(Tag, Vsn, Fields); +decom_dispatch(X) -> + {error, {invalid_data, X}}. + + + +%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags +dd2(_Account = 10, Vsn = 1, Fields) -> maybe(fun decompose_fields_account1/1 , 'Account' , Vsn, Fields); +dd2(_Account = 10, Vsn = 2, Fields) -> maybe(fun decompose_fields_account2/1 , 'Account' , Vsn, Fields); +dd2(_SignedTx = 11, Vsn = 1, Fields) -> maybe(fun decompose_fields_signedtx/1 , 'SignedTx' , Vsn, Fields); +dd2(_SpendTx = 12, Vsn = 1, Fields) -> maybe(fun decompose_fields_spendtx/1 , 'SpendTx' , Vsn, Fields); +dd2(_ContractCallTx = 43, Vsn = 1, Fields) -> maybe(fun decompose_fields_contractcalltx/1, 'ContractCallTx', Vsn, Fields); +dd2(Tag , Vsn , Fields) -> {error, {nyi, {hd2, Tag, Vsn, Fields}}}. + + + +maybe(MaybeDecompose, Type, Vsn, Fields) -> + case MaybeDecompose(Fields) of + {ok, DecFields} -> + {ok, #{type => Type, + vsn => Vsn, + fields => DecFields}}; + Error -> + Error + end. + + + +% 10 = account, version 1 +% https://github.com/aeternity/protocol/blob/master/serializations.md#accounts-version-1-basic-accounts +decompose_fields_account1([NonceBytes, BalanceBytes]) -> + {ok, #{nonce => binary:decode_unsigned(NonceBytes), + balance => binary:decode_unsigned(BalanceBytes)}}; +decompose_fields_account1(BadFields) -> + {error, {invalid_account_v1_fields, BadFields}}. + + + +% 10 = account, version 2 +% https://github.com/aeternity/protocol/blob/master/serializations.md#accounts-version-2-generalized-accounts-from-fortuna-release +decompose_fields_account2([Flags, %% :: int() + Nonce, %% :: int() + Balance, %% :: int() + GaContract, %% :: id() + GaAuthFun]) -> %% :: binary() + {ok, #{flags => bdu(Flags), + nonce => bdu(Nonce), + balance => bdu(Balance), + ga_contract => encode_id(GaContract), + ga_auth_fun => GaAuthFun}}; +decompose_fields_account2(BadFields) -> + {error, {invalid_account_v2_fields, BadFields}}. + + + +%% 11 = signedtx +decompose_fields_signedtx([Signatures, Transaction]) -> + Sigs = lists:map(fun encode_sg/1, Signatures), + TxStr = encode_tx(Transaction), + {ok, #{signatures => Sigs, + transaction => TxStr}}; +decompose_fields_signedtx(Fields) -> + {error, {invalid_signedtx_fields, Fields}}. + + + +%% 12 = spendtx +%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction +decompose_fields_spendtx([SenderBytes, + RecipBytes, + AmountBytes, + FeeBytes, + TTLBytes, + NonceBytes, + Payload]) -> + % TODO: drop-through to make sure id humanization works + SenderStr = encode_id(SenderBytes), + RecipStr = encode_id(RecipBytes), + Amount = binary:decode_unsigned(AmountBytes), + Fee = binary:decode_unsigned(FeeBytes), + TTL = binary:decode_unsigned(TTLBytes), + Nonce = binary:decode_unsigned(NonceBytes), + {ok, #{sender => SenderStr, + recipient => RecipStr, + amount => Amount, + fee => Fee, + ttl => TTL, + nonce => Nonce, + payload => Payload}}; +decompose_fields_spendtx(Fields) -> + {error, {invalid_spendtx_fields, Fields}}. + + + +%% 43 = contract call tx +%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#contract-call-transaction +decompose_fields_contractcalltx([Caller, % :: id() + Nonce, % :: int() + Contract, % :: id() + AbiVersion, % :: int() + Fee, % :: int() + Ttl, % :: int() + Amount, % :: int() + Gas, % :: int() + GasPrice, % :: int() + CallData]) -> % :: binary() + {ok, #{caller => encode_id(Caller), + nonce => binary:decode_unsigned(Nonce), + contract => encode_id(Contract), + abi_version => binary:decode_unsigned(AbiVersion), + fee => binary:decode_unsigned(Fee), + ttl => binary:decode_unsigned(Ttl), + amount => binary:decode_unsigned(Amount), + gas => binary:decode_unsigned(Gas), + gas_price => binary:decode_unsigned(GasPrice), + call_data => CallData}}; +decompose_fields_contractcalltx(X) -> + {error, {invalid_contractcalltx_fields, X}}. + + + +%% general byte array +encode_ba(Bytes) -> "ba_" ++ sha64enc(Bytes). + + + +%% contract byte array (see: https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) +encode_cb(Bytes) -> "cb_" ++ sha64enc(Bytes). + + + +%% See: https://github.com/aeternity/protocol/blob/master/serializations.md#the-id-type +%% https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md +%% ak_ account +encode_id(<<1, IdBytes:32/binary>>) -> "ak_" ++ sha58enc(IdBytes); +%% nm_ name +encode_id(<<2, IdBytes:32/binary>>) -> "nm_" ++ sha58enc(IdBytes); +%% cm_ commitment +encode_id(<<3, IdBytes:32/binary>>) -> "nm_" ++ sha58enc(IdBytes); +%% ok_ oracle +encode_id(<<4, IdBytes:32/binary>>) -> "ok_" ++ sha58enc(IdBytes); +%% ct_ contract +encode_id(<<5, IdBytes:32/binary>>) -> "ct_" ++ sha58enc(IdBytes); +%% ch_ channel +encode_id(<<6, IdBytes:32/binary>>) -> "ch_" ++ sha58enc(IdBytes). + + + +encode_sg(Sig) -> "sg_" ++ sha58enc(Sig). + + + +encode_tx(TxData) -> "tx_" ++ sha64enc(TxData). + + + +sha58enc(Bytes) -> + Check = shasha(Bytes), + vb58:enc(<>). + + + +sha64enc(Bytes) -> + Check = shasha(Bytes), + binary_to_list(base64:encode(<>)). + + + +%% tired of typing this +bdu(X) -> binary:decode_unsigned(X). diff --git a/utils/vw/src/vrlp.erl b/utils/vw/src/vrlp.erl new file mode 100644 index 0000000..b8a9c83 --- /dev/null +++ b/utils/vw/src/vrlp.erl @@ -0,0 +1,148 @@ +%% @doc +%% Vanillae RLP encoder/decoder +%% +%% Reference: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ +%% +%% Agrees with Ethereum's Python implementation in randomized tests +-module(vrlp). + +-export_type([decoded_data/0]). +-export([encode/1, decode/1]). + +-type decoded_data() :: binary() | [decoded_data()]. + + + +-spec encode(Data) -> RLP + when Data :: decoded_data(), + RLP :: binary(). +%% @doc +%% encode some data + +encode(Binary) when is_binary(Binary) -> + encode_binary(Binary); +encode(List) when is_list(List) -> + encode_list(List). + + + +-spec encode_binary(Bytes) -> RLP + when Bytes :: binary(), + RLP :: binary(). +%% @private +%% encode a binary in rlp +%% @end + +% single byte case when the byte is between 0..127 +% result is the byte itself +encode_binary(<>) when Byte =< 127 -> + <>; +% if the bytestring is 0..55 items long, the first byte is 128 + Length, +% the rest of the string is the string +encode_binary(Bytes) when byte_size(Bytes) =< 55 -> + Size = byte_size(Bytes), + <<(128 + Size), Bytes/binary>>; +% more than 55 bytes long, first byte is 183 + ByteLengthOfLength +% max byte size is 2^64 - 1 +encode_binary(Bytes) when 55 < byte_size(Bytes), byte_size(Bytes) < (1 bsl 64) -> + SizeInt = byte_size(Bytes), + SizeBytes = binary:encode_unsigned(SizeInt, big), + SizeOfSizeInt = byte_size(SizeBytes), + %% 183 = 128 + 55 + %% SizeOfSizeInt > 0 + <<(183 + SizeOfSizeInt), + SizeBytes/binary, + Bytes/binary>>. + + + +-spec encode_list(List) -> RLP + when List :: [decoded_data()], + RLP :: binary(). +%% @private +%% encode a list in rlp +%% @end + +% first we encode the total payload of the list +% depending on how long it is, we then branch +encode_list(List) -> + Payload = << (encode(Item)) || Item <- List>>, + Payload_Size = byte_size(Payload), + if + Payload_Size =< 55 -> + <<(192 + Payload_Size), Payload/binary>>; + 55 < Payload_Size -> + SizeBytes = binary:encode_unsigned(Payload_Size, big), + SizeOfSizeInt = byte_size(SizeBytes), + %% 247 = 192 + 55 + %% SizeOfSizeInt > 0 + <<(247 + SizeOfSizeInt), + SizeBytes/binary, + Payload/binary>> + end. + + + +-spec decode(RLP) -> {Data, Rest} + when RLP :: binary(), + Data :: decoded_data(), + Rest :: binary(). +%% @doc +%% decode an RLP-encoded string +%% @end + +% if the first byte is between 0 and 127, that is the data +decode(<>) when Byte =< 127 -> + {<>, Rest}; +% if the first byte is between 128 and 183 = 128 + 55, it is a bytestring and +% the length is Byte - 128 +decode(<>) when Byte =< 183 -> + PayloadByteLength = Byte - 128, + %PayloadBitLength = 8 * PayloadByteLength, + %io:format("Byte : ~p~n" + % "Rest : ~w~n" + % "PayloadByteLength : ~p~n", + % %"PayloadBitLength : ~p~n", + % [Byte, Rest, PayloadByteLength]), + <> = Rest, + {Payload, Rest2}; +% If the first byte is between 184 = 183 + 1 and 191 = 183 + 8, it is a +% bytestring. The byte length of the byte length of bytestring is FirstByte - +% 183. Then pull out the actual data +decode(<>) when Byte =< 191 -> + ByteLengthOfByteLength = Byte - 183, + BitLengthOfByteLength = 8 * ByteLengthOfByteLength, + <> = Rest, + <> = Rest2, + {Payload, Rest3}; +% If the first byte is between 192 and 247 = 192 + 55, it is a list. The byte +% length of the list-payload is FirstByte - 192. Then the list payload, which +% needs to be decoded on its own. +decode(<>) when Byte =< 247 -> + ByteLengthOfListPayload = Byte - 192, + <> = Rest, + List = decode_list(ListPayload), + {List, Rest2}; +% If the first byte is between 248 = 247 + 1 and 255 = 247 + 8, it is a list. +% The byte length of the byte length of the list-payload is FirstByte - 247. +% Then the byte length of the list. Then the list payload, which needs to be +% decoded on its own. +decode(<>) -> + ByteLengthOfByteLengthOfListPayload_int = Byte - 247, + BitLengthOfByteLengthOfListPayload_int = 8 * ByteLengthOfByteLengthOfListPayload_int, + <> = Rest, + <> = Rest2, + List = decode_list(ListPayload_bytes), + {List, Rest3}. + +decode_list(<<>>) -> + []; +decode_list(Bytes) -> + {Item, Rest} = decode(Bytes), + [Item | decode_list(Rest)]. diff --git a/utils/vw/src/vw.erl b/utils/vw/src/vw.erl new file mode 100644 index 0000000..ccffeb3 --- /dev/null +++ b/utils/vw/src/vw.erl @@ -0,0 +1,40 @@ +-module(vw). +-vsn("0.1.0"). +-author("Peter Harpending "). +-copyright("Peter Harpending "). + +-export([start/1]). +-compile([export_all, nowarn_export_all]). + +-spec start(ArgV) -> ok + when ArgV :: [string()]. + +start(ArgV) -> + go(ArgV), + zx:silent_stop(). + +%% Taking break +%% +%% WHEN back: get decompose to work + +go(["help"]) -> + help(); +go(["--help"]) -> + help(); +go(["generate", "keypair"]) -> + error(nyi); +go(["decompose", TxStr]) -> + decompose(TxStr); +go(_) -> + error(invalid_subcommand). + +help() -> + io:format("you can't help people who won't help themselves~n", []). + +decompose(TxStr) -> + case vd:decompose(TxStr) of + {ok, X} -> + io:format("~tp~n", [X]); + {error, Error} -> + io:format("ERROR: ~tp~n", [Error]) + end. diff --git a/utils/vw/zomp.meta b/utils/vw/zomp.meta new file mode 100644 index 0000000..cbc6f0c --- /dev/null +++ b/utils/vw/zomp.meta @@ -0,0 +1,18 @@ +{a_email,"ceverett@tsuriai.jp"}. +{author,"Peter Harpending"}. +{c_email,"ceverett@tsuriai.jp"}. +{copyright,"Peter Harpending"}. +{deps,[]}. +{desc,"Simple command line ae wallet"}. +{file_exts,[]}. +{key_name,none}. +{license,skip}. +{mod,"vw"}. +{modules,[]}. +{name,"Vanillae Wallet"}. +{package_id,{"otpr","vw",{0,1,0}}}. +{prefix,none}. +{repo_url,"https://github.com/aeternity/Vanillae"}. +{tags,[]}. +{type,cli}. +{ws_url,"https://github.com/aeternity/Vanillae"}.